Dengine/src/Renderer.c

626 lines
18 KiB
C
Raw Normal View History

2016-06-28 06:00:03 +00:00
#include "Dengine/Renderer.h"
#include "Dengine/AssetManager.h"
#include "Dengine/Assets.h"
#include "Dengine/Debug.h"
#include "Dengine/Entity.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/OpenGL.h"
#include "Dengine/Shader.h"
#include "Dengine/Texture.h"
2016-06-09 05:49:03 +00:00
2016-11-11 10:43:03 +00:00
typedef struct RenderQuad
{
RenderVertex vertexList[4];
} RenderQuad;
2016-11-11 10:43:03 +00:00
// NOTE(doyle): A vertex batch is the batch of vertexes comprised to make one
// shape
INTERNAL void beginVertexBatch(Renderer *renderer)
{
ASSERT(renderer->vertexBatchState == vertexbatchstate_off);
ASSERT(renderer->groupIndexForVertexBatch == -1);
renderer->vertexBatchState = vertexbatchstate_initial_add;
}
/*
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.
The 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.
*/
INTERNAL void endVertexBatch(Renderer *renderer)
{
ASSERT(renderer->vertexBatchState != vertexbatchstate_off);
ASSERT(renderer->groupIndexForVertexBatch != -1);
i32 numDegenerateVertexes = 1;
RenderGroup *group = &renderer->groups[renderer->groupIndexForVertexBatch];
i32 freeVertexSlots = renderer->groupCapacity - group->vertexIndex;
if (numDegenerateVertexes < freeVertexSlots)
{
RenderVertex degenerateVertex =
group->vertexList[group->vertexIndex - 1];
group->vertexList[group->vertexIndex++] = degenerateVertex;
}
renderer->vertexBatchState = vertexbatchstate_off;
renderer->groupIndexForVertexBatch = -1;
}
INTERNAL void applyRotationToVertexes(v2 pos, v2 pivotPoint, Radians rotate,
RenderVertex *vertexList,
i32 vertexListSize)
{
if (rotate == 0) return;
// NOTE(doyle): Move the world origin to the base position of the object.
// Then move the origin to the pivot point (e.g. center of object) and
// rotate from that point.
v2 pointOfRotation = v2_add(pivotPoint, pos);
mat4 rotateMat = mat4_translate(pointOfRotation.x, pointOfRotation.y, 0.0f);
rotateMat = mat4_mul(rotateMat, mat4_rotate(rotate, 0.0f, 0.0f, 1.0f));
rotateMat = mat4_mul(rotateMat, mat4_translate(-pointOfRotation.x,
-pointOfRotation.y, 0.0f));
for (i32 i = 0; i < vertexListSize; i++)
{
// NOTE(doyle): Manual matrix multiplication since vertex pos is 2D and
// matrix is 4D
v2 oldP = vertexList[i].pos;
v2 newP = {0};
newP.x = (oldP.x * rotateMat.e[0][0]) + (oldP.y * rotateMat.e[1][0]) +
(rotateMat.e[3][0]);
newP.y = (oldP.x * rotateMat.e[0][1]) + (oldP.y * rotateMat.e[1][1]) +
(rotateMat.e[3][1]);
vertexList[i].pos = newP;
}
}
INTERNAL void addVertexToRenderGroup_(Renderer *renderer, Texture *tex,
v4 color, RenderVertex *vertexList,
i32 numVertexes,
enum RenderMode targetRenderMode,
RenderFlags flags)
{
ASSERT(renderer->vertexBatchState != vertexbatchstate_off);
ASSERT(numVertexes > 0);
2016-09-23 11:15:22 +00:00
#ifdef DENGINE_DEBUG
2016-09-23 11:15:22 +00:00
for (i32 i = 0; i < numVertexes; i++)
debug_countIncrement(debugcount_numVertex);
#endif
/* Find vacant/matching render group */
RenderGroup *targetGroup = NULL;
for (i32 i = 0; i < ARRAY_COUNT(renderer->groups); i++)
{
2016-09-23 11:15:22 +00:00
RenderGroup *group = &renderer->groups[i];
b32 groupIsValid = FALSE;
if (group->init)
{
/* If the textures match and have the same color modulation, we can
* add these vertices to the current group */
b32 renderModeMatches = FALSE;
if (group->mode == targetRenderMode) renderModeMatches = TRUE;
b32 colorMatches = FALSE;
if (v4_equals(group->color, color)) colorMatches = TRUE;
b32 flagsMatches = FALSE;
if (group->flags == flags) flagsMatches = TRUE;
b32 texMatches = TRUE;
if (!tex && !group->tex)
{
texMatches = TRUE;
}
else if (tex && group->tex)
{
if (group->tex->id == tex->id)
{
texMatches = TRUE;
}
}
if (texMatches && colorMatches && renderModeMatches && flagsMatches)
groupIsValid = TRUE;
}
else
{
/* New group, unused so initialise it */
groupIsValid = TRUE;
group->init = TRUE;
group->tex = tex;
group->color = color;
2016-11-11 10:43:03 +00:00
group->mode = targetRenderMode;
group->flags = flags;
2016-09-23 11:15:22 +00:00
#ifdef DENGINE_DEBUG
debug_countIncrement(debugcount_renderGroups);
#endif
}
if (groupIsValid)
{
i32 freeVertexSlots = renderer->groupCapacity - group->vertexIndex;
// NOTE(doyle): Two at start, two at end
i32 numDegenerateVertexes = 0;
if (renderer->vertexBatchState == vertexbatchstate_initial_add)
numDegenerateVertexes = 1;
if ((numDegenerateVertexes + numVertexes) < freeVertexSlots)
{
if (i != 0)
{
RenderGroup tmp = renderer->groups[0];
renderer->groups[0] = renderer->groups[i];
renderer->groups[i] = tmp;
}
targetGroup = &renderer->groups[0];
break;
}
}
}
/* Valid group, add to the render group for rendering */
if (targetGroup)
{
// NOTE(doyle): If we are adding 3 vertexes, then we are adding a
// triangle to the triangle strip. If so, then depending on which "n-th"
// triangle it is we're adding, the winding order in a t-strip
// alternates with each triangle (including degenerates). Hence we track
// so we know the last winding order in the group.
// For this to work, we must ensure all incoming vertexes are winding in
// ccw order initially. There is also the presumption that, other
// rendering methods, such as the quad, consists of an even number of
// triangles such that the winding order gets alternated back to the
// same order it started with.
if (numVertexes == 3)
{
if (targetGroup->clockwiseWinding)
{
RenderVertex tmp = vertexList[0];
vertexList[0] = vertexList[2];
vertexList[2] = tmp;
}
targetGroup->clockwiseWinding =
(targetGroup->clockwiseWinding) ? FALSE : TRUE;
}
if (renderer->vertexBatchState == vertexbatchstate_initial_add)
{
if (targetGroup->vertexIndex != 0)
{
targetGroup->vertexList[targetGroup->vertexIndex++] =
vertexList[0];
}
renderer->vertexBatchState = vertexbatchstate_active;
// NOTE(doyle): We swap groups to the front if it is valid, so
// target group should always be 0
ASSERT(renderer->groupIndexForVertexBatch == -1);
renderer->groupIndexForVertexBatch = 0;
}
for (i32 i = 0; i < numVertexes; i++)
{
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");
}
}
INTERNAL inline void flipTexCoord(v4 *texCoords, b32 flipX, b32 flipY)
{
if (flipX)
{
v4 tmp = *texCoords;
texCoords->x = tmp.z;
texCoords->z = tmp.x;
}
if (flipY)
{
v4 tmp = *texCoords;
texCoords->y = tmp.w;
texCoords->w = tmp.y;
}
}
2016-11-11 10:43:03 +00:00
INTERNAL void bufferRenderGroupToGL(Renderer *renderer, RenderGroup *group)
{
RenderVertex *vertexList = group->vertexList;
2016-11-11 10:43:03 +00:00
i32 numVertex = group->vertexIndex;
// TODO(doyle): We assume that vbo and vao are assigned
renderer->numVertexesInVbo = numVertex;
2016-11-11 10:43:03 +00:00
glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo[group->mode]);
glBufferData(GL_ARRAY_BUFFER, numVertex * sizeof(RenderVertex), vertexList,
GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
INTERNAL v4 getTexRectNormaliseDeviceCoords(RenderTex renderTex)
{
/* Convert texture coordinates to normalised texture coordinates */
v4 result = renderTex.texRect;
if (renderTex.tex)
{
v2 texNdcFactor =
V2(1.0f / renderTex.tex->width, 1.0f / renderTex.tex->height);
result.e[0] *= texNdcFactor.w;
result.e[1] *= texNdcFactor.h;
result.e[2] *= texNdcFactor.w;
result.e[3] *= texNdcFactor.h;
}
return result;
}
INTERNAL RenderQuad createRenderQuad(Renderer *renderer, v2 pos, v2 size,
v2 pivotPoint, Radians rotate,
RenderTex renderTex)
{
/*
* Rendering order
*
* 0 2
* +---+
* + /+
* + / +
* +/ +
* +---+
* 1 3
*
*/
v4 vertexPair = {0};
vertexPair.vec2[0] = pos;
vertexPair.vec2[1] = v2_add(pos, size);
v4 texRectNdc = getTexRectNormaliseDeviceCoords(renderTex);
// NOTE(doyle): Create a quad composed of 4 vertexes to be rendered as
// a triangle strip using vertices v0, v1, v2, then v2, v1, v3 (note the
// order)
v2 vertexList[4] = {0};
v2 texCoordList[4] = {0};
// Top left
vertexList[0] = V2(vertexPair.x, vertexPair.w);
texCoordList[0] = V2(texRectNdc.x, texRectNdc.w);
// Bottom left
vertexList[1] = V2(vertexPair.x, vertexPair.y);
texCoordList[1] = V2(texRectNdc.x, texRectNdc.y);
// Top right
vertexList[2] = V2(vertexPair.z, vertexPair.w);
texCoordList[2] = V2(texRectNdc.z, texRectNdc.w);
// Bottom right
vertexList[3] = V2(vertexPair.z, vertexPair.y);
texCoordList[3] = V2(texRectNdc.z, texRectNdc.y);
// NOTE(doyle): Precalculate rotation on vertex positions
// NOTE(doyle): No translation/scale matrix as we pre-calculate it from
// entity data and work in world space until GLSL uses the projection matrix
math_applyRotationToVertexes(pos, pivotPoint, rotate, vertexList,
ARRAY_COUNT(vertexList));
RenderQuad result = {0};
ASSERT(ARRAY_COUNT(vertexList) == ARRAY_COUNT(result.vertexList));
for (i32 i = 0; i < ARRAY_COUNT(vertexList); i++)
{
result.vertexList[i].pos = vertexList[i];
result.vertexList[i].texCoord = texCoordList[i];
}
return result;
}
INTERNAL inline RenderQuad
createDefaultTexQuad(Renderer *renderer, RenderTex *renderTex)
{
RenderQuad result = {0};
result = createRenderQuad(renderer, V2(0, 0), V2(0, 0), V2(0, 0), 0.0f,
*renderTex);
return result;
}
INTERNAL void renderGLBufferedData(Renderer *renderer, RenderGroup *group)
{
ASSERT(group->mode < rendermode_invalid);
if (group->flags & renderflag_wireframe)
{
2016-11-16 02:00:59 +00:00
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
2016-11-16 02:00:59 +00:00
else
{
2016-11-16 02:00:59 +00:00
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
2016-11-16 02:00:59 +00:00
GL_CHECK_ERROR();
if (group->flags & renderflag_no_texture)
{
renderer->activeShaderId =
renderer->shaderList[shaderlist_default_no_tex];
2016-11-16 02:00:59 +00:00
shader_use(renderer->activeShaderId);
}
else
{
renderer->activeShaderId = renderer->shaderList[shaderlist_default];
2016-11-16 02:00:59 +00:00
shader_use(renderer->activeShaderId);
Texture *tex = group->tex;
if (tex)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->id);
shader_uniformSet1i(renderer->activeShaderId, "tex", 0);
GL_CHECK_ERROR();
}
}
#if 0
glDisable(GL_CULL_FACE);
#endif
/* Set color modulation value */
shader_uniformSetVec4f(renderer->activeShaderId, "spriteColor",
group->color);
glBindVertexArray(renderer->vao[group->mode]);
2016-11-16 02:00:59 +00:00
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
GL_CHECK_ERROR();
2016-09-23 11:15:22 +00:00
debug_countIncrement(debugcount_drawArrays);
/* Unbind */
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
GL_CHECK_ERROR();
2016-11-16 02:00:59 +00:00
}
2016-08-17 08:04:51 +00:00
RenderTex renderer_createNullRenderTex(AssetManager *const assetManager)
{
Texture *emptyTex = asset_getTex(assetManager, "nullTex");
RenderTex result = {emptyTex, V4(0, 1, 1, 0)};
return result;
}
void renderer_rect(Renderer *const renderer, Rect camera, v2 pos, v2 size,
v2 pivotPoint, Radians rotate, RenderTex *renderTex, v4 color,
RenderFlags flags)
{
// NOTE(doyle): Bottom left and top right position of quad in world space
v2 posInCameraSpace = v2_sub(pos, camera.min);
2016-11-11 10:43:03 +00:00
RenderTex emptyRenderTex = {0};
if (!renderTex) renderTex = &emptyRenderTex;
RenderQuad quad = createRenderQuad(renderer, posInCameraSpace, size,
pivotPoint, rotate, *renderTex);
beginVertexBatch(renderer);
addVertexToRenderGroup_(renderer, renderTex->tex, color, quad.vertexList,
ARRAY_COUNT(quad.vertexList), rendermode_quad,
flags);
endVertexBatch(renderer);
2016-11-11 10:43:03 +00:00
}
void renderer_polygon(Renderer *const renderer, Rect camera,
v2 *polygonPoints, i32 numPoints, v2 pivotPoint,
Radians rotate, RenderTex *renderTex, v4 color,
RenderFlags flags)
{
ASSERT(numPoints >= 3);
for (i32 i = 0; i < numPoints; i++)
polygonPoints[i] = v2_sub(polygonPoints[i], camera.min);
// TODO(doyle): Do something with render texture
RenderTex emptyRenderTex = {0};
if (!renderTex) renderTex = &emptyRenderTex;
v2 triangulationBaseP = polygonPoints[0];
RenderVertex triangulationBaseVertex = {0};
triangulationBaseVertex.pos = triangulationBaseP;
i32 numTrisInTriangulation = numPoints - 2;
i32 triangulationIndex = 0;
for (i32 i = 1; triangulationIndex < numTrisInTriangulation; i++)
{
ASSERT((i + 1) < numPoints);
v2 vertexList[3] = {triangulationBaseP, polygonPoints[i],
polygonPoints[i + 1]};
beginVertexBatch(renderer);
RenderVertex triangle[3] = {0};
triangle[0].pos = vertexList[0];
triangle[1].pos = vertexList[1];
triangle[2].pos = vertexList[2];
addVertexToRenderGroup_(renderer, renderTex->tex, color, triangle,
ARRAY_COUNT(triangle), rendermode_polygon,
flags);
endVertexBatch(renderer);
triangulationIndex++;
}
}
void renderer_string(Renderer *const renderer, MemoryArena_ *arena, Rect camera,
Font *const font, const char *const string, v2 pos,
v2 pivotPoint, Radians rotate, v4 color, RenderFlags flags)
{
i32 strLen = common_strlen(string);
if (strLen <= 0) return;
// TODO(doyle): Slightly incorrect string length in pixels calculation,
// because we use the advance metric of each character for length not
// maximum character size in rendering
v2 rightAlignedP =
v2_add(pos, V2((CAST(f32) font->maxSize.w * CAST(f32) strLen),
CAST(f32) font->maxSize.h));
v2 leftAlignedP = pos;
if (math_pointInRect(camera, leftAlignedP) ||
math_pointInRect(camera, rightAlignedP))
{
i32 vertexIndex = 0;
i32 numVertexPerQuad = 4;
i32 numVertexesToAlloc = (strLen * (numVertexPerQuad + 2));
RenderVertex *vertexList =
memory_pushBytes(arena, numVertexesToAlloc * sizeof(RenderVertex));
v2 posInCameraSpace = v2_sub(pos, camera.min);
pos = posInCameraSpace;
// TODO(doyle): Find why font is 1px off, might be arial font semantics
Texture *tex = font->atlas->tex;
f32 baseline = pos.y - font->verticalSpacing + 1;
for (i32 i = 0; i < strLen; i++)
{
i32 codepoint = string[i];
i32 relativeIndex = CAST(i32)(codepoint - font->codepointRange.x);
CharMetrics metric = font->charMetrics[relativeIndex];
pos.y = baseline - (metric.offset.y);
/* Get texture out */
SubTexture subTexture =
asset_getAtlasSubTex(font->atlas, &CAST(char)codepoint);
v4 charTexRect = {0};
charTexRect.vec2[0] = subTexture.rect.min;
charTexRect.vec2[1] =
v2_add(subTexture.rect.min, subTexture.rect.max);
flipTexCoord(&charTexRect, FALSE, TRUE);
RenderTex renderTex = {tex, charTexRect};
RenderQuad quad = createRenderQuad(renderer, pos, font->maxSize,
pivotPoint, rotate, renderTex);
beginVertexBatch(renderer);
addVertexToRenderGroup_(renderer, tex, color, quad.vertexList,
ARRAY_COUNT(quad.vertexList),
rendermode_quad, flags);
endVertexBatch(renderer);
pos.x += metric.advance;
}
}
}
void renderer_entity(Renderer *renderer, MemoryArena_ *transientArena,
Rect camera, Entity *entity, v2 pivotPoint, Degrees rotate,
v4 color, RenderFlags flags)
{
// TODO(doyle): Add early exit on entities out of camera bounds
Radians totalRotation = DEGREES_TO_RADIANS((entity->rotation + rotate));
RenderTex renderTex = {0};
if (entity->tex)
{
EntityAnim *entityAnim = &entity->animList[entity->animListIndex];
v4 texRect = {0};
if (entityAnim->anim)
{
Animation *anim = entityAnim->anim;
char *frameName = anim->frameList[entityAnim->currFrame];
SubTexture subTex = asset_getAtlasSubTex(anim->atlas, frameName);
texRect.vec2[0] = subTex.rect.min;
texRect.vec2[1] = v2_add(subTex.rect.min, subTex.rect.max);
flipTexCoord(&texRect, entity->flipX, entity->flipY);
}
else
2016-11-16 02:00:59 +00:00
{
texRect = V4(0.0f, 0.0f, (f32)entity->tex->width,
(f32)entity->tex->height);
2016-11-16 02:00:59 +00:00
}
if (entity->direction == direction_east)
2016-11-16 02:00:59 +00:00
{
flipTexCoord(&texRect, TRUE, FALSE);
}
2016-11-16 02:00:59 +00:00
renderTex.tex = entity->tex;
renderTex.texRect = texRect;
}
2016-11-16 02:00:59 +00:00
// TODO(doyle): Proper blending
v4 renderColor = color;
if (v4_equals(color, V4(0, 0, 0, 0))) renderColor = entity->color;
if (entity->renderMode == rendermode_quad)
{
renderer_rect(renderer, camera, entity->pos, entity->size,
v2_add(entity->offset, pivotPoint), totalRotation,
&renderTex, entity->color, flags);
}
else if (entity->renderMode == rendermode_polygon)
{
ASSERT(entity->numVertexPoints >= 3);
ASSERT(entity->vertexPoints);
v2 *offsetVertexPoints =
entity_generateUpdatedVertexList(transientArena, entity);
renderer_polygon(renderer, camera, offsetVertexPoints,
entity->numVertexPoints,
v2_add(entity->offset, pivotPoint), totalRotation,
&renderTex, renderColor, flags);
}
else
{
ASSERT(INVALID_CODE_PATH);
}
}
void renderer_renderGroups(Renderer *renderer)
{
for (i32 i = 0; i < ARRAY_COUNT(renderer->groups); i++)
{
RenderGroup *currGroup = &renderer->groups[i];
if (currGroup->init)
{
bufferRenderGroupToGL(renderer, currGroup);
renderGLBufferedData(renderer, currGroup);
RenderGroup cleanGroup = {0};
cleanGroup.vertexList = currGroup->vertexList;
*currGroup = cleanGroup;
}
else
{
break;
}
}
}