diff --git a/src/base/base_arena.c b/src/base/base_arena.c index f6f566ff..1a94ee4a 100644 --- a/src/base/base_arena.c +++ b/src/base/base_arena.c @@ -93,12 +93,12 @@ void *PushBytesNoZero(Arena *arena, u64 size, u64 align) Assert(align > 0); u8 *base = ArenaFirst(arena, u8); - u64 start_pos = AlignU64(arena->pos, align); u64 end_pos = start_pos + size; + void *result = base + start_pos; /* Commit new block(s) */ - if (end_pos > arena->committed) + if (size > 0 && end_pos > arena->committed) { u64 blocks_needed = (end_pos - arena->committed + ArenaBlockSize - 1) / ArenaBlockSize; u64 commit_bytes = blocks_needed * ArenaBlockSize; @@ -119,8 +119,7 @@ void *PushBytesNoZero(Arena *arena, u64 size, u64 align) AsanPoison(commit_address, commit_bytes); } - void *result = base + start_pos; - AsanUnpoison(result, end_pos - start_pos); + AsanUnpoison(result, size); arena->pos = end_pos; return result; diff --git a/src/base/base_inc.h b/src/base/base_inc.h index afbb256a..1c567077 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -10,10 +10,10 @@ # include "base_arena.h" # include "base_futex.h" # include "base_sync.h" -# include "base_wave.h" # include "base_time.h" # include "base_uid.h" # include "base_math.h" +# include "base_wave.h" # include "base_string.h" # include "base_cmdline.h" # include "base_log.h" @@ -36,13 +36,13 @@ # include "base_memory.c" # include "base_arena.c" # include "base_sync.c" -# include "base_wave.c" # include "base_uid.c" # include "base_string.c" # include "base_cmdline.c" # include "base_uni.c" # include "base_buddy.c" # include "base_math.c" +# include "base_wave.c" # include "base_rand.c" # include "base_bitbuff.c" # include "base_resource.c" diff --git a/src/base/base_math.c b/src/base/base_math.c index fdf9ea03..c9db9954 100644 --- a/src/base/base_math.c +++ b/src/base/base_math.c @@ -1125,6 +1125,16 @@ Vec2I32 SubVec2I32(Vec2I32 a, Vec2I32 b) return VEC2I32(a.x - b.x, a.y - b.y); } +Vec2I32 MulVec2I32Vec2I32(Vec2I32 a, Vec2I32 b) +{ + return VEC2I32(a.x * b.x, a.y * b.y); +} + +Vec2I32 DivVec2I32Vec2I32(Vec2I32 a, Vec2I32 b) +{ + return VEC2I32(a.x / b.x, a.y / b.y); +} + //////////////////////////////////////////////////////////// //~ Vec4 @@ -1151,6 +1161,8 @@ u32 U32FromVec4(Vec4 v) //////////////////////////////////////////////////////////// //~ Range +//- Rng2 + Vec2 DimsFromRng2(Rng2 r) { Vec2 result = ZI; @@ -1185,6 +1197,43 @@ Rng2 DivRng2Vec2(Rng2 r, Vec2 v) return result; } +//- Rng2I32 + +Vec2I32 DimsFromRng2I32(Rng2I32 r) +{ + Vec2I32 result = ZI; + result.x = r.p1.x - r.p0.x; + result.y = r.p1.y - r.p0.y; + return result; +} + +Rng2I32 UnionRng2I32(Rng2I32 a, Rng2I32 b) +{ + Rng2I32 result = ZI; + result.p0.x = MinF32(a.p0.x, b.p0.x); + result.p0.y = MinF32(a.p0.y, b.p0.y); + result.p1.x = MaxF32(a.p1.x, b.p1.x); + result.p1.y = MaxF32(a.p1.y, b.p1.y); + return result; +} + +Rng2I32 AddRng2I32Vec2I32(Rng2I32 r, Vec2I32 v) +{ + Rng2I32 result = ZI; + result.p0 = AddVec2I32(result.p0, v); + result.p1 = AddVec2I32(result.p1, v); + return result; +} + +Rng2I32 DivRng2I32Vec2I32(Rng2I32 r, Vec2I32 v) +{ + Rng2I32 result = ZI; + result.p0 = DivVec2I32Vec2I32(result.p0, v); + result.p1 = DivVec2I32Vec2I32(result.p1, v); + return result; +} + + //////////////////////////////////////////////////////////// //~ Xform diff --git a/src/base/base_math.h b/src/base/base_math.h index ce64efcb..56510d19 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -388,12 +388,18 @@ u32 U32FromVec4(Vec4 v); //////////////////////////////////////////////////////////// //~ Range +//- Rng2 Vec2 DimsFromRng2(Rng2 r); Rng2 UnionRng2(Rng2 a, Rng2 b); - Rng2 AddRng2Vec2(Rng2 r, Vec2 v); Rng2 DivRng2Vec2(Rng2 a, Vec2 v); +//- Rng2I32 +Vec2I32 DimsFromRng2I32(Rng2I32 r); +Rng2I32 UnionRng2I32(Rng2I32 a, Rng2I32 b); +Rng2I32 AddRng2I32Vec2I32(Rng2I32 r, Vec2I32 v); +Rng2I32 DivRng2I32Vec2I32(Rng2I32 a, Vec2I32 v); + //////////////////////////////////////////////////////////// //~ Xform diff --git a/src/base/base_wave.c b/src/base/base_wave.c index dd8d2389..11d7d01e 100644 --- a/src/base/base_wave.c +++ b/src/base/base_wave.c @@ -108,10 +108,34 @@ void SetWaveLaneDefaultSpin(WaveLaneCtx *lane, u64 n) } //////////////////////////////////////////////////////////// -//~ Wave task helpers +//~ Task helpers i32 WaveLaneIdxFromTaskIdx(WaveLaneCtx *lane, u64 task_idx) { WaveCtx *wave = lane->wave; return task_idx % wave->lanes_count; } + +RngU64 WaveIdxRangeFromCount(WaveLaneCtx *lane, u64 tasks_count) +{ + u64 lanes_count = lane->wave->lanes_count; + u64 lane_idx = lane->idx; + + u64 tasks_per_lane = tasks_count / lanes_count; + u64 tasks_overflow = tasks_count % lanes_count; + + u64 start = lane_idx * tasks_per_lane; + u64 end = start + tasks_per_lane; + if (lane_idx < tasks_overflow) + { + start += lane_idx; + end += lane_idx + 1; + } + else + { + start += tasks_overflow; + end += tasks_overflow; + } + + return RNGU64(start, end); +} diff --git a/src/base/base_wave.h b/src/base/base_wave.h index 4dcf99b7..3b24378c 100644 --- a/src/base/base_wave.h +++ b/src/base/base_wave.h @@ -46,9 +46,10 @@ void WaveSyncBroadcastEx_(WaveLaneCtx *lane, u32 broadcast_lane_idx, void *broad void SetWaveLaneDefaultSpin(WaveLaneCtx *lane, u64 n); //////////////////////////////////////////////////////////// -//~ Wave task helpers +//~ Task helpers i32 WaveLaneIdxFromTaskIdx(WaveLaneCtx *lane, u64 task_idx); +RngU64 WaveIdxRangeFromCount(WaveLaneCtx *lane, u64 tasks_count); //////////////////////////////////////////////////////////// //~ @hookdecl Dispatch diff --git a/src/config.h b/src/config.h index 6329442a..960b7842 100644 --- a/src/config.h +++ b/src/config.h @@ -69,8 +69,8 @@ #define FLOOD_DEBUG 0 -#define GPU_DEBUG 0 -#define GPU_DEBUG_VALIDATION 0 +#define GPU_DEBUG 1 +#define GPU_DEBUG_VALIDATION 1 #define GPU_SHADER_PRINT 1 #define GPU_SHADER_PRINT_BUFFER_SIZE Kibi(64); diff --git a/src/glyph_cache/glyph_cache.c b/src/glyph_cache/glyph_cache.c index 318d306d..a1705927 100644 --- a/src/glyph_cache/glyph_cache.c +++ b/src/glyph_cache/glyph_cache.c @@ -14,13 +14,14 @@ void GC_Bootstrap(void) GC_FontKey GC_FontKeyFromResource(ResourceKey resource) { GC_FontKey result = ZI; - result.v = resource.v; + result.r = resource; return result; } u64 GC_HashFromGlyphDesc(GC_GlyphDesc desc) { - return RandU64FromSeeds(desc.font.v, ((u64)desc.codepoint << 32) | *(u32 *)&desc.font_size); + /* TODO: Lower font-size precision to prevent unique hashes for slightly-different font sizes */ + return RandU64FromSeeds(desc.font.r.v, ((u64)desc.codepoint << 32) | *(u32 *)&desc.font_size); } //////////////////////////////////////////////////////////// @@ -31,21 +32,33 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size { GC_Run result = ZI; TempArena scratch = BeginScratch(arena); + Arena *perm = PermArena(); - String32 codepoints = String32FromString(scratch.arena, str); + u64 codepoints_count = 0; + u32 *codepoints = 0; + { + String32 str32 = String32FromString(scratch.arena, str); + codepoints_count = str32.len; + codepoints = str32.text; + } - String32 uncached_codepoints = ZI; - uncached_codepoints.text = PushStructsNoZero(scratch.arena, u32, codepoints.len); + ////////////////////////////// + //- Grab glyphs from cache u64 ready_glyphs_count = 0; - GC_Glyph **ready_glyphs = PushStructsNoZero(scratch.arena, GC_Glyph *, str.len); + GC_Glyph **ready_glyphs = PushStructsNoZero(scratch.arena, GC_Glyph *, codepoints_count); + + u32 *uncached_codepoints = PushStructsNoZero(scratch.arena, u32, codepoints_count); + u64 uncached_codepoints_count = 0; + + u64 pending_glyphs_count = 0; { - if (codepoints.len > 0) + if (codepoints_count > 0) { Lock lock = LockS(&GC.glyphs_mutex); - for (u64 codepoint_idx = 0; codepoint_idx < codepoints.len; ++codepoint_idx) + for (u64 codepoint_idx = 0; codepoint_idx < codepoints_count; ++codepoint_idx) { - u32 codepoint = codepoints.text[codepoint_idx]; + u32 codepoint = codepoints[codepoint_idx]; GC_GlyphDesc desc = ZI; desc.font = font; @@ -54,20 +67,20 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size u64 hash = GC_HashFromGlyphDesc(desc); GC_GlyphBin *bin = &GC.glyph_bins[hash % countof(GC.glyph_bins)]; - GC_Glyph *glyph = bin->first; for (; glyph; glyph = glyph->next) { - if (glyph->hash == hash) - { - break; - } + if (glyph->hash == hash) break; } if (glyph == 0) { - uncached_codepoints.text[uncached_codepoints.len] = codepoint; - uncached_codepoints.len += 1; + uncached_codepoints[uncached_codepoints_count] = codepoint; + uncached_codepoints_count += 1; + } + else if (Atomic32Fetch(&glyph->ready) == 0) + { + pending_glyphs_count += 1; } else { @@ -79,10 +92,77 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size } } - if (uncached_codepoints.len > 0) + ////////////////////////////// + //- Create cache entries + + u64 submit_cmds_count = 0; + GC_Cmd *submit_cmds = PushStructsNoZero(scratch.arena, GC_Cmd, uncached_codepoints_count); + if (uncached_codepoints_count > 0) { + Lock lock = LockE(&GC.glyphs_mutex); + { + for (u64 uncached_codepoint_idx = 0; uncached_codepoint_idx < uncached_codepoints_count; ++uncached_codepoint_idx) + { + GC_GlyphDesc desc = ZI; + desc.font = font; + desc.font_size = font_size; + desc.codepoint = uncached_codepoints[uncached_codepoint_idx]; + + u64 hash = GC_HashFromGlyphDesc(desc); + GC_GlyphBin *bin = &GC.glyph_bins[hash % countof(GC.glyph_bins)]; + GC_Glyph *glyph = bin->first; + for (; glyph; glyph = glyph->next) + { + if (glyph->hash == hash) break; + } + + if (glyph == 0) + { + glyph = PushStruct(perm, GC_Glyph); + glyph->desc = desc; + glyph->hash = hash; + SllStackPush(bin->first, glyph); + /* Create cmd */ + { + GC_Cmd *cmd = &submit_cmds[submit_cmds_count]; + cmd->glyph = glyph; + ++submit_cmds_count; + } + } + } + } + Unlock(&lock); } + ////////////////////////////// + //- Submit cmds + + if (submit_cmds_count > 0) + { + Lock lock = LockE(&GC.submit.mutex); + for (u64 cmd_idx = 0; cmd_idx < submit_cmds_count; ++cmd_idx) + { + GC_Cmd *src = &submit_cmds[cmd_idx]; + GC_CmdNode *n = GC.submit.first_free; + if (n) + { + SllStackPop(GC.submit.first_free); + ZeroStruct(n); + } + else + { + n = PushStruct(perm, GC_CmdNode); + } + n->cmd = *src; + GC.submit.count += 1; + SllQueuePush(GC.submit.first, GC.submit.last, n); + } + Unlock(&lock); + } + + ////////////////////////////// + //- Create run from glyphs + f32 baseline_pos = 0; result.rects = PushStructs(arena, GC_RunRect, ready_glyphs_count); result.rects_count = ready_glyphs_count; @@ -98,8 +178,7 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size rect->baseline_pos = baseline_pos; rect->advance = glyph->advance; - rect->bounds.p0 = glyph->baseline_offset; - rect->bounds.p1 = AddVec2(rect->bounds.p0, DimsFromRng2(glyph->tex_rect)); + rect->bounds = glyph->bounds; if (glyph_idx == 0) { @@ -123,7 +202,7 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size result.font_cap = glyph->font_cap; } - result.ready = uncached_codepoints.len == 0; + result.ready = uncached_codepoints_count == 0 && pending_glyphs_count; EndScratch(scratch); return result; @@ -137,21 +216,154 @@ void GC_TickAsync(WaveLaneCtx *lane, AsyncTickCtx *tick) GC_AsyncCtx *async = &GC.async_ctx; ////////////////////////////// - //- Begin tick + //- Collect cmds + + /* TODO: Limit cmds processed per-tick */ if (lane->idx == 0) { - Lock lock = LockE(&GC.submitted_cmds_mutex); + Lock lock = LockE(&GC.submit.mutex); { - async->cmds.count = GC.submitted_cmds_count; - async->cmds.v = PushStructsNoZero(tick->arena, GC_Cmd, GC.submitted_cmds_count); + /* Pop cmds from submission queue */ + async->cmds.count = GC.submit.count; + async->cmds.v = PushStructsNoZero(tick->arena, GC_Cmd, GC.submit.count); + u64 cmd_idx = 0; + for (GC_CmdNode *n = GC.submit.first; n; n = n->next) + { + async->cmds.v[cmd_idx] = n->cmd; + ++cmd_idx; + } + /* Reset submission queue */ + GC.submit.first_free = GC.submit.first; + GC.submit.count = 0; + GC.submit.first = 0; + GC.submit.last = 0; } Unlock(&lock); } WaveSync(lane); + ////////////////////////////// + //- Allocate atlas rects + if (lane->idx == 0) + { + /* TODO: Remove this */ + /* Create atlas */ + Vec2I32 atlas_size = VEC2I32(8192, 8192); + if (G_IsResourceNil(GC.atlas)) + { + G_ArenaHandle gpu_arena = G_PermArena(); + GC.atlas = G_PushTexture2D( + gpu_arena, + G_Format_R8G8B8A8_Unorm_Srgb, + atlas_size, + /* 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_size.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 + + WaveSync(lane); } diff --git a/src/glyph_cache/glyph_cache.h b/src/glyph_cache/glyph_cache.h index b28136d5..75c25802 100644 --- a/src/glyph_cache/glyph_cache.h +++ b/src/glyph_cache/glyph_cache.h @@ -3,38 +3,12 @@ Struct(GC_FontKey) { - u64 v; + ResourceKey r; }; //////////////////////////////////////////////////////////// //~ Glyph types -Struct(GC_Glyph) -{ - GC_Glyph *next; - u64 hash; - - /* Layout info */ - Vec2 baseline_offset; - f32 advance; - - /* Atlas info */ - G_Texture2DRef tex_ref; - Rng2 tex_uv; - Rng2 tex_rect; - - /* Font info */ - f32 font_size; - f32 font_ascent; - f32 font_descent; - f32 font_cap; -}; - -Struct(GC_GlyphBin) -{ - GC_Glyph *first; -}; - Struct(GC_GlyphDesc) { GC_FontKey font; @@ -42,6 +16,36 @@ Struct(GC_GlyphDesc) u32 codepoint; }; +Struct(GC_Glyph) +{ + GC_Glyph *next; + + GC_GlyphDesc desc; + u64 hash; + + Atomic32 ready; + + /* Font info */ + f32 font_size; + f32 font_ascent; + f32 font_descent; + f32 font_cap; + + /* Layout info */ + f32 advance; + Rng2 bounds; /* Bounds relative to baseline position */ + + /* Atlas info */ + G_Texture2DRef tex_ref; + Rng2 tex_uv; + Rng2 tex_rect; +}; + +Struct(GC_GlyphBin) +{ + GC_Glyph *first; +}; + // Struct(GC_GlyphDescChunk) // { // GC_GlyphDescChunk *next; @@ -94,12 +98,10 @@ Struct(GC_Cmd) GC_Glyph *glyph; }; -Struct(GC_CmdChunk) +Struct(GC_CmdNode) { - GC_CmdChunk *next; - - u64 cmds_count; - GC_Cmd *cmds; + GC_CmdNode *next; + GC_Cmd cmd; }; //////////////////////////////////////////////////////////// @@ -119,13 +121,25 @@ Struct(GC_Ctx) Mutex glyphs_mutex; GC_GlyphBin glyph_bins[16384]; - Mutex submitted_cmds_mutex; - u64 submitted_cmds_count; - GC_CmdChunk *first_submitted_cmd_chunk; - GC_CmdChunk *last_submitted_cmd_chunk; + /* TODO: Dynamic atlases */ + G_ResourceHandle atlas; + G_Texture2DRef atlas_ref; + Vec2I32 atlas_pos; + i32 atlas_row_height; + + struct + { + Mutex mutex; + u64 count; + GC_CmdNode *first; + GC_CmdNode *last; + GC_CmdNode *first_free; + } submit; GC_AsyncCtx async_ctx; -} extern GC; +}; + +extern GC_Ctx GC; //////////////////////////////////////////////////////////// //~ Bootstrap diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 9a74056c..d77ec7b4 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -249,7 +249,7 @@ void V_TickForever(WaveLaneCtx *lane) { /* TODO: Don't rely on ui report for draw size since it introduces one frame of delay when resizing */ UI_Report vis_rep = UI_ReportFromKey(vis_box); - draw_size = Vec2I32FromVec(DimsFromRng2(vis_rep.screen_rect)); + draw_size = RoundVec2ToVec2I32(DimsFromRng2(vis_rep.screen_rect)); } draw_size.x = MaxI32(draw_size.x, 1); draw_size.y = MaxI32(draw_size.y, 1); diff --git a/src/proto/proto.c b/src/proto/proto.c index 4dff77a3..8a4c6ae8 100644 --- a/src/proto/proto.c +++ b/src/proto/proto.c @@ -22,10 +22,10 @@ void PT_RunForever(WaveLaneCtx *lane) /* Push resources */ Vec2I32 final_target_size = window_frame.draw_size; G_ResourceHandle final_target = G_PushTexture2D(gpu_frame_arena, - G_Format_R16G16B16A16_Float, - final_target_size, - G_Layout_DirectQueue_ShaderReadWrite, - .flags = G_ResourceFlag_AllowShaderReadWrite); + G_Format_R16G16B16A16_Float, + final_target_size, + G_Layout_DirectQueue_ShaderReadWrite, + .flags = G_ResourceFlag_AllowShaderReadWrite); /* Push resource handles */ G_Texture2DRef final_target_rhandle = G_PushTexture2DRef(gpu_frame_arena, final_target); diff --git a/src/ttf/ttf.h b/src/ttf/ttf.h index f056343b..23b31138 100644 --- a/src/ttf/ttf.h +++ b/src/ttf/ttf.h @@ -1,27 +1,27 @@ -Struct(TTF_Glyph) +//////////////////////////////////////////////////////////// +//~ Glyph types + +Struct(TTF_GlyphDesc) { - i32 advance; - Vec2 baseline_offset; - Vec2I32 atlas_p0; - Vec2I32 atlas_p1; + ResourceKey ttf; + u32 codepoint; + + f32 advance; /* How far to advance the baseline position */ + Rng2 bounds; /* Bounds relative to baseline position */ + + f32 font_size; + f32 font_ascent; + f32 font_descent; + f32 font_cap; }; -Struct(TTF_Decoded) -{ - TTF_Glyph *glyphs; - u16 glyphs_count; - u16 *cache_indices; /* Array of indices into the `glyphs` array in order of `cache_chars` */ - u32 image_width; - u32 image_height; - u32 *image_pixels; /* Array of [width * height] pixels */ - - /* Metrics */ - f32 ascent; - f32 descent; - f32 cap; - -}; +//////////////////////////////////////////////////////////// +//~ Bootstrap void TTF_Bootstrap(void); -TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 em_size, u32 *cache_codes, u32 cache_codes_count); +//////////////////////////////////////////////////////////// +//~ Decode + +TTF_GlyphDesc TTF_GlyphDescFromCodepoint(u32 codepoint, ResourceKey ttf, f32 font_size); +void TTF_RasterizeGlyph(u32 *dst, Vec2I32 dst_size, Rng2I32 dst_slice, TTF_GlyphDesc glyph_desc); diff --git a/src/ttf/ttf_dwrite/ttf_dwrite.c b/src/ttf/ttf_dwrite/ttf_dwrite.c index 85fe40cc..3f901714 100644 --- a/src/ttf/ttf_dwrite/ttf_dwrite.c +++ b/src/ttf/ttf_dwrite/ttf_dwrite.c @@ -1,277 +1,804 @@ /* Based on Allen Webster's dwrite rasterizer example - * https://github.com/4th-dimention/examps */ -TTF_DW_SharedState TTF_DW_shared_state = ZI; +extern TTF_DW_Ctx TTF_DW = ZI; //////////////////////////////////////////////////////////// //~ @hookimpl Bootstrap void TTF_Bootstrap(void) { - TTF_DW_SharedState *g = &TTF_DW_shared_state; - Assert(!g->factory); /* FIXME: I think IDWriteFactory5 only exists on later updates of windows * 10? Need to verify. Maybe should just use a custom loader. (We're only * using a factory5 since I think WriteInMemoryFileLoader wasn't * implemented until then) */ - HRESULT error = DWriteCreateFactory( + HRESULT hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, &IID_IDWriteFactory5, - (void **)&g->factory + (void **)&TTF_DW.factory ); - if (error != S_OK) + if (!SUCCEEDED(hr)) { Panic(Lit("Error creating DWrite factory")); - (*(volatile int *)0) = 0; } } //////////////////////////////////////////////////////////// //~ @hookimpl Decode -TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 em_size, u32 *cache_codes, u32 cache_codes_count) +TTF_GlyphDesc TTF_GlyphDescFromCodepoint(u32 codepoint, ResourceKey ttf, f32 font_size) { - TTF_DW_SharedState *g = &TTF_DW_shared_state; - COLORREF bg_color = 0xFF000000; - COLORREF fg_color = 0xFFFFFFFF; + String encoded = DataFromResource(ttf); - IDWriteFactory5 *factory = g->factory; + /* TODO: Handle errors */ + HRESULT hr = 0; - /* TODO: handle errors */ - HRESULT error = 0; + TTF_GlyphDesc result = ZI; + result.font_size = font_size; + result.ttf = ttf; + result.codepoint = codepoint; + + f32 em_size = font_size * (3.0 / 4.0); + f32 pixel_per_em = em_size * (TTF_DW_Dpi / 72.0f); /* File */ IDWriteFontFile *font_file = 0; { /* Create in memory loader */ IDWriteInMemoryFontFileLoader *loader = 0; - error = IDWriteFactory5_CreateInMemoryFontFileLoader(factory, &loader); - error = IDWriteFactory5_RegisterFontFileLoader(factory, (IDWriteFontFileLoader *)loader); + hr = IDWriteFactory5_CreateInMemoryFontFileLoader(TTF_DW.factory, &loader); + hr = IDWriteFactory5_RegisterFontFileLoader(TTF_DW.factory, (IDWriteFontFileLoader *)loader); IDWriteFontSetBuilder1 *builder = 0; - error = IDWriteFactory5_CreateFontSetBuilder1(factory, &builder); - error = IDWriteInMemoryFontFileLoader_CreateInMemoryFontFileReference(loader, (IDWriteFactory *)factory, encoded.text, (u32)encoded.len, 0, &font_file); - error = IDWriteFontSetBuilder1_AddFontFile(builder, font_file); + hr = IDWriteFactory5_CreateFontSetBuilder1(TTF_DW.factory, &builder); + hr = IDWriteInMemoryFontFileLoader_CreateInMemoryFontFileReference(loader, (IDWriteFactory *)TTF_DW.factory, encoded.text, (u32)encoded.len, 0, &font_file); + hr = IDWriteFontSetBuilder1_AddFontFile(builder, font_file); } - //- Create face + /* Face */ IDWriteFontFace *font_face = 0; - error = IDWriteFactory5_CreateFontFace(factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); + { + hr = IDWriteFactory5_CreateFontFace(TTF_DW.factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); + } - //- Setup rendering params - IDWriteRenderingParams *default_rendering_params = 0; - error = IDWriteFactory5_CreateRenderingParams(factory, &default_rendering_params); - IDWriteRenderingParams *rendering_params = 0; - FLOAT gamma = IDWriteRenderingParams_GetGamma(default_rendering_params); - FLOAT enhanced_contrast = IDWriteRenderingParams_GetEnhancedContrast(default_rendering_params); - FLOAT clear_type_level = IDWriteRenderingParams_GetClearTypeLevel(default_rendering_params); - error = IDWriteFactory5_CreateCustomRenderingParams(factory, gamma, - enhanced_contrast, - clear_type_level, - DWRITE_PIXEL_GEOMETRY_FLAT, - DWRITE_RENDERING_MODE_DEFAULT, - &rendering_params); + /* Font metrics */ + DWRITE_FONT_METRICS font_metrics = ZI; + { + IDWriteFontFace_GetMetrics(font_face, &font_metrics); + } + f32 pixel_per_design_unit = pixel_per_em / ((f32)font_metrics.designUnitsPerEm); + result.font_ascent = font_metrics.ascent * pixel_per_design_unit; + result.font_descent = font_metrics.descent * pixel_per_design_unit; + result.font_cap = font_metrics.capHeight * pixel_per_design_unit; - //- Setup interop - IDWriteGdiInterop *dwrite_gdi_interop = 0; - error = IDWriteFactory5_GetGdiInterop(factory, &dwrite_gdi_interop); + /* Glyph idx */ + u16 glyph_idx = 0; + { + hr = IDWriteFontFace_GetGlyphIndices(font_face, &codepoint, 1, &glyph_idx); + } - //- Get metrics - DWRITE_FONT_METRICS metrics = ZI; - IDWriteFontFace_GetMetrics(font_face, &metrics); - u16 glyph_count = IDWriteFontFace_GetGlyphCount(font_face); + /* Glyph metrics */ + { + DWRITE_GLYPH_METRICS m = ZI; + { + hr = IDWriteFontFace_GetDesignGlyphMetrics(font_face, &glyph_idx, 1, &m, 0); + } + Rng2 bounds = ZI; + { + bounds.p0.x = (f32)m.leftSideBearing; + bounds.p1.x = (f32)m.advanceWidth - (f32)m.rightSideBearing; + bounds.p0.y = (f32)m.verticalOriginY - (f32)m.advanceHeight + m.bottomSideBearing; + bounds.p1.y = (f32)m.verticalOriginY - (f32)m.topSideBearing; + } + result.bounds.p0.x = bounds.p0.x * pixel_per_design_unit; + result.bounds.p0.y = bounds.p0.y * pixel_per_design_unit; + result.bounds.p1.x = bounds.p1.x * pixel_per_design_unit; + result.bounds.p1.y = bounds.p1.y * pixel_per_design_unit; + result.advance = (f32)m.advanceWidth * pixel_per_design_unit; + } + + + + + return result; +} + +void TTF_RasterizeGlyph(u32 *dst, Vec2I32 dst_size, Rng2I32 dst_slice, TTF_GlyphDesc glyph_desc) +{ + COLORREF bg_color = 0xFF000000; + COLORREF fg_color = 0xFFFFFFFF; + + f32 em_size = glyph_desc.font_size * (3.0 / 4.0); f32 pixel_per_em = em_size * (TTF_DW_Dpi / 72.0f); - f32 pixel_per_design_unit = pixel_per_em / ((f32)metrics.designUnitsPerEm); - i32 raster_target_w = (i32)(8.0f * ((f32)metrics.capHeight) * pixel_per_design_unit); - i32 raster_target_h = raster_target_w; + /* TODO: handle errors */ + HRESULT hr = 0; - f32 raster_target_x = (f32)(raster_target_w / 2); - f32 raster_target_y = raster_target_x; + String encoded = DataFromResource(glyph_desc.ttf); - Assert((f32)((i32)raster_target_x) == raster_target_x); - Assert((f32)((i32)raster_target_y) == raster_target_y); + /* File */ + IDWriteFontFile *font_file = 0; + { + /* Create in memory loader */ + IDWriteInMemoryFontFileLoader *loader = 0; + hr = IDWriteFactory5_CreateInMemoryFontFileLoader(TTF_DW.factory, &loader); + hr = IDWriteFactory5_RegisterFontFileLoader(TTF_DW.factory, (IDWriteFontFileLoader *)loader); - //- Setup render target + IDWriteFontSetBuilder1 *builder = 0; + hr = IDWriteFactory5_CreateFontSetBuilder1(TTF_DW.factory, &builder); + hr = IDWriteInMemoryFontFileLoader_CreateInMemoryFontFileReference(loader, (IDWriteFactory *)TTF_DW.factory, encoded.text, (u32)encoded.len, 0, &font_file); + hr = IDWriteFontSetBuilder1_AddFontFile(builder, font_file); + } + + /* Face */ + IDWriteFontFace *font_face = 0; + { + hr = IDWriteFactory5_CreateFontFace(TTF_DW.factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); + } + + /* Glyph idx */ + u16 glyph_idx = 0; + { + hr = IDWriteFontFace_GetGlyphIndices(font_face, &glyph_desc.codepoint, 1, &glyph_idx); + } + + + + + /* Get interop */ + IDWriteGdiInterop *dwrite_gdi_interop = 0; + { + hr = IDWriteFactory5_GetGdiInterop(TTF_DW.factory, &dwrite_gdi_interop); + } + + + + + /* FIXME: Dynamic render target */ + i32 render_target_w = 256; + i32 render_target_h = 256; + i32 render_target_baseline_x = (render_target_w / 2) - DimsFromRng2I32(dst_slice).x; + i32 render_target_baseline_y = (render_target_h / 2) - DimsFromRng2I32(dst_slice).y; + + /* Create render target */ IDWriteBitmapRenderTarget *render_target = 0; - /* FIXME: errors when em_size too high */ - error = IDWriteGdiInterop_CreateBitmapRenderTarget(dwrite_gdi_interop, 0, (UINT32)raster_target_w, (UINT32)raster_target_h, &render_target); - IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, 1.0); + { + hr = IDWriteGdiInterop_CreateBitmapRenderTarget(dwrite_gdi_interop, 0, (UINT32)render_target_w, (UINT32)render_target_h, &render_target); + hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, TTF_DW_Dpi / 96.0); + } - /* Clear the render target */ + /* Get device context */ HDC dc = 0; { dc = IDWriteBitmapRenderTarget_GetMemoryDC(render_target); + } + + /* Get dib info */ + DIBSECTION dib = ZI; + { + HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); + GetObject(bitmap, sizeof(dib), &dib); + } + + /* Clear render target */ + { HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); - SetDCPenColor(dc, bg_color); - SelectObject(dc, GetStockObject(DC_BRUSH)); - SetDCBrushColor(dc, bg_color); - Rectangle(dc, 0, 0, raster_target_w, raster_target_h); + { + SetDCPenColor(dc, bg_color); + SelectObject(dc, GetStockObject(DC_BRUSH)); + SetDCBrushColor(dc, bg_color); + Rectangle(dc, 0, 0, render_target_w, render_target_h); + } SelectObject(dc, original); } - //- Setup atlas - /* Acquire font memory */ - TTF_Glyph *glyphs = (TTF_Glyph *)PushStructs(arena, TTF_Glyph, glyph_count); - /* Acquire (starting) atlas memory - * NOTE: This is unnecessary since atlas memory will grow anyway. Could - * just start w/ atlas height 0. - */ - u64 atlas_w = 1024; - u64 atlas_h = 1; - u32 *atlas_memory = PushStructs(arena, u32, atlas_w * atlas_h); - i32 ascent = 0; - i32 descent = 0; - i32 cap = 0; - //- Fill atlas & metric data - u32 out_offset_x = 0; - u32 out_offset_y = 0; - u32 row_height = 0; + + + /* Create rendering params */ + IDWriteRenderingParams *rendering_params = 0; { - for (u16 i = 0; i < glyph_count; ++i) + /* Get default rendering params */ + IDWriteRenderingParams *default_rendering_params = 0; + { + hr = IDWriteFactory5_CreateRenderingParams(TTF_DW.factory, &default_rendering_params); + } + f32 gamma = IDWriteRenderingParams_GetGamma(default_rendering_params); + f32 enhanced_contrast = IDWriteRenderingParams_GetEnhancedContrast(default_rendering_params); + f32 clear_type_level = IDWriteRenderingParams_GetClearTypeLevel(default_rendering_params); + hr = IDWriteFactory5_CreateCustomRenderingParams( + TTF_DW.factory, + gamma, + enhanced_contrast, + clear_type_level, + DWRITE_PIXEL_GEOMETRY_FLAT, + DWRITE_RENDERING_MODE_DEFAULT, + &rendering_params + ); + } + + /* Render glyph to target */ + Rng2I32 src_slice = ZI; + { + DWRITE_GLYPH_RUN glyph_run = ZI; { - //- Render glyph to render target - DWRITE_GLYPH_RUN glyph_run = ZI; glyph_run.fontFace = font_face; glyph_run.fontEmSize = pixel_per_em; glyph_run.glyphCount = 1; - glyph_run.glyphIndices = &i; + glyph_run.glyphIndices = &glyph_idx; + } + RECT bounding_box = ZI; + hr = IDWriteBitmapRenderTarget_DrawGlyphRun( + render_target, + render_target_baseline_x, + render_target_baseline_y, + DWRITE_MEASURING_MODE_NATURAL, + &glyph_run, + rendering_params, + fg_color, + &bounding_box + ); + src_slice.p0.x = bounding_box.left; + src_slice.p0.y = bounding_box.top; + src_slice.p1.x = bounding_box.right; + src_slice.p1.y = bounding_box.bottom; + } - RECT bounding_box = ZI; - error = IDWriteBitmapRenderTarget_DrawGlyphRun(render_target, - raster_target_x, - raster_target_y, - DWRITE_MEASURING_MODE_NATURAL, - &glyph_run, - rendering_params, - fg_color, - &bounding_box - ); + Vec2I32 src_slice_dims = DimsFromRng2I32(src_slice); + Vec2I32 dst_slice_dims = DimsFromRng2I32(dst_slice); - if (bounding_box.left < 0 - || bounding_box.top < 0 - || bounding_box.right > raster_target_w - || bounding_box.bottom > raster_target_h) + /* FIXME: Handle gracefully */ + Assert(src_slice_dims.x <= dst_slice_dims.x); + Assert(src_slice_dims.y <= dst_slice_dims.y); + + /* FIXME: Account for render target overrun */ + /* Copy result from target */ + { + u64 src_pitch = (u64)dib.dsBm.bmWidthBytes / 4; + u32 *src_pixels = (u32 *)dib.dsBm.bmBits; + for (i32 y = 0; y < src_slice_dims.y; ++y) + { + u64 src_y = src_slice.p0.y + y; + u64 dst_y = dst_slice.p0.y + y; + for (i32 x = 0; x < src_slice_dims.x; ++x) { - /* Skip */ - continue; - } - - //- Compute glyph metrics - DWRITE_GLYPH_METRICS glyph_metrics = ZI; - error = IDWriteFontFace_GetDesignGlyphMetrics(font_face, &i, 1, &glyph_metrics, 0); - ascent = metrics.ascent * pixel_per_design_unit; - descent = metrics.descent * pixel_per_design_unit; - cap = metrics.capHeight * pixel_per_design_unit; - - f32 off_x = (f32)bounding_box.left - raster_target_x; - f32 off_y = (f32)bounding_box.top - raster_target_y; - i32 tex_w = bounding_box.right - bounding_box.left; - i32 tex_h = bounding_box.bottom - bounding_box.top; - f32 advance = CeilF32ToI32((f32)glyph_metrics.advanceWidth * pixel_per_design_unit); - - TTF_Glyph *glyph = &glyphs[i]; - glyph->baseline_offset = VEC2(off_x, off_y); - glyph->advance = advance; - - /* Get the bitmap */ - HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); - DIBSECTION dib = ZI; - GetObject(bitmap, sizeof(dib), &dib); - - /* Start new row if necessary */ - if ((out_offset_x + tex_w) >= atlas_w) - { - out_offset_y += row_height; - out_offset_x = 0; - row_height = 0; - } - - /* Grow atlas height */ - if ((out_offset_y + tex_h) > atlas_h) - { - u64 diff = (out_offset_y + tex_h) - atlas_h; - /* NOTE: This allocation must be contiguous with the initial atlas - * allocation (IE: No non-atlas arena PUSHes) */ - PushStructs(arena, u32, diff * atlas_w); - atlas_h += diff; - } - - /* Set bounding box metrics (now that we know atlas x & y) */ - glyph->atlas_p0 = VEC2I32(out_offset_x, out_offset_y); - glyph->atlas_p1 = VEC2I32(out_offset_x + tex_w, out_offset_y + tex_h); - - //- Fill atlas - u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4; - u32 *in_data = (u32 *)dib.dsBm.bmBits; - u32 *out_data = atlas_memory; - for (i32 y = 0; y < tex_h; ++y) - { - u64 out_y = out_offset_y + y; - u64 in_y = (u64)bounding_box.top + y; - for (i32 x = 0; x < tex_w; ++x) - { - u64 out_x = out_offset_x + x; - u64 in_x = (u64)bounding_box.left + x; - u32 *out_pixel = out_data + (out_x + (out_y * atlas_w)); - u32 *in_pixel = in_data + (in_x + (in_y * in_pitch)); - *out_pixel = 0x00FFFFFF | ((*in_pixel & 0xFF) << 24); - } - } - out_offset_x += tex_w; - - /* Grow row height */ - if ((u32)tex_h > row_height) - { - row_height = (u32)tex_h; - } - - //- Clear render target - { - HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); - SetDCPenColor(dc, bg_color); - SelectObject(dc, GetStockObject(DC_BRUSH)); - SetDCBrushColor(dc, bg_color); - Rectangle(dc, bounding_box.left, bounding_box.top, bounding_box.right, bounding_box.bottom); - SelectObject(dc, original); + u64 src_x = src_slice.p0.x + x; + u64 dst_x = dst_slice.p0.x + x; + u32 *src_pixel = src_pixels + (src_x + (src_y * src_pitch)); + u32 *dst_pixel = dst + (dst_x + (dst_y * dst_size.x)); + *dst_pixel = 0x00FFFFFF | ((*src_pixel & 0xFF) << 24); } } } - //- Construct indices - u16 *cache_indices = 0; - if (cache_codes_count > 0) - { - cache_indices = PushStructs(arena, u16, cache_codes_count); - IDWriteFontFace_GetGlyphIndices(font_face, cache_codes, cache_codes_count, cache_indices); - } - //- Release - /* FIXME: Check for leaks */ - IDWriteGdiInterop_Release(dwrite_gdi_interop); - IDWriteRenderingParams_Release(rendering_params); - IDWriteRenderingParams_Release(default_rendering_params); - // NOTE FROM ALLEN: We don't release font face because we intend to keep the font face around after the baking process - // IDWriteFontFace_Release(font_face); - IDWriteFontFile_Release(font_file); - //loader->Release(); - IDWriteFactory5_Release(factory); - /* Return */ - TTF_Decoded result = ZI; - result.glyphs = glyphs; - result.glyphs_count = glyph_count; - result.cache_indices = cache_indices; - result.image_width = (u32)atlas_w; - result.image_height = (u32)atlas_h; - result.image_pixels = (u32 *)atlas_memory; - result.ascent = ascent; - result.descent = descent; - result.cap = cap; - return result; + + + // //- Create face + // IDWriteFontFace *font_face = 0; + // error = IDWriteFactory5_CreateFontFace(factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); + + // //- Setup rendering params + // IDWriteRenderingParams *default_rendering_params = 0; + // error = IDWriteFactory5_CreateRenderingParams(factory, &default_rendering_params); + // IDWriteRenderingParams *rendering_params = 0; + // FLOAT gamma = IDWriteRenderingParams_GetGamma(default_rendering_params); + // FLOAT enhanced_contrast = IDWriteRenderingParams_GetEnhancedContrast(default_rendering_params); + // FLOAT clear_type_level = IDWriteRenderingParams_GetClearTypeLevel(default_rendering_params); + // error = IDWriteFactory5_CreateCustomRenderingParams(factory, gamma, + // enhanced_contrast, + // clear_type_level, + // DWRITE_PIXEL_GEOMETRY_FLAT, + // DWRITE_RENDERING_MODE_DEFAULT, + // &rendering_params); + + // //- Setup interop + // IDWriteGdiInterop *dwrite_gdi_interop = 0; + // error = IDWriteFactory5_GetGdiInterop(factory, &dwrite_gdi_interop); + + // //- Get metrics + // DWRITE_FONT_METRICS metrics = ZI; + // IDWriteFontFace_GetMetrics(font_face, &metrics); + + // u16 glyph_count = IDWriteFontFace_GetGlyphCount(font_face); + + // f32 pixel_per_em = em_size * (TTF_DW_Dpi / 72.0f); + // f32 pixel_per_design_unit = pixel_per_em / ((f32)metrics.designUnitsPerEm); + + // i32 raster_target_w = (i32)(8.0f * ((f32)metrics.capHeight) * pixel_per_design_unit); + // i32 raster_target_h = raster_target_w; + + // f32 raster_target_x = (f32)(raster_target_w / 2); + // f32 raster_target_y = raster_target_x; + + // Assert((f32)((i32)raster_target_x) == raster_target_x); + // Assert((f32)((i32)raster_target_y) == raster_target_y); + + // //- Setup render target + // IDWriteBitmapRenderTarget *render_target = 0; + // /* FIXME: errors when em_size too high */ + // error = IDWriteGdiInterop_CreateBitmapRenderTarget(dwrite_gdi_interop, 0, (UINT32)raster_target_w, (UINT32)raster_target_h, &render_target); + // IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, 1.0); + + // /* Clear the render target */ + // HDC dc = 0; + // { + // dc = IDWriteBitmapRenderTarget_GetMemoryDC(render_target); + // HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); + // SetDCPenColor(dc, bg_color); + // SelectObject(dc, GetStockObject(DC_BRUSH)); + // SetDCBrushColor(dc, bg_color); + // Rectangle(dc, 0, 0, raster_target_w, raster_target_h); + // SelectObject(dc, original); + // } + + // //- Setup atlas + // /* Acquire font memory */ + // TTF_Glyph *glyphs = (TTF_Glyph *)PushStructs(arena, TTF_Glyph, glyph_count); + + // /* Acquire (starting) atlas memory + // * NOTE: This is unnecessary since atlas memory will grow anyway. Could + // * just start w/ atlas height 0. + // */ + // u64 atlas_w = 1024; + // u64 atlas_h = 1; + // u32 *atlas_memory = PushStructs(arena, u32, atlas_w * atlas_h); + + // i32 ascent = 0; + // i32 descent = 0; + // i32 cap = 0; + + // //- Fill atlas & metric data + // u32 out_offset_x = 0; + // u32 out_offset_y = 0; + // u32 row_height = 0; + // { + // for (u16 i = 0; i < glyph_count; ++i) + // { + // //- Render glyph to render target + // DWRITE_GLYPH_RUN glyph_run = ZI; + // glyph_run.fontFace = font_face; + // glyph_run.fontEmSize = pixel_per_em; + // glyph_run.glyphCount = 1; + // glyph_run.glyphIndices = &i; + + // RECT bounding_box = ZI; + // error = IDWriteBitmapRenderTarget_DrawGlyphRun(render_target, + // raster_target_x, + // raster_target_y, + // DWRITE_MEASURING_MODE_NATURAL, + // &glyph_run, + // rendering_params, + // fg_color, + // &bounding_box + // ); + + // if (bounding_box.left < 0 + // || bounding_box.top < 0 + // || bounding_box.right > raster_target_w + // || bounding_box.bottom > raster_target_h) + // { + // /* Skip */ + // continue; + // } + + // //- Compute glyph metrics + // DWRITE_GLYPH_METRICS glyph_metrics = ZI; + // error = IDWriteFontFace_GetDesignGlyphMetrics(font_face, &i, 1, &glyph_metrics, 0); + // ascent = metrics.ascent * pixel_per_design_unit; + // descent = metrics.descent * pixel_per_design_unit; + // cap = metrics.capHeight * pixel_per_design_unit; + + // f32 off_x = (f32)bounding_box.left - raster_target_x; + // f32 off_y = (f32)bounding_box.top - raster_target_y; + // i32 tex_w = bounding_box.right - bounding_box.left; + // i32 tex_h = bounding_box.bottom - bounding_box.top; + // f32 advance = CeilF32ToI32((f32)glyph_metrics.advanceWidth * pixel_per_design_unit); + + // TTF_Glyph *glyph = &glyphs[i]; + // glyph->baseline_offset = VEC2(off_x, off_y); + // glyph->advance = advance; + + // /* Get the bitmap */ + // HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); + // DIBSECTION dib = ZI; + // GetObject(bitmap, sizeof(dib), &dib); + + // /* Start new row if necessary */ + // if ((out_offset_x + tex_w) >= atlas_w) + // { + // out_offset_y += row_height; + // out_offset_x = 0; + // row_height = 0; + // } + + // /* Grow atlas height */ + // if ((out_offset_y + tex_h) > atlas_h) + // { + // u64 diff = (out_offset_y + tex_h) - atlas_h; + // /* NOTE: This allocation must be contiguous with the initial atlas + // * allocation (IE: No non-atlas arena PUSHes) */ + // PushStructs(arena, u32, diff * atlas_w); + // atlas_h += diff; + // } + + // /* Set bounding box metrics (now that we know atlas x & y) */ + // glyph->atlas_p0 = VEC2I32(out_offset_x, out_offset_y); + // glyph->atlas_p1 = VEC2I32(out_offset_x + tex_w, out_offset_y + tex_h); + + // //- Fill atlas + // u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4; + // u32 *in_data = (u32 *)dib.dsBm.bmBits; + // u32 *out_data = atlas_memory; + // for (i32 y = 0; y < tex_h; ++y) + // { + // u64 out_y = out_offset_y + y; + // u64 in_y = (u64)bounding_box.top + y; + // for (i32 x = 0; x < tex_w; ++x) + // { + // u64 out_x = out_offset_x + x; + // u64 in_x = (u64)bounding_box.left + x; + // u32 *out_pixel = out_data + (out_x + (out_y * atlas_w)); + // u32 *in_pixel = in_data + (in_x + (in_y * in_pitch)); + // *out_pixel = 0x00FFFFFF | ((*in_pixel & 0xFF) << 24); + // } + // } + // out_offset_x += tex_w; + + // /* Grow row height */ + // if ((u32)tex_h > row_height) + // { + // row_height = (u32)tex_h; + // } + + // //- Clear render target + // { + // HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); + // SetDCPenColor(dc, bg_color); + // SelectObject(dc, GetStockObject(DC_BRUSH)); + // SetDCBrushColor(dc, bg_color); + // Rectangle(dc, bounding_box.left, bounding_box.top, bounding_box.right, bounding_box.bottom); + // SelectObject(dc, original); + // } + // } + // } + + // //- Construct indices + // u16 *cache_indices = 0; + // if (cache_codes_count > 0) + // { + // cache_indices = PushStructs(arena, u16, cache_codes_count); + // IDWriteFontFace_GetGlyphIndices(font_face, cache_codes, cache_codes_count, cache_indices); + // } + + // //- Release + // /* FIXME: Check for leaks */ + // IDWriteGdiInterop_Release(dwrite_gdi_interop); + // IDWriteRenderingParams_Release(rendering_params); + // IDWriteRenderingParams_Release(default_rendering_params); + // // NOTE FROM ALLEN: We don't release font face because we intend to keep the font face around after the baking process + // // IDWriteFontFace_Release(font_face); + // IDWriteFontFile_Release(font_file); + // //loader->Release(); + // IDWriteFactory5_Release(factory); + + // /* Return */ + // TTF_Decoded result = ZI; + // result.glyphs = glyphs; + // result.glyphs_count = glyph_count; + // result.cache_indices = cache_indices; + // result.image_width = (u32)atlas_w; + // result.image_height = (u32)atlas_h; + // result.image_pixels = (u32 *)atlas_memory; + // result.ascent = ascent; + // result.descent = descent; + // result.cap = cap; + // return result; } + + + + + + + + + + + + + + + + + + + + + + + + + +// /* Based on Allen Webster's dwrite rasterizer example - +// * https://github.com/4th-dimention/examps */ + +// extern TTF_DW_Ctx TTF_DW = ZI; + +// //////////////////////////////////////////////////////////// +// //~ @hookimpl Bootstrap + +// void TTF_Bootstrap(void) +// { +// TTF_DW_SharedState *g = &TTF_DW_shared_state; +// Assert(!g->factory); +// /* FIXME: I think IDWriteFactory5 only exists on later updates of windows +// * 10? Need to verify. Maybe should just use a custom loader. (We're only +// * using a factory5 since I think WriteInMemoryFileLoader wasn't +// * implemented until then) */ +// HRESULT error = DWriteCreateFactory( +// DWRITE_FACTORY_TYPE_SHARED, +// &IID_IDWriteFactory5, +// (void **)&g->factory +// ); +// if (!SUCCEEDED(error)) +// { +// Panic(Lit("Error creating DWrite factory")); +// (*(volatile int *)0) = 0; +// } +// } + +// //////////////////////////////////////////////////////////// +// //~ @hookimpl Decode + +// TTF_Decoded TTF_Decode(Arena *arena, String encoded, f32 em_size, u32 *cache_codes, u32 cache_codes_count) +// { +// TTF_DW_SharedState *g = &TTF_DW_shared_state; +// COLORREF bg_color = 0xFF000000; +// COLORREF fg_color = 0xFFFFFFFF; + +// IDWriteFactory5 *factory = g->factory; + +// /* TODO: handle errors */ +// HRESULT error = 0; + +// /* File */ +// IDWriteFontFile *font_file = 0; +// { +// /* Create in memory loader */ +// IDWriteInMemoryFontFileLoader *loader = 0; +// error = IDWriteFactory5_CreateInMemoryFontFileLoader(factory, &loader); +// error = IDWriteFactory5_RegisterFontFileLoader(factory, (IDWriteFontFileLoader *)loader); + +// IDWriteFontSetBuilder1 *builder = 0; +// error = IDWriteFactory5_CreateFontSetBuilder1(factory, &builder); +// error = IDWriteInMemoryFontFileLoader_CreateInMemoryFontFileReference(loader, (IDWriteFactory *)factory, encoded.text, (u32)encoded.len, 0, &font_file); +// error = IDWriteFontSetBuilder1_AddFontFile(builder, font_file); +// } + +// //- Create face +// IDWriteFontFace *font_face = 0; +// error = IDWriteFactory5_CreateFontFace(factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); + +// //- Setup rendering params +// IDWriteRenderingParams *default_rendering_params = 0; +// error = IDWriteFactory5_CreateRenderingParams(factory, &default_rendering_params); +// IDWriteRenderingParams *rendering_params = 0; +// FLOAT gamma = IDWriteRenderingParams_GetGamma(default_rendering_params); +// FLOAT enhanced_contrast = IDWriteRenderingParams_GetEnhancedContrast(default_rendering_params); +// FLOAT clear_type_level = IDWriteRenderingParams_GetClearTypeLevel(default_rendering_params); +// error = IDWriteFactory5_CreateCustomRenderingParams(factory, gamma, +// enhanced_contrast, +// clear_type_level, +// DWRITE_PIXEL_GEOMETRY_FLAT, +// DWRITE_RENDERING_MODE_DEFAULT, +// &rendering_params); + +// //- Setup interop +// IDWriteGdiInterop *dwrite_gdi_interop = 0; +// error = IDWriteFactory5_GetGdiInterop(factory, &dwrite_gdi_interop); + +// //- Get metrics +// DWRITE_FONT_METRICS metrics = ZI; +// IDWriteFontFace_GetMetrics(font_face, &metrics); + +// u16 glyph_count = IDWriteFontFace_GetGlyphCount(font_face); + +// f32 pixel_per_em = em_size * (TTF_DW_Dpi / 72.0f); +// f32 pixel_per_design_unit = pixel_per_em / ((f32)metrics.designUnitsPerEm); + +// i32 raster_target_w = (i32)(8.0f * ((f32)metrics.capHeight) * pixel_per_design_unit); +// i32 raster_target_h = raster_target_w; + +// f32 raster_target_x = (f32)(raster_target_w / 2); +// f32 raster_target_y = raster_target_x; + +// Assert((f32)((i32)raster_target_x) == raster_target_x); +// Assert((f32)((i32)raster_target_y) == raster_target_y); + +// //- Setup render target +// IDWriteBitmapRenderTarget *render_target = 0; +// /* FIXME: errors when em_size too high */ +// error = IDWriteGdiInterop_CreateBitmapRenderTarget(dwrite_gdi_interop, 0, (UINT32)raster_target_w, (UINT32)raster_target_h, &render_target); +// IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, 1.0); + +// /* Clear the render target */ +// HDC dc = 0; +// { +// dc = IDWriteBitmapRenderTarget_GetMemoryDC(render_target); +// HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); +// SetDCPenColor(dc, bg_color); +// SelectObject(dc, GetStockObject(DC_BRUSH)); +// SetDCBrushColor(dc, bg_color); +// Rectangle(dc, 0, 0, raster_target_w, raster_target_h); +// SelectObject(dc, original); +// } + +// //- Setup atlas +// /* Acquire font memory */ +// TTF_Glyph *glyphs = (TTF_Glyph *)PushStructs(arena, TTF_Glyph, glyph_count); + +// /* Acquire (starting) atlas memory +// * NOTE: This is unnecessary since atlas memory will grow anyway. Could +// * just start w/ atlas height 0. +// */ +// u64 atlas_w = 1024; +// u64 atlas_h = 1; +// u32 *atlas_memory = PushStructs(arena, u32, atlas_w * atlas_h); + +// i32 ascent = 0; +// i32 descent = 0; +// i32 cap = 0; + +// //- Fill atlas & metric data +// u32 out_offset_x = 0; +// u32 out_offset_y = 0; +// u32 row_height = 0; +// { +// for (u16 i = 0; i < glyph_count; ++i) +// { +// //- Render glyph to render target +// DWRITE_GLYPH_RUN glyph_run = ZI; +// glyph_run.fontFace = font_face; +// glyph_run.fontEmSize = pixel_per_em; +// glyph_run.glyphCount = 1; +// glyph_run.glyphIndices = &i; + +// RECT bounding_box = ZI; +// error = IDWriteBitmapRenderTarget_DrawGlyphRun(render_target, +// raster_target_x, +// raster_target_y, +// DWRITE_MEASURING_MODE_NATURAL, +// &glyph_run, +// rendering_params, +// fg_color, +// &bounding_box +// ); + +// if (bounding_box.left < 0 +// || bounding_box.top < 0 +// || bounding_box.right > raster_target_w +// || bounding_box.bottom > raster_target_h) +// { +// /* Skip */ +// continue; +// } + +// //- Compute glyph metrics +// DWRITE_GLYPH_METRICS glyph_metrics = ZI; +// error = IDWriteFontFace_GetDesignGlyphMetrics(font_face, &i, 1, &glyph_metrics, 0); +// ascent = metrics.ascent * pixel_per_design_unit; +// descent = metrics.descent * pixel_per_design_unit; +// cap = metrics.capHeight * pixel_per_design_unit; + +// f32 off_x = (f32)bounding_box.left - raster_target_x; +// f32 off_y = (f32)bounding_box.top - raster_target_y; +// i32 tex_w = bounding_box.right - bounding_box.left; +// i32 tex_h = bounding_box.bottom - bounding_box.top; +// f32 advance = CeilF32ToI32((f32)glyph_metrics.advanceWidth * pixel_per_design_unit); + +// TTF_Glyph *glyph = &glyphs[i]; +// glyph->baseline_offset = VEC2(off_x, off_y); +// glyph->advance = advance; + +// /* Get the bitmap */ +// HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); +// DIBSECTION dib = ZI; +// GetObject(bitmap, sizeof(dib), &dib); + +// /* Start new row if necessary */ +// if ((out_offset_x + tex_w) >= atlas_w) +// { +// out_offset_y += row_height; +// out_offset_x = 0; +// row_height = 0; +// } + +// /* Grow atlas height */ +// if ((out_offset_y + tex_h) > atlas_h) +// { +// u64 diff = (out_offset_y + tex_h) - atlas_h; +// /* NOTE: This allocation must be contiguous with the initial atlas +// * allocation (IE: No non-atlas arena PUSHes) */ +// PushStructs(arena, u32, diff * atlas_w); +// atlas_h += diff; +// } + +// /* Set bounding box metrics (now that we know atlas x & y) */ +// glyph->atlas_p0 = VEC2I32(out_offset_x, out_offset_y); +// glyph->atlas_p1 = VEC2I32(out_offset_x + tex_w, out_offset_y + tex_h); + +// //- Fill atlas +// u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4; +// u32 *in_data = (u32 *)dib.dsBm.bmBits; +// u32 *out_data = atlas_memory; +// for (i32 y = 0; y < tex_h; ++y) +// { +// u64 out_y = out_offset_y + y; +// u64 in_y = (u64)bounding_box.top + y; +// for (i32 x = 0; x < tex_w; ++x) +// { +// u64 out_x = out_offset_x + x; +// u64 in_x = (u64)bounding_box.left + x; +// u32 *out_pixel = out_data + (out_x + (out_y * atlas_w)); +// u32 *in_pixel = in_data + (in_x + (in_y * in_pitch)); +// *out_pixel = 0x00FFFFFF | ((*in_pixel & 0xFF) << 24); +// } +// } +// out_offset_x += tex_w; + +// /* Grow row height */ +// if ((u32)tex_h > row_height) +// { +// row_height = (u32)tex_h; +// } + +// //- Clear render target +// { +// HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); +// SetDCPenColor(dc, bg_color); +// SelectObject(dc, GetStockObject(DC_BRUSH)); +// SetDCBrushColor(dc, bg_color); +// Rectangle(dc, bounding_box.left, bounding_box.top, bounding_box.right, bounding_box.bottom); +// SelectObject(dc, original); +// } +// } +// } + +// //- Construct indices +// u16 *cache_indices = 0; +// if (cache_codes_count > 0) +// { +// cache_indices = PushStructs(arena, u16, cache_codes_count); +// IDWriteFontFace_GetGlyphIndices(font_face, cache_codes, cache_codes_count, cache_indices); +// } + +// //- Release +// /* FIXME: Check for leaks */ +// IDWriteGdiInterop_Release(dwrite_gdi_interop); +// IDWriteRenderingParams_Release(rendering_params); +// IDWriteRenderingParams_Release(default_rendering_params); +// // NOTE FROM ALLEN: We don't release font face because we intend to keep the font face around after the baking process +// // IDWriteFontFace_Release(font_face); +// IDWriteFontFile_Release(font_file); +// //loader->Release(); +// IDWriteFactory5_Release(factory); + +// /* Return */ +// TTF_Decoded result = ZI; +// result.glyphs = glyphs; +// result.glyphs_count = glyph_count; +// result.cache_indices = cache_indices; +// result.image_width = (u32)atlas_w; +// result.image_height = (u32)atlas_h; +// result.image_pixels = (u32 *)atlas_memory; +// result.ascent = ascent; +// result.descent = descent; +// result.cap = cap; +// return result; +// } diff --git a/src/ttf/ttf_dwrite/ttf_dwrite.h b/src/ttf/ttf_dwrite/ttf_dwrite.h index dcad9522..d541cd3b 100644 --- a/src/ttf/ttf_dwrite/ttf_dwrite.h +++ b/src/ttf/ttf_dwrite/ttf_dwrite.h @@ -140,12 +140,14 @@ static inline UINT32 IDWriteGdiInterop_Release EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE factoryType, const GUID* iid, void** factory) WIN_NOEXCEPT; //////////////////////////////////////////////////////////// -//~ State types +//~ Context types /* TODO: Determine font dpi dynamically */ #define TTF_DW_Dpi (96.0f) -Struct(TTF_DW_SharedState) +Struct(TTF_DW_Ctx) { - struct IDWriteFactory5 *factory; -} extern TTF_DW_shared_state; + IDWriteFactory5 *factory; +}; + +extern TTF_DW_Ctx TTF_DW;