GPU "dirty" optimization, wip vertexcache

This commit is contained in:
Henrik Rydgard 2013-01-20 13:15:46 +01:00
parent 04860322f4
commit 1b1a275dcc
4 changed files with 214 additions and 184 deletions

View File

@ -47,16 +47,10 @@ const int flushOnChangedBeforeCommandList[] = {
GE_CMD_BLENDMODE,
GE_CMD_BLENDFIXEDA,
GE_CMD_BLENDFIXEDB,
};
const int flushBeforeCommandList[] = {
GE_CMD_BEZIER,
GE_CMD_SPLINE,
GE_CMD_SIGNAL,
GE_CMD_FINISH,
GE_CMD_BJUMP,
GE_CMD_OFFSETADDR,
GE_CMD_REGION1,GE_CMD_REGION2,
GE_CMD_TEXOFFSETU,
GE_CMD_TEXOFFSETV,
GE_CMD_TEXSCALEU,
GE_CMD_TEXSCALEV,
GE_CMD_CULLFACEENABLE,
GE_CMD_TEXTUREMAPENABLE,
GE_CMD_LIGHTINGENABLE,
@ -67,31 +61,10 @@ const int flushBeforeCommandList[] = {
GE_CMD_COLORTESTENABLE,
GE_CMD_COLORTESTMASK,
GE_CMD_COLORREF,
GE_CMD_TEXSCALEU,GE_CMD_TEXSCALEV,
GE_CMD_TEXOFFSETU,GE_CMD_TEXOFFSETV,
GE_CMD_MINZ,GE_CMD_MAXZ,
GE_CMD_FRAMEBUFPTR,
GE_CMD_FRAMEBUFWIDTH,
GE_CMD_FRAMEBUFPIXFORMAT,
GE_CMD_TEXADDR0,
GE_CMD_CLUTADDR,
GE_CMD_LOADCLUT,
GE_CMD_TEXMAPMODE,
GE_CMD_TEXSHADELS,
GE_CMD_CLUTFORMAT,
GE_CMD_TRANSFERSTART,
GE_CMD_TEXBUFWIDTH0,
GE_CMD_TEXSIZE0,GE_CMD_TEXSIZE1,GE_CMD_TEXSIZE2,GE_CMD_TEXSIZE3,
GE_CMD_TEXSIZE4,GE_CMD_TEXSIZE5,GE_CMD_TEXSIZE6,GE_CMD_TEXSIZE7,
GE_CMD_ZBUFPTR,
GE_CMD_ZBUFWIDTH,
GE_CMD_TEXOFFSETU,
GE_CMD_TEXOFFSETV,
GE_CMD_TEXSCALEU,
GE_CMD_TEXSCALEV,
GE_CMD_OFFSETY,
GE_CMD_OFFSETX,
GE_CMD_OFFSETY,
GE_CMD_FOG1,
GE_CMD_FOG2,
GE_CMD_FOGCOLOR,
GE_CMD_LMODE,
GE_CMD_REVERSENORMAL,
GE_CMD_MATERIALUPDATE,
@ -128,35 +101,66 @@ const int flushBeforeCommandList[] = {
GE_CMD_VIEWPORTZ1,GE_CMD_VIEWPORTZ2,
GE_CMD_LIGHTENABLE0,GE_CMD_LIGHTENABLE1,GE_CMD_LIGHTENABLE2,GE_CMD_LIGHTENABLE3,
GE_CMD_CULL,
GE_CMD_REVERSENORMAL,
GE_CMD_PATCHDIVISION,
GE_CMD_MATERIALUPDATE,
GE_CMD_CLEARMODE,
GE_CMD_TEXMAPMODE,
GE_CMD_TEXSHADELS,
GE_CMD_TEXFUNC,
GE_CMD_TEXFILTER,
GE_CMD_TEXENVCOLOR,
GE_CMD_TEXMODE,
GE_CMD_TEXFORMAT,
GE_CMD_TEXFLUSH,
GE_CMD_TEXWRAP,
GE_CMD_ZTESTENABLE,
GE_CMD_STENCILTESTENABLE,
GE_CMD_STENCILOP,
GE_CMD_STENCILTEST,
GE_CMD_ZTEST,
GE_CMD_FOG1,
GE_CMD_FOG2,
GE_CMD_FOGCOLOR,
GE_CMD_MASKRGB,
GE_CMD_MASKALPHA,
};
const int flushBeforeCommandList[] = {
GE_CMD_BEZIER,
GE_CMD_SPLINE,
GE_CMD_SIGNAL,
GE_CMD_FINISH,
GE_CMD_BJUMP,
GE_CMD_OFFSETADDR,
GE_CMD_REGION1,GE_CMD_REGION2,
GE_CMD_FRAMEBUFPTR,
GE_CMD_FRAMEBUFWIDTH,
GE_CMD_FRAMEBUFPIXFORMAT,
GE_CMD_TEXADDR0,
GE_CMD_CLUTADDR,
GE_CMD_LOADCLUT,
GE_CMD_CLUTFORMAT,
GE_CMD_TRANSFERSTART,
GE_CMD_TEXBUFWIDTH0,
GE_CMD_TEXSIZE0,GE_CMD_TEXSIZE1,GE_CMD_TEXSIZE2,GE_CMD_TEXSIZE3,
GE_CMD_TEXSIZE4,GE_CMD_TEXSIZE5,GE_CMD_TEXSIZE6,GE_CMD_TEXSIZE7,
GE_CMD_ZBUFPTR,
GE_CMD_ZBUFWIDTH,
GE_CMD_OFFSETY,
GE_CMD_OFFSETX,
GE_CMD_OFFSETY,
GE_CMD_TEXFLUSH,
GE_CMD_MORPHWEIGHT0,GE_CMD_MORPHWEIGHT1,GE_CMD_MORPHWEIGHT2,GE_CMD_MORPHWEIGHT3,
GE_CMD_MORPHWEIGHT4,GE_CMD_MORPHWEIGHT5,GE_CMD_MORPHWEIGHT6,GE_CMD_MORPHWEIGHT7,
// These handle their own flushing.
/*
GE_CMD_WORLDMATRIXNUMBER,
GE_CMD_WORLDMATRIXDATA,
GE_CMD_VIEWMATRIXNUMBER,
GE_CMD_VIEWMATRIXDATA,
GE_CMD_PROJMATRIXNUMBER,
GE_CMD_PROJMATRIXDATA,
GE_CMD_TGENMATRIXNUMBER,
GE_CMD_TGENMATRIXDATA,
GE_CMD_BONEMATRIXNUMBER,
GE_CMD_MASKRGB,
GE_CMD_MASKALPHA,
GE_CMD_BONEMATRIXDATA,
*/
};
GLES_GPU::GLES_GPU(int renderWidth, int renderHeight)
@ -466,9 +470,13 @@ static void LeaveClearMode() {
void GLES_GPU::PreExecuteOp(u32 op, u32 diff) {
u32 cmd = op >> 24;
if (flushBeforeCommand_[cmd] == 1 || (diff && flushBeforeCommand_[cmd] == 2))
{
if (dumpThisFrame_) {
NOTICE_LOG(G3D, "================ FLUSH ================");
}
transformDraw_.Flush();
}
}
void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
@ -1054,10 +1062,14 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
case GE_CMD_WORLDMATRIXDATA:
{
int num = gstate.worldmtxnum & 0xF;
if (num < 12)
gstate.worldMatrix[num++] = getFloat24(data);
float newVal = getFloat24(data);
if (num < 12 && newVal != gstate.worldMatrix[num]) {
Flush();
gstate.worldMatrix[num] = getFloat24(data);
shaderManager_->DirtyUniform(DIRTY_WORLDMATRIX);
}
num++;
gstate.worldmtxnum = (gstate.worldmtxnum & 0xFF000000) | (num & 0xF);
shaderManager_->DirtyUniform(DIRTY_WORLDMATRIX);
}
break;
@ -1068,10 +1080,14 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
case GE_CMD_VIEWMATRIXDATA:
{
int num = gstate.viewmtxnum & 0xF;
if (num < 12)
gstate.viewMatrix[num++] = getFloat24(data);
float newVal = getFloat24(data);
if (num < 12 && newVal != gstate.viewMatrix[num]) {
Flush();
gstate.viewMatrix[num] = newVal;
shaderManager_->DirtyUniform(DIRTY_VIEWMATRIX);
}
num++;
gstate.viewmtxnum = (gstate.viewmtxnum & 0xFF000000) | (num & 0xF);
shaderManager_->DirtyUniform(DIRTY_VIEWMATRIX);
}
break;
@ -1082,10 +1098,15 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
case GE_CMD_PROJMATRIXDATA:
{
int num = gstate.projmtxnum & 0xF;
gstate.projMatrix[num++] = getFloat24(data);
float newVal = getFloat24(data);
if (newVal != gstate.projMatrix[num]) {
Flush();
gstate.projMatrix[num] = newVal;
shaderManager_->DirtyUniform(DIRTY_PROJMATRIX);
}
num++;
gstate.projmtxnum = (gstate.projmtxnum & 0xFF000000) | (num & 0xF);
}
shaderManager_->DirtyUniform(DIRTY_PROJMATRIX);
break;
case GE_CMD_TGENMATRIXNUMBER:
@ -1095,11 +1116,15 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
case GE_CMD_TGENMATRIXDATA:
{
int num = gstate.texmtxnum & 0xF;
if (num < 12)
gstate.tgenMatrix[num++] = getFloat24(data);
float newVal = getFloat24(data);
if (newVal != gstate.tgenMatrix[num] && num < 12) {
Flush();
gstate.tgenMatrix[num] = newVal;
shaderManager_->DirtyUniform(DIRTY_TEXMATRIX);
}
num++;
gstate.texmtxnum = (gstate.texmtxnum & 0xFF000000) | (num & 0xF);
}
shaderManager_->DirtyUniform(DIRTY_TEXMATRIX);
break;
case GE_CMD_BONEMATRIXNUMBER:
@ -1109,10 +1134,13 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
case GE_CMD_BONEMATRIXDATA:
{
int num = gstate.boneMatrixNumber & 0x7F;
shaderManager_->DirtyUniform(DIRTY_BONEMATRIX0 << (num / 12));
if (num < 96) {
gstate.boneMatrix[num++] = getFloat24(data);
float newVal = getFloat24(data);
if (newVal != gstate.boneMatrix[num] && num < 96) {
Flush();
gstate.boneMatrix[num] = newVal;
shaderManager_->DirtyUniform(DIRTY_BONEMATRIX0 << (num / 12));
}
num++;
gstate.boneMatrixNumber = (gstate.boneMatrixNumber & 0xFF000000) | (num & 0x7F);
}
break;

View File

@ -293,13 +293,13 @@ void LinkedShader::updateUniforms() {
}
for (int i = 0; i < 4; i++) {
if (u_lightdiffuse[i] != -1 && (dirtyUniforms & (DIRTY_LIGHT0 << i))) {
glUniform3fv(u_lightpos[i], 1, gstate_c.lightpos[i]);
glUniform3fv(u_lightdir[i], 1, gstate_c.lightdir[i]);
glUniform3fv(u_lightatt[i], 1, gstate_c.lightatt[i]);
glUniform3fv(u_lightambient[i], 1, gstate_c.lightColor[0][i]);
glUniform3fv(u_lightdiffuse[i], 1, gstate_c.lightColor[1][i]);
glUniform3fv(u_lightspecular[i], 1, gstate_c.lightColor[2][i]);
if (dirtyUniforms & (DIRTY_LIGHT0 << i)) {
if (u_lightpos[i] != -1) glUniform3fv(u_lightpos[i], 1, gstate_c.lightpos[i]);
if (u_lightdir[i] != -1) glUniform3fv(u_lightdir[i], 1, gstate_c.lightdir[i]);
if (u_lightatt[i] != -1) glUniform3fv(u_lightatt[i], 1, gstate_c.lightatt[i]);
if (u_lightambient[i] != -1) glUniform3fv(u_lightambient[i], 1, gstate_c.lightColor[0][i]);
if (u_lightdiffuse[i] != -1) glUniform3fv(u_lightdiffuse[i], 1, gstate_c.lightColor[1][i]);
if (u_lightspecular[i] != -1) glUniform3fv(u_lightspecular[i], 1, gstate_c.lightColor[2][i]);
}
}

View File

@ -9,7 +9,7 @@
#include "ShaderManager.h"
#include "TextureCache.h"
const GLint aLookup[] = {
const GLint aLookup[11] = {
GL_DST_COLOR,
GL_ONE_MINUS_DST_COLOR,
GL_SRC_ALPHA,
@ -23,7 +23,7 @@ const GLint aLookup[] = {
GL_CONSTANT_COLOR, // FIXA
};
const GLint bLookup[] = {
const GLint bLookup[11] = {
GL_SRC_COLOR,
GL_ONE_MINUS_SRC_COLOR,
GL_SRC_ALPHA,
@ -36,6 +36,7 @@ const GLint bLookup[] = {
GL_ONE_MINUS_DST_ALPHA, // should be 2x
GL_CONSTANT_COLOR, // FIXB
};
const GLint eqLookup[] = {
GL_FUNC_ADD,
GL_FUNC_SUBTRACT,
@ -107,6 +108,8 @@ void ApplyDrawState(int prim) {
int blendFuncA = gstate.getBlendFuncA();
int blendFuncB = gstate.getBlendFuncB();
int blendFuncEq = gstate.getBlendEq();
if (blendFuncA > GE_SRCBLEND_FIXA) blendFuncA = GE_SRCBLEND_FIXA;
if (blendFuncB > GE_DSTBLEND_FIXB) blendFuncB = GE_DSTBLEND_FIXB;
glstate.blendEquation.set(eqLookup[blendFuncEq]);

View File

@ -86,12 +86,14 @@ void TransformDrawEngine::DestroyDeviceObjects() {
glDeleteBuffers(NUM_VBOS, &ebo_[0]);
memset(vbo_, 0, sizeof(vbo_));
memset(ebo_, 0, sizeof(ebo_));
ClearTrackedVertexArrays();
}
void TransformDrawEngine::GLLost() {
// The objects have already been deleted.
memset(vbo_, 0, sizeof(vbo_));
memset(ebo_, 0, sizeof(ebo_));
ClearTrackedVertexArrays();
InitDeviceObjects();
}
@ -620,7 +622,9 @@ void TransformDrawEngine::SoftwareTransformAndDraw(
}
}
// TODO: Make a cache for glEnableVertexAttribArray and glVertexAttribPtr states,
// TODO: Add a post-transform cache here for multi-RECTANGLES only.
// Might help for text drawing.
// these spam the gDebugger log.
const int vertexSize = sizeof(transformed[0]);
@ -837,123 +841,121 @@ void TransformDrawEngine::Flush() {
LinkedShader *program = shaderManager_->ApplyShader(prim);
if (CanUseHardwareTransform(prevPrim_)) {
bool useVBO = g_Config.bUseVBO;
GLuint vbo = 0, ebo = 0;
int vertexCount = 0;
if (useVBO) {
if (g_Config.bVertexCache) {
u32 id = ComputeFastDCID();
auto iter = vai_.find(id);
VertexArrayInfo *vai;
if (vai_.find(id) != vai_.end()) {
// We've seen this before. Could have been a cached draw.
vai = iter->second;
} else {
vai = new VertexArrayInfo();
vai->decFmt = dec.GetDecVtxFmt();
vai_[id] = vai;
}
vai->lastFrame = gpuStats.numFrames;
// A pretty little state machine.
switch (vai->status) {
case VertexArrayInfo::VAI_INBUFFERABLE:
goto useSoftware;
case VertexArrayInfo::VAI_NEW:
{
// Haven't seen this one before.
u32 dataHash = ComputeHash();
vai->hash = dataHash;
vai->status = VertexArrayInfo::VAI_HASHING;
DecodeVerts(); // writes to indexGen
vertexCount = indexGen.VertexCount();
prim = indexGen.Prim();
if (!CanUseHardwareTransform(indexGen.Prim()))
{
vai->status = VertexArrayInfo::VAI_INBUFFERABLE;
goto useSoftware;
}
goto rotateVBO;
}
// Hashing - still gaining confidence about the buffer.
// But if we get this far it's likely to be worth creating a vertex buffer.
case VertexArrayInfo::VAI_HASHING:
{
u32 newHash = ComputeHash();
vai->numDraws++;
if (vai->numDraws > 100000) {
vai->status = VertexArrayInfo::VAI_RELIABLE;
}
if (newHash == vai->hash) {
gpuStats.numCachedDrawCalls++;
} else {
vai->status = VertexArrayInfo::VAI_UNRELIABLE;
}
if (vai->vbo == 0) {
DecodeVerts(); // TODO : Remove
vai->numVerts = indexGen.VertexCount();
vai->prim = indexGen.Prim();
glGenBuffers(1, &vai->vbo);
// TODO: in some cases we can avoid creating an element buffer.
// If there's only been one primitive type, and it's either TRIANGLES, LINES or POINTS,
// there is no need for the index buffer we built. We can then use glDrawArrays instead
// for a very minor speed boost.
//int seen = indexGen.SeenPrims() | 0x83204820;
//seen == (1 << GE_PRIM_TRIANGLES) || seen == (1 << GE_PRIM_LINES) || seen == (1 << GE_PRIM_POINTS)
bool useElements = true;
glBindBuffer(GL_ARRAY_BUFFER, vai->vbo);
glBufferData(GL_ARRAY_BUFFER, dec.GetDecVtxFmt().stride * indexGen.MaxIndex(), decoded, GL_STATIC_DRAW);
if (useElements) {
glGenBuffers(1, &vai->ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vai->ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short) * indexGen.VertexCount(), (GLvoid *)decIndex, GL_STATIC_DRAW);
}
} else {
glBindBuffer(GL_ARRAY_BUFFER, vai->vbo);
if (vai->ebo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vai->ebo);
}
vbo = vai->vbo;
ebo = vai->ebo;
vertexCount = vai->numVerts;
prim = vai->prim;
break;
}
// Reliable - we don't even bother hashing anymore. Right now we don't go here until after a very long time.
case VertexArrayInfo::VAI_RELIABLE:
{
vai->numDraws++;
gpuStats.numCachedDrawCalls++;
// DecodeVerts(); // TODO : Remove
vbo = vai->vbo;
ebo = vai->ebo;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (ebo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
vertexCount = vai->numVerts;
prim = vai->prim;
break;
}
case VertexArrayInfo::VAI_UNRELIABLE:
{
vai->numDraws++;
DecodeVerts();
vertexCount = indexGen.VertexCount();
prim = indexGen.Prim();
goto rotateVBO;
}
}
if (g_Config.bVertexCache) {
u32 id = ComputeFastDCID();
auto iter = vai_.find(id);
VertexArrayInfo *vai;
if (vai_.find(id) != vai_.end()) {
// We've seen this before. Could have been a cached draw.
vai = iter->second;
} else {
DecodeVerts();
vai = new VertexArrayInfo();
vai->decFmt = dec.GetDecVtxFmt();
vai_[id] = vai;
}
vai->lastFrame = gpuStats.numFrames;
// A pretty little state machine.
switch (vai->status) {
case VertexArrayInfo::VAI_INBUFFERABLE:
goto useSoftware;
case VertexArrayInfo::VAI_NEW:
{
// Haven't seen this one before.
u32 dataHash = ComputeHash();
vai->hash = dataHash;
vai->status = VertexArrayInfo::VAI_HASHING;
DecodeVerts(); // writes to indexGen
vertexCount = indexGen.VertexCount();
prim = indexGen.Prim();
if (!CanUseHardwareTransform(indexGen.Prim()))
{
vai->status = VertexArrayInfo::VAI_INBUFFERABLE;
goto useSoftware;
}
goto rotateVBO;
}
// Hashing - still gaining confidence about the buffer.
// But if we get this far it's likely to be worth creating a vertex buffer.
case VertexArrayInfo::VAI_HASHING:
{
u32 newHash = ComputeHash();
vai->numDraws++;
if (vai->numDraws > 100000) {
vai->status = VertexArrayInfo::VAI_RELIABLE;
}
if (newHash == vai->hash) {
gpuStats.numCachedDrawCalls++;
} else {
vai->status = VertexArrayInfo::VAI_UNRELIABLE;
}
if (vai->vbo == 0) {
DecodeVerts(); // TODO : Remove
vai->numVerts = indexGen.VertexCount();
vai->prim = indexGen.Prim();
glGenBuffers(1, &vai->vbo);
// TODO: in some cases we can avoid creating an element buffer.
// If there's only been one primitive type, and it's either TRIANGLES, LINES or POINTS,
// there is no need for the index buffer we built. We can then use glDrawArrays instead
// for a very minor speed boost.
//int seen = indexGen.SeenPrims() | 0x83204820;
//seen == (1 << GE_PRIM_TRIANGLES) || seen == (1 << GE_PRIM_LINES) || seen == (1 << GE_PRIM_POINTS)
bool useElements = true;
glBindBuffer(GL_ARRAY_BUFFER, vai->vbo);
glBufferData(GL_ARRAY_BUFFER, dec.GetDecVtxFmt().stride * indexGen.MaxIndex(), decoded, GL_STATIC_DRAW);
if (useElements) {
glGenBuffers(1, &vai->ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vai->ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short) * indexGen.VertexCount(), (GLvoid *)decIndex, GL_STATIC_DRAW);
}
} else {
glBindBuffer(GL_ARRAY_BUFFER, vai->vbo);
if (vai->ebo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vai->ebo);
}
vbo = vai->vbo;
ebo = vai->ebo;
vertexCount = vai->numVerts;
prim = vai->prim;
break;
}
// Reliable - we don't even bother hashing anymore. Right now we don't go here until after a very long time.
case VertexArrayInfo::VAI_RELIABLE:
{
vai->numDraws++;
gpuStats.numCachedDrawCalls++;
// DecodeVerts(); // TODO : Remove
vbo = vai->vbo;
ebo = vai->ebo;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (ebo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
vertexCount = vai->numVerts;
prim = vai->prim;
break;
}
case VertexArrayInfo::VAI_UNRELIABLE:
{
vai->numDraws++;
DecodeVerts();
vertexCount = indexGen.VertexCount();
prim = indexGen.Prim();
goto rotateVBO;
}
}
} else {
DecodeVerts();
rotateVBO:
if (g_Config.bUseVBO) {
// Just rotate VBO.
vbo = vbo_[curVbo_];
ebo = ebo_[curVbo_];
vertexCount = indexGen.VertexCount();
curVbo_++;
if (curVbo_ == NUM_VBOS)
curVbo_ = 0;
@ -962,6 +964,7 @@ rotateVBO:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short) * indexGen.VertexCount(), (GLvoid *)decIndex, GL_STREAM_DRAW);
}
vertexCount = indexGen.VertexCount();
}
DEBUG_LOG(G3D, "Flush prim %i! %i verts in one go", prim, vertexCount);
@ -971,15 +974,11 @@ rotateVBO:
glDrawArrays(glprim[prim], 0, vertexCount);
} else {
glDrawElements(glprim[prim], vertexCount, GL_UNSIGNED_SHORT, vbo ? 0 : (GLvoid*)decIndex);
if (useVBO) {
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short) * indexGen.VertexCount(), 0, GL_DYNAMIC_DRAW);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
if (useVBO) {
// glBufferData(GL_ARRAY_BUFFER, dec.GetDecVtxFmt().stride * indexGen.MaxIndex(), 0, GL_DYNAMIC_DRAW);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
if (ebo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
if (vbo)
glBindBuffer(GL_ARRAY_BUFFER, 0);
} else {
useSoftware:
DecodeVerts();