summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFrederico Linhares <fred@linhares.blue>2023-04-20 16:17:49 -0300
committerFrederico Linhares <fred@linhares.blue>2023-04-20 16:17:49 -0300
commit66cb556fb6f87d195aacf8a25ffafb86d524da19 (patch)
tree358b69272bd46190092e4297f85642033b493d0a /src
parent63748c1035d3fb39ad7b6ab1f6ad1f829ed85758 (diff)
feat Create text rendering system
Diffstat (limited to 'src')
-rw-r--r--src/core.cpp18
-rw-r--r--src/core.hpp7
-rw-r--r--src/font.cpp68
-rw-r--r--src/font.hpp28
-rw-r--r--src/texture.cpp67
-rw-r--r--src/vk/character.cpp265
-rw-r--r--src/vk/character.hpp49
-rw-r--r--src/vk/font.cpp53
-rw-r--r--src/vk/font.hpp42
-rw-r--r--src/vk/image.cpp29
-rw-r--r--src/vk/image.hpp14
-rw-r--r--src/vk/texture.cpp299
-rw-r--r--src/vk/texture.hpp4
13 files changed, 834 insertions, 109 deletions
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"
@@ -233,6 +234,19 @@ unload_window(void *obj)
}
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)
{
std::vector<const char*> vk_extensions;
@@ -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 <SDL2/SDL_vulkan.h>
#include <SDL2/SDL_mixer.h>
+#include <ft2build.h>
+#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<VK::Font*>(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<VK::Texture> *ptr;
+
+ mrb_get_args(mrb, "dz", &font_ptr, &cg_font_type, &text);
+
+ ptr = (std::shared_ptr<VK::Texture>*)DATA_PTR(texture);
+ if(ptr) mrb_free(mrb, ptr);
+ ptr = (std::shared_ptr<VK::Texture>*)mrb_malloc(
+ mrb, sizeof(std::shared_ptr<VK::Texture>));
+ new(ptr)std::shared_ptr<VK::Texture>(
+ std::make_shared<VK::Texture>(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<CharacterBuilder*>(obj);
+
+ FT_Error error;
+ std::vector<uint8_t> 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<size_t>(
+ 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<CharacterBuilder*>(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<uint32_t>
+Character::str_to_unicode(const char* str)
+{
+ std::vector<uint32_t> 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 <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "core.hpp"
+
+#include <vector>
+
+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<uint32_t>
+ 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<Character>
+Font::character(uint32_t character_code)
+{
+ if(!this->characters.contains(character_code))
+ this->characters.emplace(
+ character_code, std::make_shared<Character>(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 <memory>
+#include <unordered_map>
+
+#include "character.hpp"
+
+namespace VK
+{
+
+struct Font
+{
+ FT_Face face;
+ std::unordered_map<uint32_t, std::shared_ptr<Character>> characters;
+
+ Font(const char* font_path, int font_size);
+ ~Font();
+
+ std::shared_ptr<Character>
+ 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.
@@ -50,6 +50,18 @@ create(
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,
VkImageView *image_view,
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<TextureBuilder*>(obj);
+ auto self = static_cast<ImageTextureBuilder*>(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<TextureBuilder*>(obj);
+ auto self = static_cast<ImageBuilder*>(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<TextureBuilder*>(obj);
+ auto self = static_cast<ImageBuilder*>(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<TextureBuilder*>(obj);
+ auto self = static_cast<ImageBuilder*>(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<TextureBuilder*>(obj);
+ auto self = static_cast<ImageBuilder*>(obj);
try
{
@@ -242,27 +233,209 @@ load_view(void *obj)
void
unload_view(void *obj)
{
- auto self = static_cast<TextureBuilder*>(obj);
+ auto self = static_cast<ImageBuilder*>(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<VK::Character> character;
+
+ CharacterToDraw(int x, int y, std::shared_ptr<VK::Character> character):
+ pos_x{x},
+ pos_y{y},
+ character{character}
+ {};
+};
+
+struct TextTextureBuilder: public ImageBuilder
+{
+ VK::Font *font;
+ const char* str;
+ std::vector<CharacterToDraw> 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<TextTextureBuilder*>(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<TextTextureBuilder*>(obj);
+
+ const int NumChannels = 4;
+
+ size_t image_size{static_cast<size_t>(
+ self->texture->width * self->texture->height * NumChannels)};
+ std::vector<unsigned char> 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 <string>
#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();