diff --git a/src/DTRenderer.cpp b/src/DTRenderer.cpp index 2eda4c5..2875ba2 100644 --- a/src/DTRenderer.cpp +++ b/src/DTRenderer.cpp @@ -1,558 +1,13 @@ #include "DTRenderer.h" +#include "DTRendererAsset.h" #include "DTRendererDebug.h" #include "DTRendererPlatform.h" #include "DTRendererRender.h" -#define STB_IMAGE_IMPLEMENTATION -#include "external/stb_image.h" - -// #define DTR_DEBUG_RENDER_FONT_BITMAP -#ifdef DTR_DEBUG_RENDER_FONT_BITMAP - #define STB_IMAGE_WRITE_IMPLEMENTATION - #include "external/tests/stb_image_write.h" -#endif - #define DQN_IMPLEMENTATION #include "dqn.h" - -#include "external/tests/tinyrenderer/geometry.h" -#include "external/tests/tinyrenderer/model.cpp" -#include -#include - -FILE_SCOPE void DebugTestWavefrontFaceAndVertexParser(WavefrontObj *const obj) -{ - if (!obj) DQN_ASSERT(DQN_INVALID_CODE_PATH); - Model model = Model("african_head.obj"); - - DQN_ASSERT(obj->model.faces.count == model.nfaces()); - for (i32 i = 0; i < model.nfaces(); i++) - { - std::vector correctFace = model.face(i); - WavefrontModelFace *myFace = &obj->model.faces.data[i]; - - DQN_ASSERT(myFace->vertexIndexArray.count == correctFace.size()); - - for (i32 j = 0; j < myFace->vertexIndexArray.count; j++) - { - // Ensure the vertex index references are correct per face - DQN_ASSERT(myFace->vertexIndexArray.data[j] == correctFace[j]); - - Vec3f tmp = model.vert(correctFace[j]); - DqnV3 correctVertex = DqnV3_3f(tmp[0], tmp[1], tmp[2]); - DqnV3 myVertex = (obj->geometryArray.data[myFace->vertexIndexArray.data[j]]).xyz; - - // Ensure the vertex values read are correct - for (i32 k = 0; k < DQN_ARRAY_COUNT(correctVertex.e); k++) - { - f32 delta = DQN_ABS(correctVertex.e[k] - myVertex.e[k]); - DQN_ASSERT(delta < 0.1f); - } - } - } -} - #include -FILE_SCOPE inline WavefrontModelFace ObjWavefrontModelFaceInit(i32 capacity = 3) -{ - WavefrontModelFace result = {}; - DQN_ASSERT(DqnArray_Init(&result.vertexIndexArray, capacity)); - DQN_ASSERT(DqnArray_Init(&result.textureIndexArray, capacity)); - DQN_ASSERT(DqnArray_Init(&result.normalIndexArray, capacity)); - - return result; -} -FILE_SCOPE bool ObjWaveFrontInit(WavefrontObj *const obj, const i32 vertexInitCapacity = 1000, - const i32 faceInitCapacity = 200) -{ - if (!obj) return false; - - bool initialised = false; - - initialised |= DqnArray_Init(&obj->geometryArray, vertexInitCapacity); - initialised |= DqnArray_Init(&obj->textureArray, vertexInitCapacity); - initialised |= DqnArray_Init(&obj->normalArray, vertexInitCapacity); - initialised |= DqnArray_Init(&obj->model.faces, faceInitCapacity); - - if (!initialised) - { - DqnArray_Free(&obj->geometryArray); - DqnArray_Free(&obj->textureArray); - DqnArray_Free(&obj->normalArray); - DqnArray_Free(&obj->model.faces); - } - - return initialised; -} - -FILE_SCOPE bool ObjWavefrontLoad(const PlatformAPI api, PlatformMemory *const memory, - const char *const path, WavefrontObj *const obj) -{ - if (!memory || !path || !obj) return false; - - PlatformFile file = {}; - if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read, PlatformFileAction_OpenOnly)) - return false; // TODO(doyle): Logging - - // TODO(doyle): Make arrays use given memory not malloc - DqnTempMemStack tmpMemRegion = DqnMemStack_BeginTempRegion(&memory->transMemStack); - u8 *rawBytes = (u8 *)DqnMemStack_Push(&memory->transMemStack, file.size); - size_t bytesRead = api.FileRead(&file, rawBytes, file.size); - size_t fileSize = file.size; - api.FileClose(&file); - if (bytesRead != file.size) - { - // TODO(doyle): Logging - DqnMemStack_EndTempRegion(tmpMemRegion); - return false; - } - - enum WavefrontVertexType { - WavefrontVertexType_Invalid, - WavefrontVertexType_Geometric, - WavefrontVertexType_Texture, - WavefrontVertexType_Normal, - }; - - if (!ObjWaveFrontInit(obj)) - { - DqnMemStack_EndTempRegion(tmpMemRegion); - return false; - } - - for (char *scan = (char *)rawBytes; scan && scan < ((char *)rawBytes + fileSize);) - { - switch (DqnChar_ToLower(*scan)) - { - //////////////////////////////////////////////////////////////////// - // Polygonal Free Form Statement - //////////////////////////////////////////////////////////////////// - // Vertex Format: v[ |t|n|p] x y z [w] - case 'v': - { - scan++; - DQN_ASSERT(scan); - - enum WavefrontVertexType type = WavefrontVertexType_Invalid; - - char identifier = DqnChar_ToLower(*scan); - if (identifier == ' ') type = WavefrontVertexType_Geometric; - else if (identifier == 't' || identifier == 'n') - { - scan++; - if (identifier == 't') type = WavefrontVertexType_Texture; - else type = WavefrontVertexType_Normal; - } - else DQN_ASSERT(DQN_INVALID_CODE_PATH); - - i32 vIndex = 0; - DqnV4 v4 = {0, 0, 0, 1.0f}; - - // Progress to first non space character after vertex identifier - for (; scan && *scan == ' '; scan++) - if (!scan) DQN_ASSERT(DQN_INVALID_CODE_PATH); - - for (;;) - { - char *f32StartPtr = scan; - for (; *scan != ' ' && *scan != '\n';) - { - DQN_ASSERT(DqnChar_IsDigit(*scan) || (*scan == '.') || (*scan == '-') || - *scan == 'e'); - scan++; - } - - i32 f32Len = (i32)((size_t)scan - (size_t)f32StartPtr); - v4.e[vIndex++] = Dqn_StrToF32(f32StartPtr, f32Len); - DQN_ASSERT(vIndex < DQN_ARRAY_COUNT(v4.e)); - - while (scan && (*scan == ' ' || *scan == '\n')) scan++; - - if (!scan) break; - if (!(DqnChar_IsDigit(*scan) || *scan == '-')) break; - } - - DQN_ASSERT(vIndex == 3 || vIndex == 4); - if (type == WavefrontVertexType_Geometric) - { - DqnArray_Push(&obj->geometryArray, v4); - } - else if (type == WavefrontVertexType_Texture) - { - DqnArray_Push(&obj->textureArray, v4.xyz); - } - else if (type == WavefrontVertexType_Normal) - { - DqnArray_Push(&obj->normalArray, v4.xyz); - } - else - { - DQN_ASSERT(DQN_INVALID_CODE_PATH); - } - } - break; - - //////////////////////////////////////////////////////////////////// - // Polygonal Geometry - //////////////////////////////////////////////////////////////////// - // Vertex numbers can be negative to reference a relative offset to - // the vertex which means the relative order of the vertices - // specified in the file, i.e. - - // v 0.000000 2.000000 2.000000 - // v 0.000000 0.000000 2.000000 - // v 2.000000 0.000000 2.000000 - // v 2.000000 2.000000 2.000000 - // f -4 -3 -2 -1 - - // Point Format: p v1 v2 v3 ... - // Each point is one vertex. - case 'p': - { - DQN_ASSERT(DQN_INVALID_CODE_PATH); - } - break; - - // Line Format: l v1/vt1 v2/vt2 v3/vt3 ... - // Texture vertex is optional. Minimum of two vertex numbers, no - // limit on maximum. - case 'l': - { - DQN_ASSERT(DQN_INVALID_CODE_PATH); - } - break; - - // Face Format: f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ... - // Minimum of three vertexes, no limit on maximum. vt, vn are - // optional. But specification of vt, vn must be consistent if given - // across all the vertices for the line. - - // For example, to specify only the vertex and vertex normal - // reference numbers, you would enter: - - // f 1//1 2//2 3//3 4//4 - case 'f': - { - scan++; - while (scan && (*scan == ' ' || *scan == '\n')) scan++; - if (!scan) continue; - - WavefrontModelFace face = ObjWavefrontModelFaceInit(); - i32 numVertexesParsed = 0; - bool moreVertexesToParse = true; - while (moreVertexesToParse) - { - enum WavefrontVertexType type = WavefrontVertexType_Geometric; - - // Read a vertexes 3 attributes v, vt, vn - for (i32 i = 0; i < 3; i++) - { - char *numStartPtr = scan; - while (scan && DqnChar_IsDigit(*scan)) - scan++; - - i32 numLen = (i32)((size_t)scan - (size_t)numStartPtr); - if (numLen > 0) - { - // NOTE(doyle): Obj format starts indexing from 1, - // so offset by -1 to make it zero-based indexes. - i32 vertIndex = (i32)Dqn_StrToI64(numStartPtr, numLen) - 1; - - if (type == WavefrontVertexType_Geometric) - { - DQN_ASSERT(DqnArray_Push(&face.vertexIndexArray, vertIndex)); - } - else if (type == WavefrontVertexType_Texture) - { - DQN_ASSERT(DqnArray_Push(&face.textureIndexArray, vertIndex)); - } - else if (type == WavefrontVertexType_Normal) - { - DQN_ASSERT(DqnArray_Push(&face.normalIndexArray, vertIndex)); - } - } - - if (scan) scan++; - type = (enum WavefrontVertexType)((i32)type + 1); - } - numVertexesParsed++; - - if (scan) - { - // Move to next "non-empty" character - while (scan && (*scan == ' ' || *scan == '\n')) - scan++; - - // If it isn't a digit, then we've read all the - // vertexes for this face - if (!scan || (scan && !DqnChar_IsDigit(*scan))) - { - moreVertexesToParse = false; - } - } - } - DQN_ASSERT(numVertexesParsed >= 3); - DQN_ASSERT(DqnArray_Push(&obj->model.faces, face)); - } - break; - - //////////////////////////////////////////////////////////////////// - // Misc - //////////////////////////////////////////////////////////////////// - - // Group Name Format: g group_name1 group_name2 - // This is optional, if multiple groups are specified, then the - // following elements belong to all groups. The default group name - // is "default" - case 'g': - { - scan++; - while (scan && (*scan == ' ' || *scan == '\n')) scan++; - - if (!scan) continue; - - // Iterate to end of the name, i.e. move ptr to first space - char *namePtr = scan; - while (scan && (*scan != ' ' && *scan != '\n')) - scan++; - - if (scan) - { - i32 nameLen = (i32)((size_t)scan - (size_t)namePtr); - DQN_ASSERT(obj->model.groupNameIndex + 1 < DQN_ARRAY_COUNT(obj->model.groupName)); - - DQN_ASSERT(!obj->model.groupName[obj->model.groupNameIndex]); - obj->model.groupName[obj->model.groupNameIndex++] = (char *)DqnMemStack_Push( - &memory->permMemStack, (nameLen + 1) * sizeof(char)); - - for (i32 i = 0; i < nameLen; i++) - obj->model.groupName[obj->model.groupNameIndex - 1][i] = namePtr[i]; - - while (scan && (*scan == ' ' || *scan == '\n')) - scan++; - } - } - break; - - // Smoothing Group: s group_number - // Sets the smoothing group for the elements that follow it. If it's - // not to be used it can be specified as "off" or a value of 0. - case 's': - { - // Advance to first non space char after identifier - scan++; - while (scan && *scan == ' ' || *scan == '\n') scan++; - - if (scan && DqnChar_IsDigit(*scan)) - { - char *numStartPtr = scan; - while (scan && (*scan != ' ' && *scan != '\n')) - { - DQN_ASSERT(DqnChar_IsDigit(*scan)); - scan++; - } - - i32 numLen = (i32)((size_t)scan - (size_t)numStartPtr); - i32 groupSmoothing = (i32)Dqn_StrToI64(numStartPtr, numLen); - obj->model.groupSmoothing = groupSmoothing; - } - - while (scan && *scan == ' ' || *scan == '\n') scan++; - } - break; - - // Comment - case '#': - { - // Skip comment line until new line - while (scan && *scan != '\n') - scan++; - - // Skip new lines and any leading white spaces - while (scan && (*scan == '\n' || *scan == ' ')) - scan++; - } - break; - - default: - { - DQN_ASSERT(DQN_INVALID_CODE_PATH); - } - break; - } - } - - DqnMemStack_EndTempRegion(tmpMemRegion); - - return true; -} - -FILE_SCOPE bool BitmapFontCreate(const PlatformAPI api, - PlatformMemory *const memory, - DTRFont *const font, const char *const path, - const DqnV2i bitmapDim, - const DqnV2i codepointRange, - const f32 sizeInPt) -{ - if (!memory || !font || !path) return false; - - DTRFont loadedFont = {}; - loadedFont.bitmapDim = bitmapDim; - loadedFont.codepointRange = codepointRange; - loadedFont.sizeInPt = sizeInPt; - - //////////////////////////////////////////////////////////////////////////// - // Load font data - //////////////////////////////////////////////////////////////////////////// - PlatformFile file = {}; - if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read, PlatformFileAction_OpenOnly)) - return false; // TODO(doyle): Logging - - DqnTempMemStack tmpMemRegion = DqnMemStack_BeginTempRegion(&memory->transMemStack); - u8 *fontBuf = (u8 *)DqnMemStack_Push(&memory->transMemStack, file.size); - size_t bytesRead = api.FileRead(&file, fontBuf, file.size); - api.FileClose(&file); - if (bytesRead != file.size) - { - // TODO(doyle): Logging - DqnMemStack_EndTempRegion(tmpMemRegion); - return false; - } - - stbtt_fontinfo fontInfo = {}; - if (stbtt_InitFont(&fontInfo, fontBuf, 0) == 0) - { - DQN_ASSERT(DQN_INVALID_CODE_PATH); - return false; - } - - if (DTR_DEBUG) DQN_ASSERT(stbtt_GetNumberOfFonts(fontBuf) == 1); - //////////////////////////////////////////////////////////////////////////// - // Pack font data to bitmap - //////////////////////////////////////////////////////////////////////////// - loadedFont.bitmap = (u8 *)DqnMemStack_Push( - &memory->permMemStack, - (size_t)(loadedFont.bitmapDim.w * loadedFont.bitmapDim.h)); - - stbtt_pack_context fontPackContext = {}; - if (stbtt_PackBegin(&fontPackContext, loadedFont.bitmap, bitmapDim.w, - bitmapDim.h, 0, 1, NULL) == 1) - { - // stbtt_PackSetOversampling(&fontPackContext, 2, 2); - - i32 numCodepoints = - (i32)((codepointRange.max + 1) - codepointRange.min); - - loadedFont.atlas = (stbtt_packedchar *)DqnMemStack_Push( - &memory->permMemStack, numCodepoints * sizeof(stbtt_packedchar)); - stbtt_PackFontRange(&fontPackContext, fontBuf, 0, - STBTT_POINT_SIZE(sizeInPt), (i32)codepointRange.min, - numCodepoints, loadedFont.atlas); - stbtt_PackEnd(&fontPackContext); - DqnMemStack_EndTempRegion(tmpMemRegion); - } - else - { - DQN_ASSERT(DQN_INVALID_CODE_PATH); - DqnMemStack_EndTempRegion(tmpMemRegion); - return false; - } - - //////////////////////////////////////////////////////////////////////////// - // 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)(loadedFont.bitmap[index]) / 255.0f; - f32 color = alpha; - - color = DTRRender_SRGB1ToLinearSpacef(color); - color = color * alpha; - color = DTRRender_LinearToSRGB1Spacef(color) * 255.0f; - DQN_ASSERT(color >= 0.0f && color <= 255.0f); - - loadedFont.bitmap[index] = (u8)color; - } - } - -#ifdef DTR_DEBUG_RENDER_FONT_BITMAP - stbi_write_bmp("test.bmp", bitmapDim.w, bitmapDim.h, 1, loadedFont.bitmap); -#endif - - *font = loadedFont; - return true; -} - -// TODO(doyle): Uses malloc -FILE_SCOPE bool BitmapLoad(const PlatformAPI api, DTRBitmap *bitmap, - const char *const path, - DqnMemStack *const transMemStack) -{ - if (!bitmap) return false; - - PlatformFile file = {}; - if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read, PlatformFileAction_OpenOnly)) - return false; - - DqnTempMemStack tempBuffer = DqnMemStack_BeginTempRegion(transMemStack); - { - u8 *const rawData = - (u8 *)DqnMemStack_Push(transMemStack, file.size); - size_t bytesRead = api.FileRead(&file, rawData, file.size); - api.FileClose(&file); - - if (bytesRead != file.size) - { - DqnMemStack_EndTempRegion(tempBuffer); - return false; - } - - const u32 FORCE_4_BPP = 4; - bitmap->memory = stbi_load_from_memory(rawData, (i32)file.size, &bitmap->dim.w, - &bitmap->dim.h, NULL, FORCE_4_BPP); - bitmap->bytesPerPixel = FORCE_4_BPP; - } - DqnMemStack_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); - - DqnV4 preMulColor = color; - preMulColor *= DTRRENDER_INV_255; - preMulColor = DTRRender_PreMultiplyAlphaSRGB1WithLinearConversion(preMulColor); - preMulColor *= 255.0f; - - pixel = (((u32)preMulColor.a << 24) | - ((u32)preMulColor.b << 16) | - ((u32)preMulColor.g << 8) | - ((u32)preMulColor.r << 0)); - - pixelPtr[x] = pixel; - } - } - - return true; -} - // #include void CompAssignment(DTRRenderBuffer *const renderBuffer, PlatformInput *const input, PlatformMemory *const memory) @@ -1503,32 +958,32 @@ extern "C" void DTR_Update(PlatformRenderBuffer *const platformRenderBuffer, { DebugTestStrToF32Converter(); DTR_DEBUG_EP_TIMED_BLOCK("DTR_Update Memory Initialisation"); - // NOTE(doyle): Do premultiply ourselves - stbi_set_unpremultiply_on_load(true); - stbi_set_flip_vertically_on_load(true); - memory->isInit = true; - memory->context = - DqnMemStack_Push(&memory->permMemStack, sizeof(DTRState)); + DTRAsset_InitGlobalState(); + + memory->isInit = true; + memory->context = DqnMemStack_Push(&memory->permMemStack, sizeof(DTRState)); DQN_ASSERT(memory->context); state = (DTRState *)memory->context; - BitmapFontCreate(input->api, memory, &state->font, "Roboto-bold.ttf", - DqnV2i_2i(256, 256), DqnV2i_2i(' ', '~'), 12); - BitmapLoad(input->api, &state->bitmap, "tree00.bmp", - &memory->transMemStack); + DTRAsset_FontToBitmapLoad(input->api, memory, &state->font, "Roboto-bold.ttf", + DqnV2i_2i(256, 256), DqnV2i_2i(' ', '~'), 12); + DTRAsset_BitmapLoad(input->api, &state->bitmap, "tree00.bmp", &memory->transMemStack); - DTRBitmap test = {}; - DqnTempMemStack tmp = DqnMemStack_BeginTempRegion(&memory->transMemStack); - BitmapLoad(input->api, &test, "byte_read_check.bmp", - &memory->transMemStack); - DqnMemStack_EndTempRegion(tmp); + if (DTR_DEBUG) + { + DTRBitmap test = {}; + DqnTempMemStack tmp = DqnMemStack_BeginTempRegion(&memory->transMemStack); + DTRAsset_BitmapLoad(input->api, &test, "byte_read_check.bmp", &memory->transMemStack); + DqnMemStack_EndTempRegion(tmp); + } - DQN_ASSERT(BitmapLoad(input->api, &state->objTex, "african_head_diffuse.tga", - &memory->transMemStack)); - DQN_ASSERT(ObjWavefrontLoad(input->api, memory, "african_head.obj", &state->obj)); + if (DTRAsset_WavefModelLoad(input->api, memory, "african_head.obj", &state->obj)) + { + DTRAsset_BitmapLoad(input->api, &state->objTex, "african_head_diffuse.tga", &memory->transMemStack); + } - DebugTestWavefrontFaceAndVertexParser(&state->obj); + DTRDebug_TestWavefFaceAndVertexParser(&state->obj); } DqnTempMemStack transMemTmpRegion = DqnMemStack_BeginTempRegion(&memory->transMemStack); @@ -1584,16 +1039,16 @@ extern "C" void DTR_Update(PlatformRenderBuffer *const platformRenderBuffer, //////////////////////////////////////////////////////////////////////// const DqnV3 LIGHT = DqnV3_3i(0, 0, -1); const f32 MODEL_SCALE = DQN_MIN(renderBuffer.width, renderBuffer.height) * 0.5f; - WavefrontObj *const waveObj = &state->obj; + DTRWavefModel *const waveObj = &state->obj; DqnV3 modelP = DqnV3_3f(renderBuffer.width * 0.5f, renderBuffer.height * 0.5f, 0); - for (i32 i = 0; i < waveObj->model.faces.count; i++) + for (i32 i = 0; i < waveObj->faces.count; i++) { if (i == 852) { int BreakHere = 0; } - WavefrontModelFace face = waveObj->model.faces.data[i]; + DTRWavefModelFace face = waveObj->faces.data[i]; DQN_ASSERT(face.vertexIndexArray.count == 3); i32 vertAIndex = face.vertexIndexArray.data[0]; i32 vertBIndex = face.vertexIndexArray.data[1]; diff --git a/src/DTRenderer.h b/src/DTRenderer.h index ebc37f4..1c5e897 100644 --- a/src/DTRenderer.h +++ b/src/DTRenderer.h @@ -1,61 +1,18 @@ #ifndef DTRENDERER_H #define DTRENDERER_H +#include "DTRendererAsset.h" #include "dqn.h" -#include "external/stb_truetype.h" typedef void DTR_UpdateFunction(struct PlatformRenderBuffer *const renderBuffer, struct PlatformInput *const input, struct PlatformMemory *const memory); -typedef struct WavefrontModelFace -{ - DqnArray vertexIndexArray; - DqnArray textureIndexArray; - DqnArray normalIndexArray; -} WavefrontModelFace; - -typedef struct WavefrontModel -{ - // TODO(doyle): Fixed size - char *groupName[16]; - i32 groupNameIndex; - i32 groupSmoothing; - - DqnArray faces; -} WavefrontModel; - -typedef struct WavefrontObj -{ - DqnArray geometryArray; - DqnArray textureArray; - DqnArray normalArray; - - WavefrontModel model; -} WavefrontObj; - -typedef struct DTRFont -{ - u8 *bitmap; - DqnV2i bitmapDim; - DqnV2i codepointRange; - f32 sizeInPt; - - stbtt_packedchar *atlas; -} DTRFont; - -typedef struct DTRBitmap -{ - u8 *memory; - DqnV2i dim; - i32 bytesPerPixel; -} DTRBitmap; - typedef struct DTRState { - DTRFont font; - DTRBitmap bitmap; - WavefrontObj obj; - DTRBitmap objTex; + DTRFont font; + DTRBitmap bitmap; + DTRWavefModel obj; + DTRBitmap objTex; } DTRState; #endif diff --git a/src/DTRendererAsset.cpp b/src/DTRendererAsset.cpp new file mode 100644 index 0000000..634b8e9 --- /dev/null +++ b/src/DTRendererAsset.cpp @@ -0,0 +1,515 @@ +#include "DTRendererAsset.h" +#include "DTRendererDebug.h" +#include "DTRendererRender.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "external/stb_image.h" + +// #define DTR_DEBUG_RENDER_FONT_BITMAP +#ifdef DTR_DEBUG_RENDER_FONT_BITMAP + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include "external/tests/stb_image_write.h" +#endif + +void DTRAsset_InitGlobalState() +{ + // NOTE(doyle): Do premultiply ourselves + stbi_set_unpremultiply_on_load(true); + stbi_set_flip_vertically_on_load(true); +} + +FILE_SCOPE bool WavefModelInit(DTRWavefModel *const obj, const i32 vertexInitCapacity = 100, + const i32 faceInitCapacity = 100) +{ + if (!obj) return false; + + bool initialised = false; + + initialised |= DqnArray_Init(&obj->geometryArray, vertexInitCapacity); + initialised |= DqnArray_Init(&obj->textureArray, vertexInitCapacity); + initialised |= DqnArray_Init(&obj->normalArray, vertexInitCapacity); + initialised |= DqnArray_Init(&obj->faces, faceInitCapacity); + + if (!initialised) + { + DqnArray_Free(&obj->geometryArray); + DqnArray_Free(&obj->textureArray); + DqnArray_Free(&obj->normalArray); + DqnArray_Free(&obj->faces); + } + + return initialised; +} + +FILE_SCOPE inline DTRWavefModelFace WavefModelFaceInit(i32 capacity = 3) +{ + DTRWavefModelFace result = {}; + DQN_ASSERT(DqnArray_Init(&result.vertexIndexArray, capacity)); + DQN_ASSERT(DqnArray_Init(&result.textureIndexArray, capacity)); + DQN_ASSERT(DqnArray_Init(&result.normalIndexArray, capacity)); + + return result; +} + +bool DTRAsset_WavefModelLoad(const PlatformAPI api, PlatformMemory *const memory, + const char *const path, DTRWavefModel *const obj) +{ + if (!memory || !path || !obj) return false; + + PlatformFile file = {}; + if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read, PlatformFileAction_OpenOnly)) + return false; // TODO(doyle): Logging + + // TODO(doyle): Make arrays use given memory not malloc + DqnTempMemStack tmpMemRegion = DqnMemStack_BeginTempRegion(&memory->transMemStack); + u8 *rawBytes = (u8 *)DqnMemStack_Push(&memory->transMemStack, file.size); + size_t bytesRead = api.FileRead(&file, rawBytes, file.size); + size_t fileSize = file.size; + api.FileClose(&file); + if (bytesRead != file.size) + { + // TODO(doyle): Logging + DqnMemStack_EndTempRegion(tmpMemRegion); + return false; + } + + enum WavefVertexType { + WavefVertexType_Invalid, + WavefVertexType_Geometric, + WavefVertexType_Texture, + WavefVertexType_Normal, + }; + + if (!WavefModelInit(obj)) + { + DqnMemStack_EndTempRegion(tmpMemRegion); + return false; + } + + for (char *scan = (char *)rawBytes; scan && scan < ((char *)rawBytes + fileSize);) + { + switch (DqnChar_ToLower(*scan)) + { + //////////////////////////////////////////////////////////////////// + // Polygonal Free Form Statement + //////////////////////////////////////////////////////////////////// + // Vertex Format: v[ |t|n|p] x y z [w] + case 'v': + { + scan++; + DQN_ASSERT(scan); + + enum WavefVertexType type = WavefVertexType_Invalid; + + char identifier = DqnChar_ToLower(*scan); + if (identifier == ' ') type = WavefVertexType_Geometric; + else if (identifier == 't' || identifier == 'n') + { + scan++; + if (identifier == 't') type = WavefVertexType_Texture; + else type = WavefVertexType_Normal; + } + else DQN_ASSERT(DQN_INVALID_CODE_PATH); + + i32 vIndex = 0; + DqnV4 v4 = {0, 0, 0, 1.0f}; + + // Progress to first non space character after vertex identifier + for (; scan && *scan == ' '; scan++) + if (!scan) DQN_ASSERT(DQN_INVALID_CODE_PATH); + + for (;;) + { + char *f32StartPtr = scan; + for (; *scan != ' ' && *scan != '\n';) + { + DQN_ASSERT(DqnChar_IsDigit(*scan) || (*scan == '.') || (*scan == '-') || + *scan == 'e'); + scan++; + } + + i32 f32Len = (i32)((size_t)scan - (size_t)f32StartPtr); + v4.e[vIndex++] = Dqn_StrToF32(f32StartPtr, f32Len); + DQN_ASSERT(vIndex < DQN_ARRAY_COUNT(v4.e)); + + while (scan && (*scan == ' ' || *scan == '\n')) scan++; + + if (!scan) break; + if (!(DqnChar_IsDigit(*scan) || *scan == '-')) break; + } + + DQN_ASSERT(vIndex == 3 || vIndex == 4); + if (type == WavefVertexType_Geometric) + { + DqnArray_Push(&obj->geometryArray, v4); + } + else if (type == WavefVertexType_Texture) + { + DqnArray_Push(&obj->textureArray, v4.xyz); + } + else if (type == WavefVertexType_Normal) + { + DqnArray_Push(&obj->normalArray, v4.xyz); + } + else + { + DQN_ASSERT(DQN_INVALID_CODE_PATH); + } + } + break; + + //////////////////////////////////////////////////////////////////// + // Polygonal Geometry + //////////////////////////////////////////////////////////////////// + // Vertex numbers can be negative to reference a relative offset to + // the vertex which means the relative order of the vertices + // specified in the file, i.e. + + // v 0.000000 2.000000 2.000000 + // v 0.000000 0.000000 2.000000 + // v 2.000000 0.000000 2.000000 + // v 2.000000 2.000000 2.000000 + // f -4 -3 -2 -1 + + // Point Format: p v1 v2 v3 ... + // Each point is one vertex. + case 'p': + { + DQN_ASSERT(DQN_INVALID_CODE_PATH); + } + break; + + // Line Format: l v1/vt1 v2/vt2 v3/vt3 ... + // Texture vertex is optional. Minimum of two vertex numbers, no + // limit on maximum. + case 'l': + { + DQN_ASSERT(DQN_INVALID_CODE_PATH); + } + break; + + // Face Format: f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ... + // Minimum of three vertexes, no limit on maximum. vt, vn are + // optional. But specification of vt, vn must be consistent if given + // across all the vertices for the line. + + // For example, to specify only the vertex and vertex normal + // reference numbers, you would enter: + + // f 1//1 2//2 3//3 4//4 + case 'f': + { + scan++; + while (scan && (*scan == ' ' || *scan == '\n')) scan++; + if (!scan) continue; + + DTRWavefModelFace face = WavefModelFaceInit(); + i32 numVertexesParsed = 0; + bool moreVertexesToParse = true; + while (moreVertexesToParse) + { + enum WavefVertexType type = WavefVertexType_Geometric; + + // Read a vertexes 3 attributes v, vt, vn + for (i32 i = 0; i < 3; i++) + { + char *numStartPtr = scan; + while (scan && DqnChar_IsDigit(*scan)) + scan++; + + i32 numLen = (i32)((size_t)scan - (size_t)numStartPtr); + if (numLen > 0) + { + // NOTE(doyle): Obj format starts indexing from 1, + // so offset by -1 to make it zero-based indexes. + i32 vertIndex = (i32)Dqn_StrToI64(numStartPtr, numLen) - 1; + + if (type == WavefVertexType_Geometric) + { + DQN_ASSERT(DqnArray_Push(&face.vertexIndexArray, vertIndex)); + } + else if (type == WavefVertexType_Texture) + { + DQN_ASSERT(DqnArray_Push(&face.textureIndexArray, vertIndex)); + } + else if (type == WavefVertexType_Normal) + { + DQN_ASSERT(DqnArray_Push(&face.normalIndexArray, vertIndex)); + } + } + + if (scan) scan++; + type = (enum WavefVertexType)((i32)type + 1); + } + numVertexesParsed++; + + if (scan) + { + // Move to next "non-empty" character + while (scan && (*scan == ' ' || *scan == '\n')) + scan++; + + // If it isn't a digit, then we've read all the + // vertexes for this face + if (!scan || (scan && !DqnChar_IsDigit(*scan))) + { + moreVertexesToParse = false; + } + } + } + DQN_ASSERT(numVertexesParsed >= 3); + DQN_ASSERT(DqnArray_Push(&obj->faces, face)); + } + break; + + //////////////////////////////////////////////////////////////////// + // Misc + //////////////////////////////////////////////////////////////////// + + // Group Name Format: g group_name1 group_name2 + // This is optional, if multiple groups are specified, then the + // following elements belong to all groups. The default group name + // is "default" + case 'g': + { + scan++; + while (scan && (*scan == ' ' || *scan == '\n')) scan++; + + if (!scan) continue; + + // Iterate to end of the name, i.e. move ptr to first space + char *namePtr = scan; + while (scan && (*scan != ' ' && *scan != '\n')) + scan++; + + if (scan) + { + i32 nameLen = (i32)((size_t)scan - (size_t)namePtr); + DQN_ASSERT(obj->groupNameIndex + 1 < DQN_ARRAY_COUNT(obj->groupName)); + + DQN_ASSERT(!obj->groupName[obj->groupNameIndex]); + obj->groupName[obj->groupNameIndex++] = (char *)DqnMemStack_Push( + &memory->permMemStack, (nameLen + 1) * sizeof(char)); + + for (i32 i = 0; i < nameLen; i++) + obj->groupName[obj->groupNameIndex - 1][i] = namePtr[i]; + + while (scan && (*scan == ' ' || *scan == '\n')) + scan++; + } + } + break; + + // Smoothing Group: s group_number + // Sets the smoothing group for the elements that follow it. If it's + // not to be used it can be specified as "off" or a value of 0. + case 's': + { + // Advance to first non space char after identifier + scan++; + while (scan && *scan == ' ' || *scan == '\n') scan++; + + if (scan && DqnChar_IsDigit(*scan)) + { + char *numStartPtr = scan; + while (scan && (*scan != ' ' && *scan != '\n')) + { + DQN_ASSERT(DqnChar_IsDigit(*scan)); + scan++; + } + + i32 numLen = (i32)((size_t)scan - (size_t)numStartPtr); + i32 groupSmoothing = (i32)Dqn_StrToI64(numStartPtr, numLen); + obj->groupSmoothing = groupSmoothing; + } + + while (scan && *scan == ' ' || *scan == '\n') scan++; + } + break; + + // Comment + case '#': + { + // Skip comment line until new line + while (scan && *scan != '\n') + scan++; + + // Skip new lines and any leading white spaces + while (scan && (*scan == '\n' || *scan == ' ')) + scan++; + } + break; + + default: + { + DQN_ASSERT(DQN_INVALID_CODE_PATH); + } + break; + } + } + + DqnMemStack_EndTempRegion(tmpMemRegion); + + return true; +} + +bool DTRAsset_FontToBitmapLoad(const PlatformAPI api, PlatformMemory *const memory, + DTRFont *const font, const char *const path, const DqnV2i bitmapDim, + const DqnV2i codepointRange, const f32 sizeInPt) +{ + if (!memory || !font || !path) return false; + + DTRFont loadedFont = {}; + loadedFont.bitmapDim = bitmapDim; + loadedFont.codepointRange = codepointRange; + loadedFont.sizeInPt = sizeInPt; + + //////////////////////////////////////////////////////////////////////////// + // Load font data + //////////////////////////////////////////////////////////////////////////// + PlatformFile file = {}; + if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read, PlatformFileAction_OpenOnly)) + return false; // TODO(doyle): Logging + + DqnTempMemStack tmpMemRegion = DqnMemStack_BeginTempRegion(&memory->transMemStack); + u8 *fontBuf = (u8 *)DqnMemStack_Push(&memory->transMemStack, file.size); + size_t bytesRead = api.FileRead(&file, fontBuf, file.size); + api.FileClose(&file); + if (bytesRead != file.size) + { + // TODO(doyle): Logging + DqnMemStack_EndTempRegion(tmpMemRegion); + return false; + } + + stbtt_fontinfo fontInfo = {}; + if (stbtt_InitFont(&fontInfo, fontBuf, 0) == 0) + { + DQN_ASSERT(DQN_INVALID_CODE_PATH); + return false; + } + + if (DTR_DEBUG) DQN_ASSERT(stbtt_GetNumberOfFonts(fontBuf) == 1); + //////////////////////////////////////////////////////////////////////////// + // Pack font data to bitmap + //////////////////////////////////////////////////////////////////////////// + loadedFont.bitmap = (u8 *)DqnMemStack_Push( + &memory->permMemStack, + (size_t)(loadedFont.bitmapDim.w * loadedFont.bitmapDim.h)); + + stbtt_pack_context fontPackContext = {}; + if (stbtt_PackBegin(&fontPackContext, loadedFont.bitmap, bitmapDim.w, + bitmapDim.h, 0, 1, NULL) == 1) + { + // stbtt_PackSetOversampling(&fontPackContext, 2, 2); + + i32 numCodepoints = + (i32)((codepointRange.max + 1) - codepointRange.min); + + loadedFont.atlas = (stbtt_packedchar *)DqnMemStack_Push( + &memory->permMemStack, numCodepoints * sizeof(stbtt_packedchar)); + stbtt_PackFontRange(&fontPackContext, fontBuf, 0, + STBTT_POINT_SIZE(sizeInPt), (i32)codepointRange.min, + numCodepoints, loadedFont.atlas); + stbtt_PackEnd(&fontPackContext); + DqnMemStack_EndTempRegion(tmpMemRegion); + } + else + { + DQN_ASSERT(DQN_INVALID_CODE_PATH); + DqnMemStack_EndTempRegion(tmpMemRegion); + return false; + } + + //////////////////////////////////////////////////////////////////////////// + // 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)(loadedFont.bitmap[index]) / 255.0f; + f32 color = alpha; + + color = DTRRender_SRGB1ToLinearSpacef(color); + color = color * alpha; + color = DTRRender_LinearToSRGB1Spacef(color) * 255.0f; + DQN_ASSERT(color >= 0.0f && color <= 255.0f); + + loadedFont.bitmap[index] = (u8)color; + } + } + +#ifdef DTR_DEBUG_RENDER_FONT_BITMAP + stbi_write_bmp("test.bmp", bitmapDim.w, bitmapDim.h, 1, loadedFont.bitmap); +#endif + + *font = loadedFont; + return true; +} + +// TODO(doyle): Uses malloc +bool DTRAsset_BitmapLoad(const PlatformAPI api, DTRBitmap *bitmap, const char *const path, + DqnMemStack *const transMemStack) +{ + if (!bitmap) return false; + + PlatformFile file = {}; + if (!api.FileOpen(path, &file, PlatformFilePermissionFlag_Read, PlatformFileAction_OpenOnly)) + return false; + + DqnTempMemStack tempBuffer = DqnMemStack_BeginTempRegion(transMemStack); + { + u8 *const rawData = + (u8 *)DqnMemStack_Push(transMemStack, file.size); + size_t bytesRead = api.FileRead(&file, rawData, file.size); + api.FileClose(&file); + + if (bytesRead != file.size) + { + DqnMemStack_EndTempRegion(tempBuffer); + return false; + } + + const u32 FORCE_4_BPP = 4; + bitmap->memory = stbi_load_from_memory(rawData, (i32)file.size, &bitmap->dim.w, + &bitmap->dim.h, NULL, FORCE_4_BPP); + bitmap->bytesPerPixel = FORCE_4_BPP; + } + DqnMemStack_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); + + DqnV4 preMulColor = color; + preMulColor *= DTRRENDER_INV_255; + preMulColor = DTRRender_PreMultiplyAlphaSRGB1WithLinearConversion(preMulColor); + preMulColor *= 255.0f; + + pixel = (((u32)preMulColor.a << 24) | + ((u32)preMulColor.b << 16) | + ((u32)preMulColor.g << 8) | + ((u32)preMulColor.r << 0)); + + pixelPtr[x] = pixel; + } + } + + return true; +} diff --git a/src/DTRendererAsset.h b/src/DTRendererAsset.h new file mode 100644 index 0000000..22ca8ae --- /dev/null +++ b/src/DTRendererAsset.h @@ -0,0 +1,52 @@ +#ifndef DTRENDERER_ASSET_H +#define DTRENDERER_ASSET_H + +#include "DTRendererPlatform.h" + +#include "dqn.h" + +#include "external/stb_truetype.h" + +typedef struct DTRWavefModelFace +{ + DqnArray vertexIndexArray; + DqnArray textureIndexArray; + DqnArray normalIndexArray; +} DTRWavefModelFace; + +typedef struct DTRWavefModel +{ + DqnArray geometryArray; + DqnArray textureArray; + DqnArray normalArray; + + // TODO(doyle): Fixed size + char *groupName[16]; + i32 groupNameIndex; + i32 groupSmoothing; + + DqnArray faces; +} DTRWavefModel; + +typedef struct DTRFont +{ + u8 *bitmap; + DqnV2i bitmapDim; + DqnV2i codepointRange; + f32 sizeInPt; + + stbtt_packedchar *atlas; +} DTRFont; + +typedef struct DTRBitmap +{ + u8 *memory; + DqnV2i dim; + i32 bytesPerPixel; +} DTRBitmap; + +void DTRAsset_InitGlobalState (); +bool DTRAsset_WavefModelLoad (const PlatformAPI api, PlatformMemory *const memory, const char *const path, DTRWavefModel *const obj); +bool DTRAsset_FontToBitmapLoad(const PlatformAPI api, PlatformMemory *const memory, DTRFont *const font, const char *const path, const DqnV2i bitmapDim, const DqnV2i codepointRange, const f32 sizeInPt); +bool DTRAsset_BitmapLoad (const PlatformAPI api, DTRBitmap *bitmap, const char *const path, DqnMemStack *const transMemStack); +#endif diff --git a/src/DTRendererDebug.cpp b/src/DTRendererDebug.cpp index e561abb..2754958 100644 --- a/src/DTRendererDebug.cpp +++ b/src/DTRendererDebug.cpp @@ -1,11 +1,50 @@ #include "DTRendererDebug.h" #include "DTRenderer.h" +#include "DTRendererAsset.h" #include "DTRendererPlatform.h" #include "DTRendererRender.h" #include "dqn.h" +#include "external/tests/tinyrenderer/geometry.h" +#include "external/tests/tinyrenderer/model.cpp" +#include + DTRDebug globalDebug; +void DTRDebug_TestWavefFaceAndVertexParser(DTRWavefModel *const obj) +{ + if (DTR_DEBUG) + { + if (!obj) DQN_ASSERT(DQN_INVALID_CODE_PATH); + Model model = Model("african_head.obj"); + + DQN_ASSERT(obj->faces.count == model.nfaces()); + for (i32 i = 0; i < model.nfaces(); i++) + { + std::vector correctFace = model.face(i); + DTRWavefModelFace *myFace = &obj->faces.data[i]; + + DQN_ASSERT(myFace->vertexIndexArray.count == correctFace.size()); + + for (i32 j = 0; j < myFace->vertexIndexArray.count; j++) + { + // Ensure the vertex index references are correct per face + DQN_ASSERT(myFace->vertexIndexArray.data[j] == correctFace[j]); + + Vec3f tmp = model.vert(correctFace[j]); + DqnV3 correctVertex = DqnV3_3f(tmp[0], tmp[1], tmp[2]); + DqnV3 myVertex = (obj->geometryArray.data[myFace->vertexIndexArray.data[j]]).xyz; + + // Ensure the vertex values read are correct + for (i32 k = 0; k < DQN_ARRAY_COUNT(correctVertex.e); k++) + { + f32 delta = DQN_ABS(correctVertex.e[k] - myVertex.e[k]); + DQN_ASSERT(delta < 0.1f); + } + } + } + } +} void DTRDebug_DumpZBuffer(DTRRenderBuffer *const renderBuffer, DqnMemStack *const transMemStack) { @@ -191,4 +230,12 @@ void DTRDebug_Update(DTRState *const state, } } +void inline DTRDebug_CounterIncrement(enum DTRDebugCounter tag) +{ + if (DTR_DEBUG) + { + DQN_ASSERT(tag >= 0 && tag < DTRDebugCounter_Count); + globalDebug.counter[tag]++; + } +} diff --git a/src/DTRendererDebug.h b/src/DTRendererDebug.h index 55da968..8af68f8 100644 --- a/src/DTRendererDebug.h +++ b/src/DTRendererDebug.h @@ -2,7 +2,10 @@ #define DTRENDERER_DEBUG_H #include "dqn.h" + +// NOTE: When DTR_DEBUG is 0, _ALL_ debug code is compiled out. #define DTR_DEBUG 1 + #if DTR_DEBUG #define DTR_DEBUG_RENDER 1 @@ -36,6 +39,7 @@ typedef struct DTRFont DTRFont; typedef struct DTRState DTRState; typedef struct PlatformInput PlatformInput; typedef struct PlatformMemory PlatformMemory; +typedef struct DTRWavefObj DTRWavefObj; enum DTRDebugCounter { @@ -67,22 +71,12 @@ typedef struct DTRDebug extern DTRDebug globalDebug; -void DTRDebug_DumpZBuffer(DTRRenderBuffer *const renderBuffer, DqnMemStack *const transMemStack); -void DTRDebug_PushText(const char *const formatStr, ...); -void DTRDebug_Update(DTRState *const state, - DTRRenderBuffer *const renderBuffer, - PlatformInput *const input, PlatformMemory *const memory); - -void inline DTRDebug_BeginCycleCount(enum DTRDebugCycleCount tag); -void inline DTRDebug_EndCycleCount (enum DTRDebugCycleCount tag); - -void inline DTRDebug_CounterIncrement(enum DTRDebugCounter tag) -{ - if (DTR_DEBUG) - { - DQN_ASSERT(tag >= 0 && tag < DTRDebugCounter_Count); - globalDebug.counter[tag]++; - } -} +void DTRDebug_TestWavefFaceAndVertexParser(DTRWavefObj *const obj); +void DTRDebug_DumpZBuffer (DTRRenderBuffer *const renderBuffer, DqnMemStack *const transMemStack); +void DTRDebug_PushText (const char *const formatStr, ...); +void DTRDebug_Update (DTRState *const state, DTRRenderBuffer *const renderBuffer, PlatformInput *const input, PlatformMemory *const memory); +void inline DTRDebug_BeginCycleCount (enum DTRDebugCycleCount tag); +void inline DTRDebug_EndCycleCount (enum DTRDebugCycleCount tag); +void inline DTRDebug_CounterIncrement (enum DTRDebugCounter tag); #endif diff --git a/src/UnityBuild/UnityBuild.cpp b/src/UnityBuild/UnityBuild.cpp index cd1ac71..fc04a39 100644 --- a/src/UnityBuild/UnityBuild.cpp +++ b/src/UnityBuild/UnityBuild.cpp @@ -1,3 +1,4 @@ #include "..\DTRendererDebug.cpp" #include "..\DTRendererRender.cpp" #include "..\DTRenderer.cpp" +#include "..\DTRendererAsset.cpp"