From 66cb556fb6f87d195aacf8a25ffafb86d524da19 Mon Sep 17 00:00:00 2001 From: Frederico Linhares Date: Thu, 20 Apr 2023 16:17:49 -0300 Subject: feat Create text rendering system --- src/core.cpp | 18 +++- src/core.hpp | 7 +- src/font.cpp | 68 ++++++++++++ src/font.hpp | 28 +++++ src/texture.cpp | 67 +++++------- src/vk/character.cpp | 265 +++++++++++++++++++++++++++++++++++++++++++++ src/vk/character.hpp | 49 +++++++++ src/vk/font.cpp | 53 +++++++++ src/vk/font.hpp | 42 ++++++++ src/vk/image.cpp | 29 ++++- src/vk/image.hpp | 14 ++- src/vk/texture.cpp | 299 ++++++++++++++++++++++++++++++++++++++++----------- src/vk/texture.hpp | 4 +- 13 files changed, 834 insertions(+), 109 deletions(-) create mode 100644 src/font.cpp create mode 100644 src/font.hpp create mode 100644 src/vk/character.cpp create mode 100644 src/vk/character.hpp create mode 100644 src/vk/font.cpp create mode 100644 src/vk/font.hpp (limited to 'src') diff --git a/src/core.cpp b/src/core.cpp index c4e03a4..d87bbe9 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #include "core.hpp" #include "candy_gear.hpp" +#include "font.hpp" #include "graphic.hpp" #include "key.hpp" #include "mesh.hpp" @@ -232,6 +233,19 @@ unload_window(void *obj) SDL_DestroyWindow(cg_core.window); } +void +load_font_library(void *obj) +{ + FT_Error error{FT_Init_FreeType(&cg_core.font_library)}; + if(error) throw CommandError{"Failed to open the FreeType library."}; +} + +void +unload_font_library(void *obj) +{ + FT_Done_FreeType(cg_core.font_library); +} + void load_vk_instance(void *obj) { @@ -651,6 +665,7 @@ load_mruby_interface(void *obj) mrb_value main_obj = mrb_obj_iv_inspect(cg_core.mrb, cg_core.mrb->top_self); cg_candy_gear_init(cg_core.mrb); + cg_font_init(cg_core.mrb); cg_key_init(cg_core.mrb); cg_mesh_init(cg_core.mrb); cg_model_init(cg_core.mrb); @@ -681,6 +696,7 @@ const CommandChain cg_sCore::loader{ {&load_sdl_mixer, &unload_sdl_mixer}, {&load_sdl_open_audio, &unload_sdl_open_audio}, {&load_window, &unload_window}, + {&load_font_library, &unload_font_library}, {&load_vk_instance, &unload_vk_instance}, {&load_window_surface, &unload_window_surface}, #ifdef DEBUG diff --git a/src/core.hpp b/src/core.hpp index 660af27..ad5d136 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,9 @@ #include #include +#include +#include FT_FREETYPE_H + #include "command.hpp" #include "job_queue.hpp" #include "log.hpp" @@ -98,6 +101,8 @@ struct cg_sCore SDL_Window *window; + FT_Library font_library; + VkSurfaceKHR window_surface; VkInstance vk_instance; diff --git a/src/font.cpp b/src/font.cpp new file mode 100644 index 0000000..21559e1 --- /dev/null +++ b/src/font.cpp @@ -0,0 +1,68 @@ +/* + * Copyright 2022-2023 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font.hpp" + +#include "vk/font.hpp" + +void +cg_free_font(mrb_state *mrb, void *obj) +{ + auto ptr = static_cast(obj); + + ptr->~Font(); + mrb_free(mrb, ptr); +} + +const struct mrb_data_type +cg_font_type = {"CG_Font", cg_free_font}; + +static mrb_value +cg_cFont_initialize(mrb_state *mrb, mrb_value self) +{ + VK::Font *ptr; + const char *font_path; + mrb_int font_size; + + mrb_get_args(mrb, "zi", &font_path, &font_size); + ptr = (VK::Font*)DATA_PTR(self); + if(ptr) mrb_free(mrb, ptr); + ptr = (VK::Font*)mrb_malloc(mrb, sizeof(VK::Font)); + + try + { + new(ptr)VK::Font(font_path, font_size); + } + catch(const std::invalid_argument &e) + { + mrb_raise(mrb, E_RUNTIME_ERROR, e.what()); + } + + mrb_data_init(self, ptr, &cg_font_type); + return self; +} + +void +cg_font_init(mrb_state *mrb) +{ + struct RClass *cg_m, *cg_cFont; + + cg_m = mrb_module_get(mrb, "CandyGear"); + cg_cFont = mrb_define_class_under(mrb, cg_m, "Font", mrb->object_class); + MRB_SET_INSTANCE_TT(cg_cFont, MRB_TT_DATA); + mrb_define_method( + mrb, cg_cFont, "initialize", cg_cFont_initialize, MRB_ARGS_REQ(2)); +} diff --git a/src/font.hpp b/src/font.hpp new file mode 100644 index 0000000..0128ea0 --- /dev/null +++ b/src/font.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2022-2023 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_FONT_H +#define CANDY_GEAR_FONT_H 1 + +#include "core.hpp" + +extern const struct mrb_data_type +cg_font_type; + +void +cg_font_init(mrb_state *mrb); + +#endif /* CANDY_GEAR_FONT_H */ diff --git a/src/texture.cpp b/src/texture.cpp index ce1d479..7ef0852 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #include "texture.hpp" #include "core.hpp" +#include "font.hpp" #include "vk/texture.hpp" void @@ -61,43 +62,27 @@ cg_cTexture_from_image(mrb_state *mrb, mrb_value self) return texture; } -// mrb_value -// cg_cText_from_text(mrb_state *mrb, mrb_value self) -// { -// char *text; -// SDL_Surface *surface = nullptr; -// mrb_bool background; -// struct mrb_value texture = texture_alloc(mrb, self); -// struct cg_color *tx_color_ptr, *bg_color_ptr; -// struct cg_font *font_ptr; -// struct cg_texture *ptr; - -// mrb_get_args( -// mrb, "zdd|d?", &text, &font_ptr, &cg_font_type, -// &tx_color_ptr, &cg_color_type, &bg_color_ptr, &cg_color_type, -// &background); - -// ptr = (struct cg_texture *)DATA_PTR(texture); -// if(ptr) mrb_free(mrb, ptr); -// ptr = (struct cg_texture *)mrb_malloc(mrb, sizeof(struct cg_texture)); - -// // Create surface. -// if(background) -// surface = TTF_RenderUTF8_Shaded( -// font_ptr->data, text, tx_color_ptr->data, bg_color_ptr->data); -// else -// surface = TTF_RenderUTF8_Solid(font_ptr->data, text, tx_color_ptr->data); -// if(surface == nullptr) mrb_raise(mrb, E_RUNTIME_ERROR, TTF_GetError()); - -// // Convert the surface to a texture. -// ptr->data = SDL_CreateTextureFromSurface(cg_core.renderer, surface); -// SDL_FreeSurface(surface); -// if(ptr->data == nullptr) mrb_raise(mrb, E_RUNTIME_ERROR, SDL_GetError()); - -// SDL_QueryTexture(ptr->data, nullptr, nullptr, &ptr->width, &ptr->height); -// mrb_data_init(texture, ptr, &cg_texture_type); -// return texture; -// } +mrb_value +cg_cTexture_from_text(mrb_state *mrb, mrb_value self) +{ + const char *text; + unsigned int width, height; + struct mrb_value texture = texture_alloc(mrb, self); + VK::Font *font_ptr; + std::shared_ptr *ptr; + + mrb_get_args(mrb, "dz", &font_ptr, &cg_font_type, &text); + + ptr = (std::shared_ptr*)DATA_PTR(texture); + if(ptr) mrb_free(mrb, ptr); + ptr = (std::shared_ptr*)mrb_malloc( + mrb, sizeof(std::shared_ptr)); + new(ptr)std::shared_ptr( + std::make_shared(font_ptr, text)); + + mrb_data_init(texture, ptr, &cg_texture_type); + return texture; +} static mrb_value cg_cTexture_width(mrb_state *mrb, mrb_value self) @@ -127,9 +112,9 @@ cg_texture_init(mrb_state *mrb) mrb_define_class_method( mrb, cg_cTexture, "from_image", cg_cTexture_from_image, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); - // mrb_define_class_method( - // mrb, cg_cTexture, "from_text", cg_cText_from_text, - // MRB_ARGS_REQ(3)|MRB_ARGS_OPT(1)); + mrb_define_class_method( + mrb, cg_cTexture, "from_text", cg_cTexture_from_text, + MRB_ARGS_REQ(3)|MRB_ARGS_OPT(2)); mrb_define_method( mrb, cg_cTexture, "width", cg_cTexture_width, MRB_ARGS_NONE()); mrb_define_method( diff --git a/src/vk/character.cpp b/src/vk/character.cpp new file mode 100644 index 0000000..04ddb0c --- /dev/null +++ b/src/vk/character.cpp @@ -0,0 +1,265 @@ +/* + * Copyright 2022-2023 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "character.hpp" + +#include "../command.hpp" +#include "../core.hpp" +#include "font.hpp" +#include "image.hpp" +#include "source_buffer.hpp" + +namespace +{ + +struct CharacterBuilder +{ + VK::Character *character; + FT_Face face; + uint32_t character_code; + + CharacterBuilder( + VK::Character *character, FT_Face face, uint32_t character_code); +}; + +CharacterBuilder::CharacterBuilder( + VK::Character *character, FT_Face face, uint32_t character_code): + character{character}, + face{face}, + character_code{character_code} +{ +} + +// TODO: creating one image with one device memory for each character is +// ineficient +void +load_image(void *obj) +{ + auto self = static_cast(obj); + + FT_Error error; + std::vector source_image_raw; + const int num_channels = 4; // all images are converted to RGBA + auto glyph_index{FT_Get_Char_Index(self->face, self->character_code)}; + + error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT); + if(error) throw CommandError{"failed to load glyph"}; + + error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL); + if(error) throw CommandError{"failed to render glyph"}; + + self->character->width = self->face->glyph->bitmap.width; + self->character->height = self->face->glyph->bitmap.rows; + self->character->mip_levels = 1; + self->character->bearing_x = self->face->glyph->bitmap_left; + self->character->bearing_y = self->face->glyph->bitmap_top; + self->character->advance = self->face->glyph->bitmap.width; // self->face->glyph->advance.x; + + auto image_size{static_cast( + self->face->glyph->bitmap.width * + self->face->glyph->bitmap.rows * num_channels)}; + + { // Create the data for the image buffer + source_image_raw.resize(image_size); + + for(auto y{0}; y < self->face->glyph->bitmap.width; y++) + { + for(auto x{0}; x < self->face->glyph->bitmap.rows; x++) + { + auto image_coord = y * self->face->glyph->bitmap.rows * num_channels + + x * num_channels; + auto glyph_coord = y * self->face->glyph->bitmap.rows + x; + // Red + source_image_raw[image_coord] = 0; + // Green + source_image_raw[image_coord + 1] = 0; + // Blue + source_image_raw[image_coord + 2] = 0; + // Alpha + source_image_raw[image_coord + 3] = + self->face->glyph->bitmap.buffer[glyph_coord]; + } + } + } + + VK::SourceBuffer source_image_buffer{ + cg_core.vk_device_with_swapchain, source_image_raw.data(), + image_size}; + + { // Create Vulkan image. + try + { + VkExtent3D vk_extent3d; + vk_extent3d.width = self->face->glyph->bitmap.width; + vk_extent3d.height = self->face->glyph->bitmap.rows; + vk_extent3d.depth = 1; + + VK::Image::create( + cg_core.vk_device_with_swapchain, + &self->character->image, + &self->character->device_memory, + VK_FORMAT_R8G8B8A8_UNORM, + vk_extent3d, + self->character->mip_levels, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); + } + catch(VK::Image::Error error) + { + throw CommandError{error.what()}; + } + } + + { // Copy image from buffer into image. + auto queue_family{cg_core.vk_device_with_swapchain-> + get_queue_family_with_presentation()}; + auto queue{queue_family->get_queue()}; + VK::CommandPool command_pool{queue_family, 1}; + VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; + + queue.submit_one_time_command(vk_command_buffer, [&](){ + VK::Image::move_image_state( + vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy image_copy; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset = {0, 0, 0}; + image_copy.imageExtent = { + self->character->width, self->character->height, 1}; + + vkCmdCopyBufferToImage( + vk_command_buffer, source_image_buffer.buffer, self->character->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + VK::Image::move_image_state( + vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + }); + } +} + +void +unload_image(void *obj) +{ + auto self = static_cast(obj); + + vkDestroyImage( + cg_core.vk_device_with_swapchain->device, self->character->image, nullptr); + vkFreeMemory( + cg_core.vk_device_with_swapchain->device, self->character->device_memory, + nullptr); +} + +const CommandChain loader{ + {&load_image, &unload_image} +}; + +} + +namespace VK +{ + +Character::Character(FT_Face face, uint32_t character_code) +{ + CharacterBuilder character_builder(this, face, character_code); + loader.execute(&character_builder); +} + +Character::~Character() +{ + CharacterBuilder character_builder(this, nullptr, 0); + loader.revert(&character_builder); +} + +std::vector +Character::str_to_unicode(const char* str) +{ + std::vector unicode_text; + int text_width{0}; + int text_height{0}; + + { // Reserve memory + int size{0}; + for(auto i{0}; str[i] != '\0'; i++) + if((str[i] & 0b11000000) != 0b10000000) size++; + unicode_text.reserve(size); + } + + for(auto i{0}; str[i] != '\0'; i++) + { + int num_bytes; + uint32_t codepoint{0}; + + if(str[i] >= 0 && str[i] < 127) + { // Normal ASCI character, 1-byte. + num_bytes = 1; + codepoint = str[i]; + } + else if((str[i] & 0xE0) == 0xC0) + { // 2-byte character. + num_bytes = 2; + codepoint += ((str[i] & 0b00011111) << 6); + } + else if((str[i] & 0xF0) == 0xE0) + { // 3-byte character. + num_bytes = 3; + codepoint += ((str[i] & 0b00001111) << 12); + } + else if((str[i] & 0xF8) == 0xF0) + { // 4-byte character. + num_bytes = 4; + codepoint += ((str[i] & 0b00000111) << 18); + } + else + { // FIXME: Add support to 5-byte and 6-byte characters. + } + + switch (num_bytes) + { + case 4: + i++; + codepoint += ((str[i] & 0b00111111) << 12); + case 3: + i++; + codepoint += ((str[i] & 0b00111111) << 6); + case 2: + i++; + codepoint += (str[i] & 0b00111111); + case 1: + default: + break; + } + + unicode_text.push_back(codepoint); + } + + return unicode_text; +} + +} diff --git a/src/vk/character.hpp b/src/vk/character.hpp new file mode 100644 index 0000000..c1dfc79 --- /dev/null +++ b/src/vk/character.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2022-2023 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_CHARACTER_H +#define CANDY_GEAR_VK_CHARACTER_H 1 + +#include +#include FT_FREETYPE_H + +#include "core.hpp" + +#include + +namespace VK +{ + +struct Character +{ + VkImage image; + VkDeviceMemory device_memory; + uint32_t width, height, bearing_x, bearing_y, advance; + uint32_t mip_levels; + + Character(FT_Face face, uint32_t character_code); + ~Character(); + + Character(Character const&) = delete; + Character& operator=(Character const&) = delete; + + static std::vector + str_to_unicode(const char* str); +}; + +} + +#endif /* CANDY_GEAR_VK_CHARACTER_H */ diff --git a/src/vk/font.cpp b/src/vk/font.cpp new file mode 100644 index 0000000..cb01a51 --- /dev/null +++ b/src/vk/font.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2022-2023 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font.hpp" + +#include "../core.hpp" + +namespace VK +{ + +Font::Font(const char* font_path, int font_size) +{ + FT_Error error; + error = FT_New_Face(cg_core.font_library, font_path, 0, &this->face); + if(error == FT_Err_Unknown_File_Format) throw std::invalid_argument( + "The font file could be opened and read, but it appears that its font " + "format is unsupported."); + else if(error) throw std::invalid_argument( + "The font file could not be opened or read, or it is broken."); + + error = FT_Set_Pixel_Sizes(this->face, 0, font_size); + if(error) throw std::invalid_argument("Failed to load font size."); +} + +Font::~Font() +{ + FT_Done_Face(this->face); +} + +std::shared_ptr +Font::character(uint32_t character_code) +{ + if(!this->characters.contains(character_code)) + this->characters.emplace( + character_code, std::make_shared(this->face, character_code)); + + return this->characters.at(character_code); +} + +} diff --git a/src/vk/font.hpp b/src/vk/font.hpp new file mode 100644 index 0000000..b654183 --- /dev/null +++ b/src/vk/font.hpp @@ -0,0 +1,42 @@ +/* + * Copyright 2022-2023 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_VK_FONT_H +#define CANDY_GEAR_VK_FONT_H 1 + +#include +#include + +#include "character.hpp" + +namespace VK +{ + +struct Font +{ + FT_Face face; + std::unordered_map> characters; + + Font(const char* font_path, int font_size); + ~Font(); + + std::shared_ptr + character(uint32_t character_code); +}; + +} + +#endif /* CANDY_GEAR_VK_FONT_H */ diff --git a/src/vk/image.cpp b/src/vk/image.cpp index 11dc9a5..7d751c8 100644 --- a/src/vk/image.cpp +++ b/src/vk/image.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,6 +89,33 @@ create( vkBindImageMemory(device->device, *image, *image_memory, 0); } +void move_image_state( + VkCommandBuffer vk_command_buffer, VkImage vk_image, VkFormat format, + VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, + VkImageLayout old_layout, VkImageLayout new_layout, + VkPipelineStageFlags source_stage, VkPipelineStageFlags destination_stage) +{ + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = src_access_mask; + barrier.dstAccessMask = dst_access_mask; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = vk_image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier( + vk_command_buffer, source_stage, destination_stage, 0, 0, nullptr, + 0, nullptr, 1, &barrier); +} + void create_view( VK::Device *device, VkImageView *image_view, diff --git a/src/vk/image.hpp b/src/vk/image.hpp index 63ebab6..4849038 100644 --- a/src/vk/image.hpp +++ b/src/vk/image.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,18 @@ create( VkImageTiling image_tiling, VkImageUsageFlags usage); +void +move_image_state( + VkCommandBuffer vk_command_buffer, + VkImage vk_image, + VkFormat format, + VkAccessFlags src_access_mask, + VkAccessFlags dst_access_mask, + VkImageLayout old_layout, + VkImageLayout new_layout, + VkPipelineStageFlags source_stage, + VkPipelineStageFlags destination_stage); + void create_view( VK::Device *device, diff --git a/src/vk/texture.cpp b/src/vk/texture.cpp index 020dd21..93bfe61 100644 --- a/src/vk/texture.cpp +++ b/src/vk/texture.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,57 +27,55 @@ namespace { -struct TextureBuilder +inline void +create_vulkan_image( + VkImage *image, VkDeviceMemory *device_memory, int width, int height, + uint32_t mip_levels) +{ + VkExtent3D vk_extent3d{}; + vk_extent3d.width = width; + vk_extent3d.height = height; + vk_extent3d.depth = 1; + + VK::Image::create( + cg_core.vk_device_with_swapchain, + image, + device_memory, + VK_FORMAT_R8G8B8A8_UNORM, + vk_extent3d, + mip_levels, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); +} + +struct ImageBuilder { VK::Texture *texture; +}; + +struct ImageTextureBuilder: public ImageBuilder +{ std::string texture_path; - TextureBuilder(VK::Texture *t, std::string tp); - TextureBuilder(VK::Texture *t, const char* tp); + ImageTextureBuilder(VK::Texture *t, std::string tp); + ImageTextureBuilder(VK::Texture *t, const char* tp); }; -TextureBuilder::TextureBuilder(VK::Texture *t, std::string tp): - texture{t}, +ImageTextureBuilder::ImageTextureBuilder(VK::Texture *t, std::string tp): texture_path{tp} { + this->texture = t; } -TextureBuilder::TextureBuilder(VK::Texture *t, const char* tp): - TextureBuilder{t, std::string(tp)} +ImageTextureBuilder::ImageTextureBuilder(VK::Texture *t, const char* tp): + ImageTextureBuilder{t, std::string(tp)} { } -void move_image_state( - VkCommandBuffer vk_command_buffer, VkImage vk_image, VkFormat format, - VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, - VkImageLayout old_layout, VkImageLayout new_layout, - VkPipelineStageFlags source_stage, VkPipelineStageFlags destination_stage) -{ - VkImageMemoryBarrier barrier{}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = src_access_mask; - barrier.dstAccessMask = dst_access_mask; - barrier.oldLayout = old_layout; - barrier.newLayout = new_layout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = vk_image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - - vkCmdPipelineBarrier( - vk_command_buffer, source_stage, destination_stage, 0, 0, nullptr, - 0, nullptr, 1, &barrier); -} - void load_image(void *obj) { - auto self = static_cast(obj); + auto self = static_cast(obj); qoi_desc img_desc; const int num_channels = 4; // all images are converted to RGBA @@ -104,20 +102,12 @@ load_image(void *obj) { // Create vulkan image. try { - VkExtent3D vk_extent3d{}; - vk_extent3d.width = self->texture->width; - vk_extent3d.height = self->texture->height; - vk_extent3d.depth = 1; - - VK::Image::create( - cg_core.vk_device_with_swapchain, - &self->texture->image, - &self->texture->device_memory, - VK_FORMAT_R8G8B8A8_UNORM, - vk_extent3d, - self->texture->mip_levels, - VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + create_vulkan_image( + &self->texture->image, + &self->texture->device_memory, + self->texture->width, + self->texture->height, + self->texture->mip_levels); } catch(VK::Image::Error error) { @@ -134,7 +124,7 @@ load_image(void *obj) VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; queue.submit_one_time_command(vk_command_buffer, [&](){ - move_image_state( + VK::Image::move_image_state( vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -149,13 +139,14 @@ load_image(void *obj) image_copy.imageSubresource.baseArrayLayer = 0; image_copy.imageSubresource.layerCount = 1; image_copy.imageOffset = {0, 0, 0}; - image_copy.imageExtent = {self->texture->width, self->texture->height, 1}; + image_copy.imageExtent = { + self->texture->width, self->texture->height, 1}; vkCmdCopyBufferToImage( vk_command_buffer, source_image_buffer.buffer, self->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); - move_image_state( + VK::Image::move_image_state( vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -172,7 +163,7 @@ load_image(void *obj) void unload_image(void *obj) { - auto self = static_cast(obj); + auto self = static_cast(obj); vkDestroyImage( cg_core.vk_device_with_swapchain->device, self->texture->image, nullptr); @@ -184,7 +175,7 @@ unload_image(void *obj) void load_sampler(void *obj) { - auto self = static_cast(obj); + auto self = static_cast(obj); VkSamplerCreateInfo sampler_info{}; sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; @@ -215,7 +206,7 @@ load_sampler(void *obj) void unload_sampler(void *obj) { - auto self = static_cast(obj); + auto self = static_cast(obj); vkDestroySampler( cg_core.vk_device_with_swapchain->device, self->texture->sampler, nullptr); @@ -224,7 +215,7 @@ unload_sampler(void *obj) void load_view(void *obj) { - auto self = static_cast(obj); + auto self = static_cast(obj); try { @@ -242,27 +233,209 @@ load_view(void *obj) void unload_view(void *obj) { - auto self = static_cast(obj); + auto self = static_cast(obj); vkDestroyImageView( cg_core.vk_device_with_swapchain->device, self->texture->view, nullptr); } -const CommandChain loader{ +const CommandChain image_loader{ {&load_image, &unload_image}, {&load_sampler, &unload_sampler}, {&load_view, &unload_view} }; +struct CharacterToDraw +{ + int pos_x, pos_y; + std::shared_ptr character; + + CharacterToDraw(int x, int y, std::shared_ptr character): + pos_x{x}, + pos_y{y}, + character{character} + {}; +}; + +struct TextTextureBuilder: public ImageBuilder +{ + VK::Font *font; + const char* str; + std::vector chars_to_draw; + + TextTextureBuilder(VK::Texture *texture, VK::Font *font, const char* str): + font{font}, + str{str} + { + this->texture = texture; + } +}; + +void +load_text_proportions(void *obj) +{ + auto self = static_cast(obj); + + uint32_t texture_width, texture_height; + auto unicode_text{VK::Character::str_to_unicode(self->str)}; + + texture_width = 0; + texture_height = 0; + self->chars_to_draw.reserve(unicode_text.size()); + { // Calculate image size + int max_height; + for(auto char_code : unicode_text) + { + auto char_image = self->font->character(char_code); + uint32_t pos_x{texture_width + char_image->bearing_x}; + uint32_t pos_y{char_image->height - char_image->bearing_y}; + + self->chars_to_draw.emplace_back(pos_x, pos_y, char_image); + + if(char_image->height > texture_height) + texture_height = char_image->height; + + texture_width += char_image->advance; + } + } + + self->texture->width = texture_width; + self->texture->height = texture_height; + self->texture->mip_levels = 1; +} + +void +load_text_image(void *obj) +{ + auto self = static_cast(obj); + + const int NumChannels = 4; + + size_t image_size{static_cast( + self->texture->width * self->texture->height * NumChannels)}; + std::vector pixels(image_size); + for(auto x{0}; x < self->texture->width; x++) + { + for(auto y{0}; y < self->texture->height; y++) + { + auto image_coord = y * self->font->face->glyph->bitmap.width + + x * NumChannels; + pixels[image_coord] = 0; // Red + pixels[image_coord + 1] = 0; // Green + pixels[image_coord + 2] = 0; // Blue + pixels[image_coord + 3] = 255; // Alpha + } + } + VK::SourceBuffer source_image_buffer{ + cg_core.vk_device_with_swapchain, pixels.data(), image_size}; + + { // Create vulkan image. + try + { + create_vulkan_image( + &self->texture->image, + &self->texture->device_memory, + self->texture->width, + self->texture->height, + self->texture->mip_levels); + } + catch(VK::Image::Error error) + { + throw CommandError{error.what()}; + } + } + + { // Render text + auto queue_family{ + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation()}; + auto queue{queue_family->get_queue()}; + VK::CommandPool command_pool{queue_family, 1}; + VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; + + queue.submit_one_time_command(vk_command_buffer, [&](){ + VK::Image::move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy image_copy{}; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset = {0, 0, 0}; + image_copy.imageExtent = {self->texture->width, self->texture->height, 1}; + + vkCmdCopyBufferToImage( + vk_command_buffer, source_image_buffer.buffer, self->texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + for(auto &to_draw: self->chars_to_draw) + { + VkImageSubresourceLayers + source_subresources{}, destination_subresources{}; + source_subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + source_subresources.mipLevel = 0; + source_subresources.baseArrayLayer = 0; + source_subresources.layerCount = 1; + + destination_subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + destination_subresources.mipLevel = 0; + destination_subresources.baseArrayLayer = 0; + destination_subresources.layerCount = 1; + + VkImageCopy image_copy{}; + image_copy.srcSubresource = source_subresources; + image_copy.srcOffset = {0, 0, 0}; + image_copy.dstSubresource = destination_subresources; + image_copy.dstOffset = {to_draw.pos_x, to_draw.pos_y, 0}; + image_copy.extent = { + to_draw.character->width, to_draw.character->height, 1}; + + vkCmdCopyImage( + vk_command_buffer, + to_draw.character->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + self->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &image_copy); + } + + VK::Image::move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + }); + } +} + +const CommandChain text_loader{ + {&load_text_proportions, nullptr}, + {&load_text_image, &unload_image}, + {&load_sampler, &unload_sampler}, + {&load_view, &unload_view} +}; + } namespace VK { +Texture::Texture(Font *font, const char* str) +{ + TextTextureBuilder text_builder(this, font, str); + text_loader.execute(&text_builder); +} + Texture::Texture(std::string texture_path) { - TextureBuilder texture_builder(this, texture_path); - loader.execute(&texture_builder); + ImageTextureBuilder texture_builder(this, texture_path); + image_loader.execute(&texture_builder); } Texture::Texture(const char* texture_path): @@ -272,8 +445,8 @@ Texture::Texture(const char* texture_path): Texture::~Texture() { - TextureBuilder texture_builder(this, ""); - loader.revert(&texture_builder); + ImageTextureBuilder texture_builder(this, ""); + image_loader.revert(&texture_builder); } } diff --git a/src/vk/texture.hpp b/src/vk/texture.hpp index 35772c5..c489e90 100644 --- a/src/vk/texture.hpp +++ b/src/vk/texture.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 Frederico de Oliveira Linhares + * Copyright 2022-2023 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include #include "core.hpp" +#include "font.hpp" namespace VK { @@ -33,6 +34,7 @@ struct Texture uint32_t width, height; uint32_t mip_levels; + Texture(Font *font, const char *str); Texture(std::string texture_path); Texture(const char* texture_path); ~Texture(); -- cgit v1.2.3