381 lines
11 KiB
C
381 lines
11 KiB
C
GC_Ctx GC = Zi;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Bootstrap
|
|
|
|
void GC_Bootstrap(void)
|
|
{
|
|
OnAsyncTick(GC_TickAsync);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Key helpers
|
|
|
|
GC_FontKey GC_FontKeyFromResource(ResourceKey resource)
|
|
{
|
|
GC_FontKey result = Zi;
|
|
result.r = resource;
|
|
return result;
|
|
}
|
|
|
|
u64 GC_HashFromGlyphDesc(GC_GlyphDesc desc)
|
|
{
|
|
return MixU64s(desc.font.r.v, ((u64)desc.codepoint << 32) | *(u32 *)&desc.font_size);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Run
|
|
|
|
// TODO: Thread-local cache
|
|
// TODO: Lower font size precision to prevent unique fonts for every slightly-different size
|
|
GC_Run GC_RunFromString32(Arena *arena, String32 str32, GC_FontKey font, f32 font_size)
|
|
{
|
|
GC_Run result = Zi;
|
|
if (str32.len > 0)
|
|
{
|
|
TempArena scratch = BeginScratch(arena);
|
|
Arena *perm = PermArena();
|
|
|
|
//////////////////////////////
|
|
//- Grab glyphs from cache
|
|
|
|
u64 ready_glyphs_count = 0;
|
|
GC_Glyph **ready_glyphs = PushStructsNoZero(scratch.arena, GC_Glyph *, str32.len);
|
|
|
|
u32 *uncached_codepoints = PushStructsNoZero(scratch.arena, u32, str32.len);
|
|
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;
|
|
{
|
|
if (str32.len > 0)
|
|
{
|
|
Lock lock = LockS(&GC.glyphs_mutex);
|
|
{
|
|
i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy);
|
|
for (u64 codepoint_idx = 0; codepoint_idx < str32.len; ++codepoint_idx)
|
|
{
|
|
u32 codepoint = str32.text[codepoint_idx];
|
|
|
|
GC_GlyphDesc desc = Zi;
|
|
desc.font = font;
|
|
desc.font_size = font_size;
|
|
desc.codepoint = codepoint;
|
|
|
|
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)
|
|
{
|
|
uncached_codepoints[uncached_codepoints_count] = codepoint;
|
|
uncached_codepoints_count += 1;
|
|
}
|
|
else if (completion < Atomic64Fetch(&glyph->atlas_copy_completion_target))
|
|
{
|
|
pending_glyphs_count += 1;
|
|
}
|
|
else
|
|
{
|
|
ready_glyphs[ready_glyphs_count] = glyph;
|
|
ready_glyphs_count += 1;
|
|
}
|
|
}
|
|
}
|
|
Unlock(&lock);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- 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;
|
|
Atomic64FetchSet(&glyph->atlas_copy_completion_target, I64Max);
|
|
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);
|
|
SignalAsyncTick();
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Create run from glyphs
|
|
|
|
f32 baseline_pos = 0;
|
|
result.rects = PushStructs(arena, GC_RunRect, ready_glyphs_count);
|
|
result.rects_count = ready_glyphs_count;
|
|
for (u64 glyph_idx = 0; glyph_idx < ready_glyphs_count; ++glyph_idx)
|
|
{
|
|
GC_Glyph *glyph = ready_glyphs[glyph_idx];
|
|
GC_RunRect *rect = &result.rects[glyph_idx];
|
|
|
|
f32 advance = RoundF32(glyph->advance);
|
|
Rng2 bounds = glyph->bounds;
|
|
|
|
rect->tex = glyph->atlas->tex;
|
|
rect->tex_slice = glyph->atlas_slice;
|
|
rect->tex_slice_uv = glyph->atlas_slice_uv;
|
|
|
|
rect->baseline_pos = baseline_pos;
|
|
rect->advance = advance;
|
|
|
|
rect->bounds = bounds;
|
|
|
|
if (glyph_idx == 0)
|
|
{
|
|
result.bounds = rect->bounds;
|
|
}
|
|
else
|
|
{
|
|
result.bounds = UnionRng2(result.bounds, rect->bounds);
|
|
}
|
|
|
|
baseline_pos += rect->advance;
|
|
result.baseline_length = MaxF32(result.baseline_length, baseline_pos);
|
|
}
|
|
|
|
if (ready_glyphs_count > 0)
|
|
{
|
|
GC_Glyph *glyph = ready_glyphs[0];
|
|
result.font_size = glyph->font_size;
|
|
result.font_ascent = glyph->font_ascent;
|
|
result.font_descent = glyph->font_descent;
|
|
result.font_cap = glyph->font_cap;
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
}
|
|
|
|
// result.ready = uncached_codepoints_count == 0 && pending_glyphs_count == 0;
|
|
result.ready = 1;
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Async
|
|
|
|
void GC_TickAsync(WaveLaneCtx *lane, AsyncFrameLaneCtx *base_async_lane_frame)
|
|
{
|
|
GC_AsyncCtx *async = &GC.async_ctx;
|
|
Arena *frame_arena = base_async_lane_frame->arena;
|
|
|
|
//////////////////////////////
|
|
//- Begin tick
|
|
|
|
// TODO: Limit cmds processed per-tick
|
|
|
|
if (lane->idx == 0)
|
|
{
|
|
ZeroStruct(&async->cmds);
|
|
|
|
Lock lock = LockE(&GC.submit.mutex);
|
|
{
|
|
// Pop cmds from submission queue
|
|
async->cmds.count = GC.submit.count;
|
|
async->cmds.v = PushStructsNoZero(frame_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);
|
|
|
|
if (async->cmds.count > 0)
|
|
{
|
|
//////////////////////////////
|
|
//- 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(frame_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)
|
|
{
|
|
G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_AsyncCopy);
|
|
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;
|
|
GC_GlyphDesc desc = glyph->desc;
|
|
TTF_GlyphResult ttf_result = cmd->rasterized;
|
|
|
|
Vec2I32 image_dims = ttf_result.image_dims;
|
|
|
|
// Allocate atlas rect
|
|
// TODO: Use a more efficient atlas packing algorithm for less wasted space
|
|
GC_Atlas *atlas = GC.first_atlas;
|
|
b32 can_use_atlas = 0;
|
|
Vec2I32 pos_in_atlas = Zi;
|
|
while (can_use_atlas == 0)
|
|
{
|
|
// Create atlas
|
|
if (!atlas)
|
|
{
|
|
Arena *perm = PermArena();
|
|
atlas = PushStruct(perm, GC_Atlas);
|
|
atlas->dims = VEC2I32(1024, 1024);
|
|
{
|
|
G_ArenaHandle gpu_perm = G_PermArena();
|
|
atlas->tex_res = G_PushTexture2D(
|
|
gpu_perm, cl,
|
|
G_Format_R8G8B8A8_Unorm_Srgb,
|
|
atlas->dims,
|
|
.name = Lit("Glyph atlas")
|
|
);
|
|
atlas->tex = G_PushTexture2DRef(gpu_perm, atlas->tex_res);
|
|
}
|
|
SllStackPush(GC.first_atlas, atlas);
|
|
++GC.atlases_count;
|
|
}
|
|
if (atlas->cur_pos.x + image_dims.x > atlas->dims.x)
|
|
{
|
|
atlas->cur_pos.x = 0;
|
|
atlas->cur_pos.y += atlas->cur_row_height;
|
|
atlas->cur_row_height = 0;
|
|
}
|
|
if (atlas->cur_pos.x + image_dims.x < atlas->dims.x && atlas->cur_pos.y + image_dims.y < atlas->dims.y)
|
|
{
|
|
pos_in_atlas = atlas->cur_pos;
|
|
atlas->cur_row_height = MaxI32(atlas->cur_row_height, image_dims.y);
|
|
atlas->cur_pos.x += image_dims.x;
|
|
can_use_atlas = 1;
|
|
}
|
|
else
|
|
{
|
|
// New atlas required
|
|
atlas = 0;
|
|
}
|
|
}
|
|
|
|
// Fill glyph atlas info
|
|
glyph->atlas = atlas;
|
|
glyph->atlas_slice = RNG2I32(pos_in_atlas, AddVec2I32(pos_in_atlas, image_dims));
|
|
glyph->atlas_slice_uv.p0.x = (f32)glyph->atlas_slice.p0.x / (f32)atlas->dims.x;
|
|
glyph->atlas_slice_uv.p0.y = (f32)glyph->atlas_slice.p0.y / (f32)atlas->dims.x;
|
|
glyph->atlas_slice_uv.p1.x = (f32)glyph->atlas_slice.p1.x / (f32)atlas->dims.x;
|
|
glyph->atlas_slice_uv.p1.y = (f32)glyph->atlas_slice.p1.y / (f32)atlas->dims.x;
|
|
|
|
// Copy to atlas
|
|
u32 *image_pixels = ttf_result.image_pixels;
|
|
if (image_dims.x > 0 && image_dims.y > 0)
|
|
{
|
|
G_CopyCpuToTexture(
|
|
cl,
|
|
glyph->atlas->tex_res, VEC3I32(glyph->atlas_slice.p0.x, glyph->atlas_slice.p0.y, 0),
|
|
image_pixels, VEC3I32(image_dims.x, image_dims.y, 1),
|
|
RNG3I32(
|
|
VEC3I32(0, 0, 0),
|
|
VEC3I32(image_dims.x, image_dims.y, 1)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
i64 completion_target = G_CommitCommandList(cl);
|
|
|
|
// Update completion targets
|
|
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;
|
|
Atomic64Set(&glyph->atlas_copy_completion_target, completion_target);
|
|
}
|
|
}
|
|
}
|
|
}
|