#include "DRenderer.h" #include "DRendererPlatform.h" #define STB_RECT_PACK_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION #include "external/stb_rect_pack.h" #include "external/stb_image.h" #include "external/stb_truetype.h" // #define DR_DEBUG_RENDER_FONT_BITMAP #ifdef DR_DEBUG_RENDER_FONT_BITMAP #define STB_IMAGE_WRITE_IMPLEMENTATION #include "external/stb_image_write.h" #endif #define DQN_IMPLEMENTATION #include "dqn.h" #include #define DR_DEBUG 1 typedef struct DRFont { u8 *bitmap; DqnV2i bitmapDim; DqnV2i codepointRange; f32 sizeInPt; stbtt_packedchar *atlas; } DRFont; typedef struct DRBitmap { u8 *memory; DqnV2i dim; i32 bytesPerPixel; } DRBitmap; typedef struct DRState { DRFont font; DRBitmap bitmap; } DRState; typedef struct DRDebug { DRFont *font; PlatformRenderBuffer *renderBuffer; DqnV2 displayP; i32 displayYOffset; u64 setPixelsPerFrame; u64 totalSetPixels; } DRDebug; FILE_SCOPE inline DqnV4 PreMultiplyAlpha(DqnV4 color) { DqnV4 result; f32 normA = color.a / 255.0f; result.r = color.r * normA; result.g = color.g * normA; result.b = color.b * normA; result.a = color.a; return result; } FILE_SCOPE DRDebug globalDebug; // IMPORTANT(doyle): Color is expected to be premultiplied already FILE_SCOPE inline void SetPixel(PlatformRenderBuffer *const renderBuffer, const i32 x, const i32 y, const DqnV4 color) { if (!renderBuffer) return; if (x <= 0 || x > (renderBuffer->width - 1)) return; if (y <= 0 || y > (renderBuffer->height - 1)) return; u32 *const bitmapPtr = (u32 *)renderBuffer->memory; const u32 pitchInU32 = (renderBuffer->width * renderBuffer->bytesPerPixel) / 4; f32 newA = color.a; f32 newANorm = newA / 255.0f; f32 newR = color.r; f32 newG = color.g; f32 newB = color.b; u32 src = bitmapPtr[x + (y * pitchInU32)]; f32 srcR = (f32)((src >> 16) & 0xFF); f32 srcG = (f32)((src >> 8) & 0xFF); f32 srcB = (f32)((src >> 0) & 0xFF); // NOTE(doyle): AlphaBlend equations is (alpha * new) + (1 - alpha) * src. // IMPORTANT(doyle): We pre-multiply so we can take out the (alpha * new) f32 invANorm = 1 - newANorm; // f32 destA = (((1 - srcA) * newA) + srcA) * 255.0f; f32 destR = newR + (invANorm * srcR); f32 destG = newG + (invANorm * srcG); f32 destB = newB + (invANorm * srcB); // DQN_ASSERT(destA >= 0 && destA <= 255.0f); DQN_ASSERT(destR >= 0 && destR <= 255.0f); DQN_ASSERT(destG >= 0 && destG <= 255.0f); DQN_ASSERT(destB >= 0 && destB <= 255.0f); u32 pixel = ((u32)(destR) << 16 | (u32)(destG) << 8 | (u32)(destB) << 0); bitmapPtr[x + (y * pitchInU32)] = pixel; globalDebug.setPixelsPerFrame++; } FILE_SCOPE void DrawText(PlatformRenderBuffer *const renderBuffer, const DRFont font, DqnV2 pos, const char *const text, DqnV4 color = DqnV4_4f(255, 255, 255, 255), i32 len = -1) { if (!text) return; if (len == -1) len = Dqn_strlen(text); i32 index = 0; color = PreMultiplyAlpha(color); while (index < len) { if (text[index] < font.codepointRange.min && text[index] > font.codepointRange.max) { return; } i32 charIndex = text[index++] - (i32)font.codepointRange.min; DQN_ASSERT(charIndex >= 0 && charIndex < (i32)(font.codepointRange.max - font.codepointRange.min)); stbtt_aligned_quad alignedQuad = {}; stbtt_GetPackedQuad(font.atlas, font.bitmapDim.w, font.bitmapDim.h, charIndex, &pos.x, &pos.y, &alignedQuad, true); DqnRect fontRect = {}; fontRect.min = DqnV2_2f(alignedQuad.s0 * font.bitmapDim.w, alignedQuad.t1 * font.bitmapDim.h); fontRect.max = DqnV2_2f(alignedQuad.s1 * font.bitmapDim.w, alignedQuad.t0 * font.bitmapDim.h); DqnRect screenRect = {}; screenRect.min = DqnV2_2f(alignedQuad.x0, alignedQuad.y0); screenRect.max = DqnV2_2f(alignedQuad.x1, alignedQuad.y1); // TODO: Assumes 1bpp and pitch of font bitmap const u32 fontPitch = font.bitmapDim.w; u32 fontOffset = (u32)(fontRect.min.x + (fontRect.max.y * fontPitch)); u8 *fontPtr = font.bitmap + fontOffset; DQN_ASSERT(sizeof(u32) == renderBuffer->bytesPerPixel); // NOTE(doyle): This offset, yOffset and flipping t1, t0 is necessary // for reversing the order of the font since its convention is 0,0 top // left and -ve Y. stbtt_packedchar *const charData = font.atlas + charIndex; f32 fontHeightOffset = charData->yoff2 + charData->yoff; u32 screenOffset = (u32)(screenRect.min.x + (screenRect.min.y - fontHeightOffset) * renderBuffer->width); u32 *screenPtr = ((u32 *)renderBuffer->memory) + screenOffset; i32 fontWidth = DQN_ABS((i32)(fontRect.min.x - fontRect.max.x)); i32 fontHeight = DQN_ABS((i32)(fontRect.min.y - fontRect.max.y)); for (i32 y = 0; y < fontHeight; y++) { for (i32 x = 0; x < fontWidth; x++) { i32 yOffset = fontHeight - y; u8 srcA = fontPtr[x + (yOffset * fontPitch)]; if (srcA == 0) continue; f32 srcANorm = srcA / 255.0f; DqnV4 resultColor = {}; resultColor.r = color.r * srcANorm; resultColor.g = color.g * srcANorm; resultColor.b = color.b * srcANorm; resultColor.a = color.a * srcANorm; i32 actualX = (i32)(screenRect.min.x + x); i32 actualY = (i32)(screenRect.min.y + y - fontHeightOffset); SetPixel(renderBuffer, actualX, actualY, resultColor); } } } } FILE_SCOPE void DebugPushText(const char *const formatStr, ...) { if (DR_DEBUG) { DRDebug *const debug = &globalDebug; if (!debug->renderBuffer) return; char str[1024] = {}; va_list argList; va_start(argList, formatStr); { i32 numCopied = Dqn_vsprintf(str, formatStr, argList); DQN_ASSERT(numCopied < DQN_ARRAY_COUNT(str)); } va_end(argList); DrawText(debug->renderBuffer, *debug->font, debug->displayP, str); debug->displayP.y += globalDebug.displayYOffset; } } FILE_SCOPE void DrawLine(PlatformRenderBuffer *const renderBuffer, DqnV2i a, DqnV2i b, DqnV4 color) { if (!renderBuffer) return; color = PreMultiplyAlpha(color); bool yTallerThanX = false; if (DQN_ABS(a.x - b.x) < DQN_ABS(a.y - b.y)) { // NOTE(doyle): Enforce that the X component is always longer than the // Y component. When drawing this we just reverse the order back. // This is to ensure that the gradient is always < 1, such that we can // use the gradient to calculate the distance from the pixel origin, and // at which point we want to increment the y. yTallerThanX = true; DQN_SWAP(i32, a.x, a.y); DQN_SWAP(i32, b.x, b.y); } if (b.x < a.x) DQN_SWAP(DqnV2i, a, b); i32 rise = b.y - a.y; i32 run = b.x - a.x; i32 delta = (b.y > a.y) ? 1 : -1; i32 numIterations = b.x - a.x; i32 distFromPixelOrigin = DQN_ABS(rise) * 2; i32 distAccumulator = 0; i32 newX = a.x; i32 newY = a.y; // Unflip the points if we did for plotting the pixels i32 *plotX, *plotY; if (yTallerThanX) { plotX = &newY; plotY = &newX; } else { plotX = &newX; plotY = &newY; } for (i32 iterateX = 0; iterateX < numIterations; iterateX++) { newX = a.x + iterateX; SetPixel(renderBuffer, *plotX, *plotY, color); distAccumulator += distFromPixelOrigin; if (distAccumulator > run) { newY += delta; distAccumulator -= (run * 2); } } } FILE_SCOPE void TransformPoints(const DqnV2 origin, DqnV2 *const pList, const i32 numP, const DqnV2 scale, const f32 rotation) { if (!pList || numP == 0) return; DqnV2 xAxis = (DqnV2_2f(cosf(rotation), sinf(rotation))); DqnV2 yAxis = DqnV2_2f(-xAxis.y, xAxis.x); xAxis *= scale.x; yAxis *= scale.y; for (i32 i = 0; i < numP; i++) { DqnV2 p = pList[i]; pList[i] = origin + (xAxis * p.x) + (yAxis * p.y); } } FILE_SCOPE void DrawRectangle(PlatformRenderBuffer *const renderBuffer, DqnV2 min, DqnV2 max, DqnV4 color, const DqnV2 scale = DqnV2_1f(1.0f), f32 rotation = 0, const DqnV2 anchor = DqnV2_1f(0.5f)) { // TODO(doyle): Do edge test for quads #if 0 if (rotation > 0) { DqnV2 p1 = min; DqnV2 p2 = DqnV2_2f(max.x, min.y); DqnV2 p3 = max; DqnV2 p4 = DqnV2_2f(min.x, max.y); DrawTriangle(renderBuffer, p1, p2, p3, color, scale, rotation, anchor); DrawTriangle(renderBuffer, p1, p3, p4, color, scale, rotation, anchor); return; } #endif //////////////////////////////////////////////////////////////////////////// // Transform vertexes //////////////////////////////////////////////////////////////////////////// DqnV2 dim = DqnV2_2f(max.x - min.x, max.y - min.y); DQN_ASSERT(dim.w > 0 && dim.h > 0); DqnV2 initOrigin = DqnV2_2f(min.x + (anchor.x * dim.w), min.y + (anchor.y * dim.h)); DqnV2 p1 = min - initOrigin; DqnV2 p2 = DqnV2_2f(max.x, min.y) - initOrigin; DqnV2 p3 = max - initOrigin; DqnV2 p4 = DqnV2_2f(min.x, max.y) - initOrigin; DqnV2 pList[] = {p1, p2, p3, p4}; TransformPoints(initOrigin, pList, DQN_ARRAY_COUNT(pList), scale, rotation); min = pList[0]; max = pList[0]; for (i32 i = 1; i < DQN_ARRAY_COUNT(pList); i++) { DqnV2 checkP = pList[i]; min.x = DQN_MIN(min.x, checkP.x); min.y = DQN_MIN(min.y, checkP.y); max.x = DQN_MAX(max.x, checkP.x); max.y = DQN_MAX(max.y, checkP.y); } color = PreMultiplyAlpha(color); //////////////////////////////////////////////////////////////////////////// // Clip Drawing Space //////////////////////////////////////////////////////////////////////////// DqnRect rect = DqnRect_4f(min.x, min.y, max.x, max.y); DqnRect clip = DqnRect_4i(0, 0, renderBuffer->width, renderBuffer->height); DqnRect clippedRect = DqnRect_ClipRect(rect, clip); DqnV2 clippedSize = DqnRect_GetSizeV2(clippedRect); //////////////////////////////////////////////////////////////////////////// // Render //////////////////////////////////////////////////////////////////////////// if (rotation != 0) { for (i32 y = 0; y < clippedSize.w; y++) { i32 bufferY = (i32)clippedRect.min.y + y; for (i32 x = 0; x < clippedSize.h; x++) { i32 bufferX = (i32)clippedRect.min.x + x; bool pIsInside = true; for (i32 pIndex = 0; pIndex < DQN_ARRAY_COUNT(pList); pIndex++) { DqnV2 origin = pList[pIndex]; DqnV2 line = pList[(pIndex + 1) % DQN_ARRAY_COUNT(pList)] - origin; DqnV2 axis = DqnV2_2i(bufferX, bufferY) - origin; f32 dotResult = DqnV2_Dot(line, axis); if (dotResult < 0) { pIsInside = false; break; } } if (pIsInside) SetPixel(renderBuffer, bufferX, bufferY, color); } } } else { for (i32 y = 0; y < clippedSize.w; y++) { i32 bufferY = (i32)clippedRect.min.y + y; for (i32 x = 0; x < clippedSize.h; x++) { i32 bufferX = (i32)clippedRect.min.x + x; SetPixel(renderBuffer, bufferX, bufferY, color); } } } if (DR_DEBUG) { // Draw Bounding box { DrawLine(renderBuffer, DqnV2i_2f(min.x, min.y), DqnV2i_2f(min.x, max.y), color); DrawLine(renderBuffer, DqnV2i_2f(min.x, max.y), DqnV2i_2f(max.x, max.y), color); DrawLine(renderBuffer, DqnV2i_2f(max.x, max.y), DqnV2i_2f(max.x, min.y), color); DrawLine(renderBuffer, DqnV2i_2f(max.x, min.y), DqnV2i_2f(min.x, min.y), color); } // Draw rotating outline if (rotation > 0) { DqnV4 green = DqnV4_4f(0, 255, 0, 255); DrawLine(renderBuffer, DqnV2i_V2(pList[0]), DqnV2i_V2(pList[1]), green); DrawLine(renderBuffer, DqnV2i_V2(pList[1]), DqnV2i_V2(pList[2]), green); DrawLine(renderBuffer, DqnV2i_V2(pList[2]), DqnV2i_V2(pList[3]), green); DrawLine(renderBuffer, DqnV2i_V2(pList[3]), DqnV2i_V2(pList[0]), green); } } } FILE_SCOPE void DrawTriangle(PlatformRenderBuffer *const renderBuffer, DqnV2 p1, DqnV2 p2, DqnV2 p3, DqnV4 color, DqnV2 scale = DqnV2_1f(1.0f), f32 rotation = 0, DqnV2 anchor = DqnV2_1f(0.33f)) { // Transform vertexes DqnV2 p1p2 = p2 - p1; DqnV2 p1p3 = p3 - p1; DqnV2 p1p2Anchored = p1p2 * anchor; DqnV2 p1p3Anchored = p1p3 * anchor; DqnV2 origin = p1 + p1p2Anchored + p1p3Anchored; DqnV2 pList[3] = {p1 - origin, p2 - origin, p3 - origin}; TransformPoints(origin, pList, DQN_ARRAY_COUNT(pList), scale, rotation); p1 = pList[0]; p2 = pList[1]; p3 = pList[2]; color = PreMultiplyAlpha(color); DqnV2i max = DqnV2i_2f(DQN_MAX(DQN_MAX(p1.x, p2.x), p3.x), DQN_MAX(DQN_MAX(p1.y, p2.y), p3.y)); DqnV2i min = DqnV2i_2f(DQN_MIN(DQN_MIN(p1.x, p2.x), p3.x), DQN_MIN(DQN_MIN(p1.y, p2.y), p3.y)); min.x = DQN_MAX(min.x, 0); min.y = DQN_MAX(min.y, 0); max.x = DQN_MIN(max.x, renderBuffer->width - 1); max.y = DQN_MIN(max.y, renderBuffer->height - 1); /* ///////////////////////////////////////////////////////////////////////// // Rearranging the Determinant ///////////////////////////////////////////////////////////////////////// Given two points that form a line and an extra point to test, we can determine whether a point lies on the line, or is to the left or right of a the line. First forming a 3x3 matrix of our terms with a, b being from the triangle and test point c, we can derive a 2x2 matrix by subtracting the 1st column from the 2nd and 1st column from the third. | ax bx cx | | (bx - ax) (cx - ax) | m = | ay by cy | ==> | (by - ay) (cy - ay) | | 1 1 1 | From our 2x2 representation we can calculate the determinant which gives us the signed area of the triangle extended into a parallelogram. det(m) = (bx - ax)(cy - ay) - (by - ay)(cx - ax) Depending on the order of the vertices supplied, if it's - CCW and c(x,y) is outside the line (triangle), the signed area is negative - CCW and c(x,y) is inside the line (triangle), the signed area is positive - CW and c(x,y) is outside the line (triangle), the signed area is positive - CW and c(x,y) is inside the line (triangle), the signed area is negative ///////////////////////////////////////////////////////////////////////// // Optimising the Determinant Calculation ///////////////////////////////////////////////////////////////////////// The det(m) can be rearranged if expanded to be SignedArea(cx, cy) = (ay - by)cx + (bx - ay)cy + (ax*by - ay*bx) When we scan to fill our triangle we go pixel by pixel, left to right, bottom to top, notice that this translates to +1 for x and +1 for y, i.e. The first pixel's signed area is cx, then cx+1, cx+2 .. etc SignedArea(cx, cy) = (ay - by)cx + (bx - ax)cy + (ax*by - ay*bx) SignedArea(cx+1, cy) = (ay - by)cx+1 + (bx - ax)cy + (ax*by - ay*bx) Then SignedArea(cx+1, cy) - SignedArea(cx, cy) = (ay - by)cx+1 + (bx - ax)cy + (ax*by - ay*bx) - (ay - by)cx + (bx - ax)cy + (ax*by - ay*bx) = (ay - by)cx+1 - (ay - by)cx = (ay - by)(cx+1 - cx) = (ay - by)(1) = (ay - by) Similarly when progressing in y SignedArea(cx, cy) = (ay - by)cx + (bx - ay)cy + (ax*by - ay*bx) SignedArea(cx, cy+1) = (ay - by)cx + (bx - ay)cy+1 + (ax*by - ay*bx) Then SignedArea(cx, cy+1) - SignedArea(cx, cy) = (ay - by)cx + (bx - ax)cy+1 + (ax*by - ay*bx) - (ay - by)cx + (bx - ax)cy + (ax*by - ay*bx) = (bx - ax)cy+1 - (bx - ax)cy = (bx - ax)(cy+1 - cy) = (bx - ax)(1) = (bx - ax) Then we can see that when we progress along x, we only need to change by the value of SignedArea by (ay - by) and similarly for y, (bx - ay) ///////////////////////////////////////////////////////////////////////// // Barycentric Coordinates ///////////////////////////////////////////////////////////////////////// At this point we have an equation that can be used to calculate the 2x the signed area of a triangle, or the signed area of a parallelogram, the two of which are equivalent. det(m) = (bx - ax)(cy - ay) - (by - ay)(cx - ax) SignedArea(cx, cy) = (ay - by)cx + (bx - ay)cy + (ax*by - ay*bx) A barycentric coordinate is some coefficient on A, B, C that allows us to specify an arbitrary point in the triangle as a linear combination of the three usually with some coefficient [0, 1]. The SignedArea turns out to be actually the barycentric coord for c(x, y) normalised to the sum of the parallelogram area. For example a triangle with points, A, B, C and an arbitrary point P inside the triangle. Then SignedArea(P) with vertex A and B = Barycentric Coordinate for C SignedArea(P) with vertex B and C = Barycentric Coordinate for A SignedArea(P) with vertex C and A = Barycentric Coordinate for B B / \ / \ / P \ /_______\ A C This is normalised to the area's sum, but we can trivially turn this into a normalised version by dividing the area of the parallelogram, i.e. BaryCentricC(P) = (SignedArea(P) with vertex A and B)/SignedArea(with the orig triangle vertex) BaryCentricA(P) = (SignedArea(P) with vertex B and C)/SignedArea(with the orig triangle vertex) BaryCentricB(P) = (SignedArea(P) with vertex C and A)/SignedArea(with the orig triangle vertex) */ f32 area2Times = ((p2.x - p1.x) * (p2.y + p1.y)) + ((p3.x - p2.x) * (p3.y + p2.y)) + ((p1.x - p3.x) * (p1.y + p3.y)); if (area2Times > 0) { // Clockwise swap any point to make it clockwise DQN_SWAP(DqnV2, p2, p3); } const DqnV2 a = p1; const DqnV2 b = p2; const DqnV2 c = p3; DqnV2i scanP = DqnV2i_2i(min.x, min.y); f32 signedArea1 = ((b.x - a.x) * (scanP.y - a.y)) - ((b.y - a.y) * (scanP.x - a.x)); f32 signedArea1DeltaX = a.y - b.y; f32 signedArea1DeltaY = b.x - a.x; f32 signedArea2 = ((c.x - b.x) * (scanP.y - b.y)) - ((c.y - b.y) * (scanP.x - b.x)); f32 signedArea2DeltaX = b.y - c.y; f32 signedArea2DeltaY = c.x - b.x; f32 signedArea3 = ((a.x - c.x) * (scanP.y - c.y)) - ((a.y - c.y) * (scanP.x - c.x)); f32 signedArea3DeltaX = c.y - a.y; f32 signedArea3DeltaY = a.x - c.x; f32 invSignedAreaParallelogram = 1 / ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)); for (scanP.y = min.y; scanP.y < max.y; scanP.y++) { f32 signedArea1Row = signedArea1; f32 signedArea2Row = signedArea2; f32 signedArea3Row = signedArea3; for (scanP.x = min.x; scanP.x < max.x; scanP.x++) { if (signedArea1Row >= 0 && signedArea2Row >= 0 && signedArea3Row >= 0) { SetPixel(renderBuffer, scanP.x, scanP.y, color); } signedArea1Row += signedArea1DeltaX; signedArea2Row += signedArea2DeltaX; signedArea3Row += signedArea3DeltaX; } signedArea1 += signedArea1DeltaY; signedArea2 += signedArea2DeltaY; signedArea3 += signedArea3DeltaY; } if (DR_DEBUG) { // Draw Bounding box { DrawLine(renderBuffer, DqnV2i_2i(min.x, min.y), DqnV2i_2i(min.x, max.y), color); DrawLine(renderBuffer, DqnV2i_2i(min.x, max.y), DqnV2i_2i(max.x, max.y), color); DrawLine(renderBuffer, DqnV2i_2i(max.x, max.y), DqnV2i_2i(max.x, min.y), color); DrawLine(renderBuffer, DqnV2i_2i(max.x, min.y), DqnV2i_2i(min.x, min.y), color); } // Draw Triangle Coordinate Basis { DqnV2 xAxis = DqnV2_2f(cosf(rotation), sinf(rotation)) * scale.x; DqnV2 yAxis = DqnV2_2f(-xAxis.y, xAxis.x) * scale.y; DqnV4 coordSysColor = DqnV4_4f(0, 255, 255, 255); i32 axisLen = 50; DrawLine(renderBuffer, DqnV2i_V2(origin), DqnV2i_V2(origin) + DqnV2i_V2(xAxis * axisLen), coordSysColor); DrawLine(renderBuffer, DqnV2i_V2(origin), DqnV2i_V2(origin) + DqnV2i_V2(yAxis * axisLen), coordSysColor); } // Draw axis point { DqnV4 green = DqnV4_4f(0, 255, 0, 255); DqnV4 blue = DqnV4_4f(0, 0, 255, 255); DqnV4 purple = DqnV4_4f(255, 0, 255, 255); DrawRectangle(renderBuffer, p1 - DqnV2_1f(5), p1 + DqnV2_1f(5), green); DrawRectangle(renderBuffer, p2 - DqnV2_1f(5), p2 + DqnV2_1f(5), blue); DrawRectangle(renderBuffer, p3 - DqnV2_1f(5), p3 + DqnV2_1f(5), purple); } } } FILE_SCOPE void ClearRenderBuffer(PlatformRenderBuffer *const renderBuffer, DqnV3 color) { if (!renderBuffer) return; DQN_ASSERT(color.r >= 0.0f && color.r <= 255.0f); DQN_ASSERT(color.g >= 0.0f && color.g <= 255.0f); DQN_ASSERT(color.b >= 0.0f && color.b <= 255.0f); u32 *const bitmapPtr = (u32 *)renderBuffer->memory; for (i32 y = 0; y < renderBuffer->height; y++) { for (i32 x = 0; x < renderBuffer->width; x++) { u32 pixel = ((i32)color.r << 16) | ((i32)color.g << 8) | ((i32)color.b << 0); bitmapPtr[x + (y * renderBuffer->width)] = pixel; } } } FILE_SCOPE void BitmapFontCreate(const PlatformAPI api, PlatformMemory *const memory, DRFont *const font, const char *const path, const DqnV2i bitmapDim, const DqnV2i codepointRange, const f32 sizeInPt) { font->bitmapDim = bitmapDim; font->codepointRange = codepointRange; font->sizeInPt = sizeInPt; DqnTempBuffer transientTempBufferRegion = DqnMemBuffer_BeginTempRegion(&memory->transientBuffer); //////////////////////////////////////////////////////////////////////////// // Load font data //////////////////////////////////////////////////////////////////////////// PlatformFile file = {}; bool result = api.FileOpen(path, &file, PlatformFilePermissionFlag_Read); DQN_ASSERT(result); u8 *fontBuf = (u8 *)DqnMemBuffer_Allocate(&memory->transientBuffer, file.size); size_t bytesRead = api.FileRead(&file, fontBuf, file.size); DQN_ASSERT(bytesRead == file.size); api.FileClose(&file); stbtt_fontinfo fontInfo = {}; DQN_ASSERT(stbtt_InitFont(&fontInfo, fontBuf, 0) != 0); if (DR_DEBUG) DQN_ASSERT(stbtt_GetNumberOfFonts(fontBuf) == 1); //////////////////////////////////////////////////////////////////////////// // Pack font data to bitmap //////////////////////////////////////////////////////////////////////////// font->bitmap = (u8 *)DqnMemBuffer_Allocate( &memory->permanentBuffer, (size_t)(font->bitmapDim.w * font->bitmapDim.h)); stbtt_pack_context fontPackContext = {}; DQN_ASSERT(stbtt_PackBegin(&fontPackContext, font->bitmap, bitmapDim.w, bitmapDim.h, 0, 1, NULL) == 1); { // stbtt_PackSetOversampling(&fontPackContext, 2, 2); i32 numCodepoints = (i32)((codepointRange.max + 1) - codepointRange.min); font->atlas = (stbtt_packedchar *)DqnMemBuffer_Allocate( &memory->permanentBuffer, numCodepoints * sizeof(stbtt_packedchar)); stbtt_PackFontRange(&fontPackContext, fontBuf, 0, STBTT_POINT_SIZE(sizeInPt), (i32)codepointRange.min, numCodepoints, font->atlas); } stbtt_PackEnd(&fontPackContext); //////////////////////////////////////////////////////////////////////////// // Premultiply Alpha of Bitmap //////////////////////////////////////////////////////////////////////////// for (i32 y = 0; y < bitmapDim.h; y++) { for (i32 x = 0; x < bitmapDim.w; x++) { // NOTE: Bitmap from stb_truetype is 1BPP. So the actual color // value represents its' alpha value but also its' color. u32 index = x + (y * bitmapDim.w); f32 alpha = (f32)(font->bitmap[index]) / 255.0f; f32 color = alpha; f32 preMulAlphaColor = color * alpha; DQN_ASSERT(preMulAlphaColor >= 0.0f && preMulAlphaColor <= 255.0f); font->bitmap[index] = (u8)(preMulAlphaColor * 255.0f); } } #ifdef DR_DEBUG_RENDER_FONT_BITMAP stbi_write_bmp("test.bmp", bitmapDim.w, bitmapDim.h, 1, font->bitmap); #endif DqnMemBuffer_EndTempRegion(transientTempBufferRegion); } FILE_SCOPE void DrawBitmap(PlatformRenderBuffer *const renderBuffer, DRBitmap *const bitmap, i32 x, i32 y) { if (!bitmap || !bitmap->memory || !renderBuffer) return; DqnRect viewport = DqnRect_4i(0, 0, renderBuffer->width, renderBuffer->height); DqnRect bitmapRect = DqnRect_4i(x, y, x + bitmap->dim.w, y + bitmap->dim.h); bitmapRect = DqnRect_ClipRect(bitmapRect, viewport); if (bitmapRect.max.x < 0 || bitmapRect.max.y < 0) return; i32 startX = (x > 0) ? 0 : DQN_ABS(x); i32 startY = (y > 0) ? 0 : DQN_ABS(y); i32 endX, endY; DqnRect_GetSize2i(bitmapRect, &endX, &endY); const i32 pitch = bitmap->dim.w * bitmap->bytesPerPixel; for (i32 bitmapY = startY; bitmapY < endY; bitmapY++) { u8 *const srcRow = bitmap->memory + (bitmapY * pitch); i32 bufferY = (i32)bitmapRect.min.y + bitmapY; for (i32 bitmapX = startX; bitmapX < endX; bitmapX++) { u32 *pixelPtr = (u32 *)srcRow; u32 pixel = pixelPtr[bitmapX]; i32 bufferX = (i32)bitmapRect.min.x + bitmapX; DqnV4 color = {}; color.a = (f32)(pixel >> 24); color.b = (f32)((pixel >> 16) & 0xFF); color.g = (f32)((pixel >> 8) & 0xFF); color.r = (f32)((pixel >> 0) & 0xFF); SetPixel(renderBuffer, bufferX, bufferY, color); } } } FILE_SCOPE bool BitmapLoad(const PlatformAPI api, DRBitmap *bitmap, const char *const path, DqnMemBuffer *const transientBuffer) { if (!bitmap) return false; PlatformFile file = {}; if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read)) return false; DqnTempBuffer tempBuffer = DqnMemBuffer_BeginTempRegion(transientBuffer); { u8 *const rawData = (u8 *)DqnMemBuffer_Allocate(transientBuffer, file.size); size_t bytesRead = api.FileRead(&file, rawData, file.size); api.FileClose(&file); if (bytesRead != file.size) { DqnMemBuffer_EndTempRegion(tempBuffer); return false; } bitmap->memory = stbi_load_from_memory(rawData, (i32)file.size, &bitmap->dim.w, &bitmap->dim.h, &bitmap->bytesPerPixel, 4); } DqnMemBuffer_EndTempRegion(tempBuffer); if (!bitmap->memory) return false; const i32 pitch = bitmap->dim.w * bitmap->bytesPerPixel; for (i32 y = 0; y < bitmap->dim.h; y++) { u8 *const srcRow = bitmap->memory + (y * pitch); for (i32 x = 0; x < bitmap->dim.w; x++) { u32 *pixelPtr = (u32 *)srcRow; u32 pixel = pixelPtr[x]; DqnV4 color = {}; color.a = (f32)(pixel >> 24); color.b = (f32)((pixel >> 16) & 0xFF); color.g = (f32)((pixel >> 8) & 0xFF); color.r = (f32)((pixel >> 0) & 0xFF); color = PreMultiplyAlpha(color); pixel = (((u32)color.a << 24) | ((u32)color.b << 16) | ((u32)color.g << 8) | ((u32)color.r << 0)); pixelPtr[x] = pixel; } } return true; } void DebugDisplayMemBuffer(PlatformRenderBuffer *const renderBuffer, const char *const name, const DqnMemBuffer *const buffer, DqnV2 *const debugP, const DRFont font) { if (!name && !buffer && !debugP) return; size_t totalUsed = 0; size_t totalSize = 0; size_t totalWasted = 0; i32 numBlocks = 0; DqnMemBufferBlock *blockPtr = buffer->block; while (blockPtr) { totalUsed += blockPtr->used; totalSize += blockPtr->size; blockPtr = blockPtr->prevBlock; numBlocks++; } size_t totalUsedKb = totalUsed / 1024; size_t totalSizeKb = totalSize / 1024; size_t totalWastedKb = totalWasted / 1024; char str[128] = {}; Dqn_sprintf(str, "%s: %d block(s): %_$lld/%_$lld", name, numBlocks, totalUsed, totalSize); DrawText(renderBuffer, font, *debugP, str); debugP->y += globalDebug.displayYOffset; } void DebugUpdate(DRState *const state, PlatformRenderBuffer *const renderBuffer, PlatformInput *const input, PlatformMemory *const memory) { if (DR_DEBUG) { DRDebug *const debug = &globalDebug; if (input->executableReloaded || !memory->isInit) { debug->font = &state->font; debug->displayYOffset = -(i32)(state->font.sizeInPt + 0.5f); debug->displayP = DqnV2_2i(0, renderBuffer->height + globalDebug.displayYOffset); DQN_ASSERT(globalDebug.displayYOffset < 0); debug->renderBuffer = renderBuffer; } debug->totalSetPixels += debug->setPixelsPerFrame; debug->totalSetPixels = DQN_MAX(0, debug->totalSetPixels); // totalSetPixels { char str[128] = {}; Dqn_sprintf(str, "%s: %'lld", "TotalSetPixels", debug->totalSetPixels); DrawText(debug->renderBuffer, *debug->font, debug->displayP, str); debug->displayP.y += globalDebug.displayYOffset; } // setPixelsPerFrame { char str[128] = {}; Dqn_sprintf(str, "%s: %'lld", "SetPixelsPerFrame", debug->setPixelsPerFrame); DrawText(debug->renderBuffer, *debug->font, debug->displayP, str); debug->displayP.y += globalDebug.displayYOffset; } // memory { DebugDisplayMemBuffer(debug->renderBuffer, "PermBuffer", &memory->permanentBuffer, &debug->displayP, *debug->font); DebugDisplayMemBuffer(debug->renderBuffer, "TransBuffer", &memory->transientBuffer, &debug->displayP, *debug->font); } debug->setPixelsPerFrame = 0; debug->displayP = DqnV2_2i(0, debug->renderBuffer->height + globalDebug.displayYOffset); } } extern "C" void DR_Update(PlatformRenderBuffer *const renderBuffer, PlatformInput *const input, PlatformMemory *const memory) { DRState *state = (DRState *)memory->context; if (!memory->isInit) { stbi_set_flip_vertically_on_load(true); memory->isInit = true; memory->context = DqnMemBuffer_Allocate(&memory->permanentBuffer, sizeof(DRState)); DQN_ASSERT(memory->context); state = (DRState *)memory->context; BitmapFontCreate(input->api, memory, &state->font, "Roboto-bold.ttf", DqnV2i_2i(256, 256), DqnV2i_2i(' ', '~'), 16); DQN_ASSERT(BitmapLoad(input->api, &state->bitmap, "lune_logo.png", &memory->transientBuffer)); } ClearRenderBuffer(renderBuffer, DqnV3_3f(0, 0, 0)); DqnV4 colorRed = DqnV4_4i(180, 0, 0, 255); DqnV2i bufferMidP = DqnV2i_2f(renderBuffer->width * 0.5f, renderBuffer->height * 0.5f); i32 boundsOffset = 100; DqnV2 t0[3] = {DqnV2_2i(10, 70), DqnV2_2i(50, 160), DqnV2_2i(70, 80)}; DqnV2 t1[3] = {DqnV2_2i(180, 50), DqnV2_2i(150, 1), DqnV2_2i(70, 180)}; DqnV2 t2[3] = {DqnV2_2i(180, 150), DqnV2_2i(120, 160), DqnV2_2i(130, 180)}; LOCAL_PERSIST DqnV2 t3[3] = { DqnV2_2i(boundsOffset, boundsOffset), DqnV2_2i(bufferMidP.w, renderBuffer->height - boundsOffset), DqnV2_2i(renderBuffer->width - boundsOffset, boundsOffset)}; #if 1 DrawTriangle(renderBuffer, t0[0], t0[1], t0[2], colorRed); DrawTriangle(renderBuffer, t1[0], t1[1], t1[2], colorRed); DrawTriangle(renderBuffer, t2[0], t2[1], t2[2], colorRed); #endif DqnV4 colorRedHalfA = DqnV4_4i(255, 0, 0, 64); LOCAL_PERSIST f32 rotation = 0; rotation += input->deltaForFrame * 0.25f; DrawTriangle(renderBuffer, t3[0], t3[1], t3[2], colorRedHalfA, DqnV2_1f(1.0f), rotation, DqnV2_2f(0.33f, 0.33f)); DrawRectangle(renderBuffer, DqnV2_1f(300.0f), DqnV2_1f(300 + 20.0f), colorRed, DqnV2_1f(1.0f), 45 + rotation); DqnV2 fontP = DqnV2_2i(200, 180); // DrawText(renderBuffer, state->font, fontP, "hello world!"); // DrawBitmap(renderBuffer, &state->bitmap, 300, 250); DebugUpdate(state, renderBuffer, input, memory); }