Wrote shit + changed README = it was a good day

This commit is contained in:
Андреев Григорий 2026-04-24 20:54:09 +03:00
parent 306536c214
commit 5e3aa054d3
2 changed files with 170 additions and 55 deletions

View File

@ -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 */