diff --git a/cmake/FindFreeType.cmake b/cmake/FindFreeType.cmake new file mode 100644 index 00000000..5cf6ff55 --- /dev/null +++ b/cmake/FindFreeType.cmake @@ -0,0 +1,11 @@ + +set(FREE_TYPE_DIR "${PROJECT_SOURCE_DIR}/third_party/freetype/") + +find_library( + freetype + NAMES freetype + HINTS ${FREE_TYPE_DIR}/build/ +) + + +set(FreeType_INCLUDE_DIR "${FREE_TYPE_DIR}/include/") diff --git a/dependencies.dep b/dependencies.dep index c3569080..d2daa44d 100644 --- a/dependencies.dep +++ b/dependencies.dep @@ -19,3 +19,7 @@ custom_commands = cd Criterion && meson build && cd build && ninja [stb] git_url = https://github.com/nothings/stb.git tag = master + +[FreeType] +dl_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.gz +custom_commands = tar -xf freetype-2.10.0.tar.gz mv freetype-2.10.0 freetype && rm freetype-2.10.0.tar.gz && cmake -E make_directory build && cmake -E chdir build cmake -D BUILD_SHARED_LIBS:BOOL=true .. && cmake --build build diff --git a/graphics/CMakeLists.txt b/graphics/CMakeLists.txt index 5ce10914..a367403f 100644 --- a/graphics/CMakeLists.txt +++ b/graphics/CMakeLists.txt @@ -2,6 +2,7 @@ find_package(Vulkan REQUIRED) find_package(vma REQUIRED) find_package(glfw3 REQUIRED) find_package(stb REQUIRED) +find_package(FreeType REQUIRED) find_package(Vulkan COMPONENTS glslc) find_program(glslc_executable NAMES glslc HINTS ${Vulkan_GLSLC_EXECUTABLE}/..) @@ -42,6 +43,7 @@ ${CMAKE_SOURCE_DIR} ${vma_INCLUDE_DIR} ${glfw3_INCLUDE_DIR} ${stb_INCLUDE_DIR} + ${FreeType_INCLUDE_DIR} ) target_sources(rse_graphics PRIVATE @@ -57,10 +59,12 @@ src/rse_math.c src/mesh_controller.c src/vulkan_commons.c src/rse_vulkan_image.c +src/rse_text_renderer.c ) target_link_libraries(rse_graphics PRIVATE rse_utilities ${Vulkan_LIBRARIES} ${glfw} + ${freetype} ) diff --git a/graphics/shaders/shader.frag b/graphics/shaders/shader.frag index 64b5b629..19ed2efd 100644 --- a/graphics/shaders/shader.frag +++ b/graphics/shaders/shader.frag @@ -14,4 +14,4 @@ layout(location = 0) out vec4 outColor; void main() { outColor = vec4(fragColor * texture(texSampler[instanceTextureId], fragTexCoord).rgb, 1.0); -} \ No newline at end of file +} diff --git a/graphics/shaders/shader.frag.num b/graphics/shaders/shader.frag.num deleted file mode 100644 index 3a47c217..00000000 Binary files a/graphics/shaders/shader.frag.num and /dev/null differ diff --git a/graphics/shaders/shader.vert.num b/graphics/shaders/shader.vert.num deleted file mode 100644 index ca65223c..00000000 Binary files a/graphics/shaders/shader.vert.num and /dev/null differ diff --git a/graphics/src/locale_fonts.h b/graphics/src/locale_fonts.h new file mode 100644 index 00000000..bfb7846b --- /dev/null +++ b/graphics/src/locale_fonts.h @@ -0,0 +1,46 @@ + +/** + * @file locale_fonts.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Error messages for font handlers + * @version 0.1 + * @date 2022-05-13 + * + * @copyright Copyright (c) 2022 + * + */ + + +#ifndef RSE_LOCALE_FONTS_H +#define RSE_LOCALE_FONTS_H + +#include "utilities/localization.h" + +#define font_messages font_messages_nolocale[SELECTED_LANGUAGE] + +enum font_messages_t { + FONT_INITIALIZATION_FAILED, + FONT_LOAD_FONT_FAILED, + FONT_CHANGE_SIZE_FAILED, + FONT_LOAD_GLYPH_FAILED, + FONT_RENDER_GLYPH_FAILED, + FONT_LAST_MESSAGE +}; + + +static const char font_messages_nolocale[LAST_LANGUAGE][FONT_LAST_MESSAGE][MAX_MESSAGE_LENGHT] = { + {/* EN_US */ + /* FONT_INITIALIZATION_FAILED */ + "Font initialization failed\n", + /* FONT_LOAD_FONT_FAILED */ + "Failed to load new font\n", + /* FONT_CHANGE_SIZE_FAILED */ + "Failed to change font size\n", + /* FONT_LOAD_GLYPH_FAILED */ + "Failed to load glyph into memeory\n", + /* FONT_RENDER_GLYPH_FAILED */ + "Failed to render glypth into bitmap\n" + } +}; + +#endif // RSE_LOCALE_FONTS_H diff --git a/graphics/src/locale_vulkan.h b/graphics/src/locale_vulkan.h index d64a7fdc..adb16be6 100644 --- a/graphics/src/locale_vulkan.h +++ b/graphics/src/locale_vulkan.h @@ -1,5 +1,5 @@ /** - * @file localeVulkan.h + * @file locale_vulkan.h * @author Piotr Krygier (everyonecancode@gmail.com) * @brief Error messages for Vulkan initializers * @version 0.1 @@ -136,4 +136,4 @@ static const char vulkan_messages_nolocale[LAST_LANGUAGE][VULKAN_LAST_MESSAGE][M } }; -#endif /* RSE_LOCALE_VULKAN_H */ \ No newline at end of file +#endif /* RSE_LOCALE_VULKAN_H */ diff --git a/graphics/src/rse_font_errors.h b/graphics/src/rse_font_errors.h new file mode 100644 index 00000000..f2ff1add --- /dev/null +++ b/graphics/src/rse_font_errors.h @@ -0,0 +1,26 @@ +/** + * @file rse_font_errors.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2023-10-06 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef RSE_FONT_ERRORS_H +#define RSE_FONT_ERRORS_H + +#define RSE_FONT_MODULE_ID 0x0400U + +/* Error codes */ +enum rse_window_error_t { + FONT_ERROR_INIT_FAILED = RSE_FONT_MODULE_ID, + FONT_ERROR_LOAD_FAILED, + FONT_ERROR_CHANGE_SIZE_FAILED, + FONT_ERROR_LOAD_GLYPH_FAILED, + FONT_ERROR_RENDER_GLYPH_FAILED +}; + +#endif /* RSE_WINDOW_ERRORS_H */ diff --git a/graphics/src/rse_graphics.c b/graphics/src/rse_graphics.c index acc10f43..0f4236de 100644 --- a/graphics/src/rse_graphics.c +++ b/graphics/src/rse_graphics.c @@ -1,4 +1,5 @@ #include "rse_graphics.h" +#include "src/rse_text_renderer.h" #include "window.h" #include "vulkan_base.h" @@ -8,11 +9,13 @@ #include "utilities/logger.h" #include "utilities/rse_commons.h" #include "utilities/rse_errors_common.h" +#include "rse_font_errors.h" #include "rse_math.h" extern struct rse_vulkan_state_t g_vulkan_state; + rse_err_t rse_graphics_init(void) { rse_err_t status = RSE_ERROR_NO_ERROR; @@ -36,21 +39,23 @@ void rse_graphics_test_function(void) rse_err_t status = RSE_ERROR_NO_ERROR; uint16_t tex_id_2 = 0; - status = rse_load_texture("../../test_image.jpg", &tex_id_2); + status = rse_load_texture_from_file("../../test_image.jpg", &tex_id_2); if (status != RSE_ERROR_NO_ERROR) { - LOGE("Failed to do something. Check error messages"); + LOGE("Failed to do something. Check error messages. Error: %d", status); return; } + (rse_fonts_initialize()); + (rse_fonts_load_from_file("/usr/share/fonts/JetBrainsMonoNerdFont/JetBrainsMonoNerdFont-Medium.ttf")); // FIXME: Temporary array of vertices, for testing purposes - struct rse_vertex_t vertices[] = {{{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + struct rse_vertex_t vertices[] = {{{-0.5f, -0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}}; - struct rse_vertex_t vertices2[] = {{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + struct rse_vertex_t vertices2[] = {{{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}}; // FIXME: Temporary array of vertices, for testing purposes @@ -61,14 +66,14 @@ void rse_graphics_test_function(void) {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, rse_math_deg_to_radians(45.0f)}, 1.0f, - tex_id_2 + 4 }); create_mesh_instance(mesh_id, (struct rse_instance_data_t){ {-1.0f, 0.0f, 0.3f}, {0.0f, 0.0f, 0.0f}, 1.0f, - tex_id_2 + 49 }); mesh_id = create_mesh(vertices2, indices, 8, 6); @@ -76,7 +81,7 @@ void rse_graphics_test_function(void) {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, rse_math_deg_to_radians(0.0f)}, 1.0f, - tex_id_2 + 33 }); } diff --git a/graphics/src/rse_text_renderer.c b/graphics/src/rse_text_renderer.c new file mode 100644 index 00000000..991d5de0 --- /dev/null +++ b/graphics/src/rse_text_renderer.c @@ -0,0 +1,82 @@ +#include "rse_text_renderer.h" +#include +#include + +#include "freetype/fttypes.h" +#include "locale_fonts.h" +#include "rse_font_errors.h" +#include "utilities/logger.h" +#include "utilities/rse_commons.h" +#include "utilities/rse_errors_common.h" +#include "rse_vulkan_image.h" + +#include "ft2build.h" +#include "vulkan/vulkan_core.h" +#include FT_FREETYPE_H + +#define FONT_CHARACTERS_COUNT 126 + +uint16_t g_font_textures[FONT_CHARACTERS_COUNT]; + +FT_Library g_library; +rse_err_t rse_fonts_initialize() +{ + FT_Error error; + + error = FT_Init_FreeType(&g_library); + + if (error) { + LOGF(font_messages[FONT_INITIALIZATION_FAILED]); + return FONT_ERROR_INIT_FAILED; + } + return RSE_ERROR_NO_ERROR; +} + + +rse_err_t rse_fonts_load_from_file(const char* file_path) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + FT_Error error; + FT_Face face; + size_t iter = 0U; + + error = FT_New_Face(g_library, file_path, 0, &face); + + if (error) { + LOGF(font_messages[FONT_LOAD_FONT_FAILED]); + return FONT_ERROR_LOAD_FAILED; + } + + /* By default we set font size to 16px. TODO: change later? */ + error = FT_Set_Pixel_Sizes(face, 0, 48); + + if (error) { + LOGF(font_messages[FONT_CHANGE_SIZE_FAILED]); + return FONT_ERROR_CHANGE_SIZE_FAILED; + } + + /* Convert font face to textured image */ + /* Loading only needed ascii characters, from 40 (space) to 126 (~) */ + for (iter = 33U; iter < face->num_glyphs && iter < 127U; ++iter) { + error = FT_Load_Char(face, iter, FT_LOAD_RENDER); + + if (error) { + LOGF(font_messages[FONT_LOAD_GLYPH_FAILED]); + return FONT_ERROR_LOAD_GLYPH_FAILED; + } + + STATUS_CHECK(rse_load_texture_from_bitmat(face->glyph->bitmap.buffer, + face->glyph->bitmap.width, + face->glyph->bitmap.rows, + &g_font_textures[iter], VK_FORMAT_R8_UNORM)); + } + return status; +} + +rse_err_t rse_print_text(const char* text) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + + return status; +} diff --git a/graphics/src/rse_text_renderer.h b/graphics/src/rse_text_renderer.h new file mode 100644 index 00000000..1c14f4a2 --- /dev/null +++ b/graphics/src/rse_text_renderer.h @@ -0,0 +1,22 @@ + +/** + * @file rse_text_renderer.h + * @author Piotr Krygier (piotr.krygier@meshsystems.com) + * @brief Load and render text on screen or in space + * @version 0.1 + * @date 2024-02-02 + * + * @copyright Copyright (c) 2023 + * + */ + + +#ifndef RSE_TEXT_RENDERER_H +#define RSE_TEXT_RENDERER_H + +#include "utilities/rse_commons.h" + +rse_err_t rse_fonts_initialize(void); +rse_err_t rse_fonts_load_from_file(const char* file_path); + +#endif // !RSE_TEXT_RENDERER_H_ diff --git a/graphics/src/rse_vulkan_image.c b/graphics/src/rse_vulkan_image.c index 2d40718c..f127c7fe 100644 --- a/graphics/src/rse_vulkan_image.c +++ b/graphics/src/rse_vulkan_image.c @@ -242,6 +242,10 @@ static rse_err_t format_to_pixel_size(VkFormat format, { switch (format) { + case VK_FORMAT_R8_SRGB: + case VK_FORMAT_R8_UNORM: + *format_size = 1; + break; case VK_FORMAT_R8G8B8A8_SRGB: *format_size = 4; break; @@ -258,7 +262,7 @@ static rse_err_t format_to_pixel_size(VkFormat format, static rse_err_t create_textured_image(uint32_t width, uint32_t height, VkFormat format, - unsigned char* pixels, + const unsigned char* pixels, uint16_t* texture_id) { rse_err_t status = RSE_ERROR_NO_ERROR; @@ -403,7 +407,20 @@ rse_err_t init_vulkan_images() return status; } -rse_err_t rse_load_texture(const char* file_path, uint16_t* texture_id) +rse_err_t rse_load_texture_from_bitmat(const unsigned char* pixel_buffer, int width, int height, uint16_t* texture_id, VkFormat color_format) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + /* Create actual Vulkan Image */ + STATUS_CHECK(create_textured_image(width, + height, + color_format, + pixel_buffer, texture_id)); + + return status; +} + +rse_err_t rse_load_texture_from_file(const char* file_path, uint16_t* texture_id) { rse_err_t status = RSE_ERROR_NO_ERROR; int width = 0; @@ -418,16 +435,11 @@ rse_err_t rse_load_texture(const char* file_path, uint16_t* texture_id) return -1; } - /* Create actual Vulkan Image */ - status = create_textured_image(width, - height, - VK_FORMAT_R8G8B8A8_SRGB, - pixel_buffer, texture_id); + STATUS_CHECK(rse_load_texture_from_bitmat(pixel_buffer, width, height, texture_id, VK_FORMAT_R8G8B8A8_SRGB)); stbi_image_free(pixel_buffer); return status; - } void rse_destroy_textures() diff --git a/graphics/src/rse_vulkan_image.h b/graphics/src/rse_vulkan_image.h index 037cd977..5d551073 100644 --- a/graphics/src/rse_vulkan_image.h +++ b/graphics/src/rse_vulkan_image.h @@ -34,13 +34,22 @@ rse_err_t init_vulkan_images(void); /** - * @brief Loads image from file, for later to be uysed as a texture + * @brief Loads image from file, for later to be used as a texture * * @param file_path Path to texture file * @param texture_id Texture ID will be set up here * @return rse_err_t RSE_ERROR_NO_ERROR on success */ -rse_err_t rse_load_texture(const char* file_path, uint16_t* texture_id); +rse_err_t rse_load_texture_from_bitmat(const unsigned char* pixel_buffer, int width, int height, uint16_t* texture_id, VkFormat color_format); + +/** + * @brief Loads image from file, for later to be used as a texture + * + * @param file_path Path to texture file + * @param texture_id Texture ID will be set up here + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t rse_load_texture_from_file(const char* file_path, uint16_t* texture_id); /** * @brief Destroy all loaded textures. Usefull for closing application or just clearing. diff --git a/utilities/src/file_utils.c b/utilities/src/file_utils.c index 2f52e6c4..a8703f27 100644 --- a/utilities/src/file_utils.c +++ b/utilities/src/file_utils.c @@ -16,9 +16,9 @@ void rse_read_file(const char* file_name, long* bytes_count, char* buffer) fclose(file); return; } - - if (fread(buffer, *bytes_count, 1, file)) { - perror("Empty file read"); + fread(buffer, *bytes_count, 1, file); + if (ferror(file) != 0) { + perror("Error reading file"); } fclose(file);