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/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 +- 8 files changed, 689 insertions(+), 66 deletions(-) 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/vk') 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