/* * Copyright 2022-2024 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 { BluCat::Character *character; FT_Face face; uint32_t character_code; CharacterBuilder( BluCat::Character *character, FT_Face face, uint32_t character_code); }; CharacterBuilder::CharacterBuilder( BluCat::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->bearing_x = self->face->glyph->bitmap_left; self->character->bearing_y = self->face->glyph->bitmap_top; self->character->advance = (self->face->glyph->advance.x >> 6); self->character->width = self->face->glyph->bitmap.width; self->character->height = self->face->glyph->bitmap.rows; self->character->mip_levels = 1; // Character is a white-space. if(self->character->width <= 0) { self->character->image = VK_NULL_HANDLE; self->character->device_memory = VK_NULL_HANDLE; return; } 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, 0); 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] = 255; // Green source_image_raw[image_coord + 1] = 255; // Blue source_image_raw[image_coord + 2] = 255; // Alpha source_image_raw[image_coord + 3] = self->face->glyph->bitmap.buffer[glyph_coord]; } } } BluCat::SourceBuffer source_image_buffer{ BluCat::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; BluCat::Image::create( BluCat::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(BluCat::Image::Error error) { throw CommandError{error.what()}; } } { // Copy image from buffer into image. auto queue_family{BluCat::core.vk_device_with_swapchain-> get_queue_family_with_presentation()}; auto queue{queue_family->get_queue()}; BluCat::CommandPool command_pool{queue_family, 1}; VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; queue.submit_one_time_command(vk_command_buffer, [&](){ BluCat::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); BluCat::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( BluCat::core.vk_device_with_swapchain->device, self->character->image, nullptr); vkFreeMemory( BluCat::core.vk_device_with_swapchain->device, self->character->device_memory, nullptr); } const CommandChain loader{ {&load_image, &unload_image} }; } namespace BluCat { 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; } }