Reorganise asset files
This commit is contained in:
parent
4eb54c09b1
commit
3874526a79
@ -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 <algorithm>
|
||||
#include <vector>
|
||||
|
||||
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<i32> 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 <math.h>
|
||||
|
||||
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 <algorithm>
|
||||
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];
|
||||
|
@ -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<i32> vertexIndexArray;
|
||||
DqnArray<i32> textureIndexArray;
|
||||
DqnArray<i32> normalIndexArray;
|
||||
} WavefrontModelFace;
|
||||
|
||||
typedef struct WavefrontModel
|
||||
{
|
||||
// TODO(doyle): Fixed size
|
||||
char *groupName[16];
|
||||
i32 groupNameIndex;
|
||||
i32 groupSmoothing;
|
||||
|
||||
DqnArray<WavefrontModelFace> faces;
|
||||
} WavefrontModel;
|
||||
|
||||
typedef struct WavefrontObj
|
||||
{
|
||||
DqnArray<DqnV4> geometryArray;
|
||||
DqnArray<DqnV3> textureArray;
|
||||
DqnArray<DqnV3> 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
|
||||
|
515
src/DTRendererAsset.cpp
Normal file
515
src/DTRendererAsset.cpp
Normal file
@ -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;
|
||||
}
|
52
src/DTRendererAsset.h
Normal file
52
src/DTRendererAsset.h
Normal file
@ -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<i32> vertexIndexArray;
|
||||
DqnArray<i32> textureIndexArray;
|
||||
DqnArray<i32> normalIndexArray;
|
||||
} DTRWavefModelFace;
|
||||
|
||||
typedef struct DTRWavefModel
|
||||
{
|
||||
DqnArray<DqnV4> geometryArray;
|
||||
DqnArray<DqnV3> textureArray;
|
||||
DqnArray<DqnV3> normalArray;
|
||||
|
||||
// TODO(doyle): Fixed size
|
||||
char *groupName[16];
|
||||
i32 groupNameIndex;
|
||||
i32 groupSmoothing;
|
||||
|
||||
DqnArray<DTRWavefModelFace> 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
|
@ -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 <vector>
|
||||
|
||||
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<i32> 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]++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include "..\DTRendererDebug.cpp"
|
||||
#include "..\DTRendererRender.cpp"
|
||||
#include "..\DTRenderer.cpp"
|
||||
#include "..\DTRendererAsset.cpp"
|
||||
|
Loading…
Reference in New Issue
Block a user