From c9b1ba26198caf002a8a3675cac01184bd5363d3 Mon Sep 17 00:00:00 2001 From: WillisMedwell Date: Mon, 11 Mar 2024 12:12:14 +1100 Subject: [PATCH] Completed: Image, FontAtlas, & FontBatchRenderer --- code/Demos/src/Main.cpp | 36 ++-- code/Engine/include/Core/Texture.hpp | 2 +- code/Engine/include/Media/Font.hpp | 41 ++-- code/Engine/include/Media/Image.hpp | 11 +- .../include/Renderer/FontBatchRenderer.hpp | 36 ++-- code/Engine/include/Renderer/FontRenderer.hpp | 42 ---- code/Engine/include/Renderer/Renderer.hpp | 1 - code/Engine/src/App/AppRenderer.cpp | 2 +- code/Engine/src/Core/Texture.cpp | 24 +-- code/Engine/src/Media/Font.cpp | 153 +++++++++++--- code/Engine/src/Media/Image.cpp | 42 ++-- .../Engine/src/Renderer/FontBatchRenderer.cpp | 112 +++++----- code/Engine/src/Renderer/FontRenderer.cpp | 192 ------------------ 13 files changed, 297 insertions(+), 397 deletions(-) delete mode 100644 code/Engine/include/Renderer/FontRenderer.hpp delete mode 100644 code/Engine/src/Renderer/FontRenderer.cpp diff --git a/code/Demos/src/Main.cpp b/code/Demos/src/Main.cpp index 054fbf5..737b0e5 100644 --- a/code/Demos/src/Main.cpp +++ b/code/Demos/src/Main.cpp @@ -411,11 +411,15 @@ struct IsoData { Renderer::ResourceManager resource_manager; Renderer::InstanceRenderer instance_renderer; + std::optional font_batch_renderer; + Cameras::StationaryPerspective camera { glm::vec3(0, 1, -1), glm::normalize(glm::vec3(0, -0.25f, 0.5f)) }; }; struct IsoLogic { void init(AppRenderer& renderer, Core::AudioManager& audio, IsoData& data) { + std::cout << std::thread::hardware_concurrency() << '\n'; + Media::Sound sound {}; auto wav_file_data = Utily::FileReader::load_entire_file("assets/background_sound.wav"); @@ -430,22 +434,22 @@ struct IsoLogic { data.spinning.angle = 0; data.spinning.rotations_per_second = 1; - auto model_data = std::move( - Utily::FileReader::load_entire_file("assets/teapot.obj") - .on_error(print_then_quit) - .value()); - auto model = std::move( - Model::decode_as_static_model(model_data, ".obj") - .on_error(print_then_quit) - .value()); - - auto image_png = std::move( - Utily::FileReader::load_entire_file("assets/texture.png") - .on_error(print_then_quit) - .value()); - auto image = Media::Image {}; - image.init(image_png, true, false) - .on_error(print_then_quit); + auto model_data = Utily::FileReader::load_entire_file("assets/teapot.obj") + .on_error_panic() + .value_move(); + auto model = Model::decode_as_static_model(model_data, ".obj") + .on_error_panic() + .value_move(); + auto image = Media::Image::create("assets/texture.png") + .on_error_panic() + .value_move(); + + data.font_batch_renderer.emplace(Renderer::FontBatchRenderer::create(data.resource_manager, "assets/RobotoMono.ttf") + .on_error_panic() + .value_move()); + + auto font_atlas = Media::FontAtlas::create("assets/RobotoMono.ttf", 500).on_error_panic().value_move(); + font_atlas.atlas_image().save_to_disk("RobotoMonoAtlas.png"); data.instance_renderer.init(data.resource_manager, model, image); diff --git a/code/Engine/include/Core/Texture.hpp b/code/Engine/include/Core/Texture.hpp index bb06595..7aa5316 100644 --- a/code/Engine/include/Core/Texture.hpp +++ b/code/Engine/include/Core/Texture.hpp @@ -21,7 +21,7 @@ namespace Core { Texture(Texture&&); [[nodiscard]] auto init() noexcept -> Utily::Result; - auto upload_image(Media::Image& image, Filter filter = Filter::smooth, bool offload_image_on_success = false) noexcept -> Utily::Result; + auto upload_image(const Media::Image& image, Filter filter = Filter::smooth) noexcept -> Utily::Result; // Once texture unit is aquired, it cannot be taken away unless unbinded() or just bind(false). [[nodiscard]] auto bind(bool locked = false) noexcept -> Utily::Result; diff --git a/code/Engine/include/Media/Font.hpp b/code/Engine/include/Media/Font.hpp index 8abdc15..e95e0a5 100644 --- a/code/Engine/include/Media/Font.hpp +++ b/code/Engine/include/Media/Font.hpp @@ -10,9 +10,10 @@ #include #include +#if 0 namespace Media { - namespace FontAtlasConstants { + namespace FontAtlasConstants { consteval static auto gen_drawable_chars() { constexpr char first_printable = char(32); constexpr char last_printable = char(127); @@ -33,11 +34,6 @@ namespace Media { } constexpr static auto IS_CHAR_DRAWABLE = gen_is_char_drawable_table(); } -} - -#if 0 - -namespace Media { class Font; @@ -101,31 +97,32 @@ namespace Media { { public: /// @brief Load .ttf font from disk. Generate a font-atlas image. Can fail. - static auto create(std::filesystem::path path) noexcept -> Utily::Result; + [[nodiscard]] static auto create(std::filesystem::path path, uint32_t char_height_px) noexcept -> Utily::Result; FontAtlas(FontAtlas&& other) - : _m(std::move(other.m)) { } + : _m(std::move(other._m)) { } + + struct UvCoord { + float min_x; + float max_x; + float min_y; + float max_y; + }; + [[nodiscard]] auto uv_for(char a) const noexcept -> FontAtlas::UvCoord; - auto uv_for(char c) const noexcept -> glm::vec2; + [[nodiscard]] auto atlas_image() const noexcept -> const Media::Image& { return _m.atlas_image; } + [[nodiscard]] auto atlas_layout() const noexcept { return _m.atlas_layout; } + [[nodiscard]] auto glyph_dimensions() const noexcept { return _m.glyph_dimensions; } private: struct M { - const Media::Image atlas_image; - const glm::vec2 atlas_layout; - const glm::vec2 glyph_dimensions; + Media::Image atlas_image; + glm::vec2 atlas_layout; + glm::vec2 glyph_dimensions; } _m; - explicit FontAtlas(M m) + explicit FontAtlas(M&& m) : _m(std::move(m)) { } - - constexpr static auto PRINTABLE_CHARS = []() { - constexpr char first_printable = char(32); - constexpr char last_printable = char(127); - constexpr size_t n = last_printable - first_printable; - std::array chars {}; - std::ranges::copy(std::views::iota(first_printable, last_printable), chars.begin()); - return chars; - }(); }; } diff --git a/code/Engine/include/Media/Image.hpp b/code/Engine/include/Media/Image.hpp index 4129a60..165f4bb 100644 --- a/code/Engine/include/Media/Image.hpp +++ b/code/Engine/include/Media/Image.hpp @@ -78,15 +78,18 @@ namespace Media { [[nodiscard]] static auto create(std::filesystem::path path) -> Utily::Result; /// @brief Take decoded-raw image data and copy it. Can fail. - [[nodiscard]] static auto create(std::span raw_bytes, glm::uvec2 dimensions, InternalFormat format) + [[nodiscard]] static auto create(std::span raw_bytes, glm::uvec2 dimensions, InternalFormat format) -> Utily::Result; - Image(Image&& other); - [[nodiscard]] inline auto raw_bytes() const noexcept { return std::span { _m.data.get(), _m.data_size_bytes }; } [[nodsicard]] inline auto dimensions() const noexcept { return _m.dimensions; } + [[nodiscard]] auto format() const { return _m.format; } [[nodiscard]] auto opengl_format() const -> uint32_t; + Image(Image&& other); + Image(const Image&) = delete; + + auto save_to_disk(std::filesystem::path path) const noexcept -> Utily::Result; private: struct M { std::unique_ptr data = {}; @@ -95,7 +98,7 @@ namespace Media { InternalFormat format = InternalFormat::undefined; } _m; - explicit Image(M m) + explicit Image(M&& m) : _m(std::move(m)) { } }; } \ No newline at end of file diff --git a/code/Engine/include/Renderer/FontBatchRenderer.hpp b/code/Engine/include/Renderer/FontBatchRenderer.hpp index cce803f..9fbc723 100644 --- a/code/Engine/include/Renderer/FontBatchRenderer.hpp +++ b/code/Engine/include/Renderer/FontBatchRenderer.hpp @@ -12,12 +12,16 @@ namespace Renderer { glm::vec2 screen_dimensions; glm::vec4 font_colour; }; - void init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas); - + + static auto create(ResourceManager& resource_manager, std::filesystem::path ttf_path) noexcept -> Utily::Result; + void begin_batch(BatchConfig&& batch_config); void push_to_batch(std::string_view text, glm::vec2 bottom_left, float height_px); void end_batch(); + FontBatchRenderer(FontBatchRenderer&& other) + : _m(std::move(other._m)) { } + private: void load_text_into_vb(const std::string_view& text, glm::vec2 bottom_left, float height_px); @@ -26,17 +30,21 @@ namespace Renderer { glm::vec2 uv_coord; using VBL = Core::VertexBufferLayout; }; - - std::optional _current_batch_config = std::nullopt; - std::vector _current_batch_vertices = {}; - - glm::vec2 _glyph_dimensions = { 0, 0 }; - glm::vec2 _atlas_dimensions = { 0, 0 }; - - Renderer::ResourceHandle _s; - Renderer::ResourceHandle _t; - Renderer::ResourceHandle _vb; - Renderer::ResourceHandle _ib; - Renderer::ResourceHandle _va; + + struct M { + std::vector current_batch_vertices; + std::optional current_batch_config; + + Media::FontAtlas font_atlas; + + Renderer::ResourceHandle s; + Renderer::ResourceHandle t; + Renderer::ResourceHandle vb; + Renderer::ResourceHandle ib; + Renderer::ResourceHandle va; + } _m; + + explicit FontBatchRenderer(M&& m) + : _m(std::move(m)) { } }; } \ No newline at end of file diff --git a/code/Engine/include/Renderer/FontRenderer.hpp b/code/Engine/include/Renderer/FontRenderer.hpp deleted file mode 100644 index 9e4649c..0000000 --- a/code/Engine/include/Renderer/FontRenderer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "Core/Core.hpp" -#include "Media/Media.hpp" -#include "Renderer/ResourceManager.hpp" - -namespace Renderer { - class FontRenderer - { - private: - Renderer::ResourceHandle _s; - Renderer::ResourceHandle _t; - Renderer::ResourceHandle _vb; - Renderer::ResourceHandle _ib; - Renderer::ResourceHandle _va; - - struct FontVertex { - glm::vec2 position; - glm::vec2 uv_coord; - - using VBL = Core::VertexBufferLayout; - }; - - size_t _loaded_text_hash = 0; - - std::vector _vertices; - std::vector _indices; - - glm::vec2 _glyph_dimensions = { 0, 0 }; - glm::vec2 _atlas_dimensions = { 0, 0 }; - - void ensure_buffers_have_capacity_for(const size_t N, Core::IndexBuffer& ib); - void load_text_into_vb(const std::string_view& text, Core::VertexBuffer& vb); - bool is_init() const noexcept; - auto uv_coord_of_char(char a) const noexcept -> Media::FontAtlas::UvCoord; - - public: - void init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas); - void stop(ResourceManager& resource_manager); - void draw(ResourceManager& resource_manager, glm::vec2 screen_width, std::string_view text, float char_size_px, glm::vec2 bottom_left, glm::vec4 colour = { 1, 1, 1, 1 }); - }; -} \ No newline at end of file diff --git a/code/Engine/include/Renderer/Renderer.hpp b/code/Engine/include/Renderer/Renderer.hpp index e1eee69..94e5568 100644 --- a/code/Engine/include/Renderer/Renderer.hpp +++ b/code/Engine/include/Renderer/Renderer.hpp @@ -13,6 +13,5 @@ namespace Renderer { #include "Renderer/ResourceHandle.hpp" #include "Renderer/ResourceManager.hpp" -#include "Renderer/FontRenderer.hpp" #include "Renderer/FontBatchRenderer.hpp" #include "Renderer/InstanceRenderer.hpp" diff --git a/code/Engine/src/App/AppRenderer.cpp b/code/Engine/src/App/AppRenderer.cpp index ec4a157..c127f8f 100644 --- a/code/Engine/src/App/AppRenderer.cpp +++ b/code/Engine/src/App/AppRenderer.cpp @@ -56,7 +56,7 @@ auto AppRenderer::add_texture(Media::Image& image) noexcept -> Utily::Result(id) }; diff --git a/code/Engine/src/Core/Texture.cpp b/code/Engine/src/Core/Texture.cpp index 3e10efc..184d553 100644 --- a/code/Engine/src/Core/Texture.cpp +++ b/code/Engine/src/Core/Texture.cpp @@ -24,7 +24,7 @@ namespace Core { , _id(std::exchange(other._id, std::nullopt)) , _texture_unit_index(std::exchange(other._texture_unit_index, std::nullopt)) { } - auto getUsableTextureUnit() noexcept + auto get_usable_texture_unit() noexcept -> Utily::Result, Utily::Error> { if (!texture_units().size()) { // TODO collect analytics about how many texture slots. @@ -63,9 +63,8 @@ namespace Core { } auto Texture::upload_image( - Media::Image& image, - Filter filter, - bool offload_image_on_success) noexcept + const Media::Image& image, + Filter filter) noexcept -> Utily::Result { Core::DebugOpRecorder::instance().push("Core::Texture", "upload_image()"); Profiler::Timer timer("Core::Texture::upload_image()", { "rendering" }); @@ -90,9 +89,9 @@ namespace Core { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); int32_t bytes_per_pixel = 4; - uint32_t gl_format = GL_RGBA8; + uint32_t gl_format = GL_RGBA; - if (image.format() == Media::ColourFormat::greyscale) { + if (image.format() == Media::Image::InternalFormat::greyscale) { bytes_per_pixel = 1; gl_format = GL_RED; } else { @@ -102,17 +101,8 @@ namespace Core { const void* img_data = reinterpret_cast(image.raw_bytes().data()); glPixelStorei(GL_UNPACK_ALIGNMENT, bytes_per_pixel); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)image.format(), image.dimensions().x, image.dimensions().y, 0, gl_format, GL_UNSIGNED_BYTE, img_data); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)image.opengl_format(), image.dimensions().x, image.dimensions().y, 0, gl_format, GL_UNSIGNED_BYTE, img_data); - if (offload_image_on_success) { - glFinish(); - image.stop(); - } - if constexpr (!Config::SKIP_IMAGE_TEXTURE_FENCING) { - auto fence = Core::Fence {}; - fence.init(); - image.add_fence(std::move(fence)); - } return {}; } @@ -133,7 +123,7 @@ namespace Core { _texture_unit_index = std::nullopt; } - auto result = getUsableTextureUnit(); + auto result = get_usable_texture_unit(); if (result.has_error()) { return result.error(); } diff --git a/code/Engine/src/Media/Font.cpp b/code/Engine/src/Media/Font.cpp index e47e289..14920a2 100644 --- a/code/Engine/src/Media/Font.cpp +++ b/code/Engine/src/Media/Font.cpp @@ -303,15 +303,26 @@ namespace Media { return std::tuple { vertices, indices }; } } - -#endif +#else namespace Media { - auto FontAtlas::create(std::filesystem::path path, uint32_t chat_height_px) noexcept -> Utily::Result { + constexpr static auto PRINTABLE_CHARS = []() { + constexpr char first_printable = char(32); + constexpr char last_printable = char(127); + constexpr size_t n = last_printable - first_printable; + std::array chars {}; + std::ranges::copy(std::views::iota(first_printable, last_printable), chars.begin()); + return chars; + }(); + + auto FontAtlas::create(std::filesystem::path path, uint32_t char_height_px) noexcept -> Utily::Result { // 1. Load ttf file from disk. - // 2. Initalise the freetype font face + // 2. Initalise the font face // 3. Generate and cache the bitmap for each glyph. // 4. Determine the most compact atlas dimensions. + // 5. Allocate raw image data. + // 6. Blit each cached glyph bitmap onto the atlas, ensuring the same spanline. + // 7. Create Image and font atlas. // 1. auto file_load_result = Utily::FileReader::load_entire_file(path); @@ -340,44 +351,136 @@ namespace Media { std::vector bitmap; GlyphDimensions dimensions; }; - auto create_cached_glyph = [&](char c) { + auto create_cached_glyph = [&ft_face](char c) -> CachedGlyph { auto glyph_index = FT_Get_Char_Index(ft_face, static_cast(c)); FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_DEFAULT); FT_Render_Glyph(ft_face->glyph, FT_Render_Mode::FT_RENDER_MODE_NORMAL); - auto glyph_bitmap = std::span { + auto ft_bitmap = std::span { ft_face->glyph->bitmap.buffer, ft_face->glyph->bitmap.width * ft_face->glyph->bitmap.rows }; + auto bitmap = std::vector(ft_bitmap.size()); + std::ranges::copy(ft_bitmap, bitmap.begin()); + + auto a = std::span { + ft_face->glyph, static_cast(ft_face->num_glyphs) + }; + return CachedGlyph { .c = c, - .bitmap = { glyph_bitmap.begin(), glyph_bitmap.end() }, - .dimensions = GlyphDimensions { + .bitmap = std::move(bitmap), + .dimensions = { .bitmap_dimensions = { ft_face->glyph->bitmap.width, ft_face->glyph->bitmap.rows }, - .spanline = { ft_face->glyph->bitmap_top }, - .left_padding = { ft_face->glyph->bitmap_left }, - } + .spanline = static_cast(ft_face->glyph->bitmap_top), + .left_padding = static_cast(ft_face->glyph->bitmap_left) } }; }; std::array cached_glyphs; std::transform(PRINTABLE_CHARS.begin(), PRINTABLE_CHARS.end(), cached_glyphs.begin(), create_cached_glyph); // 4. - auto take_max_dimensions = [](AtlasGlyphDimensions&& agg, const CachedGlyph& cg) { - return AtlasGlyphDimensions { - - - // .bitmap_dimensions = { - // std::max(agg.bitmap_dimensions.x, cg.bitmap_dimensions), - // std::max(agg.) - // }, - //.spanline = std::max(cg.spanline, agg.spanline), .left_padding = std::max(cg.left_padding, agg.left_padding), + auto take_max_dimensions = [&](GlyphDimensions&& agg, const CachedGlyph& cg) { + return GlyphDimensions { + .bitmap_dimensions = { + std::max(agg.bitmap_dimensions.x, cg.dimensions.bitmap_dimensions.x), + std::max(agg.bitmap_dimensions.y, cg.dimensions.bitmap_dimensions.y), + }, + .spanline = std::max(agg.spanline, cg.dimensions.spanline), + .left_padding = std::max(agg.left_padding, cg.dimensions.left_padding), }; }; - const auto atlas_glyph_dimensions = std::reduce(cached_glyphs.begin(), cached_glyphs.end(), AtlasGlyphDimensions {}, take_max_dimensions); + auto atlas_info = std::reduce(cached_glyphs.begin(), cached_glyphs.end(), GlyphDimensions {}, take_max_dimensions); + auto take_max_height = [&](uint32_t&& agg, const CachedGlyph& cd) { + return std::max(cd.dimensions.bitmap_dimensions.y + atlas_info.spanline - cd.dimensions.spanline, agg); + }; + atlas_info.bitmap_dimensions.y = std::reduce(cached_glyphs.begin(), cached_glyphs.end(), atlas_info.bitmap_dimensions.y, take_max_height); + + // 5. + const auto [atlas_glyphs_per_row, atlas_num_rows] = [](float glyph_width, float glyph_height, float num_glyphs_in_atlas) { + auto [min_diff, min_x, min_y] = std::tuple { std::numeric_limits::max(), 0, num_glyphs_in_atlas }; + for (float i = 1; i < num_glyphs_in_atlas; ++i) { + auto x = i; + auto y = num_glyphs_in_atlas / i; + x = std::floor(x); + y = std::ceil(y); + auto diff = std::abs((x * glyph_width) - (y * glyph_height)); + if (diff < min_diff && num_glyphs_in_atlas <= x * y) { + min_diff = diff; + min_x = x; + min_y = y; + } + } + return std::tuple { min_x, min_y }; + }(atlas_info.bitmap_dimensions.x, atlas_info.bitmap_dimensions.y, PRINTABLE_CHARS.size()); - return FontAtlas(M { .atlas_image = std::move(image), .atlas_layout = {}, .glyph_dimensions = {} }); + const int atlas_img_height = atlas_info.bitmap_dimensions.y * atlas_num_rows; + const int atlas_img_width = atlas_info.bitmap_dimensions.x * atlas_glyphs_per_row; + + std::vector atlas_buffer; + atlas_buffer.resize(atlas_img_height * atlas_img_width, (uint8_t)0); + + std::cout << atlas_info.bitmap_dimensions.x << ','; + std::cout << atlas_info.bitmap_dimensions.y << '\n'; + + std::cout << atlas_img_width << ','; + std::cout << atlas_img_height << '\n'; + + // 6. + for (std::ptrdiff_t i = 0; i < static_cast(PRINTABLE_CHARS.size()); ++i) { + const auto& bitmap_ft = cached_glyphs[i]; + const auto atlas_coords = glm::ivec2 { i % atlas_glyphs_per_row, i / atlas_glyphs_per_row }; + const auto adjusted_offset = glm::ivec2 { atlas_coords.x * atlas_info.bitmap_dimensions.x, atlas_coords.y * atlas_info.bitmap_dimensions.y }; + + auto get_atlas_buffer_dest = [&](int x, int y) -> uint8_t& { + static int max_y = 0; + if (y > max_y) { + max_y = y; + std::cout << "MAX Y -> " << y << '\n'; + } + const auto px_coords = glm::ivec2(adjusted_offset.x + x, adjusted_offset.y + y); + return atlas_buffer[px_coords.y * atlas_img_width + px_coords.x + bitmap_ft.dimensions.left_padding]; + }; + for (std::ptrdiff_t y = 0; y < bitmap_ft.dimensions.bitmap_dimensions.y; ++y) { + for (std::ptrdiff_t x = 0; x < bitmap_ft.dimensions.bitmap_dimensions.x; ++x) { + // align to relative bitmap + const std::ptrdiff_t relative_y = (y + atlas_info.spanline - bitmap_ft.dimensions.spanline); + const std::ptrdiff_t ft_offset = y * bitmap_ft.dimensions.bitmap_dimensions.x + x; + get_atlas_buffer_dest(x, relative_y) = bitmap_ft.bitmap[ft_offset]; + } + } + } + + // 7. + auto image_result = Media::Image::create( + std::span(atlas_buffer.cbegin(), atlas_buffer.cend()), + glm::uvec2(atlas_img_width, atlas_img_height), + Media::Image::InternalFormat::greyscale); + + if (image_result.has_error()) { + return image_result.error(); + } + + return FontAtlas(M { + .atlas_image = std::move(image_result.value()), + .atlas_layout = { atlas_glyphs_per_row, atlas_num_rows }, + .glyph_dimensions = { + atlas_info.bitmap_dimensions.x, + atlas_info.bitmap_dimensions.y } }); } - auto FontAtlas::uv_for(char c) const noexcept -> glm::vec2 { - return {}; + auto FontAtlas::uv_for(char a) const noexcept -> FontAtlas::UvCoord { + assert(PRINTABLE_CHARS.front() <= a && a <= PRINTABLE_CHARS.back() && "Must be a printable character"); + + const auto i = static_cast(a - PRINTABLE_CHARS.front()); + const float r = static_cast(i / _m.atlas_layout.x); + const float c = static_cast(i % static_cast(_m.atlas_layout.x)); + + return { + .min_x = c / static_cast(_m.atlas_layout.x), + .max_x = (c + 1) / static_cast(_m.atlas_layout.x), + .min_y = 1 - (r + 1) / static_cast(_m.atlas_layout.y), + .max_y = 1 - r / static_cast(_m.atlas_layout.y), + }; } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/code/Engine/src/Media/Image.cpp b/code/Engine/src/Media/Image.cpp index 9f3009e..dcdc589 100644 --- a/code/Engine/src/Media/Image.cpp +++ b/code/Engine/src/Media/Image.cpp @@ -220,22 +220,22 @@ namespace Media { int spng_error = 0; spng_error = spng_set_png_buffer(ctx, encoded_png.data(), encoded_png.size()); if (spng_error) { - return Utily::Error(std::string(png_strerror(spng_error))); + return Utily::Error(std::string(spng_strerror(spng_error))); } size_t data_size_bytes = 0; spng_error = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &data_size_bytes); if (spng_error) { - return Utily::Error(std::string(png_strerror(spng_error))); + return Utily::Error(std::string(spng_strerror(spng_error))); } - auto data = std::make_unique_for_overwrite(image_size); + auto data = std::make_unique_for_overwrite(data_size_bytes); spng_error = spng_decode_image(ctx, data.get(), data_size_bytes, SPNG_FMT_RGBA8, 0); if (spng_error) { - return Utily::Error(std::string(png_strerror(spng_error))); + return Utily::Error(std::string(spng_strerror(spng_error))); } spng_ihdr idhr; - spng_error = spng_get_ihdr(ctx, idhr); + spng_error = spng_get_ihdr(ctx, &idhr); if (spng_error) { - return Utily::Error(std::string(png_strerror(spng_error))); + return Utily::Error(std::string(spng_strerror(spng_error))); } spng_ctx_free(ctx); @@ -253,7 +253,7 @@ namespace Media { // 4. Construct valid Image instance. // 1. - size_t expected_size = = dimensions.x * dimensions.y; + size_t expected_size = dimensions.x * dimensions.y; if (format == InternalFormat::rgba) { expected_size *= 4; } else if (format == InternalFormat::undefined) { @@ -262,14 +262,11 @@ namespace Media { if (expected_size != raw_bytes.size()) { return Utily::Error("The raw data is not the expected size for those dimensions and format"); } - // 2. auto data = std::make_unique_for_overwrite(raw_bytes.size()); auto data_size_bytes = raw_bytes.size(); - // 3. std::uninitialized_copy(raw_bytes.cbegin(), raw_bytes.cend(), data.get()); - // 4. return Image(M { .data = std::move(data), @@ -277,7 +274,7 @@ namespace Media { .dimensions = dimensions, .format = format }); } - + auto Image::opengl_format() const -> uint32_t { switch (_m.format) { case InternalFormat::greyscale: @@ -293,5 +290,26 @@ namespace Media { } Image::Image(Image&& other) - : _m(std::move(m)) { } + : _m(std::move(other._m)) { } + + auto Image::save_to_disk(std::filesystem::path path) const noexcept -> Utily::Result { + Profiler::Timer timer("Media::Image::save_to_disk()"); + + std::vector encoded; + lodepng::State state; + state.info_raw.colortype = LodePNGColorType::LCT_GREY; + { + Profiler::Timer timer("lodepng::encode()"); + if (auto error = lodepng::encode(encoded, _m.data.get(), _m.dimensions.x, _m.dimensions.y, state); error) { + return Utily::Error { std::string("Image.save_to_disk() failed to be converted to png: ") + lodepng_error_text(error) }; + } + } + { + Profiler::Timer timer("lodepng::save_file()"); + if (auto error = lodepng::save_file(encoded, path.string()); error) { + return Utily::Error { std::string("Image.save_to_disk() failed to save: ") + lodepng_error_text(error) }; + } + } + return {}; + } } \ No newline at end of file diff --git a/code/Engine/src/Renderer/FontBatchRenderer.cpp b/code/Engine/src/Renderer/FontBatchRenderer.cpp index 7699fb5..b3e025c 100644 --- a/code/Engine/src/Renderer/FontBatchRenderer.cpp +++ b/code/Engine/src/Renderer/FontBatchRenderer.cpp @@ -2,14 +2,13 @@ namespace Renderer { - constexpr static std::string_view FBR_SHADER_VERT_SRC = "precision highp float;\n" "layout(location = 0) in vec2 l_pos;\n" "layout(location = 1) in vec2 l_uv;\n" - + "out vec2 uv;\n" - + "void main() {\n" " gl_Position = vec4(l_pos, 0, 1.0);\n" " uv = l_uv;\n" @@ -21,9 +20,9 @@ namespace Renderer { "uniform vec4 u_colour;\n" "in vec2 uv;\n" - + "out vec4 FragColor;\n" - + "void main() {\n" " vec2 uv_flipped = vec2(uv.x, 1.0f - uv.y);\n" " float r = texture(u_texture, uv_flipped).r;" @@ -35,21 +34,15 @@ namespace Renderer { "}"; void FontBatchRenderer::load_text_into_vb(const std::string_view& text, glm::vec2 bottom_left, float height_px) { - int v = static_cast(_current_batch_vertices.size()); - _current_batch_vertices.resize(_current_batch_vertices.size() + text.size() * 4); - - Media::FontAtlas font_atlas; - font_atlas.glyph_width = _glyph_dimensions.x; - font_atlas.glyph_height = _glyph_dimensions.y; - font_atlas.columns = _atlas_dimensions.x; - font_atlas.rows = _atlas_dimensions.y; + int v = static_cast(_m.current_batch_vertices.size()); + _m.current_batch_vertices.resize(_m.current_batch_vertices.size() + text.size() * 4); - const float glyph_ratio = _glyph_dimensions.x / _glyph_dimensions.y; + const float glyph_ratio = _m.font_atlas.glyph_dimensions().x / _m.font_atlas.glyph_dimensions().y; for (int t = 0; t < text.size(); ++t, v += 4) { - Vertex* vertices = _current_batch_vertices.data() + v; + Vertex* vertices = _m.current_batch_vertices.data() + v; - const auto uv = font_atlas.uv_coord_of_char(text[t]); + const auto uv = _m.font_atlas.uv_for(text[t]); // translate it by screen coords const float translated_min_x = glyph_ratio * t * height_px + bottom_left.x; @@ -58,10 +51,10 @@ namespace Renderer { const float translated_max_y = height_px + bottom_left.y; // scale it to screen coords [-1, 1] - const float actual_min_x = translated_min_x / _current_batch_config->screen_dimensions.x * 2 - 1; - const float actual_max_x = translated_max_x / _current_batch_config->screen_dimensions.x * 2 - 1; - const float actual_min_y = translated_min_y / _current_batch_config->screen_dimensions.y * 2 - 1; - const float actual_max_y = translated_max_y / _current_batch_config->screen_dimensions.y * 2 - 1; + const float actual_min_x = translated_min_x / _m.current_batch_config->screen_dimensions.x * 2 - 1; + const float actual_max_x = translated_max_x / _m.current_batch_config->screen_dimensions.x * 2 - 1; + const float actual_min_y = translated_min_y / _m.current_batch_config->screen_dimensions.y * 2 - 1; + const float actual_max_y = translated_max_y / _m.current_batch_config->screen_dimensions.y * 2 - 1; vertices[0] = { .position = { actual_min_x, actual_min_y }, @@ -81,60 +74,78 @@ namespace Renderer { }; } } - void FontBatchRenderer::init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas) { + + auto FontBatchRenderer::create(ResourceManager& resource_manager, std::filesystem::path ttf_path) noexcept -> Utily::Result { + + auto font_atlas_result = Media::FontAtlas::create(ttf_path, 300); + if (font_atlas_result.has_error()) { + return font_atlas_result.error(); + } + auto& font_atlas = font_atlas_result.value(); + auto [s_handle, shader] = resource_manager.create_and_init_resource(FBR_SHADER_VERT_SRC, FBR_SHADER_FRAG_SRC); auto [t_handle, texture] = resource_manager.create_and_init_resource(); auto [vb_handle, vertex_buffer] = resource_manager.create_and_init_resource(); auto [ib_handle, index_buffer] = resource_manager.create_and_init_resource(); auto [va_handle, vertex_array] = resource_manager.create_and_init_resource(Vertex::VBL {}, vertex_buffer, index_buffer); - _s = s_handle; - _t = t_handle; - _vb = vb_handle; - _ib = ib_handle; - _va = va_handle; - - texture.upload_image(font_atlas.image).on_error(Panic {}); + auto texture_upload_result = texture.upload_image(font_atlas.atlas_image()); + if (texture_upload_result.has_error()) { + return texture_upload_result.error(); + } - _glyph_dimensions = { font_atlas.glyph_width, font_atlas.glyph_height }; - _atlas_dimensions = { font_atlas.columns, font_atlas.rows }; + return FontBatchRenderer(M { + .current_batch_vertices = {}, + .current_batch_config = std::nullopt, + .font_atlas = std::move(font_atlas), + .s = s_handle, + .t = t_handle, + .vb = vb_handle, + .ib = ib_handle, + .va = va_handle, + }); } + void FontBatchRenderer::begin_batch(BatchConfig&& batch_config) { - assert(!_current_batch_config); - _current_batch_config.emplace(std::move(batch_config)); + assert(!_m.current_batch_config); + _m.current_batch_config.emplace(std::move(batch_config)); } void FontBatchRenderer::push_to_batch(std::string_view text, glm::vec2 bottom_left, float height_px) { Profiler::Timer timer("FontBatchRenderer::push_to_batch()", {}); - - assert(_current_batch_config); + assert(_m.current_batch_config); load_text_into_vb(text, bottom_left, height_px); } void FontBatchRenderer::end_batch() { + // 1. Validate a batch config has been passed in. + // 2. Get resources, bind, set uniforms, load vertices. + // 3. Ensure the index buffer has enough loaded for the vertex buffer. + // 4. Disable depth testing, draw, and then re-enable depth testing. + // 5. Clear batch's config and vertices. + + // 1. Profiler::Timer timer("FontBatchRenderer::end_batch()", {}); - assert(_current_batch_config); - if (_current_batch_vertices.size() == 0) { + assert(_m.current_batch_config); + if (_m.current_batch_vertices.size() == 0) { assert(false && "redundant batching"); return; } - // bind everything - auto [s, t, va, vb, ib] = _current_batch_config->resource_manager.get_resources(_s, _t, _va, _vb, _ib); + // 2. + auto [s, t, va, vb, ib] = _m.current_batch_config->resource_manager.get_resources(_m.s, _m.t, _m.va, _m.vb, _m.ib); const int32_t texture_slot = t.bind().value(); s.bind(); va.bind(); ib.bind(); vb.bind(); - s.set_uniform("u_texture", texture_slot).on_error(Panic{}); - s.set_uniform("u_colour", _current_batch_config->font_colour).on_error(Panic{}); - - // load vertices - vb.load_vertices(_current_batch_vertices); + s.set_uniform("u_texture", texture_slot).on_error(Panic {}); + s.set_uniform("u_colour", _m.current_batch_config->font_colour).on_error(Panic {}); + vb.load_vertices(_m.current_batch_vertices); - // ensure ib has enough capacity/indices for vb - if (ib.get_count() < _current_batch_vertices.size() / 4 * 6) { + // 3. + if (ib.get_count() < _m.current_batch_vertices.size() / 4 * 6) { std::vector indices; - indices.resize(_current_batch_vertices.size() / (size_t)4 * (size_t)6, 0); + indices.resize(_m.current_batch_vertices.size() / (size_t)4 * (size_t)6, 0); for (int v = 0, i = 0; i < indices.size(); i += 6, v += 4) { indices[i + 0] = v + 0; @@ -148,15 +159,16 @@ namespace Renderer { ib.load_indices(indices); } - // gldraw indices = vb.vertices / 4 * 6 + // 4. { Profiler::Timer draw_timer("glDrawElements()", {}); glDisable(GL_DEPTH_TEST); - glDrawElements(GL_TRIANGLES, _current_batch_vertices.size() / 4 * 6, GL_UNSIGNED_INT, (void*)0); + glDrawElements(GL_TRIANGLES, _m.current_batch_vertices.size() / 4 * 6, GL_UNSIGNED_INT, (void*)0); glEnable(GL_DEPTH_TEST); } - _current_batch_config = std::nullopt; - _current_batch_vertices.resize(0); + // 5. + _m.current_batch_config = std::nullopt; + _m.current_batch_vertices.resize(0); } } \ No newline at end of file diff --git a/code/Engine/src/Renderer/FontRenderer.cpp b/code/Engine/src/Renderer/FontRenderer.cpp deleted file mode 100644 index fed0bbe..0000000 --- a/code/Engine/src/Renderer/FontRenderer.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#pragma once -#include "Renderer/FontRenderer.hpp" - -#include "Cameras/Orthographic.hpp" -#include "Core/Core.hpp" -#include "Media/Media.hpp" - -#include -#include - -namespace Renderer { - constexpr static std::string_view SHADER_VERT_SRC = - "precision highp float;\n" - - "layout(location = 0) in vec2 l_pos;\n" - "layout(location = 1) in vec2 l_uv;\n" - - "uniform mat4 u_mvp;\n" - - "out vec2 uv;\n" - - "void main() {\n" - " gl_Position = u_mvp * vec4(l_pos, 0, 1.0);\n" - " uv = l_uv;\n" - "}"; - constexpr static std::string_view SHADER_FRAG_SRC = - "precision highp float;\n" - - "uniform sampler2D u_texture;\n" - "uniform vec4 u_colour;\n" - - "in vec2 uv;\n" - "out vec4 FragColor;\n" - - "void main() {\n" - " vec2 uv_flipped = vec2(uv.x, 1.0f - uv.y);\n" - " float r = texture(u_texture, uv_flipped).r;" - " if(r > 0.1f) {\n" - " FragColor = vec4(u_colour.rgb, r * u_colour.a);\n" - " } else {\n" - " FragColor = vec4(0,0,0,0);" - " }\n" - "}"; - - void FontRenderer::ensure_buffers_have_capacity_for(const size_t N, Core::IndexBuffer& ib) { - Profiler::Timer timer("FontRenderer::ensure_buffers_have_capacity_for()", {}); - - if (_vertices.size() >= (N * 4) || _indices.size() >= (N * 6)) { - return; - } - - _vertices.resize(N * 4); - _indices.resize(N * 6); - - const float glyph_ratio = _glyph_dimensions.x / _glyph_dimensions.y; - constexpr float min_y = 0; - constexpr float max_y = 1; - - for (int i = 0, v = 0; i < _vertices.size(); ++v, i += 4) { - auto vertices = std::span { _vertices.data() + i, 4 }; - - const float min_x = glyph_ratio * v; - const float max_x = min_x + glyph_ratio; - - vertices[0].position = { min_x, min_y }; - vertices[1].position = { max_x, min_y }; - vertices[2].position = { max_x, max_y }; - vertices[3].position = { min_x, max_y }; - } - - for (int i = 0, v = 0; i < _indices.size(); v += 4, i += 6) { - auto indices = _indices.data() + i; - indices[0] = v + 0; - indices[1] = v + 1; - indices[2] = v + 2; - indices[3] = v + 2; - indices[4] = v + 3; - indices[5] = v + 0; - } - ib.load_indices(_indices); - } - - auto FontRenderer::uv_coord_of_char(char a) const noexcept -> Media::FontAtlas::UvCoord { - constexpr auto drawble_chars = Media::FontAtlasConstants::DRAWABLE_CHARS; - - assert(drawble_chars.front() <= a && a <= drawble_chars.back() && "Must be a printable character"); - - const auto i = static_cast(a - drawble_chars.front()); - - const float r = static_cast(i / _atlas_dimensions.x); - const float c = static_cast(i % static_cast(_atlas_dimensions.x)); - - return { - .min_x = c / static_cast(_atlas_dimensions.x), - .max_x = (c + 1) / static_cast(_atlas_dimensions.x), - .min_y = 1 - (r + 1) / static_cast(_atlas_dimensions.y), - .max_y = 1 - r / static_cast(_atlas_dimensions.y), - }; - } - - void FontRenderer::load_text_into_vb(const std::string_view& text, Core::VertexBuffer& vb) { - Profiler::Timer timer("FontRenderer::load_text_into_vb()", {}); - const auto text_hash = std::hash {}(text); - const auto text_size = text.size(); - - if (text_hash == _loaded_text_hash) { - return; - } - - Media::FontAtlas fa {}; - - fa.glyph_width = _glyph_dimensions.x; - fa.glyph_height = _glyph_dimensions.y; - fa.columns = _atlas_dimensions.x; - fa.rows = _atlas_dimensions.y; - - for (int i = 0, v = 0; i < _vertices.size() && v < text.size(); ++v, i += 4) { - auto vertices = std::span { _vertices.data() + i, 4 }; - const auto uv = fa.uv_coord_of_char(text[v]); - - vertices[0].uv_coord = { uv.min_x, uv.min_y }; - vertices[1].uv_coord = { uv.max_x, uv.min_y }; - vertices[2].uv_coord = { uv.max_x, uv.max_y }; - vertices[3].uv_coord = { uv.min_x, uv.max_y }; - } - vb.load_vertices(std::span { _vertices.begin(), _vertices.begin() + (text.size() * 4) }); - } - - bool FontRenderer::is_init() const noexcept { - return _glyph_dimensions.x > 0 && _glyph_dimensions.y > 0 - && _atlas_dimensions.x > 0 && _atlas_dimensions.y > 0; - } - - void FontRenderer::init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas) { - - auto [s_handle, shader] = resource_manager.create_and_init_resource(SHADER_VERT_SRC, SHADER_FRAG_SRC); - auto [t_handle, texture] = resource_manager.create_and_init_resource(); - auto [vb_handle, vertex_buffer] = resource_manager.create_and_init_resource(); - auto [ib_handle, index_buffer] = resource_manager.create_and_init_resource(); - auto [va_handle, vertex_array] = resource_manager.create_and_init_resource(FontVertex::VBL {}, vertex_buffer, index_buffer); - - _s = s_handle; - _t = t_handle; - _vb = vb_handle; - _ib = ib_handle; - _va = va_handle; - - texture.upload_image(font_atlas.image).on_error(Panic {}); - - _glyph_dimensions = { font_atlas.glyph_width, font_atlas.glyph_height }; - _atlas_dimensions = { font_atlas.columns, font_atlas.rows }; - } - - - - void FontRenderer::stop(ResourceManager& resource_manager) { - resource_manager.free_resources(_s, _t, _va, _ib, _vb); - } - - void FontRenderer::draw(ResourceManager& resource_manager, glm::vec2 screen_dimensions, std::string_view text, float char_size_px, glm::vec2 bottom_left, glm::vec4 colour) { - Profiler::Timer timer("FontRenderer::draw()", {}); - assert(is_init()); - - auto [s, t, va, vb, ib] = resource_manager.get_resources(_s, _t, _va, _vb, _ib); - - ensure_buffers_have_capacity_for(text.size(), ib); - load_text_into_vb(text, vb); - - const int32_t texture_slot = t.bind().on_error(Panic {}).value(); - s.bind(); - va.bind(); - ib.bind(); - vb.bind(); - - auto mat = glm::mat4(1.0f); - mat = glm::translate(mat, glm::vec3(bottom_left.x / screen_dimensions.x * 2 - 1, bottom_left.y / screen_dimensions.y * 2 - 1, 0)); - mat = glm::scale(mat, glm::vec3(char_size_px / screen_dimensions.x * 2, char_size_px / screen_dimensions.y * 2, 0.0f)); - - s.set_uniform("u_texture", texture_slot).on_error(Panic {}); - s.set_uniform("u_mvp", mat).on_error(Panic {}); - s.set_uniform("u_colour", colour).on_error(Panic {}); - - assert(text.size() * 6 <= ib.get_count()); - - { - Profiler::Timer draw_timer("glDrawElements()", {}); - glDisable(GL_DEPTH_TEST); - glDrawElements(GL_TRIANGLES, text.size() * 6, GL_UNSIGNED_INT, (void*)0); - glEnable(GL_DEPTH_TEST); - } - } -} \ No newline at end of file