Little refactoring of alice: Added Abigail urility (transfer_in_mainloop.h). Buffers/textures that get copied to device local memory and then never modified occured many times in alice and lucy and was generally worth moving into a separate class.

This commit is contained in:
Андреев Григорий 2026-02-12 20:58:23 +03:00
parent 71f73964cf
commit 5b8ecd8020
4 changed files with 76 additions and 74 deletions

View File

@ -829,29 +829,29 @@ ListNodeAliceGenericMeshHand* Alice_add_generic_mesh(Alice* alice, const Generic
VecU8_drop(t_paths.normal_texture_path);
VecU8_drop(t_paths.specular_texture_path);
mm->vbo = AliceBufferUplOnce_new(topology->vertices.len * sizeof(GenericMeshVertex),
mm->vbo = AliceAllSingleUploadBuffers_register_new(&alice->single_upload_buffers,
topology->vertices.len * sizeof(GenericMeshVertex),
&alice->staging_buffers, &alice->dev_local_buffers);
mm->ebo = AliceBufferUplOnce_new(topology->indexes.len * sizeof(U32),
mm->ebo = AliceAllSingleUploadBuffers_register_new(&alice->single_upload_buffers,
topology->indexes.len * sizeof(U32),
&alice->staging_buffers, &alice->dev_local_buffers);
mm->diffuse_texture = AliceTextureUplOnce_new(mm->pixels_diffuse.width, mm->pixels_diffuse.height,
VK_FORMAT_R8G8B8A8_SRGB, sizeof(cvec4),
mm->diffuse_texture = AliceAllSingleUploadTextures_register_new(&alice->single_upload_textures,
mm->pixels_diffuse.width, mm->pixels_diffuse.height,
sizeof(cvec4), VK_FORMAT_R8G8B8A8_SRGB,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
&alice->staging_buffers, &alice->dev_local_images);
mm->normal_texture = AliceTextureUplOnce_new(mm->pixels_normal.width, mm->pixels_normal.height,
VK_FORMAT_R8G8B8A8_SRGB, sizeof(cvec4),
mm->normal_texture = AliceAllSingleUploadTextures_register_new(&alice->single_upload_textures,
mm->pixels_normal.width, mm->pixels_normal.height,
sizeof(cvec4), VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
&alice->staging_buffers, &alice->dev_local_images);
mm->specular_texture = AliceTextureUplOnce_new(mm->pixels_specular.width, mm->pixels_specular.height,
VK_FORMAT_R8_SRGB, sizeof(U8),
mm->specular_texture = AliceAllSingleUploadTextures_register_new(&alice->single_upload_textures,
mm->pixels_specular.width, mm->pixels_specular.height,
sizeof(U8), VK_FORMAT_R8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
&alice->staging_buffers, &alice->dev_local_images);
AliceAllSingleUploadBuffers_register(&alice->single_upload_buffers, mm->vbo);
AliceAllSingleUploadBuffers_register(&alice->single_upload_buffers, mm->ebo);
AliceAllSingleUploadTextures_register(&alice->single_upload_textures, mm->diffuse_texture);
AliceAllSingleUploadTextures_register(&alice->single_upload_textures, mm->normal_texture);
AliceAllSingleUploadTextures_register(&alice->single_upload_textures, mm->specular_texture);
mm->scheduled_for_deletion = false;
/* We allocated enough memory, but now it's time to actually fill staging buffers */
@ -979,15 +979,13 @@ ListNodeAliceShinyMeshHand* Alice_add_shiny_mesh(Alice* alice, const ShinyMeshTo
mm->instance_attr.staging = MargaretBufAllocator_alloc(&alice->staging_buffers, 128);
mm->instance_attr.device_local = MargaretBufAllocator_alloc(&alice->dev_local_buffers, 128);
mm->vbo = AliceBufferUplOnce_new(topology->vertices.len * sizeof(ShinyMeshVertex),
mm->vbo = AliceAllSingleUploadBuffers_register_new(&alice->single_upload_buffers,
topology->vertices.len * sizeof(ShinyMeshVertex),
&alice->staging_buffers, &alice->dev_local_buffers);
mm->ebo = AliceBufferUplOnce_new(topology->indexes.len * sizeof(U32),
mm->ebo = AliceAllSingleUploadBuffers_register_new(&alice->single_upload_buffers,
topology->indexes.len * sizeof(U32),
&alice->staging_buffers, &alice->dev_local_buffers);
ListAliceShinyMeshHand_insert_node(&alice->shiny_models.hands, mm_node);
AliceAllSingleUploadBuffers_register(&alice->single_upload_buffers, mm->vbo);
AliceAllSingleUploadBuffers_register(&alice->single_upload_buffers, mm->ebo);
/* We allocated enough memory, now it's time to actually fill staging buffers */
/* And we start by filling staging VBO */
assert(mm->vbo->staging.len >= topology->vertices.len * sizeof(ShinyMeshVertex));
@ -1014,6 +1012,8 @@ ListNodeAliceShinyMeshHand* Alice_add_shiny_mesh(Alice* alice, const ShinyMeshTo
assert(mm->ebo->staging.len >= ebo_len);
U32* staging_ebo = (U32*)AliceBufferUplOnce_get_mapped_staging(mm->ebo);
memcpy(staging_ebo, topology->indexes.buf, ebo_len);
ListAliceShinyMeshHand_insert_node(&alice->shiny_models.hands, mm_node);
return mm_node;
}
@ -1921,7 +1921,7 @@ Alice* Alice_new(){
if (ft_init_err)
abortf("Can't init free type library\n");
alice->lucy_cache = LucyGlyphCache_new(engine_reference);
alice->lucy_cache = LucyGlyphCache_new(engine_reference, &alice->single_upload_textures);
alice->lucy_renderer = LucyRenderer_new(engine_reference, &alice->lucy_cache, root_dir, alice->render_pass_1, 0);

View File

@ -1,5 +1,10 @@
#pragma once
/* I sometimes call this sub-namespace of Alice namespace Abigail. Though I hadn't renamed it to Abigail yet.
* Abigail does not depend on Alice Engine. It only depends on margaret. So systems like Lucy can use it
* without creating any sus circular dependepncies.
*/
#include "../margaret/vulkan_utils.h"
/* Handler of a read-only buffer that can be uploaded and deleted at any time with it's staging buffer automatically
@ -146,6 +151,14 @@ void AliceAllSingleUploadBuffers_register(AliceAllSingleUploadBuffers* self, Ali
VecRefAliceBufferUplOnce_append(&self->to_be_copied_to_device_next_cycle, obj);
}
AliceBufferUplOnce* AliceAllSingleUploadBuffers_register_new(AliceAllSingleUploadBuffers* self, size_t len,
MargaretBufAllocator* staging_buffers, MargaretBufAllocator* dev_local_buffers
) {
AliceBufferUplOnce* res = AliceBufferUplOnce_new(len, staging_buffers, dev_local_buffers);
AliceAllSingleUploadBuffers_register(self, res);
return res;
}
void AliceAllSingleUploadBuffers_delete(AliceAllSingleUploadBuffers* self, AliceBufferUplOnce* obj) {
assert(obj);
obj->scheduled_for_deletion = true;
@ -179,6 +192,16 @@ void AliceAllSingleUploadTextures_register(AliceAllSingleUploadTextures* self, A
VecRefAliceTextureUplOnce_append(&self->to_be_copied_to_device_next_cycle, obj);
}
AliceTextureUplOnce* AliceAllSingleUploadTextures_register_new(AliceAllSingleUploadTextures* self,
U64 width, U64 height, U64 pixel_sz, VkFormat format,
VkImageUsageFlags usage, MargaretBufAllocator* staging_buffers, MargaretImgAllocator* dev_local_images
) {
AliceTextureUplOnce* res = AliceTextureUplOnce_new(width, height, pixel_sz, format, usage,
staging_buffers, dev_local_images);
AliceAllSingleUploadTextures_register(self, res);
return res;
}
void AliceAllSingleUploadTextures_delete(AliceAllSingleUploadTextures* self, AliceTextureUplOnce* obj) {
assert(obj);
obj->scheduled_for_deletion = true;

View File

@ -1,28 +1,23 @@
#pragma once
#include "../margaret/vulkan_utils.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include "../../../gen/l1/VecAndSpan_U32Segment.h"
#include "../../../gen/l1/vulkan/VecVkDescriptorImageInfo.h"
#include "../../../gen/l1/pixel_masses.h"
#include "../../../gen/l1/VecAndSpan_U32.h"
#include "../../l1_5/core/buff_rb_tree_node.h"
#include "../../l1_5/core/rb_tree_node.h"
#include "../alice/transfer_in_mainloop.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#define LUCY_MAX_DESCRIPTOR_COUNT 100
typedef struct {
/* This value is actually Option<MargaretSubbuf>. 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;
AliceTextureUplOnce* tex;
U64 usage;
/* 0 if this image isn't scheduled for deletion on th next cycle.
* 1 if it is */
int scheduled_for_deletion;
/* Is this image scheduled for deletion on th next cycle. */
bool scheduled_for_deletion;
} LucyImage;
#include "../../../gen/l1/eve/lucy/OptionLucyImage.h"
@ -69,19 +64,19 @@ struct LucyFace {
struct LucyGlyphCache {
MargaretEngineReference ve;
AliceAllSingleUploadTextures* single_upload_textures;
VecOptionLucyImage image_slots;
VkDescriptorSetLayout descriptor_set_layout;
VkDescriptorSet descriptor_set;
/* to_be_freed_of_old_staging_next_cycle never intersect with to_be_copied_to_device_next_cycle */
VecU32 to_be_freed_of_old_staging_next_cycle;
VecU32 to_be_copied_to_device_next_cycle;
/* deletion will be performed last */
/* 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;
};
LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve){
LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve, AliceAllSingleUploadTextures* single_upload_textures){
VkDescriptorSetLayout my_desc_set_layout;
VkDescriptorSetLayoutBindingFlagsCreateInfo set_layout_crinfo_flags = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
@ -106,10 +101,9 @@ LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve){
image_slots.buf[i].variant = Option_None;
}
return (LucyGlyphCache){
.ve = ve, .image_slots = image_slots,
.ve = ve, .single_upload_textures = single_upload_textures, .image_slots = image_slots,
.descriptor_set_layout = my_desc_set_layout, .descriptor_set = descriptor_set,
.to_be_freed_of_old_staging_next_cycle = VecU32_new(),
.to_be_copied_to_device_next_cycle = VecU32_new(),
.to_be_written_to_descriptor_set = VecU32_new(),
.to_be_deleted = VecU32_new()};
}
@ -124,7 +118,9 @@ void LucyFaceFixedSize_get_rid_of_myself(LucyFaceFixedSize* self){
assert(img->usage > 0);
if (--img->usage) {
assert(!img->scheduled_for_deletion);
img->scheduled_for_deletion = 1;
img->scheduled_for_deletion = true;
AliceAllSingleUploadTextures_delete(cache->single_upload_textures, img->tex);
img->tex = NULL;
VecU32_append(&cache->to_be_deleted, slot_id);
}
}
@ -190,12 +186,12 @@ void LucyGlyphCache_add_glyphs__close_img(
assert(!img->scheduled_for_deletion);
img_width = MAX_U32(img_width, 10); // Just a precaution. empty buffers aren't supported by Margaret
img_height = MAX_U32(img_height, 10);
VecU32_append(&cache->to_be_copied_to_device_next_cycle, img_slot_id);
img->staging_buffer = MargaretBufAllocator_alloc(cache->ve.staging_buffers, img_width * img_height * 1);
img->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->img_view = margaret_create_view_for_image(cache->ve.device, img->img.a.image,
VK_FORMAT_R8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
img->tex = AliceAllSingleUploadTextures_register_new(cache->single_upload_textures,
img_width, img_height, sizeof(U8), VK_FORMAT_R8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
cache->ve.staging_buffers, cache->ve.dev_local_images);
VecU32_append(&cache->to_be_written_to_descriptor_set, img_slot_id);
}
void LucyGlyphCache_add_glyphs(VecLucyGlyphCachingRequest requests_for_faces){
@ -267,7 +263,6 @@ void LucyGlyphCache_add_glyphs(VecLucyGlyphCachingRequest requests_for_faces){
LucyPositionedStagingGlyph* p_glyph;
one_more_chance:
{}
int s = 23123;
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;
@ -317,8 +312,8 @@ void LucyGlyphCache_add_glyphs(VecLucyGlyphCachingRequest requests_for_faces){
for (size_t j = 0; j < ready.len; j++) {
LucyPositionedStagingGlyph* p_glyph = &ready.buf[j];
LucyImage* image = &VecOptionLucyImage_mat(&cache->image_slots, p_glyph->img_slot_id)->some;
U64 staging_width = image->img.width;
U8* staging = (U8*)MargaretSubbuf_get_mapped(&image->staging_buffer);
U64 staging_width = image->tex->img.width;
U8* staging = (U8*)AliceTextureUplOnce_get_mapped_staging(image->tex);
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++) {
@ -337,31 +332,18 @@ 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_freed_of_old_staging_next_cycle);
VecU32_drop(self.to_be_copied_to_device_next_cycle);
VecU32_drop(self.to_be_written_to_descriptor_set);
VecU32_drop(self.to_be_deleted);
}
void LucyGlyphCache_another_frame(LucyGlyphCache* self){
for (size_t i = 0; i < self->to_be_freed_of_old_staging_next_cycle.len; i++) {
U32 slot_id = self->to_be_freed_of_old_staging_next_cycle.buf[i];
LucyImage* img = &self->image_slots.buf[slot_id].some;
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++) {
U32 slot_id = self->to_be_copied_to_device_next_cycle.buf[i];
for (size_t i = 0; i < self->to_be_written_to_descriptor_set.len; i++) {
U32 slot_id = self->to_be_written_to_descriptor_set.buf[i];
OptionLucyImage* img_slot = &self->image_slots.buf[slot_id];
assert(img_slot->variant == Option_Some);
LucyImage* img = &img_slot->some;
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);
VecU32_append(&self->to_be_freed_of_old_staging_next_cycle, slot_id);
vkUpdateDescriptorSets(self->ve.device, 1, &(VkWriteDescriptorSet){
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@ -369,7 +351,7 @@ void LucyGlyphCache_another_frame(LucyGlyphCache* self){
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &(VkDescriptorImageInfo){
.sampler = self->ve.nearest_sampler, .imageView = img->img_view,
.sampler = self->ve.nearest_sampler, .imageView = img->tex->view,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
}
}, 0, NULL);
@ -377,19 +359,16 @@ void LucyGlyphCache_another_frame(LucyGlyphCache* self){
/* 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++) {
U32 slot_id = self->to_be_copied_to_device_next_cycle.buf[i];
U32 slot_id = self->to_be_deleted.buf[i];
OptionLucyImage* img_slot = &self->image_slots.buf[slot_id];
assert(img_slot->variant == Option_Some);
LucyImage* img = &img_slot->some;
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);
assert(img->tex == NULL);
img_slot->variant = Option_None;
}
self->to_be_freed_of_old_staging_next_cycle.len = 0;
self->to_be_copied_to_device_next_cycle.len = 0;
self->to_be_written_to_descriptor_set.len = 0;
self->to_be_deleted.len = 0;
}

View File

@ -97,8 +97,8 @@ void LucyRenderer_draw_char_glyph(LucyRenderer* self, vec4 color, ivec2 pos, Luc
OptionLucyImage* img_slot = VecOptionLucyImage_mat(&self->cache->image_slots, glyph->img_slot_id);
assert(img_slot->variant == Option_Some);
LucyImage* img = &img_slot->some;
float atlas_w = (float)img->img.width;
float atlas_h = (float)img->img.height;
float atlas_w = (float)img->tex->img.width;
float atlas_h = (float)img->tex->img.height;
ivec2 positioned = ivec2_add_ivec2(pos, glyph->bearing);
U64 needed_vbo_length = (self->glyphs_count + 1) * sizeof(LucyRenderInstance);