Tilemap: Draw consecutive scanrows in one draw call

If consecutive scanrows in the scene list have no foreign
elements in between them, we batch them up and draw them
in one glDrawElements() call.

This should reduce the Tilemap induced draw calls on
average by at least 50 percent.
This commit is contained in:
Jonas Kulla 2013-10-13 22:00:38 +02:00
parent 99cfe9aefd
commit 23e712a730
2 changed files with 59 additions and 2 deletions

View File

@ -31,6 +31,7 @@ class SceneElement;
class Viewport; class Viewport;
class Window; class Window;
class ScanRow; class ScanRow;
struct TilemapPrivate;
class Scene class Scene
{ {
@ -99,6 +100,7 @@ protected:
friend class Scene; friend class Scene;
friend class Viewport; friend class Viewport;
friend struct TilemapPrivate;
}; };
#endif // SCENE_H #endif // SCENE_H

View File

@ -224,6 +224,14 @@ struct ScanRow : public ViewportElement
GLsizei vboCount; GLsizei vboCount;
TilemapPrivate *p; TilemapPrivate *p;
/* If this row is part of a batch and not
* the head, it is 'muted' via this flag */
bool batchedFlag;
/* If this row is a batch head, this variable
* holds the element count of the entire batch */
GLsizei vboBatchCount;
ScanRow(TilemapPrivate *p, Viewport *viewport, int index); ScanRow(TilemapPrivate *p, Viewport *viewport, int index);
void draw(); void draw();
@ -1022,6 +1030,47 @@ struct TilemapPrivate
} }
} }
/* When there are two or more scanrows with no other
* elements between them in the scene list, we can
* render them in a batch (as the scanrow data itself
* is ordered sequentially in VRAM). Every frame, we
* scan the scene list for such sequential rows and
* batch them up for drawing. The first row of the batch
* (the "batch head") executes the draw call, all others
* are muted via the 'batchedFlag'. For simplicity,
* single sized batches are possible. */
void prepareScanrowBatches()
{
const QVector<ScanRow*> &scanrows = elem.scanrows;
for (int i = 0; i < scanrows.size(); ++i)
{
ScanRow *batchHead = scanrows[i];
batchHead->batchedFlag = false;
GLsizei vboBatchCount = batchHead->vboCount;
IntruListLink<SceneElement> *iter = &batchHead->link;
for (i = i+1; i < scanrows.size(); ++i)
{
iter = iter->next;
ScanRow *row = scanrows[i];
/* Check if the next SceneElement is also
* the next scanrow in our list. If not,
* the current batch is complete */
if (iter != &row->link)
break;
vboBatchCount += row->vboCount;
row->batchedFlag = true;
}
batchHead->vboBatchCount = vboBatchCount;
--i;
}
}
void prepare() void prepare()
{ {
if (!verifyResources()) if (!verifyResources())
@ -1064,6 +1113,8 @@ struct TilemapPrivate
zOrderDirty = false; zOrderDirty = false;
} }
prepareScanrowBatches();
tilemapReady = true; tilemapReady = true;
} }
}; };
@ -1152,7 +1203,8 @@ void GroundLayer::onGeometryChange(const Scene::Geometry &geo)
ScanRow::ScanRow(TilemapPrivate *p, Viewport *viewport, int index) ScanRow::ScanRow(TilemapPrivate *p, Viewport *viewport, int index)
: ViewportElement(viewport, 32 + index*32, p->elem.scanrowStamp), : ViewportElement(viewport, 32 + index*32, p->elem.scanrowStamp),
index(index), index(index),
p(p) p(p),
vboBatchCount(0)
{ {
vboOffset = p->scanrowBases[index] * sizeof(uint32_t) * 6; vboOffset = p->scanrowBases[index] * sizeof(uint32_t) * 6;
vboCount = p->scanrowSize(index) * 6; vboCount = p->scanrowSize(index) * 6;
@ -1160,6 +1212,9 @@ ScanRow::ScanRow(TilemapPrivate *p, Viewport *viewport, int index)
void ScanRow::draw() void ScanRow::draw()
{ {
if (batchedFlag)
return;
SimpleShader &shader = shState->simpleShader(); SimpleShader &shader = shState->simpleShader();
shader.bind(); shader.bind();
shader.applyViewportProj(); shader.applyViewportProj();
@ -1187,7 +1242,7 @@ void ScanRow::draw()
void ScanRow::drawInt() void ScanRow::drawInt()
{ {
glDrawElements(GL_TRIANGLES, vboCount, glDrawElements(GL_TRIANGLES, vboBatchCount,
GL_UNSIGNED_INT, (GLvoid*) (vboOffset + p->tiles.frameIdx * p->tiles.bufferFrameSize)); GL_UNSIGNED_INT, (GLvoid*) (vboOffset + p->tiles.frameIdx * p->tiles.bufferFrameSize));
} }