Improved render groups, use degenerate triangles

Degenerate triangles allows a single triangle strip call to be called on
multiple independent triangles without "connecting" strips inbetween the
triangles.
This commit is contained in:
Doyle Thai 2016-09-22 02:16:14 +10:00
parent ec7a9e41ff
commit ebe09bd345
3 changed files with 161 additions and 56 deletions

View File

@ -10,8 +10,8 @@
#define RENDER_BOUNDING_BOX FALSE #define RENDER_BOUNDING_BOX FALSE
INTERNAL addToRenderGroup(Renderer *renderer, Texture *texture, INTERNAL void addToRenderGroup(Renderer *renderer, Texture *texture,
RenderQuad renderQuad) Vertex *vertexList, i32 numVertexes)
{ {
/* Find vacant/matching render group */ /* Find vacant/matching render group */
RenderGroup *targetGroup = NULL; RenderGroup *targetGroup = NULL;
@ -20,8 +20,19 @@ INTERNAL addToRenderGroup(Renderer *renderer, Texture *texture,
RenderGroup *group = &renderer->groups[i]; RenderGroup *group = &renderer->groups[i];
if (group->tex == NULL || group->tex->id == texture->id) if (group->tex == NULL || group->tex->id == texture->id)
{ {
if (!group->tex) group->tex = texture; i32 freeVertexSlots = renderer->groupCapacity - group->vertexIndex;
targetGroup = &renderer->groups[i]; if (numVertexes < freeVertexSlots)
{
if (!group->tex)
{
// NOTE(doyle): Mark first vertex as degenerate vertex
group->vertexIndex++;
group->tex = texture;
}
targetGroup = &renderer->groups[i];
}
break; break;
} }
} }
@ -29,18 +40,21 @@ INTERNAL addToRenderGroup(Renderer *renderer, Texture *texture,
/* Valid group, add to the render group for rendering */ /* Valid group, add to the render group for rendering */
if (targetGroup) if (targetGroup)
{ {
if (targetGroup->quadIndex < ARRAY_COUNT(targetGroup->quads)) for (i32 i = 0; i < numVertexes; i++)
{ {
targetGroup->quads[targetGroup->quadIndex++] = renderQuad; targetGroup->vertexList[targetGroup->vertexIndex++] = vertexList[i];
}
else
{
// TODO(doyle): Log no remaining render quad slots in group
} }
} }
else else
{ {
// TODO(doyle): Log no remaining render groups // TODO(doyle): Log no remaining render groups
DEBUG_LOG(
"WARNING: All render groups used up, some items will not be "
"rendered!");
printf(
"WARNING: All render groups used up, some items will not be "
"rendered!\n");
} }
} }
@ -62,29 +76,42 @@ INTERNAL inline void flipTexCoord(v4 *texCoords, b32 flipX, b32 flipY)
} }
INTERNAL void updateBufferObject(Renderer *const renderer, INTERNAL void updateBufferObject(Renderer *const renderer,
RenderQuad *const quads, const i32 numQuads) const Vertex *const vertexList,
const i32 numVertex)
{ {
// TODO(doyle): We assume that vbo and vao are assigned // TODO(doyle): We assume that vbo and vao are assigned
const i32 numVertexesInQuad = 4; renderer->numVertexesInVbo = numVertex;
renderer->numVertexesInVbo = numQuads * numVertexesInQuad;
glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo); glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo);
glBufferData(GL_ARRAY_BUFFER, numQuads * sizeof(RenderQuad), quads, glBufferData(GL_ARRAY_BUFFER, numVertex * sizeof(Vertex), vertexList,
GL_STREAM_DRAW); GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
} }
INTERNAL void bufferRenderGroupToGL(Renderer *renderer, RenderGroup *group) INTERNAL void bufferRenderGroupToGL(Renderer *renderer, RenderGroup *group)
{ {
updateBufferObject(renderer, group->quads, group->quadIndex); updateBufferObject(renderer, group->vertexList, group->vertexIndex);
} }
INTERNAL RenderQuad createTexQuad(Renderer *renderer, v4 quadRect, INTERNAL RenderQuad_ createTexQuad(Renderer *renderer, v4 quadRect,
RenderTex renderTex) RenderTex renderTex)
{ {
/*
* Rendering order
*
* 0 2
* +---+
* + /+
* + / +
* +/ +
* +---+
* 1 3
*
*/
// NOTE(doyle): Draws a series of triangles using vertices v0, v1, v2, then // NOTE(doyle): Draws a series of triangles using vertices v0, v1, v2, then
// v2, v1, v3 (note the order) // v2, v1, v3 (note the order)
RenderQuad result = {0}; RenderQuad_ result = {0};
/* Convert screen coordinates to normalised device coordinates */ /* Convert screen coordinates to normalised device coordinates */
v4 quadRectNdc = quadRect; v4 quadRectNdc = quadRect;
@ -106,21 +133,21 @@ INTERNAL RenderQuad createTexQuad(Renderer *renderer, v4 quadRect,
} }
/* Form the quad */ /* Form the quad */
result.vertex[0] = V4(quadRectNdc.x, quadRectNdc.w, texRectNdc.x, result.vertex[0].e = V4(quadRectNdc.x, quadRectNdc.w, texRectNdc.x,
texRectNdc.w); // Top left texRectNdc.w); // Top left
result.vertex[1] = V4(quadRectNdc.x, quadRectNdc.y, texRectNdc.x, result.vertex[1].e = V4(quadRectNdc.x, quadRectNdc.y, texRectNdc.x,
texRectNdc.y); // Bottom left texRectNdc.y); // Bottom left
result.vertex[2] = V4(quadRectNdc.z, quadRectNdc.w, texRectNdc.z, result.vertex[2].e = V4(quadRectNdc.z, quadRectNdc.w, texRectNdc.z,
texRectNdc.w); // Top right texRectNdc.w); // Top right
result.vertex[3] = V4(quadRectNdc.z, quadRectNdc.y, texRectNdc.z, result.vertex[3].e = V4(quadRectNdc.z, quadRectNdc.y, texRectNdc.z,
texRectNdc.y); // Bottom right texRectNdc.y); // Bottom right
return result; return result;
} }
INTERNAL inline RenderQuad INTERNAL inline RenderQuad_
createDefaultTexQuad(Renderer *renderer, RenderTex renderTex) createDefaultTexQuad(Renderer *renderer, RenderTex renderTex)
{ {
RenderQuad result = {0}; RenderQuad_ result = {0};
v4 defaultQuad = V4(0.0f, 0.0f, renderer->size.w, renderer->size.h); v4 defaultQuad = V4(0.0f, 0.0f, renderer->size.w, renderer->size.h);
result = createTexQuad(renderer, defaultQuad, renderTex); result = createTexQuad(renderer, defaultQuad, renderTex);
return result; return result;
@ -193,10 +220,11 @@ RenderTex renderer_createNullRenderTex(AssetManager *const assetManager)
void renderer_rect(Renderer *const renderer, Rect camera, v2 pos, v2 size, void renderer_rect(Renderer *const renderer, Rect camera, v2 pos, v2 size,
v2 pivotPoint, f32 rotate, RenderTex renderTex, v4 color) v2 pivotPoint, f32 rotate, RenderTex renderTex, v4 color)
{ {
RenderQuad quad = createDefaultTexQuad(renderer, renderTex); // TODO(doyle): Use render groups
updateBufferObject(renderer, &quad, 1);
v2 posInCameraSpace = v2_sub(pos, camera.pos); v2 posInCameraSpace = v2_sub(pos, camera.pos);
RenderQuad_ quad = createDefaultTexQuad(renderer, renderTex);
updateBufferObject(renderer, quad.vertex, ARRAY_COUNT(quad.vertex));
renderObject(renderer, posInCameraSpace, size, pivotPoint, rotate, renderObject(renderer, posInCameraSpace, size, pivotPoint, rotate,
color, renderTex.tex); color, renderTex.tex);
} }
@ -217,8 +245,21 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
if (math_pointInRect(camera, leftAlignedP) || if (math_pointInRect(camera, leftAlignedP) ||
math_pointInRect(camera, rightAlignedP)) math_pointInRect(camera, rightAlignedP))
{ {
i32 quadIndex = 0;
RenderQuad *stringQuads = PLATFORM_MEM_ALLOC(arena, strLen, RenderQuad); #define DISABLE_TEXT_RENDER_GROUPS TRUE
#if RENDERER_USE_RENDER_GROUPS && !DISABLE_TEXT_RENDER_GROUPS
// NOTE(doyle): 2 degenerate vertexes, at start and end of string since-
// chars are rendered side by side. Reserve first vertex as degenerate.
i32 vertexIndex = 1;
const i32 numVertexPerQuad = 4;
const i32 numVertexesToAlloc = (strLen * numVertexPerQuad) + 2;
#else
i32 vertexIndex = 0;
const i32 numVertexPerQuad = 4;
const i32 numVertexesToAlloc = (strLen * numVertexPerQuad);
#endif
Vertex *vertexList =
PLATFORM_MEM_ALLOC(arena, numVertexesToAlloc, Vertex);
v2 posInCameraSpace = v2_sub(pos, camera.pos); v2 posInCameraSpace = v2_sub(pos, camera.pos);
@ -250,18 +291,32 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
flipTexCoord(&deprecatedTexRect, FALSE, TRUE); flipTexCoord(&deprecatedTexRect, FALSE, TRUE);
RenderTex renderTex = {tex, deprecatedTexRect}; RenderTex renderTex = {tex, deprecatedTexRect};
RenderQuad charQuad = RenderQuad_ charQuad =
createTexQuad(renderer, charRectOnScreen, renderTex); createTexQuad(renderer, charRectOnScreen, renderTex);
stringQuads[quadIndex++] = charQuad;
for (i32 i = 0; i < ARRAY_COUNT(charQuad.vertex); i++)
{
vertexList[vertexIndex++] = charQuad.vertex[i];
}
} }
// NOTE(doyle): We render at the renderer's size because we create quads // NOTE(doyle): We render at the renderer's size because we create quads
// relative to the window size, hence we also render at the origin since // relative to the window size, hence we also render at the origin since
// we're rendering a window sized buffer // we're rendering a window sized buffer
updateBufferObject(renderer, stringQuads, quadIndex);
// TODO(doyle): Render group differentiate between null tex and colors
#if RENDERER_USE_RENDER_GROUPS && !DISABLE_TEXT_RENDER_GROUPS
// NOTE(doyle): Degenerate vertex at end of string
vertexList[vertexIndex++] = vertexList[vertexIndex - 1];
addToRenderGroup(renderer, tex, vertexList, numVertexesToAlloc);
#else
updateBufferObject(renderer, vertexList, vertexIndex);
renderObject(renderer, V2(0.0f, 0.0f), renderer->size, pivotPoint, renderObject(renderer, V2(0.0f, 0.0f), renderer->size, pivotPoint,
rotate, color, tex); rotate, color, tex);
PLATFORM_MEM_FREE(arena, stringQuads, strLen * sizeof(RenderQuad)); #endif
PLATFORM_MEM_FREE(arena, vertexList,
sizeof(Vertex) * numVertexesToAlloc);
} }
} }
@ -296,19 +351,49 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity,
RenderTex renderTex = {entity->tex, animTexRect}; RenderTex renderTex = {entity->tex, animTexRect};
#if RENDERER_USE_RENDER_GROUPS
// TODO(doyle): getRect needs a better name // TODO(doyle): getRect needs a better name
v2 posInCameraSpace = v2_sub(entity->pos, camera.pos); v2 posInCameraSpace = v2_sub(entity->pos, camera.pos);
#if RENDERER_USE_RENDER_GROUPS
v4 entityVertexOnScreen = math_getRect(posInCameraSpace, entity->size); v4 entityVertexOnScreen = math_getRect(posInCameraSpace, entity->size);
RenderQuad entityQuad = RenderQuad_ entityQuad =
createTexQuad(renderer, entityVertexOnScreen, renderTex); createTexQuad(renderer, entityVertexOnScreen, renderTex);
addToRenderGroup(renderer, entity->tex, entityQuad); /*
NOTE(doyle): Entity rendering is always done in two pairs of
triangles, i.e. quad. To batch render quads as a triangle strip, we
need to create zero-area triangles which OGL will omit from
rendering. Render groups are initialised with 1 degenerate vertex and
then the first two vertexes sent to the render group are the same to
form 1 zero-area triangle strip.
A degenerate vertex has to be copied from the last vertex in the
rendering quad, to repeat this process as more entities are
renderered.
Alternative implementation is recognising if the rendered
entity is the first in its render group, then we don't need to init
a degenerate vertex, and only at the end of its vertex list. But on
subsequent renders, we need a degenerate vertex at the front to
create the zero-area triangle strip.
The first has been chosen for simplicity of code, at the cost of
2 degenerate vertexes at the start of each render group.
*/
Vertex degenerateVertexes[2] = {entityQuad.vertex[0],
entityQuad.vertex[3]};
Vertex vertexList[6] = {degenerateVertexes[0], entityQuad.vertex[0],
entityQuad.vertex[1], entityQuad.vertex[2],
entityQuad.vertex[3], degenerateVertexes[1]};
addToRenderGroup(renderer, entity->tex, vertexList,
ARRAY_COUNT(vertexList));
#else #else
RenderQuad entityQuad = createDefaultTexQuad(renderer, renderTex); RenderQuad_ entityQuad = createDefaultTexQuad(renderer, renderTex);
// TODO(doyle): getRect needs a better name // TODO(doyle): getRect needs a better name
updateBufferObject(renderer, &entityQuad, 1); updateBufferObject(renderer, entityQuad.vertex,
v2 posInCameraSpace = v2_sub(entity->pos, camera.pos); ARRAY_COUNT(entityQuad.vertex));
renderObject(renderer, posInCameraSpace, renderObject(renderer, posInCameraSpace,
entity->size, pivotPoint, entity->size, pivotPoint,
entity->rotation + rotate, color, entity->tex); entity->rotation + rotate, color, entity->tex);
@ -331,8 +416,9 @@ void renderer_renderGroups(Renderer *renderer)
renderObject(renderer, V2(0.0f, 0.0f), renderer->size, pivotPoint, renderObject(renderer, V2(0.0f, 0.0f), renderer->size, pivotPoint,
rotate, color, currGroup->tex); rotate, color, currGroup->tex);
RenderGroup clear = {0}; RenderGroup cleanGroup = {0};
*currGroup = clear; cleanGroup.vertexList = currGroup->vertexList;
*currGroup = cleanGroup;
} }
} }
} }

