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
INTERNAL addToRenderGroup(Renderer *renderer, Texture *texture,
RenderQuad renderQuad)
INTERNAL void addToRenderGroup(Renderer *renderer, Texture *texture,
Vertex *vertexList, i32 numVertexes)
{
/* Find vacant/matching render group */
RenderGroup *targetGroup = NULL;
@ -20,8 +20,19 @@ INTERNAL addToRenderGroup(Renderer *renderer, Texture *texture,
RenderGroup *group = &renderer->groups[i];
if (group->tex == NULL || group->tex->id == texture->id)
{
if (!group->tex) group->tex = texture;
i32 freeVertexSlots = renderer->groupCapacity - group->vertexIndex;
if (numVertexes < freeVertexSlots)
{
if (!group->tex)
{
// NOTE(doyle): Mark first vertex as degenerate vertex
group->vertexIndex++;
group->tex = texture;
}
targetGroup = &renderer->groups[i];
}
break;
}
}
@ -29,18 +40,21 @@ INTERNAL addToRenderGroup(Renderer *renderer, Texture *texture,
/* Valid group, add to the render group for rendering */
if (targetGroup)
{
if (targetGroup->quadIndex < ARRAY_COUNT(targetGroup->quads))
for (i32 i = 0; i < numVertexes; i++)
{
targetGroup->quads[targetGroup->quadIndex++] = renderQuad;
}
else
{
// TODO(doyle): Log no remaining render quad slots in group
targetGroup->vertexList[targetGroup->vertexIndex++] = vertexList[i];
}
}
else
{
// 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,
RenderQuad *const quads, const i32 numQuads)
const Vertex *const vertexList,
const i32 numVertex)
{
// TODO(doyle): We assume that vbo and vao are assigned
const i32 numVertexesInQuad = 4;
renderer->numVertexesInVbo = numQuads * numVertexesInQuad;
renderer->numVertexesInVbo = numVertex;
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);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
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)
{
/*
* Rendering order
*
* 0 2
* +---+
* + /+
* + / +
* +/ +
* +---+
* 1 3
*
*/
// NOTE(doyle): Draws a series of triangles using vertices v0, v1, v2, then
// v2, v1, v3 (note the order)
RenderQuad result = {0};
RenderQuad_ result = {0};
/* Convert screen coordinates to normalised device coordinates */
v4 quadRectNdc = quadRect;
@ -106,21 +133,21 @@ INTERNAL RenderQuad createTexQuad(Renderer *renderer, v4 quadRect,
}
/* 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
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
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
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
return result;
}
INTERNAL inline RenderQuad
INTERNAL inline RenderQuad_
createDefaultTexQuad(Renderer *renderer, RenderTex renderTex)
{
RenderQuad result = {0};
RenderQuad_ result = {0};
v4 defaultQuad = V4(0.0f, 0.0f, renderer->size.w, renderer->size.h);
result = createTexQuad(renderer, defaultQuad, renderTex);
return result;
@ -193,10 +220,11 @@ RenderTex renderer_createNullRenderTex(AssetManager *const assetManager)
void renderer_rect(Renderer *const renderer, Rect camera, v2 pos, v2 size,
v2 pivotPoint, f32 rotate, RenderTex renderTex, v4 color)
{
RenderQuad quad = createDefaultTexQuad(renderer, renderTex);
updateBufferObject(renderer, &quad, 1);
// TODO(doyle): Use render groups
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,
color, renderTex.tex);
}
@ -217,8 +245,21 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
if (math_pointInRect(camera, leftAlignedP) ||
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);
@ -250,18 +291,32 @@ void renderer_string(Renderer *const renderer, MemoryArena *arena, Rect camera,
flipTexCoord(&deprecatedTexRect, FALSE, TRUE);
RenderTex renderTex = {tex, deprecatedTexRect};
RenderQuad charQuad =
RenderQuad_ charQuad =
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
// relative to the window size, hence we also render at the origin since
// 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,
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};
#if RENDERER_USE_RENDER_GROUPS
// TODO(doyle): getRect needs a better name
v2 posInCameraSpace = v2_sub(entity->pos, camera.pos);
#if RENDERER_USE_RENDER_GROUPS
v4 entityVertexOnScreen = math_getRect(posInCameraSpace, entity->size);
RenderQuad entityQuad =
RenderQuad_ entityQuad =
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
RenderQuad entityQuad = createDefaultTexQuad(renderer, renderTex);
RenderQuad_ entityQuad = createDefaultTexQuad(renderer, renderTex);
// TODO(doyle): getRect needs a better name
updateBufferObject(renderer, &entityQuad, 1);
v2 posInCameraSpace = v2_sub(entity->pos, camera.pos);
updateBufferObject(renderer, entityQuad.vertex,
ARRAY_COUNT(entityQuad.vertex));
renderObject(renderer, posInCameraSpace,
entity->size, pivotPoint,
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,
rotate, color, currGroup->tex);
RenderGroup clear = {0};
*currGroup = clear;
RenderGroup cleanGroup = {0};
cleanGroup.vertexList = currGroup->vertexList;
*currGroup = cleanGroup;
}
}
}

View File

@ -88,7 +88,7 @@ INTERNAL void rendererInit(GameState *state, v2 windowSize)
/* Configure VAO */
const GLuint numVertexElements = 4;
const GLuint vertexSize = sizeof(v4);
const GLuint vertexSize = sizeof(Vertex);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, numVertexElements, GL_FLOAT, GL_FALSE, vertexSize,
(GLvoid *)0);
@ -99,6 +99,14 @@ INTERNAL void rendererInit(GameState *state, v2 windowSize)
glBindVertexArray(0);
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
DEBUG_LOG("Renderer initialised");
#endif
@ -1673,7 +1681,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
}
if (getKeyStatus(&keys[keycode_left_square_bracket],
readkeytype_delayedRepeat, 0.25f, dt))
readkeytype_delayedRepeat, 0.0f, dt))
{
Renderer *renderer = &state->renderer;

View File

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