From 5e3aa054d324559822efc4934aad0ec7293fbc5e Mon Sep 17 00:00:00 2001 From: Andreew Gregory Date: Fri, 24 Apr 2026 20:54:09 +0300 Subject: [PATCH] Wrote shit + changed README = it was a good day --- README.txt => README.md | 0 src/l2/lucy/glyph_cache.h | 225 ++++++++++++++++++++++++++++---------- 2 files changed, 170 insertions(+), 55 deletions(-) rename README.txt => README.md (100%) diff --git a/README.txt b/README.md similarity index 100% rename from README.txt rename to README.md diff --git a/src/l2/lucy/glyph_cache.h b/src/l2/lucy/glyph_cache.h index b83b8f9..95b3829 100644 --- a/src/l2/lucy/glyph_cache.h +++ b/src/l2/lucy/glyph_cache.h @@ -15,6 +15,8 @@ #include FT_FREETYPE_H #define LUCY_MAX_DESCRIPTOR_COUNT 100 +#define LUCY_INITIAL_DYN_IMAGE_DIM 300 +#define LUCY_INITIAL_DYN_STAGING_BUF_SIZE 30000 typedef struct { MargaretTexture tex; @@ -63,20 +65,76 @@ struct LucyFace { RBTree_MapU32ToLucyFaceFixedSize sizes; }; +struct { + U64 pos_in_staging; + U32 img_slot_id; + uvec2 pos_in_atlas; + /* The rest is determined on the fly */ +} LucyPositionedDynGlyph; + struct LucyGlyphCache { MargaretEngineReference ve; Abigail* abigail; + U32 max_dim; VecOptionLucyImage image_slots; VkDescriptorSetLayout descriptor_set_layout; VkDescriptorSet descriptor_set; - /* We can delete images and link images to descriptor set only when frame isn't in flight */ - VecU32 to_be_written_to_descriptor_set; - VecU32 to_be_deleted; + MargaretSubbuf dyn_staging; + U32 dynamic_image_slot; + VecU32 dynamic_image_landscape; + U32 dynamic_landscape_progress_x; }; +/* Helper function */ +U32 LucyGlyphCache__find_image_slot(LucyGlyphCache* cache){ + for (U32 i = 0; i < cache->image_slots.len; i++) { + OptionLucyImage* slot = &cache->image_slots.buf[i]; + if (slot->variant == Option_None) { + slot->variant = Option_Some; + slot->some.usage = 0; + return i; + } + } + abortf("LucyCache run out of image descriptor in a descriptor set (dictated by layout).\n" + "You better add up on them\n"); +} + + +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, + 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); + OptionLucyImage* img_slot = VecOptionLucyImage_mat(&cache->image_slots, image_slot_id); + assert(img_slot->variant == Option_None); + img_slot->some.usage = 1; + img_slot->some.tex = (MargaretTexture){}; + + + vkUpdateDescriptorSets(cache->ve.device, 1, &(VkWriteDescriptorSet){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = cache->descriptor_set, .dstBinding = 0, .dstArrayElement = image_slot_id, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &(VkDescriptorImageInfo){ + .sampler = cache->ve.nearest_sampler, .imageView = view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + } + }, 0, NULL); + + // for (size_t i = 0; i < cache->dynamic_image_landscape.len; i++) { + // cache->dynamic_image_landscape.buf[i] = 0; + // } + // VecU32_expand(&cache->dynamic_image_landscape, new_atlas_dim, 0); +} + + LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve, Abigail* abigail){ VkDescriptorSetLayout my_desc_set_layout; VkDescriptorSetLayoutBindingFlagsCreateInfo set_layout_crinfo_flags = { @@ -101,11 +159,107 @@ LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve, Abigail* abigail){ for (size_t i = 0; i < LUCY_MAX_DESCRIPTOR_COUNT; i++) { image_slots.buf[i].variant = Option_None; } - return (LucyGlyphCache){ - .ve = ve, .abigail = abigail, .image_slots = image_slots, + + /* Dynamic image stuff */ + MargaretSubbuf dyn_staging = MargaretBufAllocator_alloc(ve.staging_buffers, LUCY_INITIAL_DYN_STAGING_BUF_SIZE); + + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(ve.physical_device, &properties); + U32 max_dim = properties.limits.maxImageDimension2D; + check(max_dim >= 10); + + LucyGlyphCache cache = (LucyGlyphCache){ + .ve = ve, .abigail = abigail, .max_dim = max_dim, + .image_slots = image_slots, .descriptor_set_layout = my_desc_set_layout, .descriptor_set = descriptor_set, - .to_be_written_to_descriptor_set = VecU32_new(), - .to_be_deleted = VecU32_new()}; + .dyn_staging = dyn_staging, .dynamic_image_slot = -1, + /* .dynamic_image_landscape = VecU32_new(), */ + .dynamic_landscape_progress_x = 0 + }; + + LucyGlyphCache__create_dyn_texture(&cache, LUCY_INITIAL_DYN_IMAGE_DIM); + cache.dynamic_image_landscape = VecU32_new_zeroinit(LUCY_INITIAL_DYN_IMAGE_DIM); + + return cache; +} + +void LucyGlyphCache__extract_bitmap(FT_Face ft_face, U32 codepoint, U32 max_dim, + TextureDataR8* ret_my_bitmap, FT_GlyphSlot* ret_slot + ) { + FT_UInt glyph_index = FT_Get_Char_Index(ft_face, (FT_ULong)codepoint); + check(FT_Load_Glyph(ft_face, glyph_index, 0) == 0); + FT_GlyphSlot slot = ft_face->glyph; + *ret_slot = slot; + FT_Bitmap* bitmap = &slot->bitmap; + *ret_my_bitmap = TextureDataR8_new(bitmap->width, bitmap->rows); + check(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); + if (slot->format != FT_GLYPH_FORMAT_BITMAP) { + check(FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL) == 0); + } + check(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); + /* Here we dismiss very big glyphs. This guarantees that each glyph on it's own fits into VkImage */ + check(bitmap->width <= max_dim && bitmap->rows <= max_dim); + assert(bitmap->rows == 0 || bitmap->width != 0 || bitmap->buffer != NULL); + for (S64 y = 0; y < bitmap->rows; y++) { + for (S64 x = 0; x < bitmap->width; x++) { + *TextureDataR8_mat(ret_my_bitmap, x, y) = *(bitmap->buffer + y * bitmap->pitch + x * sizeof(U8)); + } + } +} + +/* pos on atlas may be left undefined */ +void LucyGlyphCache__add_glyph_to_glyph_set(BufRBTree_MapU32ToLucyStoredGlyph* glyph_set, + U32 codepoint, FT_GlyphSlot slot, uvec2 pos_on_atlas) { + BufRBTree_MapU32ToLucyStoredGlyph_insert(glyph_set, codepoint, (LucyStoredGlyph){ + .w = slot->bitmap.width, .h = slot->bitmap.rows, + .advance_x = slot->advance.x >> 6, + .bearing = (ivec2){ slot->bitmap_left, -slot->bitmap_top }, + .pos_on_atlas = pos_on_atlas, + }); +} + +/* Returned pointer will be invalidated after you modify the set of codepoints for this size of this face */ +LucyStoredGlyph* LucyCache_add_glyph_on_the_fly(RBTreeNodeLucyFaceFixedSize* ffs_node, U32 codepoint) { + LucyFaceFixedSize* ffs = &ffs_node->value; + BufRBTree_MapU32ToLucyStoredGlyph* glyph_set = &ffs->glyphs; + U64 map_it = BufRBTree_MapU32ToLucyStoredGlyph_find(glyph_set, codepoint); + if (map_it != 0) + return &glyph_set->el.buf[map_it - 1].value; + LucyGlyphCache* cache = ffs->p->p; + + U32 max_dim = cache->max_dim; + FT_Face ft_face = ffs->p->ft_face; + U32 font_height = ffs_node->key; + check(FT_Set_Pixel_Sizes(ft_face, 0, font_height) == 0); + + TextureDataR8 my_bitmap; + FT_GlyphSlot slot; + LucyGlyphCache__extract_bitmap(ft_face, codepoint, cache->max_dim, &my_bitmap, &slot); + if (my_bitmap.width > max_dim || my_bitmap.height > max_dim) { + abortf("LucyCache_add_glyph_on_the_fly there is no way to fit this monstrosity\n"); + } + another_attempt: + {} + 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); + U32 required_atlas_width = cache->dynamic_landscape_progress_x + my_bitmap.width; + if (required_atlas_width > cur_dyn_img_dim) { + cache->dynamic_landscape_progress_x = 0; + goto another_attempt; + } + U32 start_y = 0; + for (size_t gx = 0; gx < my_bitmap.width; gx++) { + start_y = MAX_U32(start_y, cache->dynamic_image_landscape.buf[gx]); + } + U32 ceiling = start_y + my_bitmap.height; + if (ceiling > cur_dyn_img_dim) { + /* This is serious. We need to swap */ + } + // todo: but we forgot to even write LucyAccumDynTransferImage, that would contain LucyPositionedDynGlyph + // todo: write swap, that would change the LucyAccumDynTransferImage we are writing to. + return NULL; // todo: fix } void LucyFaceFixedSize_get_rid_of_myself(LucyFaceFixedSize* self){ @@ -117,9 +271,10 @@ void LucyFaceFixedSize_get_rid_of_myself(LucyFaceFixedSize* self){ assert(img_slot->variant == Option_Some); LucyImage* img = &img_slot->some; assert(img->usage > 0); - if (--img->usage) { + if (--img->usage == 0) { /* Nothing is written to descriptor set. And luckily, we don't have to */ img_slot->variant = Option_None; + // todo: add image to deletion queue } } BufRBTree_MapU32ToLucyStoredGlyph_sink(glyphs); @@ -171,20 +326,6 @@ void LucyPrepMassTransferImage_drop(LucyPrepMassTransferImage self) { #include "../../../gen/l1/eve/lucy/VecLucyPrepMassTransferImage.h" -/* Helper function */ -U32 LucyGlyphCache__find_image_slot(LucyGlyphCache* cache){ - for (U32 i = 0; i < cache->image_slots.len; i++) { - OptionLucyImage* slot = &cache->image_slots.buf[i]; - if (slot->variant == Option_None) { - slot->variant = Option_Some; - slot->some.usage = 0; - return i; - } - } - abortf("LucyCache run out of image descriptor in a descriptor set (dictated by layout).\n" - "You better add up on them\n"); -} - /* Helper function */ LucyPrepMassTransferImage LucyGlyphCache_add_glyphs__another_image(LucyGlyphCache* cache) { return (LucyPrepMassTransferImage){ @@ -232,11 +373,6 @@ void LucyGlyphCache_add_glyphs(LucyGlyphCache* cache, VecLucyGlyphCachingRequest for (size_t fi = 0; fi < requests_for_faces.len; fi++) { assert(cache == requests_for_faces.buf[fi].sized_face->value.p->p); } - VkPhysicalDeviceProperties properties; - vkGetPhysicalDeviceProperties(cache->ve.physical_device, &properties); - U32 max_dim = properties.limits.maxImageDimension2D; - check(max_dim >= 10); - VecLucyPositionedStagingGlyph ready = VecLucyPositionedStagingGlyph_new(); for (size_t fi = 0; fi < requests_for_faces.len; fi++) { @@ -253,30 +389,11 @@ void LucyGlyphCache_add_glyphs(LucyGlyphCache* cache, VecLucyGlyphCachingRequest for (U32 codepoint = range_start; codepoint < range_end; codepoint++) { if (BufRBTree_MapU32ToLucyStoredGlyph_find(glyph_set, codepoint) != 0) continue; - FT_UInt glyph_index = FT_Get_Char_Index(ft_face, (FT_ULong)codepoint); - check(FT_Load_Glyph(ft_face, glyph_index, 0) == 0); - FT_GlyphSlot slot = ft_face->glyph; - FT_Bitmap* bitmap = &slot->bitmap; - TextureDataR8 my_bitmap = TextureDataR8_new(bitmap->width, bitmap->rows); - check(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); - if (slot->format != FT_GLYPH_FORMAT_BITMAP) { - check(FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL) == 0); - } - check(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); - /* Here we dismiss very big glyphs. This guarantees that each glyph on it's own fits into VkImage */ - check(bitmap->width <= max_dim && bitmap->rows <= max_dim); - assert(bitmap->rows == 0 || bitmap->width != 0 || bitmap->buffer != NULL); - for (S64 y = 0; y < bitmap->rows; y++) { - for (S64 x = 0; x < bitmap->width; x++) { - *TextureDataR8_mat(&my_bitmap, x, y) = *(bitmap->buffer + y * bitmap->pitch + x * sizeof(U8)); - } - } - BufRBTree_MapU32ToLucyStoredGlyph_insert(glyph_set, codepoint, (LucyStoredGlyph){ - .w = bitmap->width, .h = bitmap->rows, - .advance_x = slot->advance.x >> 6, - .bearing = (ivec2){ slot->bitmap_left, -slot->bitmap_top }, - /* x_on_atlas, y_on_atlas and img will be set later, when `ready` vector is ready */ - }); + 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}); VecLucyPositionedStagingGlyph_append(&ready, (LucyPositionedStagingGlyph){ .sized_face = &req.sized_face->value, .codepoint = codepoint, .bitmap = my_bitmap, /* pos be filled later by packing algorithm */ @@ -301,7 +418,7 @@ void LucyGlyphCache_add_glyphs(LucyGlyphCache* cache, VecLucyGlyphCachingRequest LucyPositionedStagingGlyph* p_glyph = &ready.buf[j]; LucyImage* img = &VecOptionLucyImage_mat(&cache->image_slots, img_slot_id)->some; U64 new_width_required = p_glyph->bitmap.width + starting_x; - if (new_width_required > max_dim) { + if (new_width_required > cache->max_dim) { /* Resetting row */ starting_x = 0; goto one_more_chance; @@ -313,7 +430,7 @@ void LucyGlyphCache_add_glyphs(LucyGlyphCache* cache, VecLucyGlyphCachingRequest height_here = MAX_U32(height_here, *VecU32_at(&landscape, starting_x + x)); } U64 new_height_required = height_here + p_glyph->bitmap.height; - if (new_height_required > max_dim) { + if (new_height_required > cache->max_dim) { /* Resetting image */ LucyGlyphCache_add_glyphs__close_img(cache, &mass_transfer, unprepared_image, img_width, img_height); starting_x = 0; @@ -377,8 +494,6 @@ void LucyGlyphCache_drop(LucyGlyphCache self){ for (size_t i = 0; i < self.image_slots.len; i++) { assert(self.image_slots.buf[i].variant == Option_None); } - VecU32_drop(self.to_be_written_to_descriptor_set); - VecU32_drop(self.to_be_deleted); } /* This function does not check font file for correctness, use only with trusted fonts */