From 19f92d92076525c00ad0f29bb0c14c3ef12a7292 Mon Sep 17 00:00:00 2001 From: Andreew Gregory Date: Tue, 23 Dec 2025 22:47:15 +0300 Subject: [PATCH] I wrote Lucy. It works. I can render text. Example is written in r0. Too bad I am running out of time. Exam is the next day after tomorrow :( --- src/l1/anne/lucy.h | 7 + src/l1/anne/margaret/margaret_misc.h | 3 +- src/l1/anne/util_temp_vulkan.h | 1 + src/l1/codegen/list_template_inst.h | 5 +- src/l1/codegen/util_template_inst.h | 60 ++-- src/l1/core/VecU8_as_str.h | 32 ++ src/l1_5/anne/lucy.h | 2 +- src/l2/lucy/glyph_cache.h | 337 ++++++++++++++++-- src/l2/lucy/glyph_render.h | 168 +++++++-- src/l2/margaret/vulkan_buffer_claire.h | 31 +- src/l2/margaret/vulkan_utils.h | 32 +- src/l2/tests/r0/fonts/DMSerifText-Regular.ttf | Bin 0 -> 73988 bytes src/l2/tests/r0/r0.c | 75 +++- src/l2/tests/r0/r0_scene.h | 13 +- src/l_adele/lucy/lucy.frag | 2 +- 15 files changed, 657 insertions(+), 111 deletions(-) create mode 100644 src/l2/tests/r0/fonts/DMSerifText-Regular.ttf diff --git a/src/l1/anne/lucy.h b/src/l1/anne/lucy.h index de57d28..120fe3d 100644 --- a/src/l1/anne/lucy.h +++ b/src/l1/anne/lucy.h @@ -11,7 +11,14 @@ void generate_l1_lucy_headers(){ .T = cstr("LucyImage"), .t_primitive = true}, true); generate_eve_span_company_for_primitive(l, ns, cstr("KVPU32ToLucyStoredGlyph"), true, false); generate_eve_span_company_for_primitive(l, ns, cstr("KVPU32ToLucyFaceFixedSize"), true, false); + generate_eve_span_company_for_non_primitive_non_clonable(l, ns, cstr("LucyGlyphCachingRequest"), true, true); + /* Vector of iterators */ + generate_eve_span_company_for_primitive(l, ns, cstr("RefListNodeLucyImage"), true, false); + + generate_util_templ_inst_eve_header(l, ns, (util_templates_instantiation_options){ + .T = cstr("LucyPositionedStagingGlyph"), .vec = true, .sort = true, + }); } #endif \ No newline at end of file diff --git a/src/l1/anne/margaret/margaret_misc.h b/src/l1/anne/margaret/margaret_misc.h index 30efee4..67fe12d 100644 --- a/src/l1/anne/margaret/margaret_misc.h +++ b/src/l1/anne/margaret/margaret_misc.h @@ -9,8 +9,7 @@ void generate_margaret_eve_for_vulkan_utils() { SpanU8 ns = cstr("margaret"); mkdir_nofail("l1/eve/margaret"); generate_util_templ_inst_eve_header(l, ns, (util_templates_instantiation_options){ - .T = cstr("MargaretScoredPhysicalDevice"), .t_primitive = true, .vec = true, .span = true, - .mut_span = true, .collab_vec_span = true, .span_sort = true + .T = cstr("MargaretScoredPhysicalDevice"), .t_primitive = true, .vec = true, .sort = true }); /* For l2/margaret/{ vulkan_img_claire.h , vulkan_buffer_claire.h } */ diff --git a/src/l1/anne/util_temp_vulkan.h b/src/l1/anne/util_temp_vulkan.h index c9a5a20..310b450 100644 --- a/src/l1/anne/util_temp_vulkan.h +++ b/src/l1/anne/util_temp_vulkan.h @@ -41,6 +41,7 @@ void generate_util_templ_inst_for_vulkan_headers() { generate_guarded_span_company_for_primitive(l, ns, cstr("VkSemaphore"), vulkan_dep, true, false); generate_guarded_span_company_for_primitive(l, ns, cstr("VkBufferCopy"), vulkan_dep, true, false); generate_guarded_span_company_for_primitive(l, ns, cstr("VkImageMemoryBarrier"), vulkan_dep, true, false); + generate_guarded_span_company_for_primitive(l, ns, cstr("VkDescriptorImageInfo"), vulkan_dep, true, false); } #endif \ No newline at end of file diff --git a/src/l1/codegen/list_template_inst.h b/src/l1/codegen/list_template_inst.h index 7e4495b..991d3d2 100644 --- a/src/l1/codegen/list_template_inst.h +++ b/src/l1/codegen/list_template_inst.h @@ -44,7 +44,7 @@ NODISCARD VecU8 generate_List_template_instantiation(list_instantiation_op op, b op.T, op.T, op.T, op.T, op.T, op.t_primitive ? vcstr("") : VecU8_fmt(SPACE SPACE "%s_drop(cur->el);\n", op.T))); VecU8_append_vec(&res, VecU8_fmt( - "void List%s_insert(List%s* self, %s el) {\n" /* op.T, op.T, op.T */ + "ListNode%s* List%s_insert(List%s* self, %s el) {\n" /* op.T, op.T, op.T, op.T */ SPACE "ListNode%s* new_node = safe_malloc(sizeof(ListNode%s));\n" /* op.T, op.T */ SPACE "new_node->prev = NULL;\n" SPACE "new_node->next = self->first;\n" @@ -52,7 +52,8 @@ NODISCARD VecU8 generate_List_template_instantiation(list_instantiation_op op, b SPACE "if (self->first)\n" SPACE SPACE "self->first->prev = new_node;\n" SPACE "self->first = new_node;\n" - "}\n\n", op.T, op.T, op.T, op.T, op.T)); + SPACE "return new_node;\n" + "}\n\n", op.T, op.T, op.T, op.T, op.T, op.T)); VecU8_append_vec(&res, VecU8_fmt( "void List%s_insert_node(List%s* self, ListNode%s* new_node) {\n" /* op.T, op.T, op.T */ SPACE "new_node->prev = NULL;\n" diff --git a/src/l1/codegen/util_template_inst.h b/src/l1/codegen/util_template_inst.h index 720b6bb..8c6eac8 100644 --- a/src/l1/codegen/util_template_inst.h +++ b/src/l1/codegen/util_template_inst.h @@ -260,9 +260,9 @@ void codegen_append_some_span_span_method(VecU8* res, SpanU8 SpanT) { /* T must be sized. Option `add_sort` requires option `add_mutable` and method T_less * add_mutable option generates MutSpanT. * add_equal option generates equal method. add_extended option generated extended methods - * add_sort option generates T_qcompare and MutSpanT_sort methods */ + */ NODISCARD VecU8 generate_SpanT_struct_and_methods( - SpanU8 T, bool integer, bool add_mutable, bool add_equal, bool add_extended, bool add_sort + SpanU8 T, bool integer, bool add_mutable, bool add_equal, bool add_extended ) { VecU8 g_SpanT = VecU8_fmt("Span%s", T); VecU8 g_MutSpanT = VecU8_fmt("MutSpan%s", T); @@ -293,31 +293,38 @@ NODISCARD VecU8 generate_SpanT_struct_and_methods( codegen_append_some_span_span_method(&res, MutSpanT); } - if (add_sort) { - assert(add_mutable); - VecU8_append_vec(&res, VecU8_fmt( - "int %s_qcompare(const void* a, const void* b) {\n" - SPACE "const %s* A = a;\n" - SPACE "const %s* B = b;\n", T, T, T)); - if (integer) { - VecU8_append_span(&res, cstr(SPACE "return (int)(B < A) - (int)(A < B);\n")); - } else { - VecU8_append_vec(&res, VecU8_fmt( - SPACE "return (int)%s_less_%s(B, A) - (int)%s_less_%s(A, B);\n", T, T, T, T)); - } - VecU8_append_vec(&res, VecU8_fmt( - "}\n\n" - "void %s_sort(%s self) {\n" - SPACE "qsort(self.data, self.len, sizeof(%s), %s_qcompare);\n" - "}\n\n", MutSpanT, MutSpanT, T, T)); - } - VecU8_drop(g_MutSpanT); VecU8_drop(g_SpanT); return res; } -// void codegen_append_vec_some_span_method(VecU8* res, SpanU8 mod, SpanU8 ) +NODISCARD VecU8 generate_span_company_sort_methods(SpanU8 T, bool t_integer, bool mut_span, bool vec){ + VecU8 res = VecU8_new(); + + VecU8_append_vec(&res, VecU8_fmt( + "int %s_qcompare(const void* a, const void* b) {\n" /* T */ + SPACE "const %s* A = a;\n" /* T */ + SPACE "const %s* B = b;\n" /* T */ + SPACE "return %v;\n" /* we return stuff */ + "}\n\n", + T, T, T, t_integer ? vcstr("(int)(*B < *A) - (int)(*A < *B)") : + VecU8_fmt("(int)%s_less_%s(B, A) - (int)%s_less_%s(A, B)", T, T, T, T))); + + if (mut_span) { + VecU8_append_vec(&res, VecU8_fmt( + "void MutSpan%s_sort(MutSpan%s self) {\n" + SPACE "qsort(self.data, self.len, sizeof(%s), %s_qcompare);\n" + "}\n\n", T, T, T, T)); + } + if (vec) { + VecU8_append_vec(&res, VecU8_fmt( + "void Vec%s_sort(Vec%s* self) {\n" + SPACE "qsort(self->buf, self->len, sizeof(%s), %s_qcompare);\n" + "}\n\n", T, T, T, T)); + } + + return res; +} /* T must be trivially movable. If !primitive, requires methods T_drop (implicitly) and, if clonable, requires T_clone */ NODISCARD VecU8 generate_SpanT_VecT_trivmove_collab(SpanU8 T, bool primitive, bool clonable, bool add_mutable, bool add_extended) { @@ -416,7 +423,7 @@ typedef struct { bool span; bool mut_span; bool span_extended; - bool span_sort; + bool sort; bool collab_vec_span; bool collab_vec_span_extended; } util_templates_instantiation_options; @@ -433,8 +440,6 @@ void util_templates_instantiation_options_fix(util_templates_instantiation_optio op->vec = true; if (op->span_extended) op->span = true; - if (op->span_sort) - op->span = true; if (op->mut_span) op->span = true; if (op->collab_vec_span_extended) @@ -467,7 +472,10 @@ NODISCARD VecU8 generate_util_templates_instantiation(util_templates_instantiati VecU8_append_vec(&res, generate_VecT_new_of_size_method(op.T)); } if (op.span) { - VecU8_append_vec(&res, generate_SpanT_struct_and_methods(op.T, op.t_integer, op.mut_span, false, op.span_extended, op.span_sort)); + VecU8_append_vec(&res, generate_SpanT_struct_and_methods(op.T, op.t_integer, op.mut_span, false, op.span_extended)); + } + if (op.sort) { + VecU8_append_vec(&res, generate_span_company_sort_methods(op.T, op.t_integer, op.mut_span, op.vec)); } if (op.collab_vec_span) { assert(op.vec && op.span); diff --git a/src/l1/core/VecU8_as_str.h b/src/l1/core/VecU8_as_str.h index ba7b0a0..90ac0ee 100644 --- a/src/l1/core/VecU8_as_str.h +++ b/src/l1/core/VecU8_as_str.h @@ -219,5 +219,37 @@ bool strings_in_spans_equal(SpanU8 a, SpanU8 b) { return true; } +/* 0 means error */ +U32 SpanU8_decode_as_utf8(SpanU8* rem){ + assert(rem->len > 0); + U8 first = rem->data[0]; + rem->data++; + rem->len--; + if (!(first & 0b10000000)) + return first; + uint8_t a = 0b11000000; + uint8_t b = 0b00100000; + for (int sz = 1; sz <= 3; sz++){ + if ((first & (a | b)) == a) { + /* sz is the character size in bytes */ + if (rem->len < (size_t)sz) + return 0; + U32 res = first & (b - 1); + for (int i = 1; i < sz; i++) { + U8 th = rem->data[0]; + if ((th & 0b11000000) != 0b10000000) + return 0; + res <<= 6; + res |= (th & 0b00111111); + rem->data++; + } + rem->len -= sz; + return res; + } + a |= b; + b >>= 1; + } + return 0; +} #endif diff --git a/src/l1_5/anne/lucy.h b/src/l1_5/anne/lucy.h index 7504aca..ebd85ce 100644 --- a/src/l1_5/anne/lucy.h +++ b/src/l1_5/anne/lucy.h @@ -9,7 +9,7 @@ void generate_l1_5_lucy_headers(){ generate_buf_rbtree_Map_templ_inst_eve_header(l, ns, (map_instantiation_op){ .K = cstr("U32"), .k_integer = true, .V = cstr("LucyStoredGlyph"), .v_primitive = true}); generate_rbtree_Map_templ_inst_eve_header(l, ns, (map_instantiation_op){ - .K = cstr("U32"), .k_integer = true, .V = cstr("LucyFaceFixedSize"), .v_primitive = true}, true); + .K = cstr("U32"), .k_integer = true, .V = cstr("LucyFaceFixedSize")}, true); } diff --git a/src/l2/lucy/glyph_cache.h b/src/l2/lucy/glyph_cache.h index 1dd6017..f579162 100644 --- a/src/l2/lucy/glyph_cache.h +++ b/src/l2/lucy/glyph_cache.h @@ -5,20 +5,36 @@ #include #include FT_FREETYPE_H #include "../../../gen/l1/VecAndSpan_U32Segment.h" +#include "../../../gen/l1/vulkan/VecVkDescriptorImageInfo.h" #include "../../l1_5/core/buff_rb_tree_node.h" #include "../../l1_5/core/rb_tree_node.h" +#define LUCY_MAX_DESCRIPTOR_COUNT 100 + typedef struct { + /* This value is actually Option. If staging_buffer is already deleted (after it is no longer used), + * staging_buffer.len will be 0 */ + MargaretSubbuf staging_buffer; MargaretImg img; + VkImageView img_view; U64 usage; U64 pos_in_desc_array; + /* 0 if this image isn't scheduled for deletion on th next cycle. + * 1 if it is */ + int scheduled_for_deletion; } LucyImage; #include "../../../gen/l1/eve/lucy/ListLucyImage.h" +typedef ListNodeLucyImage* RefListNodeLucyImage; +#include "../../../gen/l1/eve/lucy/VecRefListNodeLucyImage.h" + typedef struct { ListNodeLucyImage* img; - U32 x, y, w, h; + U32 w, h; + U32 advance_x; + ivec2 bearing; + uvec2 pos_on_atlas; } LucyStoredGlyph; typedef struct { @@ -28,35 +44,79 @@ typedef struct { #include "../../../gen/l1/eve/lucy/VecKVPU32ToLucyStoredGlyph.h" #include "../../../gen/l1_5/eve/lucy/BufRBTree_MapU32ToLucyStoredGlyph.h" -typedef struct { - U64 usage; +typedef struct LucyFace LucyFace; + +typedef struct{ + LucyFace* p; BufRBTree_MapU32ToLucyStoredGlyph glyphs; } LucyFaceFixedSize; +void LucyFaceFixedSize_drop(LucyFaceFixedSize self){ + BufRBTree_MapU32ToLucyStoredGlyph_drop(self.glyphs); +} + #include "../../../gen/l1_5/eve/lucy/RBTree_MapU32ToLucyFaceFixedSize.h" -typedef struct{ +// This is a very useful alias +typedef RBTreeNode_KVPU32ToLucyFaceFixedSize RBTreeNodeLucyFaceFixedSize; + +typedef struct LucyGlyphCache LucyGlyphCache; + +struct LucyFace { + LucyGlyphCache* p; FT_Face ft_face; RBTree_MapU32ToLucyFaceFixedSize sizes; -} LucyFace; +}; -typedef struct { +struct LucyGlyphCache { MargaretEngineReference ve; ListLucyImage images; VkDescriptorSetLayout descriptor_set_layout; VkDescriptorSet descriptor_set; -} LucyGlyphCache; + + /* to_be_freed_of_old_staging_next_cycle never intersect with to_be_copied_to_device_next_cycle */ + VecRefListNodeLucyImage to_be_freed_of_old_staging_next_cycle; + VecRefListNodeLucyImage to_be_copied_to_device_next_cycle; + /* deletion will be performed last */ + VecRefListNodeLucyImage to_be_deleted; +}; -// todo: write -LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve, VkDescriptorSetLayout descriptor_set_layout){ - VkDescriptorSet descriptor_set = margaret_allocate_descriptor_set(ve.device, ve.descriptor_pool, descriptor_set_layout); - return (LucyGlyphCache){.ve = ve, - .images = ListLucyImage_new(), .descriptor_set_layout = descriptor_set_layout, .descriptor_set = descriptor_set}; +LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve){ + VkDescriptorSetLayout my_desc_set_layout; + check(vkCreateDescriptorSetLayout(ve.device, &(VkDescriptorSetLayoutCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, + .pBindings = (VkDescriptorSetLayoutBinding[]){{ + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = LUCY_MAX_DESCRIPTOR_COUNT, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + }}, + }, NULL, &my_desc_set_layout) == VK_SUCCESS); + VkDescriptorSet descriptor_set = margaret_allocate_descriptor_set(ve.device, ve.descriptor_pool, + my_desc_set_layout); + return (LucyGlyphCache){.ve = ve, .images = ListLucyImage_new(), + .descriptor_set_layout = my_desc_set_layout, .descriptor_set = descriptor_set}; +} + +void LucyFaceFixedSize_get_rid_of_myself(LucyFaceFixedSize* self){ + LucyGlyphCache* cache = self->p->p; + BufRBTree_MapU32ToLucyStoredGlyph* glyphs = &self->glyphs; + for (size_t gid = 0; gid < glyphs->el.len; gid++) { + ListNodeLucyImage* img = glyphs->el.buf[gid].value.img; + assert(img->el.usage > 0); + if (--img->el.usage) { + assert(!img->el.scheduled_for_deletion); + img->el.scheduled_for_deletion = 1; + VecRefListNodeLucyImage_append(&cache->to_be_deleted, img); + } + } + BufRBTree_MapU32ToLucyStoredGlyph_sink(glyphs); } typedef struct { - RBTreeNode_KVPU32ToLucyFaceFixedSize* sized_face; + RBTreeNodeLucyFaceFixedSize* sized_face; VecU32Segment codepoint_ranges; } LucyGlyphCachingRequest; @@ -66,28 +126,259 @@ void LucyGlyphCachingRequest_drop(LucyGlyphCachingRequest self){ #include "../../../gen/l1/eve/lucy/VecAndSpan_LucyGlyphCachingRequest.h" -void LucyGlyphCache_add_glyphs(SpanLucyGlyphCachingRequest requests_for_faces){ - // todo: write it all +/* Helper structure */ +typedef struct { + LucyFaceFixedSize* sized_face; + U32 codepoint; + TextureDataR8 bitmap; + /* Will be determined in the next phase */ + uvec2 pos; + ListNodeLucyImage* img; +} LucyPositionedStagingGlyph; + +bool LucyPositionedStagingGlyph_less_LucyPositionedStagingGlyph( + const LucyPositionedStagingGlyph* A, const LucyPositionedStagingGlyph* B){ + return A->bitmap.height < B->bitmap.height; +} + +void LucyPositionedStagingGlyph_drop(LucyPositionedStagingGlyph self){ + TextureDataR8_drop(self.bitmap); +} + +/* Instantiation for helper type */ +#include "../../../gen/l1/eve/lucy/VecLucyPositionedStagingGlyph.h" + +/* Helper function */ +void LucyGlyphCache_add_glyphs__close_img( + LucyGlyphCache* cache, ListNodeLucyImage* img, U32 img_width, U32 img_height + ){ + assert(img->el.usage > 0); + img_width = MAX_U32(img_width, 10); // Just a precaution. empty buffers aren't supported by Margaret + img_height = MAX_U32(img_height, 10); + VecRefListNodeLucyImage_append(&cache->to_be_copied_to_device_next_cycle, img); + img->el.staging_buffer = MargaretBufAllocator_alloc(cache->ve.staging_buffers, img_width * img_height * 1); + img->el.img = MargaretImgAllocator_alloc(cache->ve.dev_local_images, img_width, img_height, VK_FORMAT_R8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); + img->el.img_view = margaret_create_view_for_image(cache->ve.device, img->el.img.a.image, + VK_FORMAT_R8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); +} + +void LucyGlyphCache_add_glyphs(VecLucyGlyphCachingRequest requests_for_faces){ + if (requests_for_faces.len == 0) + return; + LucyGlyphCache* cache = requests_for_faces.buf[0].sized_face->value.p->p; for (size_t fi = 0; fi < requests_for_faces.len; fi++) { - LucyGlyphCachingRequest req = requests_for_faces.data[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++) { + LucyGlyphCachingRequest req = requests_for_faces.buf[fi]; + FT_Face ft_face = req.sized_face->value.p->ft_face; U32 font_height = req.sized_face->key; + check(FT_Set_Pixel_Sizes(ft_face, 0, font_height) == 0); BufRBTree_MapU32ToLucyStoredGlyph* glyph_set = &req.sized_face->value.glyphs; + /* Phase 1, where we add some elements to glyph_set, but we don't + * know how many image we will have and we don't know where new glyphs will be placed */ for (size_t ri = 0; ri < req.codepoint_ranges.len; ri++) { U32 range_start = req.codepoint_ranges.buf[ri].start; U32 range_end = range_start + req.codepoint_ranges.buf[ri].len; for (U32 codepoint = range_start; codepoint < range_end; codepoint++) { - // todo: render it all + 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); + } + /* 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 */ + }); + VecLucyPositionedStagingGlyph_append(&ready, (LucyPositionedStagingGlyph){ + .sized_face = &req.sized_face->value, .codepoint = codepoint, .bitmap = my_bitmap, + /* pos and img will be filled later by packing algorithm */ + }); + } + } + /* Phase 2. Here we determine (in ready vector) where each new glyph sits (atlas image + pos). + * But we won't copy TextureDataR8 to staging buffer before everything is known */ + VecLucyPositionedStagingGlyph_sort(&ready); + /* Variables, that have to be reset after each image overflow */ + U32 starting_x = 0; + VecU32 landscape = VecU32_new_reserved(200); + U32 img_width = 0, img_height = 0; + ListNodeLucyImage* img = ListLucyImage_insert(&cache->images, (LucyImage){0}); + for (size_t j = 0; j < ready.len; j++) { + LucyPositionedStagingGlyph* p_glyph; + one_more_chance: + p_glyph = &ready.buf[j]; + U64 new_width_required = p_glyph->bitmap.width + starting_x; + if (new_width_required > max_dim) { + /* Resetting row */ + starting_x = 0; + goto one_more_chance; + } + for (U32 h = img_width; h < new_width_required; h++) { + VecU32_append(&landscape, 0); + } + img_width = MAX_U64(img_width, new_width_required); + U32 height_here = 0; + for (size_t x = 0; x < p_glyph->bitmap.width; x++) { + 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) { + /* Resetting image */ + LucyGlyphCache_add_glyphs__close_img(cache, img, img_width, img_height); + starting_x = 0; + landscape.len = 0; + img_width = 0; + img_height = 0; + img = ListLucyImage_insert(&cache->images, (LucyImage){0}); + goto one_more_chance; + } + /* Success */ + for (size_t x = 0; x < p_glyph->bitmap.width; x++) { + *VecU32_mat(&landscape, starting_x + x) = new_height_required; + } + img_height = MAX_U64(img_height, new_height_required); + p_glyph->img = img; + p_glyph->pos = (uvec2){starting_x, height_here}; + img->el.usage++; /* p_glyph uses it, that's a rock fact */ + BufRBTree_MapU32ToLucyStoredGlyph *glyphs = &p_glyph->sized_face->glyphs; + U64 map_it = BufRBTree_MapU32ToLucyStoredGlyph_find(glyphs, p_glyph->codepoint); + assert(map_it > 0 && map_it < glyphs->tree.len); + LucyStoredGlyph* actual_glyph = &glyphs->el.buf[map_it - 1].value; + actual_glyph->pos_on_atlas = (uvec2){starting_x, height_here}; + actual_glyph->img = img; + starting_x += p_glyph->bitmap.width; + } + LucyGlyphCache_add_glyphs__close_img(cache, img, img_width, img_height); + /* Phase 3. We have all the data. Now what? + * Now we fill staging buffers with glyphs bitsets from `ready` vector */ + for (size_t j = 0; j < ready.len; j++) { + LucyPositionedStagingGlyph* p_glyph = &ready.buf[j]; + U64 staging_width = p_glyph->img->el.img.width; + U8* staging = (U8*)MargaretSubbuf_get_mapped(&p_glyph->img->el.staging_buffer); + for (U64 y = 0; y < p_glyph->bitmap.height; y++) { + U64 Y = y + p_glyph->pos.y; + for (U64 x = 0; x < p_glyph->bitmap.width; x++) { + U64 X = x + p_glyph->pos.x; + staging[Y * staging_width + X] = *TextureDataR8_at(&p_glyph->bitmap, x, y); + } } } } + VecLucyPositionedStagingGlyph_drop(ready); + VecLucyGlyphCachingRequest_drop(requests_for_faces); } -void LucyGlyphCache_delete(LucyGlyphCache* self){ - -} - +/* This must not happen before all the LucyFaceFixedSizes are destroyed */ void LucyGlyphCache_drop(LucyGlyphCache self){ - ListLucyImage_drop(self.images); + assert(self.images.first == NULL); + VecRefListNodeLucyImage_drop(self.to_be_freed_of_old_staging_next_cycle); + VecRefListNodeLucyImage_drop(self.to_be_copied_to_device_next_cycle); + VecRefListNodeLucyImage_drop(self.to_be_deleted); } -#endif \ No newline at end of file +void LucyGlyphCache_another_frame(LucyGlyphCache* self){ + for (size_t i = 0; i < self->to_be_freed_of_old_staging_next_cycle.len; i++) { + LucyImage* img = &self->to_be_freed_of_old_staging_next_cycle.buf[i]->el; + assert(img->staging_buffer.len != 0); + MargaretBufAllocator_free(self->ve.staging_buffers, img->staging_buffer); + img->staging_buffer.len = 0; + } + for (size_t i = 0; i < self->to_be_copied_to_device_next_cycle.len; i++) { + ListNodeLucyImage* img_node = self->to_be_copied_to_device_next_cycle.buf[i]; + LucyImage* img = &img_node->el; + assert(img->staging_buffer.len != 0); + if (img->scheduled_for_deletion) + continue; + margaret_rec_cmd_copy_buffer_to_image_one_to_one_color_aspect(self->ve.transfer_cmd_buffer, + &img->staging_buffer, &img->img, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); + VecRefListNodeLucyImage_append(&self->to_be_freed_of_old_staging_next_cycle, img_node); + } + /* We technically could carry out each deletion request in O(1) and each img creation request in O(1), + * but who cares, it's no problem going over the entire descriptor set when something get's added or deleted */ + for (size_t i = 0; i < self->to_be_deleted.len; i++) { + ListNodeLucyImage* img_node = self->to_be_deleted.buf[i]; + LucyImage* img = &img_node->el; + assert(img->scheduled_for_deletion); + assert(img->usage == 0); + if (img->staging_buffer.len != 0) + MargaretBufAllocator_free(self->ve.staging_buffers, img->staging_buffer); + MargaretImgAllocator_free(self->ve.dev_local_images, img->img.a); + ListLucyImage_erase_by_it(&self->images, img_node); + } + if ((self->to_be_copied_to_device_next_cycle.len > 0) || (self->to_be_deleted.len > 0)) { + U32 descriptor_i = 0; + VecVkDescriptorImageInfo desc_elements = VecVkDescriptorImageInfo_new(); + for (ListNodeLucyImage* list_node = self->images.first; list_node; list_node = list_node->next) { + if (descriptor_i == LUCY_MAX_DESCRIPTOR_COUNT) { + abortf("Today you are out of luck\n"); + } + LucyImage* img = &list_node->el; + img->pos_in_desc_array = descriptor_i; + VecVkDescriptorImageInfo_append(&desc_elements, (VkDescriptorImageInfo){ + .sampler = self->ve.nearest_sampler, .imageView = img->img_view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + }); + descriptor_i++; + } + vkUpdateDescriptorSets(self->ve.device, 1, &(VkWriteDescriptorSet){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = self->descriptor_set, .dstBinding = 0, .dstArrayElement = 0, + .descriptorCount = desc_elements.len, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = desc_elements.buf + }, 0, NULL); + VecVkDescriptorImageInfo_drop(desc_elements); + } + self->to_be_freed_of_old_staging_next_cycle.len = 0; + self->to_be_copied_to_device_next_cycle.len = 0; + self->to_be_deleted.len = 0; +} + +/* This function does not check font file for correctness, use only with trusted fonts */ +LucyFace LucyFace_new(FT_Library lib, LucyGlyphCache* cache, VecU8 path){ + VecU8_append(&path, 0); // Making it null-terminated + FT_Face face; + FT_Error ret = FT_New_Face(lib, (const char*)path.buf, 0, &face); + check(ret == 0); + VecU8_drop(path); + return (LucyFace){.p = cache, .ft_face = face, .sizes = RBTree_MapU32ToLucyFaceFixedSize_new()}; +} + +RBTreeNodeLucyFaceFixedSize* LucyFace_of_size(LucyFace* self, U32 size){ + RBTreeNodeLucyFaceFixedSize* nahodka = RBTree_MapU32ToLucyFaceFixedSize_find(&self->sizes, size); + if (nahodka) + return nahodka; + RBTree_MapU32ToLucyFaceFixedSize_insert(&self->sizes, size, (LucyFaceFixedSize){ + .p = self, .glyphs = BufRBTree_MapU32ToLucyStoredGlyph_new() + }); + // todo: add a method to RBTree for proper node insertion. This is just pure crap + return RBTree_MapU32ToLucyFaceFixedSize_find(&self->sizes, size); +} + +#endif diff --git a/src/l2/lucy/glyph_render.h b/src/l2/lucy/glyph_render.h index 7a16daa..002b096 100644 --- a/src/l2/lucy/glyph_render.h +++ b/src/l2/lucy/glyph_render.h @@ -5,6 +5,7 @@ #include "../../../gen/l1/pixel_masses.h" #include "../../../gen/l1/geom.h" +// todo: rewrite this shit crap using instances typedef struct{ vec4 color; vec2 pos; @@ -12,44 +13,38 @@ typedef struct{ U32 tex_ind; } LucyVertex; -typedef struct{ - LucyGlyphCache cache; +typedef struct { + MargaretEngineReference ve; + LucyGlyphCache* cache; VkPipelineLayout pipeline_layout; VkPipeline pipeline; -} LucyGlyphRenderer; -#define LUCY_MAX_DESCRIPTOR_COUNT 10 + U64 vertex_count; + MargaretSubbuf staging_vbo; + MargaretSubbuf vbo; + bool need_to_transfer; +} LucyRenderer; -LucyGlyphRenderer LucyGlyphRenderer_new( - MargaretEngineReference engine_reference, SpanU8 root_dir, +typedef struct { + float width, height; +} LucyRendererPushConstants; + +LucyRenderer LucyRenderer_new( + MargaretEngineReference ve, LucyGlyphCache* cache, SpanU8 root_dir, VkRenderPass render_pass, U32 renderpass_subpass){ - VkDescriptorSetLayout descriptor_set_layout; - check(vkCreateDescriptorSetLayout(engine_reference.device, &(VkDescriptorSetLayoutCreateInfo){ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .bindingCount = 1, - .pBindings = (VkDescriptorSetLayoutBinding[]){{ - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = LUCY_MAX_DESCRIPTOR_COUNT, - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - }}, - }, NULL, &descriptor_set_layout) == VK_SUCCESS); - - - LucyGlyphCache cache = LucyGlyphCache_new(engine_reference, descriptor_set_layout); VkPipelineLayout pipeline_layout; - check(vkCreatePipelineLayout(engine_reference.device, &(VkPipelineLayoutCreateInfo){ + check(vkCreatePipelineLayout(ve.device, &(VkPipelineLayoutCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, - .pSetLayouts = &descriptor_set_layout, + .pSetLayouts = &cache->descriptor_set_layout, .pushConstantRangeCount = 1, .pPushConstantRanges = (VkPushConstantRange[]){{ - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(float) * 2 + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(LucyRendererPushConstants) }}, }, NULL, &pipeline_layout) == VK_SUCCESS); - VkPipeline pipeline = margaret_create_triangle_pipeline_one_attachment(engine_reference.device, + VkPipeline pipeline = margaret_create_triangle_pipeline_one_attachment(ve.device, render_pass, renderpass_subpass, (MargaretMostImportantPipelineOptions){ .pipeline_layout = pipeline_layout, .vertex_shader_code = read_file_by_path(VecU8_fmt("%s/gen/l_adele/lucy/vert.spv", root_dir)), @@ -71,7 +66,130 @@ LucyGlyphRenderer LucyGlyphRenderer_new( .depthTestEnable = false, .depthWriteEnable = false, .blendEnable = true, }); - return (LucyGlyphRenderer){.cache = cache, .pipeline_layout = pipeline_layout, .pipeline = pipeline}; + return (LucyRenderer){.ve = ve, .cache = cache, .pipeline_layout = pipeline_layout, .pipeline = pipeline, + .vertex_count = 0, .staging_vbo = MargaretBufAllocator_alloc(ve.staging_buffers, 67), + .vbo = MargaretBufAllocator_alloc(ve.dev_local_buffers, 67) + }; +} + +/* When another_frame starts, you are safe to call this function, but you also have to call it + * before LucyRenderer_another_frame + */ +void LucyRenderer_clear(LucyRenderer* self){ + self->vertex_count = 0; +} + +void LucyRenderer__append_vertex_to_vao(LucyRenderer* self, LucyVertex vert_data){ + self->vertex_count++; + assert(self->vertex_count * sizeof(LucyVertex) <= self->staging_vbo.len); + LucyVertex* staging = (LucyVertex*)MargaretSubbuf_get_mapped(&self->staging_vbo); + staging[self->vertex_count - 1] = vert_data; +} + +/* When another_frame starts, you are safe to call this function, but you also have to call it + * before LucyRenderer_another_frame, because _another_frame method records transfer of data + */ +void LucyRenderer_add_text( + LucyRenderer* self, RBTreeNodeLucyFaceFixedSize* ffs, vec4 color, + U32 additional_y_advance, SpanU8 text, ivec2 start_pos + ){ + self->need_to_transfer = true; + U32 font_height = ffs->key; + ivec2 pos = (ivec2){start_pos.x, start_pos.y + (S32)font_height}; + /* `text` variable will be modified during decoding */ + while (text.len > 0) { + U32 codepoint = SpanU8_decode_as_utf8(&text); + if (codepoint == (U32)'\n') { + pos = (ivec2){start_pos.x, pos.y + (S32)font_height + (S32)additional_y_advance}; + continue; + } + BufRBTree_MapU32ToLucyStoredGlyph *glyphs = &ffs->value.glyphs; + U64 map_it = BufRBTree_MapU32ToLucyStoredGlyph_find(glyphs, codepoint); + if (map_it == 0) { + /* We probably should have requested LucyCache to load more glyphs or draw 'unknown character' + * character, but we just skip things. We will force someone else to do that job */ + continue; + } + assert(map_it > 0 && map_it < glyphs->tree.len); + LucyStoredGlyph* glyph = &glyphs->el.buf[map_it - 1].value; + if (glyph->w > 0 && glyph->h > 0) { + float atlas_w = (float)glyph->img->el.img.width; + float atlas_h = (float)glyph->img->el.img.height; + U32 desc_elem_id = glyph->img->el.pos_in_desc_array; + ivec2 positioned = ivec2_add_ivec2(pos, glyph->bearing); + LucyVertex v0 = { + .color = color, .pos = (vec2){(float)positioned.x, (float)positioned.y}, + .tex_cord = (vec2){ + (float)(glyph->pos_on_atlas.x) / atlas_w, + (float)(glyph->pos_on_atlas.y) / atlas_h + }, .tex_ind = desc_elem_id + }; + LucyVertex v1 = { + .color = color, .pos = (vec2){(float)(positioned.x + glyph->w), (float)positioned.y}, + .tex_cord = (vec2){ + (float)(glyph->pos_on_atlas.x + glyph->w) / atlas_w, + (float)(glyph->pos_on_atlas.y) / atlas_h + }, .tex_ind = desc_elem_id + }; + LucyVertex v2 = { + .color = color, .pos = (vec2){(float)positioned.x, (float)(positioned.y + glyph->h)}, + .tex_cord = (vec2){ + (float)(glyph->pos_on_atlas.x) / atlas_w, + (float)(glyph->pos_on_atlas.y + glyph->h) / atlas_h + }, .tex_ind = desc_elem_id + }; + LucyVertex v3 = { + .color = color, .pos = (vec2){(float)(positioned.x + glyph->w), (float)(positioned.y + glyph->h)}, + .tex_cord = (vec2){ + (float)(glyph->pos_on_atlas.x + glyph->w) / atlas_w, + (float)(glyph->pos_on_atlas.y + glyph->h) / atlas_h + }, .tex_ind = desc_elem_id + }; + /* What if we run out of space? */ + U64 needed_vbo_length = (self->vertex_count + 6) * sizeof(LucyVertex); + if (self->staging_vbo.len < needed_vbo_length) { + printf("LucyRenderer Staging Buffer: Gotta replace %lu with %lu\n", + self->staging_vbo.len, needed_vbo_length); + MargaretBufAllocator_expand_or_move_old_host_visible( + self->ve.staging_buffers, &self->staging_vbo, needed_vbo_length); + } + LucyRenderer__append_vertex_to_vao(self, v1); + LucyRenderer__append_vertex_to_vao(self, v0); + LucyRenderer__append_vertex_to_vao(self, v2); + LucyRenderer__append_vertex_to_vao(self, v1); + LucyRenderer__append_vertex_to_vao(self, v2); + LucyRenderer__append_vertex_to_vao(self, v3); + + } + pos.x += (S32)glyph->advance_x; + } +} + +/* It only records transfer commands (transfer command buffer is passed in MargaretEngineReference object) */ +void LucyRenderer_another_frame(LucyRenderer* self){ + if (self->vbo.len < self->vertex_count * sizeof(LucyVertex)) { + MargaretBufAllocator_expand_or_free_old(self->ve.dev_local_buffers, &self->vbo, + self->vertex_count * sizeof(LucyVertex)); + } + if (self->need_to_transfer && self->vertex_count > 0) { + self->need_to_transfer = false; + margaret_rec_cmd_copy_buffer_one_to_one_part(self->ve.transfer_cmd_buffer, + &self->staging_vbo, &self->vbo, 0, self->vertex_count * sizeof(LucyVertex)); + } +} + +// todo: merge with the function above. In the future all the shit will be recorded simultaneously +void LucyRenderer_another_frame_rec_drawing( + LucyRenderer* self, VkCommandBuffer drawing_cmd_buf, VkExtent2D image_extent){ + vkCmdBindPipeline(drawing_cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, self->pipeline); + vkCmdPushConstants(drawing_cmd_buf, self->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, + 0, sizeof(LucyRendererPushConstants), + &(LucyRendererPushConstants){ .width = (float)image_extent.width, .height = (float)image_extent.height }); + vkCmdBindVertexBuffers(drawing_cmd_buf, 0, 1, + (VkBuffer[]){MargaretSubbuf_get_buffer(&self->vbo)}, (VkDeviceSize[]){self->vbo.start}); + vkCmdBindDescriptorSets(drawing_cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, self->pipeline_layout, 0, + 1, (VkDescriptorSet[]){self->cache->descriptor_set}, 0, NULL); + vkCmdDraw(drawing_cmd_buf, self->vertex_count, 1, 0, 0); } #endif \ No newline at end of file diff --git a/src/l2/margaret/vulkan_buffer_claire.h b/src/l2/margaret/vulkan_buffer_claire.h index ad2447e..c7b0f4f 100644 --- a/src/l2/margaret/vulkan_buffer_claire.h +++ b/src/l2/margaret/vulkan_buffer_claire.h @@ -288,7 +288,8 @@ void MargaretBufAllocator_shrink(MargaretBufAllocator* self, MargaretSubbuf* all * But if ret value .len field is non-zero it means a valid MargaretSubbuf object was returned and the * `allocation` argument was untouched. It remains a valid object, you need to deallocate it yourself */ -NODISCARD MargaretSubbuf MargaretBufAllocator_expand(MargaretBufAllocator* self, MargaretSubbuf* allocation, U64 bigger_size){ +NODISCARD MargaretSubbuf MargaretBufAllocator_expand( + MargaretBufAllocator* self, MargaretSubbuf* allocation, U64 bigger_size){ bigger_size = margaret_bump_buffer_size_to_alignment(bigger_size, self->alignment_exp); U64Segment right_free_space = MargaretBufAllocator__get_right_free_space(self, allocation); @@ -314,3 +315,31 @@ VkBuffer MargaretSubbuf_get_buffer(const MargaretSubbuf* allocation){ assert(allocation->start + allocation->len <= allocation->block->capacity); return allocation->block->buf_hand; } + +/* It tries to expand buffer, but if it fails, it creates a freshly-new buffer. It copies all + * the data from old buffer to new one and frees the old buffer, while replacing + * info in `allocation` variable with info about new allocation. + */ +void MargaretBufAllocator_expand_or_move_old_host_visible( + MargaretBufAllocator* self, MargaretSubbuf* allocation, U64 bigger_size){ + assert(self->host_visible); + MargaretSubbuf maybe_bigger = MargaretBufAllocator_expand(self, allocation, bigger_size); + if (maybe_bigger.len > 0) { + memcpy(MargaretSubbuf_get_mapped(&maybe_bigger), MargaretSubbuf_get_mapped(allocation), allocation->len); + MargaretBufAllocator_free(self, *allocation); + *allocation = maybe_bigger; + } +} + +/* It tries to expand buffer, but if it fails, it creates a freshly-new buffer. It + * frees the old buffer, while replacing + * info in `allocation` variable with info about new allocation. Old data gets lost + */ +void MargaretBufAllocator_expand_or_free_old( + MargaretBufAllocator* self, MargaretSubbuf* allocation, U64 bigger_size){ + MargaretSubbuf maybe_bigger = MargaretBufAllocator_expand(self, allocation, bigger_size); + if (maybe_bigger.len > 0) { + MargaretBufAllocator_free(self, *allocation); + *allocation = maybe_bigger; + } +} \ No newline at end of file diff --git a/src/l2/margaret/vulkan_utils.h b/src/l2/margaret/vulkan_utils.h index 9624070..132f7e3 100644 --- a/src/l2/margaret/vulkan_utils.h +++ b/src/l2/margaret/vulkan_utils.h @@ -302,7 +302,7 @@ NODISCARD VecU8 margaret_stringify_device_memory_properties_2(VkPhysicalDevice p return VecU8_fmt( "maxMemoryAllocationsCount: %u\n" "maxMemoryAllocationSize: %u\n" - "maxBufferSize: %u!!!!!!!!!\n", + "maxBufferSize: %u\n", maxMemoryAllocationCount, maxMemoryAllocationSize, maxBufferSize); } @@ -320,14 +320,20 @@ VkDevice margaret_create_logical_device(VkPhysicalDevice physical_device, Margar logical_device_queue_crinfo[0].queueFamilyIndex = queue_fam.for_graphics; logical_device_queue_crinfo[1].queueFamilyIndex = queue_fam.for_presentation; + VkPhysicalDeviceVulkan12Features used_vk12_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .runtimeDescriptorArray = true, + .shaderSampledImageArrayNonUniformIndexing = true, + }; // We DEMAND synchronization2 VkPhysicalDeviceSynchronization2Features used_synchronization2_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, + .pNext = &used_vk12_features, .synchronization2 = VK_TRUE, }; VkPhysicalDeviceFeatures2 used_features2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, - .pNext = (void*)&used_synchronization2_features, + .pNext = &used_synchronization2_features, .features = (VkPhysicalDeviceFeatures) { .geometryShader = true, .samplerAnisotropy = physical_features.samplerAnisotropy, @@ -547,12 +553,16 @@ MargaretScoredPhysicalDevice margaret_score_physical_device( SpanU8_print(VecU8_to_span(&txt)); VecU8_drop(txt); } + VkPhysicalDeviceVulkan12Features vk12_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES + }; VkPhysicalDeviceSynchronization2Features synchronization2_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, + .pNext = &vk12_features }; VkPhysicalDeviceFeatures2 features2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, - .pNext = (void*)&synchronization2_features, + .pNext = &synchronization2_features, }; vkGetPhysicalDeviceFeatures2(dev, &features2); // printf("Device %s\nmaxBoundDescriptorSets: %" PRIu32 " \nmaxPerStageDescriptorUniformBuffers: %" PRIu32 "\n" @@ -569,8 +579,10 @@ MargaretScoredPhysicalDevice margaret_score_physical_device( return (MargaretScoredPhysicalDevice){dev, -1, cstr("No geometry shaders")}; if (!synchronization2_features.synchronization2) return (MargaretScoredPhysicalDevice){dev, -1, cstr("No synchronization2")}; - if (features2.features.samplerAnisotropy) - score += 2; + if (!vk12_features.shaderSampledImageArrayNonUniformIndexing) + return (MargaretScoredPhysicalDevice){dev, -1, cstr("No shaderSampledImageArrayNonUniformIndexing")}; + if (!vk12_features.runtimeDescriptorArray) + return (MargaretScoredPhysicalDevice){dev, -1, cstr("No runtimeDescriptorArray")}; ResultMargaretChosenQueueFamiliesOrSpanU8 queue_families = margaret_choose_good_queue_families(dev, surface); if (queue_families.variant == Result_Err) return (MargaretScoredPhysicalDevice){dev, -1, queue_families.err}; @@ -602,7 +614,7 @@ MargaretScoredPhysicalDevice margaret_score_physical_device( #define MargaretScoredPhysicalDevice_less_MargaretScoredPhysicalDevice(cap, cbp) ((cap)->score < (cbp)->score) -#include "../../../gen/l1/eve/margaret/VecAndSpan_MargaretScoredPhysicalDevice.h" +#include "../../../gen/l1/eve/margaret/VecMargaretScoredPhysicalDevice.h" VecMargaretScoredPhysicalDevice margaret_get_physical_devices_scored( VkInstance instance, VkSurfaceKHR surface, @@ -621,7 +633,7 @@ VecMargaretScoredPhysicalDevice margaret_get_physical_devices_scored( favourite_word, forbidden_word, sane_image_extent_limit ); } - MutSpanMargaretScoredPhysicalDevice_sort(VecMargaretScoredPhysicalDevice_to_mspan(&scored_devices)); + VecMargaretScoredPhysicalDevice_sort(&scored_devices); VecVkPhysicalDevice_drop(physical_devices); return scored_devices; } @@ -1065,8 +1077,8 @@ VkPipeline margaret_create_triangle_pipeline_one_attachment( .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, - .cullMode = VK_CULL_MODE_BACK_BIT, - // .cullMode = VK_CULL_MODE_NONE, + // .cullMode = VK_CULL_MODE_BACK_BIT, + .cullMode = VK_CULL_MODE_NONE, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, @@ -1138,6 +1150,8 @@ typedef struct { MargaretBufAllocator* dev_local_buffers; MargaretBufAllocator* staging_buffers; VkDescriptorPool descriptor_pool; + VkSampler linear_sampler; + VkSampler nearest_sampler; } MargaretEngineReference; void margaret_rec_cmd_copy_buffer( diff --git a/src/l2/tests/r0/fonts/DMSerifText-Regular.ttf b/src/l2/tests/r0/fonts/DMSerifText-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e3f62486196f3d599a8379ce9631d02f78ab7f84 GIT binary patch literal 73988 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.len; mi++) { const GenericModelOnSceneMem* model = VecGenericModelOnSceneMem_at(&scene->generic_models, mi); assert(model->instance_attr.count * sizeof(GenericMeshInstance) <= model->instance_attr.staging_busy.len); @@ -710,7 +711,6 @@ void record_copying_entire_scene_from_staging_to_device_local(VkCommandBuffer co margaret_rec_cmd_copy_buffer_one_to_one_part(command_buffer, ubo_staging, ubo_device_local, offsetof(Pipeline0UBO, spotlight_arr), sizeof(Pipeline0Spotlight) * mem->spotlight_count); } - margaret_end_command_buffer(command_buffer); } } @@ -753,6 +753,12 @@ typedef struct { MargaretImg IT1_image; MargaretImg zbuffer_image; + FT_Library ft_library; + LucyGlyphCache lucy_cache; + LucyFace font_face; + RBTreeNodeLucyFaceFixedSize* font_face_of_size_40; + LucyRenderer lucy_renderer; + VecGenericModelTexVulkPointers generic_model_tex_vulk_pointers; VkImageView zbuffer_view; VkImageView IT1_view; @@ -1097,14 +1103,14 @@ void vulkano_frame_drawing(state_r0* state) { mat4 camera_translation_matrix = marie_translation_mat4(vec3_minus(state->vk.scene.cam.pos)); mat4 t_mat = mat4_mul_mat4(projection_matrix, mat4_mul_mat4(camera_rotation_matrix, camera_translation_matrix)); + margaret_reset_and_begin_command_buffer(state->vk.transfer_command_buf); record_copying_entire_scene_from_staging_to_device_local(state->vk.transfer_command_buf, &state->vk.scene); - check(vkQueueSubmit(state->vk.queues.graphics_queue, 1, &(VkSubmitInfo){ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = (VkCommandBuffer[]){ state->vk.transfer_command_buf }, - .signalSemaphoreCount = 1, - .pSignalSemaphores = (VkSemaphore[]){ state->vk.jane.in_frame_transfer_complete }, - }, NULL) == VK_SUCCESS); + LucyGlyphCache_another_frame(&state->vk.lucy_cache); + LucyRenderer_clear(&state->vk.lucy_renderer); + LucyRenderer_add_text(&state->vk.lucy_renderer, state->vk.font_face_of_size_40, (vec4){1, 0, 0, 1}, 0, + VecU8_to_span(&state->vk.scene.text_on_screen), (ivec2){10, 10}); + LucyRenderer_another_frame(&state->vk.lucy_renderer); + margaret_end_command_buffer(state->vk.transfer_command_buf); reset_and_record_command_buffer_0( state->vk.rendering_command_buf_0, state->vk.render_pass_0, @@ -1119,7 +1125,16 @@ void vulkano_frame_drawing(state_r0* state) { &state->vk.pipeline_hands_1, *VecVkFramebuffer_at(&state->vk.swfb.framebuffers, ij), state->vk.swfb.extent, - state->sane_image_extent_limit, &state->vk.scene, state->vk.descriptor_set_for_pipeline_1); + state->sane_image_extent_limit, &state->vk.scene, state->vk.descriptor_set_for_pipeline_1, + &state->vk.lucy_renderer); + + check(vkQueueSubmit(state->vk.queues.graphics_queue, 1, &(VkSubmitInfo){ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = (VkCommandBuffer[]){ state->vk.transfer_command_buf }, + .signalSemaphoreCount = 1, + .pSignalSemaphores = (VkSemaphore[]){ state->vk.jane.in_frame_transfer_complete }, + }, NULL) == VK_SUCCESS); check(vkQueueSubmit(state->vk.queues.graphics_queue, 1, &(VkSubmitInfo){ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, @@ -1306,6 +1321,10 @@ static void main_h_wl_keyboard_key( } else if (keysym == XKB_KEY_3) { state->vk.scene.hdr_factor *= 1.05f; printf("hdr factor increased to %f\n", state->vk.scene.hdr_factor); + } else if (keysym == XKB_KEY_7) { + VecU8_append(&state->vk.scene.text_on_screen, '7'); + } else if (keysym == XKB_KEY_Return) { + VecU8_append(&state->vk.scene.text_on_screen, '\n'); } } } @@ -1484,6 +1503,7 @@ int main() { compile_shader_dir(cstr("0b"), false); compile_shader_dir(cstr("1"), false); + SpanU8 root_dir = cstr("../../../.."); SpanU8 GPU = cstr("nvidia"); SpanU8 bugged_GPU = cstr("nothere"); bool ENABLE_VALIDATION_LAYERS = true; @@ -1541,11 +1561,6 @@ int main() { vk->physical_device = margaret_select_one_physical_device( instance, vk->surface, GPU, bugged_GPU, state.sane_image_extent_limit); - { /* Funny vibe check */ - VecU8 txt = margaret_stringify_device_memory_properties_2(vk->physical_device); - SpanU8_print(VecU8_to_span(&txt)); - } - // print_physical_device_available_extensions(physical_device); ResultMargaretChosenQueueFamiliesOrSpanU8 queue_fam_res = margaret_choose_good_queue_families( @@ -1743,6 +1758,32 @@ int main() { vk->zbuffer_image = MargaretImgAllocator_alloc(&vk->dev_local_images, MAX_WIN_WIDTH, MAX_WIN_HEIGHT, vk->zbuffer_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + MargaretEngineReference engine_reference = { + .device = vk->device, .physical_device = vk->physical_device, .transfer_cmd_buffer = vk->transfer_command_buf, + .dev_local_images = &vk->dev_local_images, .dev_local_buffers = &vk->dev_local_buffers, + .staging_buffers = &vk->staging_buffers, .descriptor_pool = vk->descriptor_pool, + .linear_sampler = vk->linear_sampler, .nearest_sampler = vk->nearest_sampler + }; + + FT_Error ft_init_err = FT_Init_FreeType(&vk->ft_library); + if (ft_init_err) + abortf("Can't init free type library\n"); + + vk->lucy_cache = LucyGlyphCache_new(engine_reference); + vk->font_face = LucyFace_new(vk->ft_library, &vk->lucy_cache, vcstr("fonts/DMSerifText-Regular.ttf")); + vk->font_face_of_size_40 = LucyFace_of_size(&vk->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 = vk->font_face_of_size_40, .codepoint_ranges = ranges_needed, + }); + LucyGlyphCache_add_glyphs(lucy_requests); + } + VecU8_append_span(&vk->scene.text_on_screen, cstr("I am late :(\nExam is in 25 hours...")); + vk->lucy_renderer = LucyRenderer_new(engine_reference, &vk->lucy_cache, root_dir, vk->render_pass_1, 0); + { GenericModelOnSceneMem *model_g = VecGenericModelOnSceneMem_mat(&vk->scene.generic_models, 0); assert(model_g->instance_attr.cap >= 100); diff --git a/src/l2/tests/r0/r0_scene.h b/src/l2/tests/r0/r0_scene.h index 597f6ae..95c4e56 100644 --- a/src/l2/tests/r0/r0_scene.h +++ b/src/l2/tests/r0/r0_scene.h @@ -4,13 +4,16 @@ #include "r0_assets.h" #include "../../margaret/vulkan_utils.h" +#include "../../lucy/glyph_render.h" typedef struct { U64 count; MargaretSubbuf staging_busy; MargaretSubbuf staging_updatable; MargaretSubbuf device_local; - U64 cap; /* All 3 buffers are synced to the same capacity */ + U64 cap; + // todo: delete this crap. This crap turned out to be completely useless. It is another one of my very very dumb ideas + // todo: remove updatable buffer, fill staging buffer in main thread } PatriciaBuf; void PatriciaBuf_swap_staging(PatriciaBuf* self){ @@ -164,8 +167,9 @@ typedef struct { CamControlInfo cam; VecObjectInfo smeshnyavka_1; - VecObjectInfo smeshnyavka_2; VecObjectInfo smeshnyavka_3; + + VecU8 text_on_screen; } Scene; ShinyMeshInstanceInc ShinyMeshInstanceInc_from_ObjectInfo(const ObjectInfo* oi){ @@ -186,7 +190,7 @@ void Scene_add_smeshnyavka_3(Scene* self, ObjectInfo oi){ ShinyModelOnSceneMem_set(model_sh, ni, ShinyMeshInstanceInc_from_ObjectInfo(&oi)); } -// todo: remove this shit +// todo: remove this shit (and rewrite everything in haskell) void Scene_update_smeshnyavka_3(Scene* self, size_t sh_id){ assert(sh_id < self->smeshnyavka_3.len); const ObjectInfo* oi = VecObjectInfo_at(&self->smeshnyavka_3, sh_id); @@ -227,8 +231,9 @@ Scene Scene_new(VecGenericModelOnSceneMem generic_models, VecShinyModelOnSceneMe .color = {.float32 = {0, 0, 0, 1}}, .gamma_correction_factor = 2.2f, .hdr_factor = 1, .lsd_factor = 0, .anim_time = 0, .pipeline0_ubo = pipeline0_ubo, .cam = CamControlInfo_new(), - .smeshnyavka_1 = VecObjectInfo_new(), .smeshnyavka_2 = VecObjectInfo_new(), + .smeshnyavka_1 = VecObjectInfo_new(), .smeshnyavka_3 = VecObjectInfo_new(), // todo: remove this shit and rewrite everything in haskell + .text_on_screen = VecU8_new(), }; } diff --git a/src/l_adele/lucy/lucy.frag b/src/l_adele/lucy/lucy.frag index 972aac8..24272f9 100644 --- a/src/l_adele/lucy/lucy.frag +++ b/src/l_adele/lucy/lucy.frag @@ -12,5 +12,5 @@ layout (binding=0) uniform sampler2D images[]; void main(){ float I = texture(images[nonuniformEXT(tex_ind)], tex_cord).r; - fin_color = color * vec4(I, I, I, I); + fin_color = vec4(color.rgb, color.a * I); }