cache DirectWrite render targets

This commit is contained in:
jacob 2025-12-14 15:53:00 -06:00
parent c140b6271b
commit 9fc666d49d
6 changed files with 199 additions and 221 deletions

View File

@ -51,41 +51,45 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size
u32 *uncached_codepoints = PushStructsNoZero(scratch.arena, u32, codepoints_count); u32 *uncached_codepoints = PushStructsNoZero(scratch.arena, u32, codepoints_count);
u64 uncached_codepoints_count = 0; u64 uncached_codepoints_count = 0;
/* TODO: Include advances for glyphs in run that have rasterized but not finished uploading to atlas */
u64 pending_glyphs_count = 0; u64 pending_glyphs_count = 0;
{ {
if (codepoints_count > 0) if (codepoints_count > 0)
{ {
Lock lock = LockS(&GC.glyphs_mutex); Lock lock = LockS(&GC.glyphs_mutex);
for (u64 codepoint_idx = 0; codepoint_idx < codepoints_count; ++codepoint_idx)
{ {
u32 codepoint = codepoints[codepoint_idx]; i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy);
for (u64 codepoint_idx = 0; codepoint_idx < codepoints_count; ++codepoint_idx)
{
u32 codepoint = codepoints[codepoint_idx];
GC_GlyphDesc desc = ZI; GC_GlyphDesc desc = ZI;
desc.font = font; desc.font = font;
desc.font_size = font_size; desc.font_size = font_size;
desc.codepoint = codepoint; desc.codepoint = codepoint;
u64 hash = GC_HashFromGlyphDesc(desc); u64 hash = GC_HashFromGlyphDesc(desc);
GC_GlyphBin *bin = &GC.glyph_bins[hash % countof(GC.glyph_bins)]; GC_GlyphBin *bin = &GC.glyph_bins[hash % countof(GC.glyph_bins)];
GC_Glyph *glyph = bin->first; GC_Glyph *glyph = bin->first;
for (; glyph; glyph = glyph->next) for (; glyph; glyph = glyph->next)
{ {
if (glyph->hash == hash) break; if (glyph->hash == hash) break;
} }
if (glyph == 0) if (glyph == 0)
{ {
uncached_codepoints[uncached_codepoints_count] = codepoint; uncached_codepoints[uncached_codepoints_count] = codepoint;
uncached_codepoints_count += 1; uncached_codepoints_count += 1;
} }
else if (Atomic32Fetch(&glyph->ready) == 0) else if (completion < Atomic64Fetch(&glyph->async_copy_completion_target))
{ {
pending_glyphs_count += 1; pending_glyphs_count += 1;
} }
else else
{ {
ready_glyphs[ready_glyphs_count] = glyph; ready_glyphs[ready_glyphs_count] = glyph;
ready_glyphs_count += 1; ready_glyphs_count += 1;
}
} }
} }
Unlock(&lock); Unlock(&lock);
@ -121,6 +125,7 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size
glyph = PushStruct(perm, GC_Glyph); glyph = PushStruct(perm, GC_Glyph);
glyph->desc = desc; glyph->desc = desc;
glyph->hash = hash; glyph->hash = hash;
Atomic64FetchSet(&glyph->async_copy_completion_target, I64Max);
SllStackPush(bin->first, glyph); SllStackPush(bin->first, glyph);
/* Create cmd */ /* Create cmd */
{ {
@ -217,12 +222,14 @@ void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick)
GC_AsyncCtx *async = &GC.async_ctx; GC_AsyncCtx *async = &GC.async_ctx;
////////////////////////////// //////////////////////////////
//- Collect cmds //- Begin tick
/* TODO: Limit cmds processed per-tick */ /* TODO: Limit cmds processed per-tick */
if (lane->idx == 0) if (lane->idx == 0)
{ {
ZeroStruct(&async->cmds);
Lock lock = LockE(&GC.submit.mutex); Lock lock = LockE(&GC.submit.mutex);
{ {
/* Pop cmds from submission queue */ /* Pop cmds from submission queue */
@ -245,12 +252,43 @@ void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick)
WaveSync(lane); WaveSync(lane);
////////////////////////////// //////////////////////////////
//- Allocate atlas rects //- Rasterize glyphs
/* TODO: Process cmds unevenly to account for varying work size */
{
RngU64 cmd_idxs = WaveIdxRangeFromCount(lane, async->cmds.count);
for (u64 cmd_idx = cmd_idxs.min; cmd_idx < cmd_idxs.max; ++cmd_idx)
{
GC_Cmd *cmd = &async->cmds.v[cmd_idx];
GC_Glyph *glyph = cmd->glyph;
ResourceKey resource = glyph->desc.font.r;
GC_GlyphDesc desc = glyph->desc;
TTF_GlyphResult ttf_result = TTF_RasterizeGlyphFromCodepoint(tick->arena, desc.codepoint, resource, desc.font_size);;
glyph->font_size = desc.font_size;
glyph->font_ascent = ttf_result.font_ascent;
glyph->font_descent = ttf_result.font_descent;
glyph->font_cap = ttf_result.font_cap;
glyph->advance = ttf_result.advance;
glyph->bounds = ttf_result.bounds;
cmd->rasterized = ttf_result;
}
}
/* TODO: Only sync first lane? */
WaveSync(lane);
////////////////////////////
//- Allocate atlas slices
if (lane->idx == 0) if (lane->idx == 0)
{ {
/* TODO: Remove this */ /* FIXME: Dynamic atlases */
/* Create atlas */ /* Create atlas */
Vec2I32 atlas_dims = VEC2I32(8192, 8192); Vec2I32 atlas_dims = VEC2I32(8192, 8192);
if (G_IsResourceNil(GC.atlas)) if (G_IsResourceNil(GC.atlas))
@ -270,27 +308,10 @@ void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick)
{ {
GC_Cmd *cmd = &async->cmds.v[cmd_idx]; GC_Cmd *cmd = &async->cmds.v[cmd_idx];
GC_Glyph *glyph = cmd->glyph; GC_Glyph *glyph = cmd->glyph;
ResourceKey resource = glyph->desc.font.r;
String resource_data = DataFromResource(resource);
String resource_name = NameFromResource(resource);
GC_GlyphDesc desc = glyph->desc; GC_GlyphDesc desc = glyph->desc;
TTF_GlyphResult ttf_result = cmd->rasterized;
TTF_GlyphResult ttf_info = TTF_RasterizeGlyphFromCodepoint(tick->arena, desc.codepoint, resource, desc.font_size); Vec2I32 image_dims = ttf_result.image_dims;
glyph->font_size = desc.font_size;
glyph->font_ascent = ttf_info.font_ascent;
glyph->font_descent = ttf_info.font_descent;
glyph->font_cap = ttf_info.font_cap;
glyph->advance = ttf_info.advance;
glyph->bounds = ttf_info.bounds;
u32 *image_pixels = ttf_info.image_pixels;
Vec2I32 image_dims = ttf_info.image_dims;
Vec2I32 atlas_offset = GC.atlas_pos; Vec2I32 atlas_offset = GC.atlas_pos;
GC.atlas_row_height = MaxI32(GC.atlas_row_height, image_dims.y); GC.atlas_row_height = MaxI32(GC.atlas_row_height, image_dims.y);
@ -309,14 +330,36 @@ void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick)
glyph->tex_slice_uv.p0.y = (f32)glyph->tex_slice.p0.y / (f32)atlas_dims.x; glyph->tex_slice_uv.p0.y = (f32)glyph->tex_slice.p0.y / (f32)atlas_dims.x;
glyph->tex_slice_uv.p1.x = (f32)glyph->tex_slice.p1.x / (f32)atlas_dims.x; glyph->tex_slice_uv.p1.x = (f32)glyph->tex_slice.p1.x / (f32)atlas_dims.x;
glyph->tex_slice_uv.p1.y = (f32)glyph->tex_slice.p1.y / (f32)atlas_dims.x; glyph->tex_slice_uv.p1.y = (f32)glyph->tex_slice.p1.y / (f32)atlas_dims.x;
}
}
WaveSync(lane);
//////////////////////////////
//- Upload glyphs to atlas
{
RngU64 cmd_idxs = WaveIdxRangeFromCount(lane, async->cmds.count);
for (u64 cmd_idx = cmd_idxs.min; cmd_idx < cmd_idxs.max; ++cmd_idx)
{
GC_Cmd *cmd = &async->cmds.v[cmd_idx];
GC_Glyph *glyph = cmd->glyph;
ResourceKey resource = glyph->desc.font.r;
GC_GlyphDesc desc = glyph->desc;
TTF_GlyphResult ttf_result = TTF_RasterizeGlyphFromCodepoint(tick->arena, desc.codepoint, resource, desc.font_size);;
u32 *image_pixels = ttf_result.image_pixels;
Vec2I32 image_dims = ttf_result.image_dims;
i64 completion_target = 0;
if (image_dims.x > 0 && image_dims.y > 0) if (image_dims.x > 0 && image_dims.y > 0)
{ {
G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_AsyncCopy); G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_AsyncCopy);
{ {
G_CopyCpuToTexture( G_CopyCpuToTexture(
cl, cl,
GC.atlas, VEC3I32(atlas_offset.x, atlas_offset.y, 0), GC.atlas, VEC3I32(glyph->tex_slice.p0.x, glyph->tex_slice.p0.y, 0),
image_pixels, VEC3I32(image_dims.x, image_dims.y, 1), image_pixels, VEC3I32(image_dims.x, image_dims.y, 1),
RNG3I32( RNG3I32(
VEC3I32(0, 0, 0), VEC3I32(0, 0, 0),
@ -324,133 +367,13 @@ void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick)
) )
); );
} }
G_CommitCommandList(cl); completion_target = G_CommitCommandList(cl);
G_SyncCpu(G_QueueMask_All);
} }
Atomic32Set(&glyph->ready, 1); Atomic64Set(&glyph->async_copy_completion_target, completion_target);
} }
} }
WaveSync(lane);
// //////////////////////////////
// //- Allocate atlas rects
// if (lane->idx == 0)
// {
// /* TODO: Remove this */
// /* Create atlas */
// Vec2I32 atlas_dims = VEC2I32(8192, 8192);
// if (G_IsResourceNil(GC.atlas))
// {
// G_ArenaHandle gpu_perm = G_PermArena();
// GC.atlas = G_PushTexture2D(
// gpu_perm,
// G_Format_R8G8B8A8_Unorm_Srgb,
// atlas_dims,
// /* FIXME: We may need simultaneous access? */
// G_Layout_AnyQueue_ShaderRead_CopyRead_CopyWrite_Present,
// );
// }
// for (u64 cmd_idx = 0; cmd_idx < async->cmds.count; ++cmd_idx)
// {
// GC_Cmd *cmd = &async->cmds.v[cmd_idx];
// GC_Glyph *glyph = cmd->glyph;
// ResourceKey resource = glyph->desc.font.r;
// String resource_data = DataFromResource(resource);
// String resource_name = NameFromResource(resource);
// GC_GlyphDesc desc = glyph->desc;
// TTF_GlyphDesc ttf_desc = TTF_GlyphDescFromCodepoint(desc.codepoint, resource, desc.font_size);
// glyph->font_size = ttf_desc.font_size;
// glyph->font_ascent = ttf_desc.font_ascent;
// glyph->font_descent = ttf_desc.font_descent;
// glyph->font_cap = ttf_desc.font_cap;
// glyph->advance = ttf_desc.advance;
// glyph->bounds = ttf_desc.bounds;
// Vec2 dims = DimsFromRng2(glyph->bounds);
// Vec2I32 atlas_offset = GC.atlas_pos;
// GC.atlas_row_height = MaxI32(GC.atlas_row_height, dims.y);
// if (atlas_offset.x + dims.x > atlas_dims.x);
// {
// GC.atlas_pos.x = 0;
// GC.atlas_pos.y += GC.atlas_row_height;
// GC.atlas_row_height = dims.y;
// }
// GC.atlas_pos.x += dims.x;
// Rng2I32 src_slice = ZI;
// src_slice.p0.x = 0;
// src_slice.p0.y = 0;
// src_slice.p1.x = RoundF32ToI32(dims.x);
// src_slice.p1.y = RoundF32ToI32(dims.y);
// Vec2I32 src_dims = DimsFromRng2I32(src_slice);
// if (src_dims.x > 0 && src_dims.y > 0)
// {
// u64 src_pixels_count = src_dims.x * src_dims.y;
// u32 *src_pixels = PushStructsNoZero(tick->arena, u32, src_pixels_count);
// TTF_RasterizeGlyph(src_pixels, src_dims, src_slice, ttf_desc);
// DEBUGBREAKABLE;
// // G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_AsyncCopy);
// G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_Direct);
// {
// G_CopyCpuToTexture(
// cl,
// GC.atlas, VEC3I32(atlas_offset.x, atlas_offset.y, 0),
// src_pixels, VEC3I32(src_dims.x, src_dims.y, 0),
// RNG3I32(
// VEC3I32(src_slice.p0.x, src_slice.p0.y, 0),
// VEC3I32(src_slice.p1.x, src_slice.p1.y, 1)
// )
// );
// }
// G_CommitCommandList(cl);
// G_SyncCpu(G_QueueMask_All);
// }
// Atomic32Set(&glyph->ready, 1);
// // ResourceKey resource = desc.font.r;
// // String resource_data = DataFromResource(resource);
// }
// }
// WaveSync(lane);
//////////////////////////////
//- Process cmds
/* TODO: Process cmds unevenly to account for varying work size */
// if (async->cmds.count > 0)
// {
// RngU64 cmd_idxs = WaveIdxRangeFromCount(lane, async->cmds.count);
// for (u64 cmd_idx = cmd_idxs.min; cmd_idx < cmd_idxs.max; ++cmd_idx)
// {
// GC_Cmd *cmd = &async->cmds.v[cmd_idx];
// GC_Glyph *glyph = cmd->glyph;
// GC_GlyphDesc desc = glyph->desc;
// ResourceKey resource = desc.font.r;
// String resource_data = DataFromResource(resource);
// }
// }
////////////////////////////// //////////////////////////////
//- End tick //- End tick

View File

@ -22,8 +22,7 @@ Struct(GC_Glyph)
GC_GlyphDesc desc; GC_GlyphDesc desc;
u64 hash; u64 hash;
Atomic64 async_copy_completion_target;
Atomic32 ready;
/* Font info */ /* Font info */
f32 font_size; f32 font_size;
@ -96,6 +95,9 @@ Struct(GC_Run)
Struct(GC_Cmd) Struct(GC_Cmd)
{ {
GC_Glyph *glyph; GC_Glyph *glyph;
/* Async temporary data */
TTF_GlyphResult rasterized;
}; };
Struct(GC_CmdNode) Struct(GC_CmdNode)

View File

@ -729,7 +729,6 @@ i64 G_D12_CommitRawCommandList(G_D12_RawCommandList *cl)
{ {
/* Execute */ /* Execute */
ID3D12CommandQueue_ExecuteCommandLists(queue->d3d_queue, 1, (ID3D12CommandList **)&cl->d3d_cl); ID3D12CommandQueue_ExecuteCommandLists(queue->d3d_queue, 1, (ID3D12CommandList **)&cl->d3d_cl);
Lock lock = LockE(&queue->commit_mutex); Lock lock = LockE(&queue->commit_mutex);
{ {
completion_target = ++queue->commit_fence_target; completion_target = ++queue->commit_fence_target;
@ -1462,6 +1461,7 @@ G_D12_Cmd *G_D12_PushCmd(G_D12_CmdList *cl)
/* Push cmd to chunk */ /* Push cmd to chunk */
G_D12_Cmd *cmd = &chunk->cmds[chunk->cmds_count++]; G_D12_Cmd *cmd = &chunk->cmds[chunk->cmds_count++];
ZeroStruct(cmd);
++cl->cmds_count; ++cl->cmds_count;
return cmd; return cmd;
} }
@ -1488,7 +1488,7 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
{ {
G_D12_StagingRing *old_ring = 0; G_D12_StagingRing *old_ring = 0;
G_D12_StagingRing *ring = queue->staging_ring; G_D12_StagingRing *ring = queue->staging_ring;
i64 completed = ID3D12Fence_GetCompletedValue(queue->commit_fence); i64 completion = ID3D12Fence_GetCompletedValue(queue->commit_fence);
/* Find first completed region with matching size. /* Find first completed region with matching size.
* For each region in ring: * For each region in ring:
@ -1504,9 +1504,6 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
/* FIXME: Region completion target should be atomic, and initialized to
* u64/i64 max until cl submission actually sets value */
/* Find region with large enough size */ /* Find region with large enough size */
G_D12_StagingRegionNode *match = 0; G_D12_StagingRegionNode *match = 0;
if (ring && ring->size >= size) if (ring && ring->size >= size)
@ -1515,7 +1512,7 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
for (;;) for (;;)
{ {
G_D12_StagingRegionNode *next = r->next; G_D12_StagingRegionNode *next = r->next;
b32 is_completed = completed >= Atomic64Fetch(&r->completion_target); b32 is_completed = completion >= Atomic64Fetch(&r->completion_target);
if (is_completed) if (is_completed)
{ {
u64 region_size = 0; u64 region_size = 0;
@ -1530,7 +1527,7 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
if (region_size < size) if (region_size < size)
{ {
b32 next_is_completed = completed >= Atomic64Fetch(&next->completion_target); b32 next_is_completed = completion >= Atomic64Fetch(&next->completion_target);
if (next_is_completed) if (next_is_completed)
{ {
if (next->pos > r->pos) if (next->pos > r->pos)
@ -1587,8 +1584,7 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
/* Queue old ring for deletion */ /* Queue old ring for deletion */
old_ring = ring; old_ring = ring;
ring = 0; ring = 0;
u64 new_ring_size = MaxU64(AlignU64ToNextPow2(size), Kibi(64)); u64 new_ring_size = MaxU64(AlignU64ToNextPow2(size), Mebi(8));
// u64 new_ring_size = MaxU64(AlignU64ToNextPow2(size), Kibi(128));
if (old_ring) if (old_ring)
{ {
new_ring_size = MaxU64(new_ring_size, old_ring->size * 2); new_ring_size = MaxU64(new_ring_size, old_ring->size * 2);
@ -1667,6 +1663,7 @@ G_D12_StagingRegionNode *G_D12_PushStagingRegion(G_D12_CmdList *cl, u64 size)
if (old_ring) if (old_ring)
{ {
/* FIXME: Queue old ring for deletion with command list */ /* FIXME: Queue old ring for deletion with command list */
DEBUGBREAKABLE;
} }
} }
Unlock(&lock); Unlock(&lock);
@ -2425,14 +2422,16 @@ void G_CopyCpuToTexture(G_CommandListHandle cl_handle, G_ResourceHandle dst_hand
ID3D12Device_GetCopyableFootprints(g->device, &src_desc, 0, 1, 0, &footprint, (u32 *)&footprint_rows_count, &footprint_row_size, &footprint_size); ID3D12Device_GetCopyableFootprints(g->device, &src_desc, 0, 1, 0, &footprint, (u32 *)&footprint_rows_count, &footprint_row_size, &footprint_size);
} }
G_D12_StagingRegionNode *region = G_D12_PushStagingRegion(cl, footprint_size); G_D12_StagingRegionNode *staging_region = G_D12_PushStagingRegion(cl, footprint_size);
footprint.Offset = region->pos; G_D12_Resource *staging_resource = staging_region->ring->resource;
G_ResourceHandle staging_resource_handle = G_D12_MakeHandle(G_ResourceHandle, staging_resource);
footprint.Offset = staging_region->pos;
/* Fill staging buffer */ /* Fill staging buffer */
{ {
D3D12_RANGE read_range = ZI; D3D12_RANGE read_range = ZI;
u8 *src_base = src; u8 *src_base = src;
u8 *dst_base = (u8 *)region->ring->base + footprint.Offset; u8 *dst_base = (u8 *)staging_region->ring->base + footprint.Offset;
u32 z_size = footprint_row_size * footprint_rows_count; u32 z_size = footprint_row_size * footprint_rows_count;
for (i32 z = 0; z < src_dims.z; ++z) for (i32 z = 0; z < src_dims.z; ++z)
{ {
@ -2454,7 +2453,7 @@ void G_CopyCpuToTexture(G_CommandListHandle cl_handle, G_ResourceHandle dst_hand
G_CopyBufferToTexture( G_CopyBufferToTexture(
cl_handle, cl_handle,
dst_handle, dst_copy_range, dst_handle, dst_copy_range,
G_D12_MakeHandle(G_ResourceHandle, region->ring->resource), footprint.Offset staging_resource_handle, footprint.Offset
); );
} }
} }

View File

@ -8,8 +8,11 @@ V_WidgetTheme V_GetWidgetTheme(void)
// theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf"))); // theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf")));
// theme.font_size = 16; // theme.font_size = 16;
theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/roboto-med.ttf"))); theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf")));
theme.font_size = 100; theme.font_size = 64;
// theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/roboto-med.ttf")));
// theme.font_size = 100;
theme.window_background_color = Rgb32(0xff1a1d1e); theme.window_background_color = Rgb32(0xff1a1d1e);
theme.window_border_color = Rgb32(0xff343a3b); theme.window_border_color = Rgb32(0xff343a3b);

View File

@ -79,12 +79,13 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
f32 em_size = font_size * (3.0 / 4.0); f32 em_size = font_size * (3.0 / 4.0);
f32 pixels_per_em = em_size * (TTF_DW_Dpi / 72.0f); f32 pixels_per_em = em_size * (TTF_DW_Dpi / 72.0f);
/* Load font */ //////////////////////////////
//- Load font
HRESULT hr = 0; HRESULT hr = 0;
TTF_DW_Font *font = 0; TTF_DW_Font *font = 0;
{ {
u64 hash = RandU64FromSeeds(ttf.v, (u64)(*(u32 *)&font_size)); u64 hash = RandU64FromSeeds(ttf.v, (u64)(*(u32 *)&font_size));
/* Grab font from cache */
{ {
Lock lock = LockS(&TTF_DW.font_bins_mutex); Lock lock = LockS(&TTF_DW.font_bins_mutex);
{ {
@ -99,7 +100,6 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
} }
Unlock(&lock); Unlock(&lock);
} }
/* Create font new font */
if (!font) if (!font)
{ {
Lock lock = LockE(&TTF_DW.font_bins_mutex); Lock lock = LockE(&TTF_DW.font_bins_mutex);
@ -172,11 +172,60 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
f32 font_descent = font->design_metrics.descent * pixels_per_design_unit; f32 font_descent = font->design_metrics.descent * pixels_per_design_unit;
f32 font_cap = font->design_metrics.capHeight * pixels_per_design_unit; f32 font_cap = font->design_metrics.capHeight * pixels_per_design_unit;
/* Render glyph */
TTF_GlyphResult result = ZI; TTF_GlyphResult result = ZI;
{ {
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
/* TODO: Dynamic render target dimensions? */
//////////////////////////////
//- Fetch render target
Vec2I32 rt_dims = VEC2I32(256, 256);
TTF_DW_RenderTarget *rt = 0;
{
Lock lock = LockE(&TTF_DW.free_render_targets_mutex);
{
rt = TTF_DW.first_free_render_target;
if (rt)
{
SllStackPop(TTF_DW.first_free_render_target);
}
}
Unlock(&lock);
if (!rt)
{
Arena *perm = PermArena();
rt = PushStruct(perm, TTF_DW_RenderTarget);
IDWriteBitmapRenderTarget *dw_rt = 0;
HDC dc = 0;
DIBSECTION dib = ZI;
if (SUCCEEDED(hr))
{
hr = IDWriteGdiInterop_CreateBitmapRenderTarget(TTF_DW.gdi_interop, 0, (UINT32)rt_dims.x, (UINT32)rt_dims.y, &dw_rt);
if (SUCCEEDED(hr))
{
hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(dw_rt, TTF_DW_Dpi / 96.0);
dc = IDWriteBitmapRenderTarget_GetMemoryDC(dw_rt);
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
GetObject(bitmap, sizeof(dib), &dib);
}
}
rt->dims = rt_dims;
rt->dw_rt = dw_rt;
rt->dc = dc;
rt->dib = dib;
rt->hr = hr;
}
if (SUCCEEDED(hr))
{
hr = font->hr;
}
}
//////////////////////////////
//- Render
/* Glyph idx */ /* Glyph idx */
u16 glyph_idx = 0; u16 glyph_idx = 0;
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
@ -192,44 +241,25 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
} }
f32 advance = (f32)m.advanceWidth * pixels_per_design_unit; f32 advance = (f32)m.advanceWidth * pixels_per_design_unit;
/* FIXME: Dynamic render target */
Vec2I32 rt_dims = VEC2I32(256, 256);
/* Best-guess a position in the middle of the render target based on metrics */ /* Best-guess a position in the middle of the render target based on metrics */
Vec2I32 rt_baseline = ZI; Vec2I32 rt_baseline = ZI;
rt_baseline.x = (rt_dims.x / 2) - (advance / 2); rt_baseline.x = (rt_dims.x / 2) - (advance / 2);
rt_baseline.y = (rt_dims.y / 2) + (font_cap / 2); rt_baseline.y = (rt_dims.y / 2) + (font_cap / 2);
/* Create render target */
IDWriteBitmapRenderTarget *render_target = 0;
HDC dc = 0;
DIBSECTION dib = ZI;
if (SUCCEEDED(hr))
{
hr = IDWriteGdiInterop_CreateBitmapRenderTarget(TTF_DW.gdi_interop, 0, (UINT32)rt_dims.x, (UINT32)rt_dims.y, &render_target);
if (SUCCEEDED(hr))
{
hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, TTF_DW_Dpi / 96.0);
dc = IDWriteBitmapRenderTarget_GetMemoryDC(render_target);
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
GetObject(bitmap, sizeof(dib), &dib);
}
}
/* Render */ /* Render */
Rng2I32 rt_slice = ZI; Rng2I32 rt_slice = ZI;
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
/* Clear target */ /* Clear target */
{ {
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); HGDIOBJ original = SelectObject(rt->dc, GetStockObject(DC_PEN));
{ {
SetDCPenColor(dc, bg_color); SetDCPenColor(rt->dc, bg_color);
SelectObject(dc, GetStockObject(DC_BRUSH)); SelectObject(rt->dc, GetStockObject(DC_BRUSH));
SetDCBrushColor(dc, bg_color); SetDCBrushColor(rt->dc, bg_color);
Rectangle(dc, 0, 0, rt_dims.x, rt_dims.y); Rectangle(rt->dc, 0, 0, rt_dims.x, rt_dims.y);
} }
SelectObject(dc, original); SelectObject(rt->dc, original);
} }
/* Draw glyph */ /* Draw glyph */
{ {
@ -242,7 +272,7 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
} }
RECT bounding_box = ZI; RECT bounding_box = ZI;
hr = IDWriteBitmapRenderTarget_DrawGlyphRun( hr = IDWriteBitmapRenderTarget_DrawGlyphRun(
render_target, rt->dw_rt,
rt_baseline.x, rt_baseline.x,
rt_baseline.y, rt_baseline.y,
DWRITE_MEASURING_MODE_NATURAL, DWRITE_MEASURING_MODE_NATURAL,
@ -262,6 +292,9 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
rt_slice.p1.y = MinI32(rt_slice.p1.y, rt_dims.y); rt_slice.p1.y = MinI32(rt_slice.p1.y, rt_dims.y);
} }
//////////////////////////////
//- Copy result
/* Copy from target to result */ /* Copy from target to result */
Vec2I32 dst_dims = ZI; Vec2I32 dst_dims = ZI;
u32 *dst_pixels = 0; u32 *dst_pixels = 0;
@ -269,8 +302,8 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
{ {
dst_dims = DimsFromRng2I32(rt_slice); dst_dims = DimsFromRng2I32(rt_slice);
dst_pixels = PushStructsNoZero(arena, u32, dst_dims.x * dst_dims.y); dst_pixels = PushStructsNoZero(arena, u32, dst_dims.x * dst_dims.y);
u64 src_pitch = (u64)dib.dsBm.bmWidthBytes / 4; u64 src_pitch = (u64)rt->dib.dsBm.bmWidthBytes / 4;
u32 *src_pixels = (u32 *)dib.dsBm.bmBits; u32 *src_pixels = (u32 *)rt->dib.dsBm.bmBits;
for (i32 y = 0; y < dst_dims.y; ++y) for (i32 y = 0; y < dst_dims.y; ++y)
{ {
u64 src_y = rt_slice.p0.y + y; u64 src_y = rt_slice.p0.y + y;
@ -292,7 +325,12 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
result.image_dims = dst_dims; result.image_dims = dst_dims;
result.image_pixels = dst_pixels; result.image_pixels = dst_pixels;
IDWriteBitmapRenderTarget_Release(render_target); /* Free render target */
{
Lock lock = LockE(&TTF_DW.free_render_targets_mutex);
SllStackPush(TTF_DW.first_free_render_target, rt);
Unlock(&lock);
}
} }
result.ttf = ttf; result.ttf = ttf;
result.codepoint = codepoint; result.codepoint = codepoint;

View File

@ -158,6 +158,16 @@ Struct(TTF_DW_Font)
HRESULT hr; HRESULT hr;
}; };
Struct(TTF_DW_RenderTarget)
{
TTF_DW_RenderTarget *next;
Vec2I32 dims;
IDWriteBitmapRenderTarget *dw_rt;
HDC dc;
DIBSECTION dib;
HRESULT hr;
};
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Context types //~ Context types
@ -174,6 +184,9 @@ Struct(TTF_DW_Ctx)
Mutex font_bins_mutex; Mutex font_bins_mutex;
TTF_DW_Font *font_bins[1024]; TTF_DW_Font *font_bins[1024];
Mutex free_render_targets_mutex;
TTF_DW_RenderTarget *first_free_render_target;
}; };
extern TTF_DW_Ctx TTF_DW; extern TTF_DW_Ctx TTF_DW;