View File

@ -88,7 +88,7 @@ INTERNAL void rendererInit(GameState *state, v2 windowSize)
/* Configure VAO */ /* Configure VAO */
const GLuint numVertexElements = 4; const GLuint numVertexElements = 4;
const GLuint vertexSize = sizeof(v4); const GLuint vertexSize = sizeof(Vertex);
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, numVertexElements, GL_FLOAT, GL_FALSE, vertexSize, glVertexAttribPointer(0, numVertexElements, GL_FLOAT, GL_FALSE, vertexSize,
(GLvoid *)0); (GLvoid *)0);
@ -99,6 +99,14 @@ INTERNAL void rendererInit(GameState *state, v2 windowSize)
glBindVertexArray(0); glBindVertexArray(0);
GL_CHECK_ERROR(); GL_CHECK_ERROR();
// TODO(doyle): Lazy allocate render group capacity
renderer->groupCapacity = 1024;
for (i32 i = 0; i < ARRAY_COUNT(renderer->groups); i++)
{
renderer->groups[i].vertexList =
PLATFORM_MEM_ALLOC(&state->arena, renderer->groupCapacity, Vertex);
}
#ifdef DENGINE_DEBUG #ifdef DENGINE_DEBUG
DEBUG_LOG("Renderer initialised"); DEBUG_LOG("Renderer initialised");
#endif #endif
@ -1673,7 +1681,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
} }
if (getKeyStatus(&keys[keycode_left_square_bracket], if (getKeyStatus(&keys[keycode_left_square_bracket],
readkeytype_delayedRepeat, 0.25f, dt)) readkeytype_delayedRepeat, 0.0f, dt))
{ {
Renderer *renderer = &state->renderer; Renderer *renderer = &state->renderer;

View File

@ -12,13 +12,22 @@ typedef struct MemoryArena MemoryArena;
typedef struct Shader Shader; typedef struct Shader Shader;
typedef struct Texture Texture; typedef struct Texture Texture;
typedef union Vertex
{
struct
{
v2 pos;
v2 texCoords;
};
v4 e;
} Vertex;
typedef struct RenderQuad typedef struct RenderQuad
{ {
// Vertex composition Vertex vertex[4];
// x, y: Coordinates - of entity on screen } RenderQuad_;
// z, w: Texture Coords - of texture for this quad
v4 vertex[4];
} RenderQuad;
typedef struct RenderTex typedef struct RenderTex
{ {
@ -30,8 +39,9 @@ typedef struct RenderTex
typedef struct RenderGroup typedef struct RenderGroup
{ {
Texture *tex; Texture *tex;
RenderQuad quads[100];
i32 quadIndex; Vertex *vertexList;
i32 vertexIndex;
} RenderGroup; } RenderGroup;
typedef struct Renderer typedef struct Renderer
@ -43,7 +53,8 @@ typedef struct Renderer
v2 vertexNdcFactor; v2 vertexNdcFactor;
v2 size; v2 size;
RenderGroup groups[100]; RenderGroup groups[16];
i32 groupCapacity;
} Renderer; } Renderer;
#define RENDERER_USE_RENDER_GROUPS TRUE #define RENDERER_USE_RENDER_GROUPS TRUE