From ebe09bd345889a0df48a085ae62c705318533fb4 Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Thu, 22 Sep 2016 02:16:14 +1000 Subject: [PATCH] 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. --- src/Renderer.c | 178 ++++++++++++++++++++++++--------- src/WorldTraveller.c | 12 ++- src/include/Dengine/Renderer.h | 27 +++-- 3 files changed, 161 insertions(+), 56 deletions(-) diff --git a/src/Renderer.c b/src/Renderer.c index c62cb64..617e707 100644 --- a/src/Renderer.c +++ b/src/Renderer.c @@ -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; - targetGroup = &renderer->groups[i]; + 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, - RenderTex renderTex) +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, - texRectNdc.w); // Top left - result.vertex[1] = V4(quadRectNdc.x, quadRectNdc.y, texRectNdc.x, - texRectNdc.y); // Bottom left - result.vertex[2] = V4(quadRectNdc.z, quadRectNdc.w, texRectNdc.z, - texRectNdc.w); // Top right - result.vertex[3] = V4(quadRectNdc.z, quadRectNdc.y, texRectNdc.z, - texRectNdc.y); // Bottom right + result.vertex[0].e = V4(quadRectNdc.x, quadRectNdc.w, texRectNdc.x, + texRectNdc.w); // Top left + result.vertex[1].e = V4(quadRectNdc.x, quadRectNdc.y, texRectNdc.x, + texRectNdc.y); // Bottom left + result.vertex[2].e = V4(quadRectNdc.z, quadRectNdc.w, texRectNdc.z, + texRectNdc.w); // Top right + 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 + // 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; } } } diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 149391f..28341fc 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -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; diff --git a/src/include/Dengine/Renderer.h b/src/include/Dengine/Renderer.h index 4f9589e..212654f 100644 --- a/src/include/Dengine/Renderer.h +++ b/src/include/Dengine/Renderer.h @@ -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