I did it! I added dynamic glyph support. Now Lucy is unstoppable

This commit is contained in:
Андреев Григорий 2026-04-26 01:11:32 +03:00
parent 76a3469e4e
commit b913d115bf
5 changed files with 78 additions and 50 deletions

0
.codex Normal file
View File

View File

@ -1077,6 +1077,7 @@ void alice_frame_drawing(Alice* alice) {
// MutRefWidget_draw(alice->overlay_ui_root, (ivec2){0}, (uvec2){win_width, win_height}, (BorderS32){.lt = {0},
// .rb.x = (S32)win_width, .rb.y = (S32)win_height});
Plain2dShapeRenderer_another_frame(&alice->plain_shape_renderer);
LucyGlyphCache_another_frame_flush_dyn_images_transfer(&alice->lucy_cache);
LucyRenderer_another_frame(&alice->lucy_renderer);
margaret_end_command_buffer(alice->transfer_command_buf);
margaret_end_command_buffer(alice->compute_command_buf);

View File

@ -13,7 +13,7 @@
#include FT_FREETYPE_H
#define LUCY_MAX_DESCRIPTOR_COUNT 100
#define LUCY_INITIAL_DYN_IMAGE_DIM 300
#define LUCY_INITIAL_DYN_IMAGE_DIM 1
#define LUCY_INITIAL_DYN_STAGING_BUF_SIZE 30000
typedef struct {
@ -113,16 +113,15 @@ U32 LucyGlyphCache__find_image_slot(LucyGlyphCache* cache){
void LucyGlyphCache__create_dyn_texture(LucyGlyphCache* cache, U32 new_atlas_dim) {
assert(new_atlas_dim <= cache->max_dim);
U32 image_slot_id = LucyGlyphCache__find_image_slot(cache);
MargaretImg dev_local = MargaretImgAllocator_alloc(cache->ve.dev_local_images,
MargaretImg dev_local_img = MargaretImgAllocator_alloc(cache->ve.dev_local_images,
new_atlas_dim, new_atlas_dim, VK_FORMAT_R8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
VkImageView view = margaret_create_view_for_image(
cache->ve.device, dev_local.a.image, VK_FORMAT_R8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
cache->ve.device, dev_local_img.a.image, VK_FORMAT_R8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
OptionLucyImage* img_slot = VecOptionLucyImage_mat(&cache->image_slots, image_slot_id);
assert(img_slot->variant == Option_Some);
img_slot->some.usage = 1;
img_slot->some.tex = (MargaretTexture){};
img_slot->some.tex = (MargaretTexture){ .img = dev_local_img, .view = view};
vkUpdateDescriptorSets(cache->ve.device, 1, &(VkWriteDescriptorSet){
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@ -240,10 +239,11 @@ void LucyGlyphCache__extract_bitmap(FT_Face ft_face, U32 codepoint, U32 max_dim,
}
}
/* pos on atlas may be left undefined */
/* pos_on_atlas may be left undefined, img_slot_id may be left undefined */
LucyStoredGlyph* LucyGlyphCache__add_glyph_to_glyph_set(BufRBTree_MapU32ToLucyStoredGlyph* glyph_set,
U32 codepoint, FT_GlyphSlot slot, uvec2 pos_on_atlas) {
U32 codepoint, FT_GlyphSlot slot, uvec2 pos_on_atlas, U32 img_slot_id) {
bool iret = BufRBTree_MapU32ToLucyStoredGlyph_insert(glyph_set, codepoint, (LucyStoredGlyph){
.img_slot_id = img_slot_id,
.w = slot->bitmap.width, .h = slot->bitmap.rows,
.advance_x = slot->advance.x >> 6,
.bearing = (ivec2){ slot->bitmap_left, -slot->bitmap_top },
@ -261,6 +261,27 @@ U32 LucyGlyphCache__pick_size_next_dyn_img(U32 max_dim, U32 cur_dyn_img_dim) {
return cur_dyn_img_dim * 2 <= max_dim ? cur_dyn_img_dim * 2 : max_dim;
}
/* Recurring piece of code in LucyGlyphCache_get_glyph_on_the_fly() to reset current atlas */
void LucyGlyphCache_get_glyph_on_the_fly__another_dyn_atlas(LucyGlyphCache* cache, U32 cur_dyn_img_dim) {
/* Suppose we sent some dynamic atlas to deleter. Ok, so what? We will first perform some meaningless
* copying from staging to device local image. */
LucyGlyphCache__decrease_image_usage(cache, cache->dynamic_image_slot);
U32 new_dyn_img_dim = LucyGlyphCache__pick_size_next_dyn_img(cache->max_dim, cur_dyn_img_dim);
LucyGlyphCache__create_dyn_texture(cache, new_dyn_img_dim);
/* cache->dynamic_img_slot was updated */
VecLucyAccumDynTransferImage_append(&cache->accumulated_dyn_images, (LucyAccumDynTransferImage){
.img_slot_id = cache->dynamic_image_slot, .glyphs = VecVkBufferImageCopy_new(),
});
for (size_t x = 0; x < cur_dyn_img_dim; x++) {
cache->dynamic_image_landscape.buf[x] = 0;
}
VecU32_expand(&cache->dynamic_image_landscape, new_dyn_img_dim, 0);
cache->dynamic_landscape_progress_x = 0;
}
/* Returned pointer will be invalidated after you modify the set of codepoints for this size of this face */
LucyStoredGlyph* LucyGlyphCache_get_glyph_on_the_fly(RBTreeNodeLucyFaceFixedSize* ffs_node, U32 codepoint) {
LucyFaceFixedSize* ffs = &ffs_node->value;
@ -282,48 +303,45 @@ LucyStoredGlyph* LucyGlyphCache_get_glyph_on_the_fly(RBTreeNodeLucyFaceFixedSize
}
another_attempt:
{}
printf("DEBUG: Attempting to add %u\n", codepoint);
assert(cache->image_slots.buf[cache->dynamic_image_slot].variant == Option_Some);
LucyImage* dyn_img = &cache->image_slots.buf[cache->dynamic_image_slot].some;
U32 cur_dyn_img_dim = dyn_img->tex.img.width;
assert(cur_dyn_img_dim == dyn_img->tex.img.height);
if (ft_slot->bitmap.width > cur_dyn_img_dim) {
LucyGlyphCache_get_glyph_on_the_fly__another_dyn_atlas(cache, cur_dyn_img_dim);
printf("Cause 0: %u > %u\n", ft_slot->bitmap.width, cur_dyn_img_dim);
goto another_attempt;
}
U32 required_atlas_width = cache->dynamic_landscape_progress_x + ft_slot->bitmap.width;
if (required_atlas_width > cur_dyn_img_dim) {
printf("Cause 1: %u > %u\n", required_atlas_width, cur_dyn_img_dim);
cache->dynamic_landscape_progress_x = 0;
goto another_attempt;
}
assert(cur_dyn_img_dim == cache->dynamic_image_landscape.len);
U32 start_y = 0;
for (size_t gx = 0; gx < ft_slot->bitmap.width; gx++) {
start_y = MAX_U32(start_y, cache->dynamic_image_landscape.buf[gx]);
start_y = MAX_U32(start_y, cache->dynamic_image_landscape.buf[cache->dynamic_landscape_progress_x + gx]);
}
U32 ceiling = start_y + ft_slot->bitmap.rows;
if (ceiling > cur_dyn_img_dim) {
/* This is serious. We need to issue a new image */
// If we see that usage is zero, we want to pop the last img
// from accumulated_syn_images and send it to deleter (for images)
if (LucyGlyphCache__decrease_image_usage(cache, cache->dynamic_image_slot)) {
assert(cache->accumulated_dyn_images.buf[cache->accumulated_dyn_images.len - 1].glyphs.capacity == 0);
cache->accumulated_dyn_images.len--;
}
U32 new_dyn_img_dim = LucyGlyphCache__pick_size_next_dyn_img(max_dim, cur_dyn_img_dim);
LucyGlyphCache__create_dyn_texture(cache, new_dyn_img_dim);
/* cache->dynamic_img_slot was updated */
VecLucyAccumDynTransferImage_append(&cache->accumulated_dyn_images, (LucyAccumDynTransferImage){
.img_slot_id = cache->dynamic_image_slot, .glyphs = VecVkBufferImageCopy_new(),
});
for (size_t x = 0; x < cur_dyn_img_dim; x++) {
cache->dynamic_image_landscape.buf[x] = 0;
}
VecU32_expand(&cache->dynamic_image_landscape, new_dyn_img_dim, 0);
cache->dynamic_landscape_progress_x = 0;
LucyGlyphCache_get_glyph_on_the_fly__another_dyn_atlas(cache, cur_dyn_img_dim);
goto another_attempt;
}
/* Successful placement */
for (size_t gx = 0; gx < ft_slot->bitmap.width; gx++) {
cache->dynamic_image_landscape.buf[cache->dynamic_landscape_progress_x + gx] = ceiling;
}
dyn_img->usage++;
/* This is actually annoying, I may todo: make a function that forms VkBufferImageCopy for me */
VecVkBufferImageCopy_append(&cache->accumulated_dyn_images.buf[cache->accumulated_dyn_images.len - 1].glyphs,
(VkBufferImageCopy){ .bufferOffset = cache->dyn_staging_usage,
(VkBufferImageCopy){
.bufferOffset = cache->dyn_staging.start + cache->dyn_staging_usage,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = (VkImageSubresourceLayers){
@ -333,25 +351,33 @@ LucyStoredGlyph* LucyGlyphCache_get_glyph_on_the_fly(RBTreeNodeLucyFaceFixedSize
.imageExtent = { .width = ft_slot->bitmap.width, .height = ft_slot->bitmap.rows, .depth = 1 },
});
U64 needed_staging_sz = cache->dyn_staging_usage + bitmap->rows * bitmap->width * sizeof(U8);
if (cache->dyn_staging.len < needed_staging_sz) {
MargaretSubbuf_expand_or_move_old_host_visible(&cache->dyn_staging,
Vec_get_new_capacity(cache->dyn_staging.len, needed_staging_sz));
}
assert(cache->dyn_staging.len >= needed_staging_sz);
U8* staging_pixels = (U8*)MargaretSubbuf_get_mapped(&cache->dyn_staging) + cache->dyn_staging_usage;
for (S64 y = 0; y < bitmap->rows; y++) {
for (S64 x = 0; x < bitmap->width; x++) {
*(staging_pixels + (y * bitmap->width + x) * sizeof(U8)) =
*(bitmap->buffer + (y * bitmap->pitch + x) * sizeof(U8));
}
}
cache->dyn_staging_usage += bitmap->rows * bitmap->width * sizeof(U8);
cache->dyn_staging_usage = needed_staging_sz;
LucyStoredGlyph* res = LucyGlyphCache__add_glyph_to_glyph_set(glyph_set, codepoint, ft_slot,
(uvec2){cache->dynamic_landscape_progress_x, start_y});
(uvec2){cache->dynamic_landscape_progress_x, start_y}, cache->dynamic_image_slot);
cache->dynamic_landscape_progress_x += bitmap->width;
return res;
}
/* This function must be called at the end of another_frame callback, after every single operation with glyph cache
* has been done. It will record copy commands for transferring dynamic images. All the accumulated dyn-glyph
* records will be cleared */
void LucyGlyphCache__flush_dyn_images_transfer(LucyGlyphCache* cache) {
void LucyGlyphCache_another_frame_flush_dyn_images_transfer(LucyGlyphCache* cache) {
for (size_t ii = 0; ii < cache->accumulated_dyn_images.len; ii++) {
LucyAccumDynTransferImage* img_regions = &cache->accumulated_dyn_images.buf[ii];
margaret_rec_cmd_copy_buffer_to_image(cache->ve.transfer_cmd_buffer,
@ -489,8 +515,8 @@ void LucyGlyphCache_add_glyphs(LucyGlyphCache* cache, VecLucyGlyphCachingRequest
TextureDataR8 my_bitmap;
FT_GlyphSlot slot;
LucyGlyphCache__extract_bitmap(ft_face, codepoint, cache->max_dim, &my_bitmap, &slot);
/* x_on_atlas, y_on_atlas and img will be set later, when `ready` vector is ready */
LucyGlyphCache__add_glyph_to_glyph_set(glyph_set, codepoint, slot, (uvec2){0});
/* x_on_atlas, y_on_atlas and img_slot_id will be set later, when `ready` vector is ready */
LucyGlyphCache__add_glyph_to_glyph_set(glyph_set, codepoint, slot, (uvec2){0}, 0);
VecLucyPositionedStagingGlyph_append(&ready, (LucyPositionedStagingGlyph){
.sized_face = &req.sized_face->value, .codepoint = codepoint, .bitmap = my_bitmap,
/* pos be filled later by packing algorithm */

View File

@ -94,7 +94,8 @@ void LucyRenderer_draw_char_glyph(LucyRenderer* self, vec4 color, ivec2 pos, Luc
U64 needed_vbo_length = (self->glyphs_count + 1) * sizeof(LucyRenderInstance);
if (self->staging_vbo.len < needed_vbo_length) {
MargaretSubbuf_expand_or_move_old_host_visible(&self->staging_vbo, needed_vbo_length);
MargaretSubbuf_expand_or_move_old_host_visible(&self->staging_vbo,
Vec_get_new_capacity(self->staging_vbo.len, needed_vbo_length));
}
LucyRenderInstance* vbo_data = (LucyRenderInstance*)MargaretSubbuf_get_mapped(&self->staging_vbo);

View File

@ -333,19 +333,19 @@ void run_app(){
// st.font_face = Alice_new_LucyFace(alice, vcstr("./src/l3/fonts/DMSerifText-Regular.ttf"));
st.font_face = Alice_new_LucyFace(alice, vcstr("./src/l3/fonts/Roboto-VariableFont_wdth,wght.ttf"));
st.font_face_of_size_40 = LucyFace_of_size(st.font_face, 40);
VecLucyGlyphCachingRequest lucy_requests = VecLucyGlyphCachingRequest_new();
VecU32Segment ranges_needed = VecU32Segment_new();
VecU32Segment_append(&ranges_needed, (U32Segment){.start = 32, .len = 126 - 32 + 1});
VecLucyGlyphCachingRequest_append(&lucy_requests, (LucyGlyphCachingRequest){
.sized_face = st.font_face_of_size_40, .codepoint_ranges = ranges_needed,
});
ranges_needed = VecU32Segment_new();
VecU32Segment_append(&ranges_needed, (U32Segment){.start = 0x430, .len = 0x44f - 0x430 + 1});
VecU32Segment_append(&ranges_needed, (U32Segment){.start = 0x410, .len = 0x42f - 0x410 + 1});
VecLucyGlyphCachingRequest_append(&lucy_requests, (LucyGlyphCachingRequest){
.sized_face = st.font_face_of_size_40, .codepoint_ranges = ranges_needed,
});
Alice_lucy_cache_add_glyphs(alice, lucy_requests);
// VecLucyGlyphCachingRequest lucy_requests = VecLucyGlyphCachingRequest_new();
// VecU32Segment ranges_needed = VecU32Segment_new();
// VecU32Segment_append(&ranges_needed, (U32Segment){.start = 32, .len = 126 - 32 + 1});
// VecLucyGlyphCachingRequest_append(&lucy_requests, (LucyGlyphCachingRequest){
// .sized_face = st.font_face_of_size_40, .codepoint_ranges = ranges_needed,
// });
// ranges_needed = VecU32Segment_new();
// VecU32Segment_append(&ranges_needed, (U32Segment){.start = 0x430, .len = 0x44f - 0x430 + 1});
// VecU32Segment_append(&ranges_needed, (U32Segment){.start = 0x410, .len = 0x42f - 0x410 + 1});
// VecLucyGlyphCachingRequest_append(&lucy_requests, (LucyGlyphCachingRequest){
// .sized_face = st.font_face_of_size_40, .codepoint_ranges = ranges_needed,
// });
// Alice_lucy_cache_add_glyphs(alice, lucy_requests);
Alice_lucy_renderer_add_simple_label(alice, st.font_face_of_size_40, (vec4){0, 0, 0, 1}, 0,
cstr("..."), (ivec2){10, 10});