From 34c000ea8ce8e04fe93cdf388fa5ca52afb172e3 Mon Sep 17 00:00:00 2001 From: life4gal Date: Thu, 10 Apr 2025 15:17:12 +0800 Subject: [PATCH 01/54] =?UTF-8?q?`feat`:=20gui=20(window=20only).=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 37 +- src/draw/context.cpp | 352 ------ src/draw/context.hpp | 185 --- src/draw/def.hpp | 125 -- src/draw/draw.hpp | 14 - src/draw/draw_list.cpp | 1857 ------------------------------ src/draw/draw_list.hpp | 569 --------- src/draw/flag.hpp | 162 --- src/draw/font.cpp | 548 --------- src/draw/font.hpp | 252 ---- src/draw/mouse.cpp | 97 -- src/draw/mouse.hpp | 69 -- src/draw/shared_data.cpp | 165 --- src/draw/shared_data.hpp | 63 - src/draw/theme.cpp | 126 -- src/draw/theme.hpp | 63 - src/draw/window.cpp | 648 ----------- src/draw/window.hpp | 151 --- src/gui/gui.hpp | 462 ++++++++ src/gui/internal/common.cpp | 166 +++ src/gui/internal/common.hpp | 105 ++ src/gui/internal/context.cpp | 862 ++++++++++++++ src/gui/internal/context.hpp | 174 +++ src/gui/internal/draw_list.cpp | 1970 ++++++++++++++++++++++++++++++++ src/gui/internal/draw_list.hpp | 492 ++++++++ src/gui/internal/font.cpp | 553 +++++++++ src/gui/internal/font.hpp | 97 ++ src/gui/internal/mouse.cpp | 143 +++ src/gui/internal/mouse.hpp | 62 + src/gui/internal/window.cpp | 1152 +++++++++++++++++++ src/gui/internal/window.hpp | 299 +++++ 31 files changed, 6554 insertions(+), 5466 deletions(-) delete mode 100644 src/draw/context.cpp delete mode 100644 src/draw/context.hpp delete mode 100644 src/draw/def.hpp delete mode 100644 src/draw/draw.hpp delete mode 100644 src/draw/draw_list.cpp delete mode 100644 src/draw/draw_list.hpp delete mode 100644 src/draw/flag.hpp delete mode 100644 src/draw/font.cpp delete mode 100644 src/draw/font.hpp delete mode 100644 src/draw/mouse.cpp delete mode 100644 src/draw/mouse.hpp delete mode 100644 src/draw/shared_data.cpp delete mode 100644 src/draw/shared_data.hpp delete mode 100644 src/draw/theme.cpp delete mode 100644 src/draw/theme.hpp delete mode 100644 src/draw/window.cpp delete mode 100644 src/draw/window.hpp create mode 100644 src/gui/gui.hpp create mode 100644 src/gui/internal/common.cpp create mode 100644 src/gui/internal/common.hpp create mode 100644 src/gui/internal/context.cpp create mode 100644 src/gui/internal/context.hpp create mode 100644 src/gui/internal/draw_list.cpp create mode 100644 src/gui/internal/draw_list.hpp create mode 100644 src/gui/internal/font.cpp create mode 100644 src/gui/internal/font.hpp create mode 100644 src/gui/internal/mouse.cpp create mode 100644 src/gui/internal/mouse.hpp create mode 100644 src/gui/internal/window.cpp create mode 100644 src/gui/internal/window.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index 6eeccc7..cd7c2a7 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -257,6 +257,8 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/memory/rw.hpp + ${PROJECT_SOURCE_DIR}/src/memory/unique_ptr.hpp + ${PROJECT_SOURCE_DIR}/src/memory/reference_wrapper.hpp ${PROJECT_SOURCE_DIR}/src/memory/memory.hpp @@ -356,20 +358,16 @@ set( ${PROJECT_SOURCE_DIR}/src/unit_test/unit_test.hpp # ========================= - # DRAW + # GUI # ========================= - ${PROJECT_SOURCE_DIR}/src/draw/flag.hpp - ${PROJECT_SOURCE_DIR}/src/draw/def.hpp - ${PROJECT_SOURCE_DIR}/src/draw/font.hpp - ${PROJECT_SOURCE_DIR}/src/draw/shared_data.hpp - ${PROJECT_SOURCE_DIR}/src/draw/theme.hpp - ${PROJECT_SOURCE_DIR}/src/draw/mouse.hpp - ${PROJECT_SOURCE_DIR}/src/draw/draw_list.hpp - ${PROJECT_SOURCE_DIR}/src/draw/window.hpp - ${PROJECT_SOURCE_DIR}/src/draw/context.hpp - - ${PROJECT_SOURCE_DIR}/src/draw/draw.hpp + ${PROJECT_SOURCE_DIR}/src/gui/gui.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/common.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/font.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/draw_list.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/mouse.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/window.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/context.hpp ) set( @@ -404,16 +402,15 @@ set( ${PROJECT_SOURCE_DIR}/src/chars/icelake.cpp # ========================= - # DRAW + # GUI # ========================= - ${PROJECT_SOURCE_DIR}/src/draw/font.cpp - ${PROJECT_SOURCE_DIR}/src/draw/shared_data.cpp - ${PROJECT_SOURCE_DIR}/src/draw/theme.cpp - ${PROJECT_SOURCE_DIR}/src/draw/mouse.cpp - ${PROJECT_SOURCE_DIR}/src/draw/draw_list.cpp - ${PROJECT_SOURCE_DIR}/src/draw/window.cpp - ${PROJECT_SOURCE_DIR}/src/draw/context.cpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/common.cpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/font.cpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/draw_list.cpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/mouse.cpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/window.cpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/context.cpp ) set_source_files_properties( diff --git a/src/draw/context.cpp b/src/draw/context.cpp deleted file mode 100644 index e55d341..0000000 --- a/src/draw/context.cpp +++ /dev/null @@ -1,352 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -namespace {} - -namespace gal::prometheus::draw -{ - auto Context::find_window(const Window& window) const noexcept -> index_type - { - const auto it = std::ranges::find( - windows_ | std::views::reverse, - std::addressof(window), - [](const auto& w) noexcept -> const auto* { - return std::addressof(w); - } - ); - - const auto it_base = it.base(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_base != std::ranges::end(windows_)); - return std::ranges::distance(std::ranges::begin(windows_), it_base); - } - - Context::Context() noexcept - : - // default_draw_list_shared_data_{}, - // font_default_{}, - theme_default_{Theme::default_theme()}, - draw_list_shared_data_stack_{}, - font_stack_{}, - theme_stack_{}, - draw_list_shared_data_current_{stack_use_default}, - font_current_{stack_use_default}, - theme_current_{stack_use_default}, - pad_{0}, - mouse_{.3f, 36}, - window_current_{nullptr}, - window_hovered_{nullptr}, - widget_id_hovered_{invalid_id}, - widget_id_activated_{invalid_id} - { - std::ignore = pad_; - } - - auto Context::instance() noexcept -> Context& - { - static Context context{}; - return context; - } - - auto Context::draw_list_shared_data() const noexcept -> const DrawListSharedData& - { - if (draw_list_shared_data_current_ == stack_use_default) - { - return draw_list_shared_data_default_; - } - - const auto* p = draw_list_shared_data_stack_[draw_list_shared_data_current_]; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p != nullptr); - - return *p; - } - - auto Context::push_draw_list_shared_data(DrawListSharedData& shared_data) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data_current_ == stack_use_default or draw_list_shared_data_current_ < draw_list_shared_data_stack_size); - - draw_list_shared_data_current_ += 1; - draw_list_shared_data_stack_[draw_list_shared_data_current_] = std::addressof(shared_data); - } - - auto Context::pop_draw_list_shared_data() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data_current_ != stack_use_default, "Unable to popup the default DrawListSharedData!"); - - draw_list_shared_data_stack_[draw_list_shared_data_current_] = nullptr; - if (draw_list_shared_data_current_ == 0) - { - draw_list_shared_data_current_ = stack_use_default; - } - else - { - draw_list_shared_data_current_ -= 1; - } - } - - auto Context::set_default_font(font_type font) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font != nullptr); - - font_default_ = std::move(font); - } - - auto Context::font() const noexcept -> const Font& - { - if (font_current_ == stack_use_default) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_default_ != nullptr); - - return *font_default_; - } - - const auto* p = font_stack_[font_current_]; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p->get() != nullptr); - - return **p; - } - - auto Context::push_font(font_type& font) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_current_ == stack_use_default or font_current_ < font_stack_size); - - font_current_ += 1; - font_stack_[font_current_] = std::addressof(font); - } - - auto Context::pop_font() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_current_ != stack_use_default, "Unable to popup the default Font!"); - - font_stack_[font_current_] = nullptr; - if (font_current_ == 0) - { - font_current_ = stack_use_default; - } - else - { - font_current_ -= 1; - } - } - - auto Context::theme() const noexcept -> const Theme& - { - if (theme_current_ == stack_use_default) - { - return theme_default_; - } - - const auto* p = theme_stack_[theme_current_]; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p != nullptr); - - return *p; - } - - auto Context::push_theme(Theme& theme) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(theme_current_== stack_use_default or theme_current_ < theme_stack_size); - - theme_current_ += 1; - theme_stack_[theme_current_] = std::addressof(theme); - } - - auto Context::pop_theme() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(theme_current_ != stack_use_default, "Unable to popup the default Theme!"); - - theme_stack_[theme_current_] = nullptr; - if (theme_current_ == 0) - { - theme_current_ = stack_use_default; - } - else - { - theme_current_ -= 1; - } - } - - auto Context::tooltip() const noexcept -> std::string_view - { - return tooltip_; - } - - auto Context::mouse() const noexcept -> const Mouse& - { - return mouse_; - } - - auto Context::is_widget_hovered(const id_type id) const noexcept -> bool - { - return widget_id_hovered_ == id; - } - - auto Context::is_widget_activated(const id_type id) const noexcept -> bool - { - return widget_id_activated_ == id; - } - - auto Context::invalidate_widget_hovered( - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - const std::string& reason, - const std::source_location& location - #endif - ) noexcept -> void - { - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - (void)reason; - (void)location; - #endif - - widget_id_hovered_ = Window::invalid_id; - } - - auto Context::invalidate_widget_activated( - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - const std::string& reason, - const std::source_location& location - #endif - ) noexcept -> void - { - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - (void)reason; - (void)location; - #endif - - widget_id_activated_ = Window::invalid_id; - } - - auto Context::test_widget_status( - const id_type id, - const rect_type& widget_rect, - const bool repeat - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - , - const std::string& reason - #endif - ) noexcept -> widget_status_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window_current_ != nullptr); - - auto& window = *window_current_; - const rect_type rect{widget_rect.point + window.rect().left_top(), widget_rect.extent}; - const auto hovered = window_hovered_ == std::addressof(window) and rect.includes(mouse_.position()); - - widget_status_type widget_status{.hovered = hovered, .pressed = false, .keeping = false}; - if (hovered) - { - widget_id_hovered_ = id; - if (mouse_.clicked()) - { - widget_id_activated_ = id; - } - else if (repeat and widget_id_activated_ != Window::invalid_id) - { - widget_status.pressed = true; - } - } - - if (widget_id_activated_ == id) - { - if (mouse_.down()) - { - widget_status.keeping = true; - } - else - { - if (hovered) - { - widget_status.pressed = true; - } - - invalidate_widget_activated( - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - std::format("Release mouse on widget #{}.({})", id, reason) - #endif - ); - } - } - - return widget_status; - } - - auto Context::find_window(const std::string_view name) const noexcept -> std::optional> - { - const auto it = std::ranges::find_if( - windows_, - [name](const auto& window) noexcept -> bool - { - return name == window.name(); - } - ); - - if (it == std::ranges::end(windows_)) - { - return std::nullopt; - } - - return std::make_optional(std::cref(*it)); - } - - auto Context::new_frame() noexcept -> void - { - tooltip_[0] = '\0'; - - // todo - mouse_.tick(1 / 60.f); - - std::ranges::for_each( - windows_ | std::views::reverse, - [this](auto& window) noexcept -> void - { - if (window.hovered(mouse_.position())) - { - window_hovered_ = std::addressof(window); - } - } - ); - - if (mouse_.clicked_) - { - if (window_hovered_ != nullptr) - { - const auto index = find_window(*window_hovered_); - const auto it = windows_.begin() + index; - - auto&& window = std::move(windows_[index]); - windows_.erase(it); - windows_.emplace_back(std::move(window)); - } - } - } - - auto Context::render() noexcept -> void - { - window_draw_lists_.clear(); - std::ranges::transform( - windows_, - std::back_inserter(window_draw_lists_), - [](auto& window) noexcept -> auto - { - return std::ref(window.render()); - } - ); - - if (window_current_ != nullptr and tooltip_[0] != '\0') - { - const auto index = find_window(*window_current_); - - auto& draw_list = window_draw_lists_[index].get(); - const auto& theme = this->theme(); - - // todo - const rect_type tooltip_rect{mouse_.position(), extent_type{100, 100}}; - draw_list.rect_filled(tooltip_rect, theme.color()); - draw_list.text(theme.font_size, mouse_.position(), theme.color(), tooltip_); - } - } -} diff --git a/src/draw/context.hpp b/src/draw/context.hpp deleted file mode 100644 index 429e804..0000000 --- a/src/draw/context.hpp +++ /dev/null @@ -1,185 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace gal::prometheus::draw -{ - class Context final - { - public: - template - using container_type = DrawListDef::container_type; - - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using font_type = std::shared_ptr; - - constexpr static std::size_t draw_list_shared_data_stack_size = 8; - constexpr static std::size_t font_stack_size = 8; - constexpr static std::size_t theme_stack_size = 8; - - using id_type = Window::id_type; - constexpr static auto invalid_id = Window::invalid_id; - - using windows_type = container_type; - using index_type = windows_type::difference_type; - - private: - using stack_pointer_type = std::uint8_t; - constexpr static auto stack_use_default = std::numeric_limits::max(); - - // ---------------------------------------------------------------------- - // DrawListSharedData + Font + Theme - - DrawListSharedData draw_list_shared_data_default_; - font_type font_default_; - Theme theme_default_; - - DrawListSharedData* draw_list_shared_data_stack_[draw_list_shared_data_stack_size]; - font_type* font_stack_[font_stack_size]; - Theme* theme_stack_[theme_stack_size]; - - stack_pointer_type draw_list_shared_data_current_; - stack_pointer_type font_current_; - stack_pointer_type theme_current_; - stack_pointer_type pad_; - - // ---------------------------------------------------------------------- - // TOOLTIP - - std::string tooltip_; - - // ---------------------------------------------------------------------- - // MOUSE - - Mouse mouse_; - - // ---------------------------------------------------------------------- - // WINDOW - - windows_type windows_; - Window* window_current_; - Window* window_hovered_; - - id_type widget_id_hovered_; - id_type widget_id_activated_; - - container_type> window_draw_lists_; - - [[nodiscard]] auto find_window(const Window& window) const noexcept -> index_type; - - Context() noexcept; - - public: - // --------------------------------------------- - // SINGLETON - - [[nodiscard]] static auto instance() noexcept -> Context&; - - // --------------------------------------------- - // DRAW LIST SHARED DATA - - [[nodiscard]] auto draw_list_shared_data() const noexcept -> const DrawListSharedData&; - - auto push_draw_list_shared_data(DrawListSharedData& shared_data) noexcept -> void; - - auto pop_draw_list_shared_data() noexcept -> void; - - // --------------------------------------------- - // FONT - - auto set_default_font(font_type font) noexcept -> void; - - [[nodiscard]] auto font() const noexcept -> const Font&; - - auto push_font(font_type& font) noexcept -> void; - - auto pop_font() noexcept -> void; - - // --------------------------------------------- - // THEME - - [[nodiscard]] auto theme() const noexcept -> const Theme&; - - auto push_theme(Theme& theme) noexcept -> void; - - auto pop_theme() noexcept -> void; - - // --------------------------------------------- - // TOOLTIP - - [[nodiscard]] auto tooltip() const noexcept -> std::string_view; - - // --------------------------------------------- - // MOUSE - - [[nodiscard]] auto mouse() const noexcept -> const Mouse&; - - // --------------------------------------------- - // WINDOW & WIDGET - - [[nodiscard]] auto is_widget_hovered(id_type id) const noexcept -> bool; - - [[nodiscard]] auto is_widget_activated(id_type id) const noexcept -> bool; - - auto invalidate_widget_hovered( - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - const std::string& reason, - const std::source_location& location = std::source_location::current() - #endif - ) noexcept -> void; - - auto invalidate_widget_activated( - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - const std::string& reason, - const std::source_location& location = std::source_location::current() - #endif - ) noexcept -> void; - - struct widget_status_type - { - bool hovered; - bool pressed; - bool keeping; - }; - - [[nodiscard]] auto test_widget_status( - id_type id, - const rect_type& widget_rect, - bool repeat - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - , - const std::string& reason - #endif - ) noexcept -> widget_status_type; - - [[nodiscard]] auto find_window(std::string_view name) const noexcept -> std::optional>; - - // --------------------------------------------- - // RENDER - - auto new_frame() noexcept -> void; - - auto render() noexcept -> void; - - // --------------------------------------------- - // for test only - - auto test_set_window(Window& window) noexcept -> void - { - window_current_ = &window; - } - }; -} diff --git a/src/draw/def.hpp b/src/draw/def.hpp deleted file mode 100644 index 11c615d..0000000 --- a/src/draw/def.hpp +++ /dev/null @@ -1,125 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include -#include -#include - -namespace gal::prometheus::draw -{ - class DrawListDef - { - public: - template - using container_type = std::vector; - - // ---------------------------------------------------- - - using rect_type = primitive::basic_rect_2d; - using point_type = rect_type::point_type; - using extent_type = rect_type::extent_type; - - using circle_type = primitive::basic_circle_2d; - using ellipse_type = primitive::basic_ellipse_2d; - - // ---------------------------------------------------- - - using uv_type = primitive::basic_point_2d; - using color_type = primitive::basic_color; - using vertex_type = primitive::basic_vertex; - using index_type = std::uint16_t; - - // ---------------------------------------------------- - - using path_list_type = container_type; - using vertex_list_type = container_type; - using index_list_type = container_type; - - // ---------------------------------------------------- - - using texture_id_type = std::uintptr_t; - using size_type = vertex_list_type::size_type; - - struct command_type - { - rect_type clip_rect; - texture_id_type texture_id; - - // ======================= - - // set by DrawList::index_list.size() - // start offset in `DrawList::index_list` - size_type index_offset; - // set by subsequent `DrawList::draw_xxx` - // number of indices (multiple of 3) to be rendered as triangles - size_type element_count; - }; - - using command_list_type = container_type; - - // ---------------------------------------------------- - - class Accessor - { - // command_type::element_count - std::reference_wrapper element_count_; - // DrawList::vertex_list - std::reference_wrapper vertex_list_; - // DrawList::index_list_; - std::reference_wrapper index_list_; - - public: - constexpr Accessor(command_type& command, vertex_list_type& vertex_list, index_list_type& index_list) noexcept - : element_count_{command.element_count}, - vertex_list_{vertex_list}, - index_list_{index_list} {} - - Accessor(const Accessor& other) = delete; - Accessor(Accessor&& other) noexcept = delete; - auto operator=(const Accessor& other) -> Accessor& = delete; - auto operator=(Accessor&& other) noexcept -> Accessor& = delete; - - ~Accessor() noexcept = default; - - constexpr auto reserve(const size_type vertex_count, const size_type index_count) const noexcept -> void - { - auto& element_count = element_count_.get(); - auto& vertex = vertex_list_.get(); - auto& index = index_list_.get(); - - element_count += index_count; - vertex.reserve(vertex.size() + vertex_count); - index.reserve(index.size() + index_count); - } - - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - const auto& list = vertex_list_.get(); - - return list.size(); - } - - constexpr auto add_vertex(const point_type& position, const uv_type& uv, const color_type& color) const noexcept -> void - { - auto& list = vertex_list_.get(); - - list.emplace_back(position, uv, color); - } - - constexpr auto add_index(const index_type a, const index_type b, const index_type c) const noexcept -> void - { - auto& list = index_list_.get(); - - list.push_back(a); - list.push_back(b); - list.push_back(c); - } - }; - }; -} diff --git a/src/draw/draw.hpp b/src/draw/draw.hpp deleted file mode 100644 index a30a862..0000000 --- a/src/draw/draw.hpp +++ /dev/null @@ -1,14 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include diff --git a/src/draw/draw_list.cpp b/src/draw/draw_list.cpp deleted file mode 100644 index b8290bf..0000000 --- a/src/draw/draw_list.cpp +++ /dev/null @@ -1,1857 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. -#include - -#include -#include - -#include -#include -#include - -namespace -{ - using namespace gal::prometheus; - - [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const draw::DrawFlag flag) noexcept -> draw::DrawFlag - { - using enum draw::DrawFlag; - - if ((flag & ROUND_CORNER_MASK) == NONE) - { - return ROUND_CORNER_ALL | flag; - } - - return flag; - } - - [[nodiscard]] constexpr auto to_fixed_normal(const float x, const float y) noexcept -> std::pair - { - if (const auto d = math::pow(x, 2) + math::pow(y, 2); - d > 1e-6f) - { - // fixme - const auto inv_len = [d] - { - // #if defined(__AVX512F__) - // __m512 d_v = _mm512_set1_ps(d); - // __m512 inv_len_v = _mm512_rcp14_ps(d_v); - // return _mm512_cvtss_f32(inv_len_v); - // #elif defined(__AVX__) - // __m256 d_v = _mm256_set1_ps(d); - // __m256 inv_len_v = _mm256_rcp_ps(d_v); - // return _mm256_cvtss_f32(inv_len_v); - // #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) - // __m128 d_v = _mm_set_ss(d); - // __m128 inv_len_v = _mm_rcp_ss(d_v); - // return _mm_cvtss_f32(inv_len_v); - // #else - return 1.0f / d; - // #endif - }(); - - return {x * inv_len, y * inv_len}; - } - - return {x, y}; - } - - // fixme - constexpr std::size_t bezier_curve_casteljau_max_level = 10; - - using point_type = draw::DrawList::point_type; - - constexpr auto bezier_cubic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const float tolerance) noexcept -> point_type - { - const auto u = 1.f - tolerance; - - const auto w1 = math::pow(u, 3); - const auto w2 = 3 * math::pow(u, 2) * tolerance; - const auto w3 = 3 * u * math::pow(tolerance, 2); - const auto w4 = math::pow(tolerance, 3); - - return - { - p1.x * w1 + p2.x * w2 + p3.x * w3 + p4.x * w4, - p1.y * w1 + p2.y * w2 + p3.y * w3 + p4.y * w4 - }; - }; - - constexpr auto bezier_quadratic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const float tolerance) noexcept -> point_type - { - const auto u = 1.f - tolerance; - - const auto w1 = math::pow(u, 2); - const auto w2 = 2 * u * tolerance; - const auto w3 = math::pow(tolerance, 2); - - return - { - p1.x * w1 + p2.x * w2 + p3.x * w3, - p1.y * w1 + p2.y * w2 + p3.y * w3 - }; - }; -} - -namespace gal::prometheus::draw -{ - auto DrawList::make_accessor() noexcept -> DrawListDef::Accessor - { - auto& [command_list, vertex_list, index_list] = private_data_; - - return - { - command_list.back(), - vertex_list, - index_list - }; - } - - auto DrawList::push_command() noexcept -> void - { - // fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); - - auto& [command_list, _, index_list] = private_data_; - - command_list.emplace_back( - command_type - { - .clip_rect = this_command_clip_rect_, - .texture_id = this_command_texture_id_, - .index_offset = index_list.size(), - // set by subsequent draw_xxx - .element_count = 0 - } - ); - } - - auto DrawList::on_element_changed(const ChangedElement element) noexcept -> void - { - auto& [command_list, vertex_list, index_list] = private_data_; - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - - const auto command_count = command_list.size(); - const auto& [current_clip_rect, current_texture_id, current_index_offset, current_element_count] = command_list.back(); - - if (current_element_count != 0) - { - if (element == ChangedElement::CLIP_RECT) - { - if (current_clip_rect != this_command_clip_rect_) - { - push_command(); - - return; - } - } - else if (element == ChangedElement::TEXTURE_ID) - { - if (current_texture_id != this_command_texture_id_) - { - push_command(); - - return; - } - } - else - { - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); - } - } - - // try to merge with previous command if it matches, else use current command - if (command_count > 1) - { - if ( - const auto& [previous_clip_rect, previous_texture, previous_index_offset, previous_element_count] = command_list[command_count - 2]; - current_element_count == 0 and - ( - this_command_clip_rect_ == previous_clip_rect and - this_command_texture_id_ == previous_texture - ) and - // sequential - current_index_offset == previous_index_offset + previous_element_count - ) - { - command_list.pop_back(); - return; - } - } - - if (element == ChangedElement::CLIP_RECT) - { - command_list.back().clip_rect = this_command_clip_rect_; - } - else if (element == ChangedElement::TEXTURE_ID) - { - command_list.back().texture_id = this_command_texture_id_; - } - else - { - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); - } - } - - auto DrawList::draw_polygon_line(const color_type& color, const DrawFlag draw_flag, const float thickness) noexcept -> void - { - const auto& font = Context::instance().font(); - - const auto accessor = make_accessor(); - - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 2 or color.alpha == 0) - { - return; - } - - const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; - const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - - const auto vertex_count = segments_count * 4; - const auto index_count = segments_count * 6; - accessor.reserve(vertex_count, index_count); - - for (std::decay_t i = 0; i < segments_count; ++i) - { - const auto n = (i + 1) % path_point_count; - - const auto& p1 = path_point[i]; - const auto& p2 = path_point[n]; - - auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); - normalized_x *= (thickness * .5f); - normalized_y *= (thickness * .5f); - - const auto current_vertex_index = static_cast(accessor.size()); - const auto& opaque_uv = font.white_pixel_uv(); - - accessor.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - accessor.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - accessor.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - accessor.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - - accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - } - } - - auto DrawList::draw_polygon_line_aa(const color_type& color, const DrawFlag draw_flag, float thickness) noexcept -> void - { - const auto& font = Context::instance().font(); - - const auto accessor = make_accessor(); - - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 2 or color.alpha == 0) - { - return; - } - - const auto& opaque_uv = font.white_pixel_uv(); - const auto transparent_color = color.transparent(); - - const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; - const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - const auto is_thick_line = thickness > 1.f; - - thickness = std::ranges::max(thickness, 1.f); - const auto thickness_integer = static_cast(thickness); - const auto thickness_fractional = thickness - static_cast(thickness_integer); - - const auto is_use_texture = - ( - (draw_list_flag_ & DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and - (thickness_integer < font.baked_line_max_width()) and - (thickness_fractional <= .00001f)); - - const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); - const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); - accessor.reserve(vertex_cont, index_count); - - // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - container_type temp_buffer{}; - temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); - auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; - - // Calculate normals (tangents) for each line segment - for (std::decay_t i = 0; i < segments_count; ++i) - { - const auto n = (i + 1) % path_point_count; - const auto d = path_point[n] - path_point[i]; - - const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); - temp_buffer_normals[i].x = normalized_y; - temp_buffer_normals[i].y = -normalized_x; - } - - if (not is_closed) - { - temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; - } - - // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point - if (is_use_texture or not is_thick_line) - { - // [PATH 1] Texture-based lines (thick or non-thick) - - // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA - const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; - - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (not is_closed) - { - temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; - temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; - temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; - temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; - } - - const auto current_vertex_index = static_cast(accessor.size()); - - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - auto vertex_index_for_start = current_vertex_index; - for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - { - const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - const auto vertex_index_for_end = static_cast( - // closed - (first_point_of_segment + 1) == path_point_count - ? current_vertex_index - : (vertex_index_for_start + (is_use_texture ? 2 : 3)) - ); - - // Average normals - const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - // dm_x, dm_y are offset to the outer edge of the AA area - auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - dm_x *= half_draw_size; - dm_y *= half_draw_size; - - // Add temporary vertexes for the outer edges - temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; - temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; - - if (is_use_texture) - { - // Add indices for two triangles - - // right - accessor.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); - // left - accessor.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); - } - else - { - // Add indexes for four triangles - - // right 1 - accessor.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); - // right 2 - accessor.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); - // left 1 - accessor.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); - // left 2 - accessor.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); - } - - vertex_index_for_start = vertex_index_for_end; - } - - // Add vertexes for each point on the line - if (is_use_texture) - { - GAL_PROMETHEUS_ERROR_ASSUME(not font.baked_line_uv().empty(), "draw::FontAtlasFlag::NO_BAKED_LINE"); - - const auto& uv = font.baked_line_uv()[thickness_integer]; - - const auto uv0 = uv.left_top(); - const auto uv1 = uv.right_bottom(); - for (std::decay_t i = 0; i < path_point_count; ++i) - { - // left-side outer edge - accessor.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); - // right-side outer edge - accessor.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); - } - } - else - { - // If we're not using a texture, we need the center vertex as well - for (std::decay_t i = 0; i < path_point_count; ++i) - { - // center of line - accessor.add_vertex(path_point[i], opaque_uv, color); - // left-side outer edge - accessor.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); - // right-side outer edge - accessor.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); - } - } - } - else - { - // [PATH 2] Non-texture-based lines (non-thick) - - // we need to draw the solid line core and thus require four vertices per point - const auto half_inner_thickness = (thickness - 1.f) * .5f; - - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (not is_closed) - { - const auto point_last = path_point_count - 1; - temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); - temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); - temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); - temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); - temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - } - - const auto current_vertex_index = static_cast(accessor.size()); - - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - auto vertex_index_for_start = current_vertex_index; - for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - { - const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - const auto vertex_index_for_end = static_cast( - (first_point_of_segment + 1) == path_point_count - ? current_vertex_index - : (vertex_index_for_start + 4) - ); - - // Average normals - const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); - const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); - const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); - const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); - - // Add temporary vertices - temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; - temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; - temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; - temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; - - // Add indexes - accessor.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); - accessor.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); - accessor.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); - accessor.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); - accessor.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); - accessor.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); - - vertex_index_for_start = vertex_index_for_end; - } - - // Add vertices - for (std::decay_t i = 0; i < path_point_count; ++i) - { - accessor.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); - accessor.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); - accessor.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); - accessor.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); - } - } - } - - auto DrawList::draw_convex_polygon_line_filled(const color_type& color) noexcept -> void - { - const auto& font = Context::instance().font(); - - const auto accessor = make_accessor(); - - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 3 or color.alpha == 0) - { - return; - } - - const auto vertex_count = path_point_count; - const auto index_count = (path_point_count - 2) * 3; - accessor.reserve(vertex_count, index_count); - - const auto current_vertex_index = static_cast(accessor.size()); - const auto& opaque_uv = font.white_pixel_uv(); - - std::ranges::for_each( - path_point, - [&](const point_type& point) noexcept -> void - { - accessor.add_vertex(point, opaque_uv, color); - } - ); - for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); - - accessor.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); - } - } - - auto DrawList::draw_convex_polygon_line_filled_aa(const color_type& color) noexcept -> void - { - const auto& font = Context::instance().font(); - - const auto accessor = make_accessor(); - - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 3 or color.alpha == 0) - { - return; - } - - const auto& opaque_uv = font.white_pixel_uv(); - const auto transparent_color = color.transparent(); - - const auto vertex_count = path_point_count * 2; - const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; - accessor.reserve(vertex_count, index_count); - - const auto current_vertex_inner_index = static_cast(accessor.size()); - const auto current_vertex_outer_index = static_cast(accessor.size() + 1); - - // Add indexes for fill - for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); - - accessor.add_index( - current_vertex_inner_index + 0, - current_vertex_inner_index + static_cast((i - 1) << 1), - current_vertex_inner_index + static_cast(i << 1) - ); - } - - container_type temp_buffer{}; - temp_buffer.resize(path_point_count); - auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - - for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - { - const auto d = path_point[n] - path_point[i]; - - const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); - temp_buffer_normals[i].x = normalized_y; - temp_buffer_normals[i].y = -normalized_x; - } - for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - { - // Average normals - const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; - auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - dm_x *= .5f; - dm_y *= .5f; - - // inner - accessor.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); - // outer - accessor.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); - - // Add indexes for fringes - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); - - accessor.add_index( - current_vertex_inner_index + static_cast(n << 1), - current_vertex_inner_index + static_cast(i << 1), - current_vertex_outer_index + static_cast(i << 1) - ); - accessor.add_index( - current_vertex_outer_index + static_cast(i << 1), - current_vertex_outer_index + static_cast(n << 1), - current_vertex_inner_index + static_cast(n << 1) - ); - } - } - - auto DrawList::draw_rect_filled( - const rect_type& rect, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void - { - const auto& font = Context::instance().font(); - - const auto accessor = make_accessor(); - - // two triangle without path - constexpr auto vertex_count = 4; - constexpr auto index_count = 6; - accessor.reserve(vertex_count, index_count); - - const auto& opaque_uv = font.white_pixel_uv(); - - const auto current_vertex_index = static_cast(accessor.size()); - - accessor.add_vertex(rect.left_top(), opaque_uv, color_left_top); - accessor.add_vertex(rect.right_top(), opaque_uv, color_right_top); - accessor.add_vertex(rect.right_bottom(), opaque_uv, color_right_bottom); - accessor.add_vertex(rect.left_bottom(), opaque_uv, color_left_bottom); - - accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - } - - auto DrawList::draw_text( - const Font& font, - const float font_size, - const point_type& p, - const color_type& color, - const std::string_view utf8_text, - const float wrap_width - ) noexcept -> void - { - const auto new_texture = this_command_texture_id_ != font.texture_id(); - - if (new_texture) - { - push_texture_id(font.texture_id()); - } - - font.text_draw(utf8_text, font_size, wrap_width, p, color, make_accessor()); - - if (new_texture) - { - pop_texture_id(); - } - } - - auto DrawList::draw_image( - const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - const color_type& color - ) noexcept -> void - { - const auto new_texture = this_command_texture_id_ != texture_id; - - if (new_texture) - { - push_texture_id(texture_id); - } - - const auto accessor = make_accessor(); - - // two triangle without path - constexpr auto vertex_count = 4; - constexpr auto index_count = 6; - accessor.reserve(vertex_count, index_count); - - const auto current_vertex_index = static_cast(accessor.size()); - - accessor.add_vertex(display_p1, uv_p1, color); - accessor.add_vertex(display_p2, uv_p2, color); - accessor.add_vertex(display_p3, uv_p3, color); - accessor.add_vertex(display_p4, uv_p4, color); - - accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - - if (new_texture) - { - pop_texture_id(); - } - } - - auto DrawList::draw_image_rounded( - const texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - const color_type& color, - float rounding, - DrawFlag flag - ) noexcept -> void - { - // @see `path_rect` - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); - - if (rounding >= .5f) - { - flag = to_fixed_rect_corner_flag(flag); - - const auto v = - (flag & DrawFlag::ROUND_CORNER_TOP) == DrawFlag::ROUND_CORNER_TOP or - (flag & DrawFlag::ROUND_CORNER_BOTTOM) == DrawFlag::ROUND_CORNER_BOTTOM; - const auto h = - (flag & DrawFlag::ROUND_CORNER_LEFT) == DrawFlag::ROUND_CORNER_LEFT or - (flag & DrawFlag::ROUND_CORNER_RIGHT) == DrawFlag::ROUND_CORNER_RIGHT; - - rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); - rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); - } - - if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) - { - draw_image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); - } - else - { - const auto new_texture = this_command_texture_id_ != texture_id; - - if (new_texture) - { - push_texture_id(texture_id); - } - - const auto rounding_left_top = (flag & DrawFlag::ROUND_CORNER_LEFT_TOP) != DrawFlag::NONE ? rounding : 0; - const auto rounding_right_top = (flag & DrawFlag::ROUND_CORNER_RIGHT_TOP) != DrawFlag::NONE ? rounding : 0; - const auto rounding_left_bottom = (flag & DrawFlag::ROUND_CORNER_LEFT_BOTTOM) != DrawFlag::NONE ? rounding : 0; - const auto rounding_right_bottom = (flag & DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) != DrawFlag::NONE ? rounding : 0; - - path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, DrawArcFlag::Q2_CLOCK_WISH); - path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, DrawArcFlag::Q1_CLOCK_WISH); - path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, DrawArcFlag::Q4_CLOCK_WISH); - path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, DrawArcFlag::Q3_CLOCK_WISH); - - auto& vertex_list = private_data_.vertex_list; - - const auto before_vertex_count = vertex_list.size(); - // draw - path_stroke(color); - const auto after_vertex_count = vertex_list.size(); - - // set uv manually - - const auto display_size = display_rect.size(); - const auto uv_size = uv_rect.size(); - const auto scale = uv_size / display_size; - - auto it = vertex_list.begin() + static_cast(before_vertex_count); - const auto end = vertex_list.begin() + static_cast(after_vertex_count); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == vertex_list.end()); - - // note: linear uv - const auto uv_min = uv_rect.left_top(); - // const auto uv_max = uv_rect.right_bottom(); - while (it != end) - { - const auto v = uv_min + (it->position - display_rect.left_top()) * scale; - - it->uv = - { - // std::ranges::clamp(v.x, uv_min.x, uv_max.x), - v.x, - // std::ranges::clamp(v.y, uv_min.y, uv_max.y) - v.y - }; - it += 1; - } - - if (new_texture) - { - pop_texture_id(); - } - } - } - - auto DrawList::path_stroke(const color_type& color, const DrawFlag flag, const float thickness) noexcept -> void - { - if ((draw_list_flag_ & DrawListFlag::ANTI_ALIASED_LINE) != DrawListFlag::NONE) - { - draw_polygon_line_aa(color, flag, thickness); - } - else - { - draw_polygon_line(color, flag, thickness); - } - - path_clear(); - } - - auto DrawList::path_stroke(const color_type& color) noexcept -> void - { - if ((draw_list_flag_ & DrawListFlag::ANTI_ALIASED_FILL) != DrawListFlag::NONE) - { - draw_convex_polygon_line_filled_aa(color); - } - else - { - draw_convex_polygon_line_filled(color); - } - - path_clear(); - } - - auto DrawList::path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void - { - const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); - - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - // Calculate arc auto segment step size - auto step = DrawListSharedData::vertex_sample_points_count / draw_list_shared_data.get_circle_auto_segment_count(radius); - // Make sure we never do steps larger than one quarter of the circle - step = std::clamp(step, static_cast(1), DrawListSharedData::vertex_sample_points_count / 4); - - const auto sample_range = math::abs(to - from); - const auto next_step = step; - - auto extra_max_sample = false; - if (step > 1) - { - const auto overstep = sample_range % step; - if (overstep > 0) - { - extra_max_sample = true; - - // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, - // distribute first step range evenly between them by reducing first step size. - step -= (step - overstep) / 2; - } - - path_reserve_extra(sample_range / step + 1 + (overstep > 0)); - } - else - { - path_reserve_extra(sample_range + 1); - } - - auto sample_index = from; - if (sample_index < 0 or std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) - { - sample_index = sample_index % static_cast(DrawListSharedData::vertex_sample_points_count); - if (sample_index < 0) - { - sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); - } - } - - if (to >= from) - { - for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) - { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) - { - sample_index -= static_cast(DrawListSharedData::vertex_sample_points_count); - } - - const auto& sample_point = draw_list_shared_data.get_vertex_sample_point(sample_index); - - path_pin({center + sample_point * radius}); - } - } - else - { - for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) - { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (sample_index < 0) - { - sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); - } - - const auto& sample_point = draw_list_shared_data.get_vertex_sample_point(sample_index); - - path_pin({center + sample_point * radius}); - } - } - - if (extra_max_sample) - { - auto normalized_max_sample_index = to % static_cast(DrawListSharedData::vertex_sample_points_count); - if (normalized_max_sample_index < 0) - { - normalized_max_sample_index += DrawListSharedData::vertex_sample_points_count; - } - - const auto& sample_point = draw_list_shared_data.get_vertex_sample_point(normalized_max_sample_index); - - path_pin({center + sample_point * radius}); - } - } - - auto DrawList::path_arc_fast(const circle_type& circle, const DrawArcFlag flag) noexcept -> void - { - const auto [from, to] = range_of_arc_quadrant(flag); - - return path_arc_fast(circle, from, to); - } - - auto DrawList::path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); - - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - path_reserve_extra(segments); - for (std::uint32_t i = 0; i < segments; ++i) - { - const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); - } - } - - auto DrawList::path_arc(const circle_type& circle, const float from, const float to) noexcept -> void - { - const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); - - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - // Automatic segment count - if (radius <= draw_list_shared_data.get_arc_fast_radius_cutoff()) - { - const auto is_reversed = to < from; - - // We are going to use precomputed values for mid-samples. - // Determine first and last sample in lookup table that belong to the arc - const auto sample_from_f = DrawListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); - const auto sample_to_f = DrawListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); - - const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); - const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); - const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); - - const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; - const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; - - const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; - const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; - - if (emit_start) - { - // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); - } - if (sample_mid > 0) - { - path_arc_fast(circle, sample_from, sample_to); - } - if (emit_end) - { - // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); - } - } - else - { - const auto arc_length = to - from; - const auto circle_segment_count = draw_list_shared_data.get_circle_auto_segment_count(radius); - const auto arc_segment_count = std::ranges::max( - static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), - static_cast(std::numbers::pi_v * 2 / arc_length) - ); - path_arc_n(circle, from, to, arc_segment_count); - } - } - - auto DrawList::path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void - { - const auto& [center, radius, rotation] = ellipse; - const auto cos_theta = math::cos(rotation); - const auto sin_theta = math::sin(rotation); - - path_reserve_extra(segments); - for (std::uint32_t i = 0; i < segments; ++i) - { - const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - const auto offset = point_type{math::cos(a), math::sin(a)} * radius; - const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; - const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; - path_pin({center + point_type{prime_x, prime_y}}); - } - } - - auto DrawList::path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void - { - path_pin(p1); - path_pin(p2); - path_pin(p3); - path_pin(p4); - } - - auto DrawList::path_rect(const rect_type& rect, float rounding, DrawFlag flag) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); - - if (rounding >= .5f) - { - flag = to_fixed_rect_corner_flag(flag); - - const auto v = - (flag & DrawFlag::ROUND_CORNER_TOP) == DrawFlag::ROUND_CORNER_TOP or - (flag & DrawFlag::ROUND_CORNER_BOTTOM) == DrawFlag::ROUND_CORNER_BOTTOM; - const auto h = - (flag & DrawFlag::ROUND_CORNER_LEFT) == DrawFlag::ROUND_CORNER_LEFT or - (flag & DrawFlag::ROUND_CORNER_RIGHT) == DrawFlag::ROUND_CORNER_RIGHT; - - rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); - rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); - } - - if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) - { - path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); - } - else - { - const auto rounding_left_top = (flag & DrawFlag::ROUND_CORNER_LEFT_TOP) != DrawFlag::NONE ? rounding : 0; - const auto rounding_right_top = (flag & DrawFlag::ROUND_CORNER_RIGHT_TOP) != DrawFlag::NONE ? rounding : 0; - const auto rounding_left_bottom = (flag & DrawFlag::ROUND_CORNER_LEFT_BOTTOM) != DrawFlag::NONE ? rounding : 0; - const auto rounding_right_bottom = (flag & DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) != DrawFlag::NONE ? rounding : 0; - - path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, DrawArcFlag::Q2_CLOCK_WISH); - path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, DrawArcFlag::Q1_CLOCK_WISH); - path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, DrawArcFlag::Q4_CLOCK_WISH); - path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, DrawArcFlag::Q3_CLOCK_WISH); - } - } - - auto DrawList::path_bezier_cubic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const float tessellation_tolerance, - const std::size_t level - ) noexcept -> void - { - const auto dx = p4.x - p1.x; - const auto dy = p4.y - p1.y; - const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); - const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); - - if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) - { - path_pin(p4); - } - else if (level < bezier_curve_casteljau_max_level) - { - const auto p_12 = (p1 + p2) * .5f; - const auto p_23 = (p2 + p3) * .5f; - const auto p_34 = (p3 + p4) * .5f; - const auto p_123 = (p_12 + p_23) * .5f; - const auto p_234 = (p_23 + p_34) * .5f; - const auto p_1234 = (p_123 + p_234) * .5f; - - path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); - path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); - } - } - - auto DrawList::path_bezier_quadratic_curve_casteljau(const point_type& p1, const point_type& p2, const point_type& p3, const float tessellation_tolerance, const std::size_t level) noexcept -> void - { - const auto dx = p3.x - p1.x; - const auto dy = p3.y - p1.y; - const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; - - if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) - { - path_pin(p3); - } - else if (level < bezier_curve_casteljau_max_level) - { - const auto p_12 = (p1 + p2) * .5f; - const auto p_23 = (p2 + p3) * .5f; - const auto p_123 = (p_12 + p_23) * .5f; - - path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); - path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); - } - } - - auto DrawList::path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void - { - const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); - - path_pin(p1); - if (segments == 0) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data.get_curve_tessellation_tolerance() > 0); - - path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // auto-tessellated - path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, draw_list_shared_data.get_curve_tessellation_tolerance(), 0); - } - else - { - path_reserve_extra(segments); - const auto step = 1.f / static_cast(segments); - for (std::uint32_t i = 1; i <= segments; ++i) - { - path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); - } - } - } - - auto DrawList::path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void - { - const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); - - path_pin(p1); - if (segments == 0) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data.get_curve_tessellation_tolerance() > 0); - - path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // auto-tessellated - path_bezier_quadratic_curve_casteljau(p1, p2, p3, draw_list_shared_data.get_curve_tessellation_tolerance(), 0); - } - else - { - path_reserve_extra(segments); - const auto step = 1.f / static_cast(segments); - for (std::uint32_t i = 1; i <= segments; ++i) - { - path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); - } - } - } - - DrawList::DrawList() noexcept - : draw_list_flag_{DrawListFlag::NONE}, - this_command_clip_rect_{0, 0, 0, 0}, - this_command_texture_id_{0} - { - // reset(); - } - - auto DrawList::draw_list_flag(const DrawListFlag flag) noexcept -> void - { - draw_list_flag_ = flag; - } - - auto DrawList::draw_list_flag(const std::underlying_type_t flag) noexcept -> void - { - draw_list_flag(static_cast(flag)); - } - - auto DrawList::reset() noexcept -> void - { - const auto& font = Context::instance().font(); - - auto& [command_list, vertex_list, index_list] = private_data_; - - command_list.clear(); - vertex_list.resize(0); - index_list.resize(0); - - // we don't know the size of the clip rect, so we need the user to set it - this_command_clip_rect_ = {}; - // the first texture is always the (default) font texture - this_command_texture_id_ = font.texture_id(); - - path_list_.clear(); - - // we always have a command ready in the buffer - command_list.emplace_back( - command_type - { - .clip_rect = this_command_clip_rect_, - .texture_id = this_command_texture_id_, - .index_offset = index_list.size(), - // set by subsequent draw_xxx - .element_count = 0 - } - ); - } - - auto DrawList::push_clip_rect(const rect_type& rect, const bool intersect_with_current_clip_rect) noexcept -> rect_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - - auto& [command_list, _1, _2] = private_data_; - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - - const auto& [current_clip_rect, current_texture, current_index_offset, current_element_count] = command_list.back(); - - this_command_clip_rect_ = intersect_with_current_clip_rect ? rect.combine_min(current_clip_rect) : rect; - - on_element_changed(ChangedElement::CLIP_RECT); - return command_list.back().clip_rect; - } - - auto DrawList::push_clip_rect(const point_type& left_top, const point_type& right_bottom, const bool intersect_with_current_clip_rect) noexcept -> rect_type& - { - return push_clip_rect({left_top, right_bottom}, intersect_with_current_clip_rect); - } - - auto DrawList::pop_clip_rect() noexcept -> void - { - const auto& [command_list, _1, _2] = private_data_; - - // at least one command - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); - this_command_clip_rect_ = command_list[command_list.size() - 2].clip_rect; - - on_element_changed(ChangedElement::CLIP_RECT); - } - - auto DrawList::push_texture_id(const texture_id_type texture) noexcept -> void - { - this_command_texture_id_ = texture; - - on_element_changed(ChangedElement::TEXTURE_ID); - } - - auto DrawList::pop_texture_id() noexcept -> void - { - const auto& [command_list, _1, _2] = private_data_; - - // at least one command - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); - this_command_texture_id_ = command_list[command_list.size() - 2].texture_id; - - on_element_changed(ChangedElement::TEXTURE_ID); - } - - auto DrawList::line( - const point_type& from, - const point_type& to, - const color_type& color, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - // path_pin(from + point_type{.5f, .5f}); - // path_pin(to + point_type{.5f, .5f}); - path_pin(from); - path_pin(to); - - path_stroke(color, DrawFlag::NONE, thickness); - } - - - auto DrawList::triangle( - const point_type& a, - const point_type& b, - const point_type& c, - const color_type& color, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - path_pin(a); - path_pin(b); - path_pin(c); - - path_stroke(color, DrawFlag::CLOSED, thickness); - } - - auto DrawList::triangle_filled( - const point_type& a, - const point_type& b, - const point_type& c, - const color_type& color - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - path_pin(a); - path_pin(b); - path_pin(c); - - path_stroke(color); - } - - auto DrawList::rect( - const rect_type& rect, - const color_type& color, - const float rounding, - const DrawFlag flag, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - // path_rect(rect_type{rect.left_top() + point_type{.5f, .5f}, rect.right_bottom() - point_type{.5f, .5f}}, rounding, flag); - path_rect(rect, rounding, flag); - - path_stroke(color, DrawFlag::CLOSED, thickness); - } - - auto DrawList::rect( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color, - const float rounding, - const DrawFlag flag, - const float thickness - ) noexcept -> void - { - return rect({left_top, right_bottom}, color, rounding, flag, thickness); - } - - auto DrawList::rect_filled( - const rect_type& rect, - const color_type& color, - const float rounding, - const DrawFlag flag - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) - { - draw_rect_filled(rect, color, color, color, color); - } - else - { - path_rect(rect, rounding, flag); - path_stroke(color); - } - } - - auto DrawList::rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color, - const float rounding, - const DrawFlag flag - ) noexcept -> void - { - return rect_filled({left_top, right_bottom}, color, rounding, flag); - } - - auto DrawList::rect_filled( - const rect_type& rect, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void - { - if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) - { - return; - } - - draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - } - - auto DrawList::rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void - { - return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - } - - auto DrawList::quadrilateral( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - path_quadrilateral(p1, p2, p3, p4); - - path_stroke(color, DrawFlag::CLOSED, thickness); - } - - auto DrawList::quadrilateral_filled( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - path_quadrilateral(p1, p2, p3, p4); - - path_stroke(color); - } - - auto DrawList::circle_n( - const circle_type& circle, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f or segments < 3) - { - return; - } - - path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); - - path_stroke(color, DrawFlag::CLOSED, thickness); - } - - auto DrawList::circle_n( - const point_type& center, - const float radius, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - return circle_n({center, radius}, color, segments, thickness); - } - - auto DrawList::ellipse_n( - const ellipse_type& ellipse, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) - { - return; - } - - path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); - - path_stroke(color, DrawFlag::CLOSED, thickness); - } - - auto DrawList::ellipse_n( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - return ellipse_n({center, radius, rotation}, color, segments, thickness); - } - - auto DrawList::circle_n_filled( - const circle_type& circle, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f or segments < 3) - { - return; - } - - path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); - - path_stroke(color); - } - - auto DrawList::circle_n_filled( - const point_type& center, - const float radius, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - return circle_n_filled({center, radius}, color, segments); - } - - auto DrawList::ellipse_n_filled( - const ellipse_type& ellipse, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) - { - return; - } - - path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); - - path_stroke(color); - } - - auto DrawList::ellipse_n_filled( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - return ellipse_n_filled({center, radius, rotation}, color, segments); - } - - auto DrawList::circle( - const circle_type& circle, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f) - { - return; - } - - if (segments == 0) - { - path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); - - path_stroke(color, DrawFlag::CLOSED, thickness); - } - else - { - const auto clamped_segments = std::ranges::clamp(segments, DrawListSharedData::circle_segments_min, DrawListSharedData::circle_segments_max); - - circle_n(circle, color, clamped_segments, thickness); - } - } - - auto DrawList::circle( - const point_type& center, - const float radius, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - return circle({center, radius}, color, segments, thickness); - } - - auto DrawList::circle_filled( - const circle_type& circle, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f) - { - return; - } - - if (segments == 0) - { - path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); - - path_stroke(color); - } - else - { - const auto clamped_segments = std::ranges::clamp(segments, DrawListSharedData::circle_segments_min, DrawListSharedData::circle_segments_max); - - circle_n_filled(circle, color, clamped_segments); - } - } - - auto DrawList::circle_filled( - const point_type& center, - const float radius, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - return circle_filled({center, radius}, color, segments); - } - - auto DrawList::ellipse( - const ellipse_type& ellipse, - const color_type& color, - std::uint32_t segments, - const float thickness - ) noexcept -> void - { - const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); - - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) - { - return; - } - - if (segments == 0) - { - // fixme: maybe there's a better computation to do here - segments = draw_list_shared_data.get_circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - } - - ellipse_n(ellipse, color, segments, thickness); - } - - auto DrawList::ellipse( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - return ellipse({center, radius, rotation}, color, segments, thickness); - } - - auto DrawList::ellipse_filled( - const ellipse_type& ellipse, - const color_type& color, - std::uint32_t segments - ) noexcept -> void - { - const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); - - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) - { - return; - } - - if (segments == 0) - { - // fixme: maybe there's a better computation to do here - segments = draw_list_shared_data.get_circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - } - - ellipse_n_filled(ellipse, color, segments); - } - - auto DrawList::ellipse_filled( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - return ellipse_filled({center, radius, rotation}, color, segments); - } - - auto DrawList::bezier_cubic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - path_bezier_curve(p1, p2, p3, p4, segments); - - path_stroke(color, DrawFlag::NONE, thickness); - } - - auto DrawList::bezier_quadratic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const color_type& color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - path_bezier_quadratic_curve(p1, p2, p3, segments); - - path_stroke(color, DrawFlag::NONE, thickness); - } - - auto DrawList::text( - const Font& font, - const float font_size, - const point_type& p, - const color_type& color, - const std::string_view utf8_text, - const float wrap_width - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - draw_text(font, font_size, p, color, utf8_text, wrap_width); - } - - auto DrawList::text( - const float font_size, - const point_type& p, - const color_type& color, - const std::string_view utf8_text, - const float wrap_width - ) noexcept -> void - { - const auto& font = Context::instance().font(); - - this->text(font, font_size, p, color, utf8_text, wrap_width); - } - - auto DrawList::image( - const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - const color_type& color - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); - } - - auto DrawList::image( - const texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - const color_type& color - ) noexcept -> void - { - image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); - } - - auto DrawList::image( - const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const uv_type& uv_left_top, - const uv_type& uv_right_bottom, - const color_type& color - ) noexcept -> void - { - image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); - } - - auto DrawList::image_rounded( - const texture_id_type texture_id, - const rect_type& display_rect, - const float rounding, - const DrawFlag flag, - const rect_type& uv_rect, - const color_type& color - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } - - draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); - } - - auto DrawList::image_rounded( - const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const float rounding, - const DrawFlag flag, - const uv_type& uv_left_top, - const uv_type& uv_right_bottom, - const color_type& color - ) noexcept -> void - { - image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); - } -} diff --git a/src/draw/draw_list.hpp b/src/draw/draw_list.hpp deleted file mode 100644 index fe3454b..0000000 --- a/src/draw/draw_list.hpp +++ /dev/null @@ -1,569 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include - -namespace gal::prometheus::draw -{ - class Font; - - class DrawList final - { - public: - template - using container_type = DrawListDef::container_type; - - // ---------------------------------------------------- - - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using circle_type = DrawListDef::circle_type; - using ellipse_type = DrawListDef::ellipse_type; - - // ---------------------------------------------------- - - using uv_type = DrawListDef::uv_type; - using color_type = DrawListDef::color_type; - using vertex_type = DrawListDef::vertex_type; - using index_type = DrawListDef::index_type; - - // ---------------------------------------------------- - - using path_list_type = DrawListDef::path_list_type; - using vertex_list_type = DrawListDef::vertex_list_type; - using index_list_type = DrawListDef::index_list_type; - - // ---------------------------------------------------- - - using texture_id_type = DrawListDef::texture_id_type; - using size_type = DrawListDef::size_type; - - using command_type = DrawListDef::command_type; - using command_list_type = DrawListDef::command_list_type; - - private: - DrawListFlag draw_list_flag_; - - /** - * @brief This wrapper structure expects to limit the data writes to a limited number of functions to avoid unintended data writes. - * @see make_accessor - * @see push_command - * @see on_element_changed - * @see reset - * @see push_clip_rect - * @see draw_image_rounded (fill image uv) - */ - struct private_data_type - { - // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) - // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 - // command_list: - // 0: .clip_rect = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) - // 1: .clip_rect = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) - // 2: .clip_rect = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) - command_list_type command_list; - vertex_list_type vertex_list; - index_list_type index_list; - }; - - private_data_type private_data_; - - rect_type this_command_clip_rect_; - texture_id_type this_command_texture_id_; - - path_list_type path_list_; - - [[nodiscard]] auto make_accessor() noexcept -> DrawListDef::Accessor; - - auto push_command() noexcept -> void; - - enum class ChangedElement : std::uint8_t - { - CLIP_RECT, - TEXTURE_ID, - }; - - auto on_element_changed(ChangedElement element) noexcept -> void; - - // ---------------------------------------------------------------------------- - // DRAW - - auto draw_polygon_line(const color_type& color, DrawFlag draw_flag, float thickness) noexcept -> void; - - auto draw_polygon_line_aa(const color_type& color, DrawFlag draw_flag, float thickness) noexcept -> void; - - auto draw_convex_polygon_line_filled(const color_type& color) noexcept -> void; - - auto draw_convex_polygon_line_filled_aa(const color_type& color) noexcept -> void; - - auto draw_rect_filled( - const rect_type& rect, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void; - - auto draw_text( - const Font& font, - float font_size, - const point_type& p, - const color_type& color, - std::string_view utf8_text, - float wrap_width - ) noexcept -> void; - - auto draw_image( - texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - const color_type& color - ) noexcept -> void; - - auto draw_image_rounded( - texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - const color_type& color, - float rounding, - DrawFlag flag - ) noexcept -> void; - - // ---------------------------------------------------------------------------- - // PATH - - constexpr auto path_clear() noexcept -> void - { - path_list_.clear(); - } - - constexpr auto path_reserve(const std::size_t size) noexcept -> void - { - path_list_.reserve(size); - } - - constexpr auto path_reserve_extra(const std::size_t size) noexcept -> void - { - path_reserve(path_list_.size() + size); - } - - constexpr auto path_pin(const point_type& point) noexcept -> void - { - path_list_.push_back(point); - } - - auto path_stroke(const color_type& color, DrawFlag flag, float thickness) noexcept -> void; - - auto path_stroke(const color_type& color) noexcept -> void; - - auto path_arc_fast(const circle_type& circle, int from, int to) noexcept -> void; - - auto path_arc_fast(const circle_type& circle, DrawArcFlag flag) noexcept -> void; - - auto path_arc_n(const circle_type& circle, float from, float to, std::uint32_t segments) noexcept -> void; - - auto path_arc(const circle_type& circle, float from, float to) noexcept -> void; - - auto path_arc_elliptical_n(const ellipse_type& ellipse, float from, float to, std::uint32_t segments) noexcept -> void; - - auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void; - - auto path_rect(const rect_type& rect, float rounding, DrawFlag flag) noexcept -> void; - - auto path_bezier_cubic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - float tessellation_tolerance, - std::size_t level - ) noexcept -> void; - - auto path_bezier_quadratic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - float tessellation_tolerance, - std::size_t level - ) noexcept -> void; - - auto path_bezier_curve( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - std::uint32_t segments - ) noexcept -> void; - - auto path_bezier_quadratic_curve( - const point_type& p1, - const point_type& p2, - const point_type& p3, - std::uint32_t segments - ) noexcept -> void; - - public: - /** - * @note The default font may not be set at this point, so we must manually call @c reset before using it. - */ - DrawList() noexcept; - - // ---------------------------------------------------------------------------- - // FLAG - - auto draw_list_flag(DrawListFlag flag) noexcept -> void; - - auto draw_list_flag(std::underlying_type_t flag) noexcept -> void; - - // ---------------------------------------------------------------------------- - // RESET - - auto reset() noexcept -> void; - - // ---------------------------------------------------------------------------- - // DRAW DATA - - [[nodiscard]] constexpr auto command_list() const noexcept -> auto - { - return private_data_.command_list | std::views::all; - } - - [[nodiscard]] constexpr auto vertex_list() const noexcept -> auto - { - return private_data_.vertex_list | std::views::all; - } - - [[nodiscard]] constexpr auto index_list() const noexcept -> auto - { - return private_data_.index_list | std::views::all; - } - - // ---------------------------------------------------------------------------- - // CLIP RECT & TEXTURE - - auto push_clip_rect(const rect_type& rect, bool intersect_with_current_clip_rect) noexcept -> rect_type&; - - auto push_clip_rect(const point_type& left_top, const point_type& right_bottom, bool intersect_with_current_clip_rect) noexcept -> rect_type&; - - auto pop_clip_rect() noexcept -> void; - - auto push_texture_id(texture_id_type texture) noexcept -> void; - - auto pop_texture_id() noexcept -> void; - - // ---------------------------------------------------------------------------- - // PRIMITIVE - - auto line( - const point_type& from, - const point_type& to, - const color_type& color, - float thickness = 1.f - ) noexcept -> void; - - auto triangle( - const point_type& a, - const point_type& b, - const point_type& c, - const color_type& color, - float thickness = 1.f - ) noexcept -> void; - - auto triangle_filled( - const point_type& a, - const point_type& b, - const point_type& c, - const color_type& color - ) noexcept -> void; - - auto rect( - const rect_type& rect, - const color_type& color, - float rounding = .0f, - DrawFlag flag = DrawFlag::NONE, - float thickness = 1.f - ) noexcept -> void; - - auto rect( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color, - float rounding = .0f, - DrawFlag flag = DrawFlag::NONE, - float thickness = 1.f - ) noexcept -> void; - - auto rect_filled( - const rect_type& rect, - const color_type& color, - float rounding = .0f, - DrawFlag flag = DrawFlag::NONE - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color, - float rounding = .0f, - DrawFlag flag = DrawFlag::NONE - ) noexcept -> void; - - auto rect_filled( - const rect_type& rect, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void; - - auto quadrilateral( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color, - float thickness = 1.f - ) noexcept -> void; - - auto quadrilateral_filled( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color - ) noexcept -> void; - - auto circle_n( - const circle_type& circle, - const color_type& color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto circle_n( - const point_type& center, - float radius, - const color_type& color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n( - const ellipse_type& ellipse, - const color_type& color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n( - const point_type& center, - const extent_type& radius, - float rotation, - const color_type& color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto circle_n_filled( - const circle_type& circle, - const color_type& color, - std::uint32_t segments - ) noexcept -> void; - - auto circle_n_filled( - const point_type& center, - float radius, - const color_type& color, - std::uint32_t segments - ) noexcept -> void; - - auto ellipse_n_filled( - const ellipse_type& ellipse, - const color_type& color, - std::uint32_t segments - ) noexcept -> void; - - auto ellipse_n_filled( - const point_type& center, - const extent_type& radius, - float rotation, - const color_type& color, - std::uint32_t segments - ) noexcept -> void; - - auto circle( - const circle_type& circle, - const color_type& color, - std::uint32_t segments = 0, - float thickness = 1.f - ) noexcept -> void; - - auto circle( - const point_type& center, - float radius, - const color_type& color, - std::uint32_t segments = 0, - float thickness = 1.f - ) noexcept -> void; - - auto circle_filled( - const circle_type& circle, - const color_type& color, - std::uint32_t segments = 0 - ) noexcept -> void; - - auto circle_filled( - const point_type& center, - float radius, - const color_type& color, - std::uint32_t segments = 0 - ) noexcept -> void; - - auto ellipse( - const ellipse_type& ellipse, - const color_type& color, - std::uint32_t segments = 0, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse( - const point_type& center, - const extent_type& radius, - float rotation, - const color_type& color, - std::uint32_t segments = 0, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_filled( - const ellipse_type& ellipse, - const color_type& color, - std::uint32_t segments = 0 - ) noexcept -> void; - - auto ellipse_filled( - const point_type& center, - const extent_type& radius, - float rotation, - const color_type& color, - std::uint32_t segments = 0 - ) noexcept -> void; - - auto bezier_cubic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color, - std::uint32_t segments = 0, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_quadratic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const color_type& color, - std::uint32_t segments = 0, - float thickness = 1.f - ) noexcept -> void; - - // ---------------------------------------------------------------------------- - // TEXT - - auto text( - const Font& font, - float font_size, - const point_type& p, - const color_type& color, - std::string_view utf8_text, - float wrap_width = std::numeric_limits::max() - ) noexcept -> void; - - auto text( - float font_size, - const point_type& p, - const color_type& color, - std::string_view utf8_text, - float wrap_width = std::numeric_limits::max() - ) noexcept -> void; - - // ---------------------------------------------------------------------------- - // IMAGE - - // p1________ p2 - // | | - // | | - // p4|_______| p3 - auto image( - texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1 = {0, 0}, - const uv_type& uv_p2 = {1, 0}, - const uv_type& uv_p3 = {1, 1}, - const uv_type& uv_p4 = {0, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect = {0, 0, 1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const rect_type& display_rect, - float rounding = .0f, - DrawFlag flag = DrawFlag::NONE, - const rect_type& uv_rect = {0, 0, 1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - float rounding = .0f, - DrawFlag flag = DrawFlag::NONE, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void; - }; -} diff --git a/src/draw/flag.hpp b/src/draw/flag.hpp deleted file mode 100644 index 03f390b..0000000 --- a/src/draw/flag.hpp +++ /dev/null @@ -1,162 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include -#include - -#include - -namespace gal::prometheus::draw -{ - enum class DrawFlag : std::uint8_t - { - NONE = 0, - // specify that shape should be closed - // @see DrawList::draw_polygon_line - // @see DrawList::draw_polygon_line_aa - // @see DrawList::path_stroke - CLOSED = 1 << 0, - // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) - // @see DrawList::path_rect - // @see DrawList::rect - // @see DrawList::rect_filled - ROUND_CORNER_LEFT_TOP = 1 << 1, - // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) - // @see DrawList::path_rect - // @see DrawList::rect - // @see DrawList::rect_filled - ROUND_CORNER_RIGHT_TOP = 1 << 2, - // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see DrawList::path_rect - // @see DrawList::rect - // @see DrawList::rect_filled - ROUND_CORNER_LEFT_BOTTOM = 1 << 3, - // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see DrawList::path_rect - // @see DrawList::rect - // @see DrawList::rect_filled - ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, - // disable rounding on all corners (when rounding > 0.0f) - ROUND_CORNER_NONE = 1 << 5, - - ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, - ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, - ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - - ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, - ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, - - PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), - }; - - enum class DrawListFlag : std::uint8_t - { - NONE = 0, - ANTI_ALIASED_LINE = 1 << 0, - ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, - ANTI_ALIASED_FILL = 1 << 2, - - PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), - }; - - enum class DrawArcFlag : std::uint8_t - { - // [0~3) - Q1 = 1 << 0, - // [3~6) - Q2 = 1 << 1, - // [6~9) - Q3 = 1 << 2, - // [9~12) - Q4 = 1 << 3, - - RIGHT_TOP = Q1, - LEFT_TOP = Q2, - LEFT_BOTTOM = Q3, - RIGHT_BOTTOM = Q4, - TOP = Q1 | Q2, - BOTTOM = Q3 | Q4, - LEFT = Q2 | Q3, - RIGHT = Q1 | Q4, - ALL = Q1 | Q2 | Q3 | Q4, - - // [3, 0) - Q1_CLOCK_WISH = 1 << 4, - // [6, 3) - Q2_CLOCK_WISH = 1 << 5, - // [9, 6) - Q3_CLOCK_WISH = 1 << 6, - // [12, 9) - Q4_CLOCK_WISH = 1 << 7, - - RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, - LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, - LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, - RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, - TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, - BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, - LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, - RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, - ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, - - PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), - }; - - [[nodiscard]] auto range_of_arc_quadrant(DrawArcFlag quadrant) noexcept -> std::pair; - - enum class ThemeCategory : std::uint8_t - { - TEXT = 0, - BORDER, - - WINDOW_BACKGROUND, - - WIDGET_BACKGROUND, - WIDGET_ACTIVATED, - - TITLE_BAR, - TITLE_BAR_COLLAPSED, - - SLIDER, - SLIDER_ACTIVATED, - - BUTTON, - BUTTON_HOVERED, - BUTTON_ACTIVATED, - - RESIZE_GRIP, - RESIZE_GRIP_HOVERED, - RESIZE_GRIP_ACTIVATED, - - TOOLTIP_BACKGROUND, - TOOLTIP_TEXT, - - // ------------------------------- - INTERNAL_COUNT - }; - - constexpr auto theme_category_count = static_cast(ThemeCategory::INTERNAL_COUNT); - - enum class WindowFlag : std::uint8_t - { - NONE = 0, - - BORDERED = 1 << 0, - - NO_TITLE_BAR = 1 << 1, - // Meaningful if and only if NO_TITLE_BAR is not set - NO_CLOSE = 1 << 2, - NO_RESIZE = 1 << 3, - NO_MOVE = 1 << 4, - - PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), - }; -} diff --git a/src/draw/font.cpp b/src/draw/font.cpp deleted file mode 100644 index 2c6b318..0000000 --- a/src/draw/font.cpp +++ /dev/null @@ -1,548 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include FT_FREETYPE_H // -// #include FT_MODULE_H // -// #include FT_GLYPH_H // -// #include FT_SYNTHESIS_H // - -#define STB_RECT_PACK_IMPLEMENTATION -#include - -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -namespace -{ - struct ft_type - { - FT_Library library; - FT_Face face; - - [[nodiscard]] constexpr auto valid() const noexcept -> bool - { - return library and face; - } - }; - - [[nodiscard]] auto create_ft(const std::string_view font_path, const std::uint32_t pixel_height) noexcept -> ft_type - { - FT_Library ft_library; - if (FT_Init_FreeType(&ft_library)) - { - // Could not initialize FreeType library - return {.library = nullptr, .face = nullptr}; - } - - FT_Face ft_face; - if (FT_New_Face(ft_library, font_path.data(), 0, &ft_face)) - { - FT_Done_FreeType(ft_library); - // Could not load font - return {.library = nullptr, .face = nullptr}; - } - - FT_Set_Pixel_Sizes(ft_face, 0, pixel_height); - return {.library = ft_library, .face = ft_face}; - } - - auto destroy_ft(const ft_type ft) noexcept -> void - { - auto [ft_library, ft_face] = ft; - - FT_Done_Face(ft_face); - FT_Done_FreeType(ft_library); - } -} - -namespace gal::prometheus::draw -{ - Font::Texture::~Texture() noexcept - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_texture_id, "Texture is not bound to a GPU resource id!"); - } - - auto Font::Texture::bind(const texture_id_type id) const noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid(), "Only valid textures can be bound to a GPU resource id"); - - id_.get() = id; - } - - auto Font::reset() noexcept -> void - { - font_path_.clear(); - pixel_height_ = std::numeric_limits::min(); - baked_line_max_width_ = std::numeric_limits::min(); - - glyphs_.clear(); - fallback_glyph_ = {}; - - white_pixel_uv_ = {}; - baked_line_uv_ = {}; - - texture_id_ = invalid_texture_id; - } - - Font::Font() noexcept - { - reset(); - } - - Font::~Font() noexcept = default; - - auto Font::option() noexcept -> Option - { - return {}; - } - - auto Font::load(const Option& option) noexcept -> Texture - { - reset(); - - font_path_ = std::format("{}-{}px", option.font_path_, option.pixel_height_); - pixel_height_ = option.pixel_height_; - - if (option.baked_line_max_width_ == 0 or option.baked_line_max_width_ > default_baked_line_max_width) - { - baked_line_max_width_ = default_baked_line_max_width; - } - else - { - baked_line_max_width_ = option.baked_line_max_width_; - } - - Texture texture{texture_id_}; - - auto ft = create_ft(option.font_path_, option.pixel_height_); - if (not ft.valid()) - { - return texture; - } - - auto [library, face] = ft; - - // =============================== - - std::vector rects; - - // baked line - constexpr auto id_baked_line = std::numeric_limits::min() + 0; - { - const auto baked_line_uv_width = baked_line_max_width_ + 1; - const auto baked_line_uv_height = baked_line_max_width_ + 2; - - baked_line_uv_.reserve(baked_line_uv_height); - - rects.emplace_back( - stbrp_rect - { - .id = id_baked_line, - .w = static_cast(baked_line_uv_width), - .h = static_cast(baked_line_uv_height), - .x = 0, - .y = 0, - .was_packed = 0 - } - ); - } - - // =============================== - - std::ranges::for_each( - option.glyph_ranges_, - [&face, &rects](const auto& pair) noexcept -> void - { - const auto [from, to] = pair; - - for (auto c = from; c <= to; ++c) - { - if (FT_Load_Char(face, c, FT_LOAD_RENDER)) - { - continue; - } - - const auto& g = face->glyph; - rects.emplace_back( - stbrp_rect - { - .id = std::bit_cast(c), - .w = static_cast(g->bitmap.width), - .h = static_cast(g->bitmap.rows), - .x = static_cast(g->bitmap_left), - .y = static_cast(g->bitmap_top), - .was_packed = 0 - } - ); - } - } - ); - - // =============================== - - // todo: texture size? - const auto size = [&rects]() - { - Texture::size_type total_area = 0; - Texture::size_type max_width = 0; - Texture::size_type max_height = 0; - - for (const auto& [id, w, h, x, y, was_packed]: rects) - { - total_area += w * h; - max_width = std::ranges::max(max_width, static_cast(w)); - max_height = std::ranges::max(max_height, static_cast(h)); - } - - const auto min_side = static_cast(std::sqrt(total_area)); - const auto max_side = std::ranges::max(max_width, max_height); - - return std::bit_ceil(std::ranges::max(min_side, max_side)); - }(); - auto atlas_width = size; - auto atlas_height = size; - - stbrp_context context; - std::vector nodes{atlas_width}; - while (true) - { - stbrp_init_target(&context, static_cast(atlas_width), static_cast(atlas_height), nodes.data(), static_cast(nodes.size())); - if (stbrp_pack_rects(&context, rects.data(), static_cast(rects.size()))) - { - break; - } - - atlas_width *= 2; - atlas_height *= 2; - nodes.resize(atlas_width); - } - - // =============================== - - // note: We don't necessarily overwrite all the memory, but it doesn't matter. - // auto texture_data = std::make_unique(static_cast(atlas_width * atlas_height)); - auto texture_data = std::make_unique_for_overwrite(static_cast(atlas_width * atlas_height)); - - const uv_extent_type texture_uv_scale{1.f / static_cast(atlas_width), 1.f / static_cast(atlas_height)}; - - // =============================== - - for (const auto& [id, rect_width, rect_height, rect_x, rect_y, was_packed]: rects) - { - if (id == id_baked_line) - { - using value_type = uv_point_type::value_type; - constexpr std::uint32_t white_color = 0xff'ff'ff'ff; - - // hacky: baked line rect area, one pixel - { - const auto x = rect_x + rect_y * atlas_width; - texture_data[x] = white_color; - - const auto uv_x = static_cast(static_cast(rect_x) + .5f) * texture_uv_scale.width; - const auto uv_y = static_cast(static_cast(rect_y) + .5f) * texture_uv_scale.height; - - white_pixel_uv_ = uv_point_type{uv_x, uv_y}; - } - - // ◿ - for (stbrp_coord y = 0; y < rect_height; ++y) - { - const auto line_width = y; - const auto offset_y = (rect_y + y) * atlas_width; - - for (stbrp_coord x = line_width; x > 0; --x) - { - const auto offset_x = rect_x + (rect_width - x); - const auto index = offset_x + offset_y; - texture_data[index] = white_color; - } - - const auto p_x = rect_x + (rect_width - line_width); - const auto p_y = rect_y + y; - const auto width = line_width; - constexpr auto height = .5f; - - const auto uv_x = static_cast(p_x) * texture_uv_scale.width; - const auto uv_y = static_cast(p_y) * texture_uv_scale.height; - const auto uv_width = static_cast(width) * texture_uv_scale.width; - const auto uv_height = static_cast(height) * texture_uv_scale.height; - - baked_line_uv_.emplace_back(uv_x, uv_y, uv_width, uv_height); - } - - continue; - } - - const auto c = std::bit_cast(id); - - // todo - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(c <= std::numeric_limits::max()); - - if (FT_Load_Char(face, c, FT_LOAD_RENDER)) - { - continue; - } - - const auto& g = face->glyph; - - for (std::uint32_t y = 0; y < g->bitmap.rows; ++y) - { - for (std::uint32_t x = 0; x < g->bitmap.width; ++x) - { - const auto index = rect_x + x + (rect_y + y) * atlas_width; - const auto a = g->bitmap.buffer[x + y * g->bitmap.pitch] << 24; - const auto color = - // A - a | - // B - std::uint32_t{0xff} << 16 | - // G - std::uint32_t{0xff} << 8 | - // R - std::uint32_t{0xff}; - texture_data[index] = color; - } - } - - const auto p_x = static_cast(g->bitmap_left); - const auto p_y = static_cast(g->bitmap_top); - const auto p_width = static_cast(g->bitmap.width); - const auto p_height = static_cast(g->bitmap.rows); - - const auto uv_x = static_cast(rect_x) * texture_uv_scale.width; - const auto uv_y = static_cast(rect_y) * texture_uv_scale.height; - const auto uv_width = static_cast(g->bitmap.width) * texture_uv_scale.width; - const auto uv_height = static_cast(g->bitmap.rows) * texture_uv_scale.height; - - const glyph_type glyph{ - .rect = {p_x, p_y, p_width, p_height}, - .uv = {uv_x, uv_y, uv_width, uv_height}, - .advance_x = static_cast(g->advance.x) / 64.f - }; - - glyphs_.insert_or_assign(static_cast(c), glyph); - } - - fallback_glyph_ = glyphs_[static_cast('?')]; - - // =============================== - - destroy_ft(ft); - - texture.set_size(atlas_width, atlas_height); - texture.set_data(std::move(texture_data)); - return texture; - } - - auto Font::loaded() const noexcept -> bool - { - return not glyphs_.empty() and texture_id_ != invalid_texture_id; - } - - auto Font::font_path() const noexcept -> std::string_view - { - return font_path_; - } - - auto Font::pixel_height() const noexcept -> std::uint32_t - { - return pixel_height_; - } - - auto Font::baked_line_max_width() const noexcept -> std::uint32_t - { - return baked_line_max_width_; - } - - auto Font::glyphs() const noexcept -> const glyphs_type& - { - return glyphs_; - } - - auto Font::fallback_glyph() const noexcept -> const glyph_type& - { - return fallback_glyph_; - } - - auto Font::white_pixel_uv() const noexcept -> const uv_point_type& - { - return white_pixel_uv_; - } - - auto Font::baked_line_uv() const noexcept -> const baked_line_uv_type& - { - return baked_line_uv_; - } - - auto Font::texture_id() const noexcept -> texture_id_type - { - return texture_id_; - } - - auto Font::text_size( - const std::basic_string_view utf8_text, - const float font_size, - const float wrap_width, - std::basic_string& out_text - ) const noexcept -> extent_type - { - // todo - auto utf16_text = chars::convert(utf8_text); - static_assert(std::is_same_v); - - const auto line_height = font_size; - const auto scale = line_height / static_cast(pixel_height_); - const auto& glyphs = glyphs_; - const auto& fallback_glyph = fallback_glyph_; - - float max_width = 0; - float current_width = 0; - float total_height = line_height; - - auto it_input_current = utf16_text.begin(); - const auto it_input_end = utf16_text.end(); - - while (it_input_current != it_input_end) - { - const auto this_char = *it_input_current; - it_input_current += 1; - - if (this_char == u'\n') - { - max_width = std::ranges::max(max_width, current_width); - current_width = 0; - total_height += line_height; - } - else - { - const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { - if (const auto it = glyphs.find(c); - it != glyphs.end()) - { - return it->second; - } - - return fallback_glyph; - }(this_char); - - if (const auto advance_x = glyph_advance_x * scale; - current_width + advance_x > wrap_width) - { - max_width = std::ranges::max(max_width, current_width); - current_width = advance_x; - total_height += line_height; - } - else - { - current_width += advance_x; - } - } - } - - max_width = std::ranges::max(max_width, current_width); - out_text = std::move(utf16_text); - - return {max_width, total_height}; - } - - auto Font::text_size( - const std::basic_string_view utf8_text, - const float font_size, - const float wrap_width - ) const noexcept -> extent_type - { - std::basic_string out; - return text_size(utf8_text, font_size, wrap_width, out); - } - - auto Font::text_draw( - const std::basic_string_view utf8_text, - const float font_size, - const float wrap_width, - const point_type point, - const color_type color, - const DrawListDef::Accessor accessor - ) const noexcept -> void - { - // todo - auto utf16_text = chars::convert(utf8_text); - static_assert(std::is_same_v); - - const auto newline_count = std::ranges::count(utf16_text, u'\n'); - const auto text_size_exclude_newline = utf16_text.size() - newline_count; - - const auto vertex_count = 4 * text_size_exclude_newline; - const auto index_count = 6 * text_size_exclude_newline; - accessor.reserve(vertex_count, index_count); - - const auto line_height = font_size; - const auto scale = line_height / static_cast(pixel_height_); - const auto& glyphs = glyphs_; - const auto& fallback_glyph = fallback_glyph_; - - auto cursor = point + point_type{0, line_height}; - - auto it_input_current = utf16_text.begin(); - const auto it_input_end = utf16_text.end(); - - while (it_input_current != it_input_end) - { - const auto this_char = *it_input_current; - it_input_current += 1; - - if (this_char == u'\n') - { - cursor.x = point.x; - cursor.y += line_height; - continue; - } - - const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { - if (const auto it = glyphs.find(c); - it != glyphs.end()) - { - return it->second; - } - - return fallback_glyph; - }(this_char); - - const auto advance_x = glyph_advance_x * scale; - if (cursor.x + advance_x > point.x + wrap_width) - { - cursor.x = point.x; - cursor.y += line_height; - } - - const rect_type char_rect - { - cursor + point_type{glyph_rect.left_top().x, -glyph_rect.left_top().y} * scale, - glyph_rect.size() * scale - }; - cursor.x += advance_x; - - const auto current_vertex_index = static_cast(accessor.size()); - - accessor.add_vertex(char_rect.left_top(), glyph_uv.left_top(), color); - accessor.add_vertex(char_rect.right_top(), glyph_uv.right_top(), color); - accessor.add_vertex(char_rect.right_bottom(), glyph_uv.right_bottom(), color); - accessor.add_vertex(char_rect.left_bottom(), glyph_uv.left_bottom(), color); - - accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - } - } -} diff --git a/src/draw/font.hpp b/src/draw/font.hpp deleted file mode 100644 index 6eece7a..0000000 --- a/src/draw/font.hpp +++ /dev/null @@ -1,252 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include - -#include - -#include - -namespace gal::prometheus::draw -{ - // ReSharper disable once CppClassCanBeFinal - class Font - { - public: - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using color_type = DrawListDef::color_type; - using index_type = DrawListDef::index_type; - - using texture_id_type = DrawListDef::texture_id_type; - constexpr static texture_id_type invalid_texture_id = 0; - - using uv_rect_type = primitive::basic_rect_2d; - using uv_point_type = uv_rect_type::point_type; - using uv_extent_type = uv_rect_type::extent_type; - - // todo: char32_t ? - using char_type = char16_t; - - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH - // Variable '%1$s' is uninitialized. Always initialize a member variable (type.6). - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(26495) - #endif - - struct glyph_type - { - rect_type rect; - uv_rect_type uv; - float advance_x; - }; - - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP - #endif - - using glyphs_type = std::unordered_map; - - using glyph_value_type = i18n::RangeBuilder::value_type; - using glyph_ranges_type = i18n::RangeBuilder::ranges_type; - - using baked_line_uv_type = std::vector; - - // todo - constexpr static std::uint32_t default_baked_line_max_width = 63; - - class Option - { - friend Font; - - std::string font_path_; - glyph_ranges_type glyph_ranges_; - - std::uint32_t pixel_height_; - std::uint32_t baked_line_max_width_; - - Option() noexcept - : pixel_height_{18}, - baked_line_max_width_{default_baked_line_max_width} {} - - public: - constexpr auto path(const std::string_view path) noexcept -> Option& - { - font_path_ = path; - - return *this; - } - - constexpr auto glyph_ranges(glyph_ranges_type&& ranges) noexcept -> Option& - { - glyph_ranges_ = std::move(ranges); - - return *this; - } - - constexpr auto pixel_height(const std::uint32_t height) noexcept -> Option& - { - pixel_height_ = height; - - return *this; - } - - constexpr auto baked_line_max_width(const std::uint32_t width) noexcept -> Option& - { - baked_line_max_width_ = width; - - return *this; - } - }; - - class Texture final - { - friend Font; - - public: - using size_type = std::uint32_t; - // size.width * size.height (RGBA) - using data_type = std::unique_ptr; - - private: - size_type width_; - size_type height_; - data_type data_; - std::reference_wrapper id_; - - explicit Texture(texture_id_type& id) noexcept - : width_{0}, - height_{0}, - data_{nullptr}, - id_{id} {} - - constexpr auto set_size(const size_type width, const size_type height) noexcept -> void - { - width_ = width; - height_ = height; - } - - constexpr auto set_data(data_type&& data) noexcept -> void - { - data_ = std::move(data); - } - - public: - Texture(const Texture& other) = delete; - Texture(Texture&& other) noexcept = default; - auto operator=(const Texture& other) -> Texture& = delete; - auto operator=(Texture&& other) noexcept -> Texture& = default; - - ~Texture() noexcept; - - [[nodiscard]] constexpr auto valid() const noexcept -> bool - { - return data_ != nullptr; - } - - [[nodiscard]] constexpr auto width() const noexcept -> size_type - { - return width_; - } - - [[nodiscard]] constexpr auto height() const noexcept -> size_type - { - return height_; - } - - [[nodiscard]] constexpr auto data() const & noexcept -> const data_type& - { - return data_; - } - - [[nodiscard]] constexpr auto data() && noexcept -> data_type&& - { - return std::move(data_); - } - - auto bind(texture_id_type id) const noexcept -> void; - }; - - private: - std::string font_path_; - std::uint32_t pixel_height_; - std::uint32_t baked_line_max_width_; - - glyphs_type glyphs_; - glyph_type fallback_glyph_; - - uv_point_type white_pixel_uv_; - baked_line_uv_type baked_line_uv_; - - texture_id_type texture_id_; - - auto reset() noexcept -> void; - - public: - explicit Font() noexcept; - - Font(const Font& other) = delete; - Font(Font&& other) noexcept = default; - auto operator=(const Font& other) -> Font& = delete; - auto operator=(Font&& other) noexcept -> Font& = default; - - virtual ~Font() noexcept; - - [[nodiscard]] static auto option() noexcept -> Option; - - [[nodiscard]] auto load(const Option& option) noexcept -> Texture; - - // --------------------------------------------------------- - - [[nodiscard]] auto loaded() const noexcept -> bool; - - [[nodiscard]] auto font_path() const noexcept -> std::string_view; - - [[nodiscard]] auto pixel_height() const noexcept -> std::uint32_t; - - [[nodiscard]] auto baked_line_max_width() const noexcept -> std::uint32_t; - - [[nodiscard]] auto glyphs() const noexcept -> const glyphs_type&; - - [[nodiscard]] auto fallback_glyph() const noexcept -> const glyph_type&; - - [[nodiscard]] auto white_pixel_uv() const noexcept -> const uv_point_type&; - - [[nodiscard]] auto baked_line_uv() const noexcept -> const baked_line_uv_type&; - - [[nodiscard]] auto texture_id() const noexcept -> texture_id_type; - - // ========================================= - // DRAW TEXT - - [[nodiscard]] virtual auto text_size( - std::basic_string_view utf8_text, - float font_size, - float wrap_width, - std::basic_string& out_text - ) const noexcept -> extent_type; - - [[nodiscard]] virtual auto text_size( - std::basic_string_view utf8_text, - float font_size, - float wrap_width - ) const noexcept -> extent_type; - - virtual auto text_draw( - std::basic_string_view utf8_text, - float font_size, - float wrap_width, - point_type point, - color_type color, - DrawListDef::Accessor accessor - ) const noexcept -> void; - }; -} diff --git a/src/draw/mouse.cpp b/src/draw/mouse.cpp deleted file mode 100644 index 6e3f7b1..0000000 --- a/src/draw/mouse.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -namespace gal::prometheus::draw -{ - Mouse::Mouse(const time_type double_click_interval_threshold, const value_type double_click_distance_threshold) noexcept - : double_click_interval_threshold_{double_click_interval_threshold}, - double_click_distance_threshold_{double_click_distance_threshold}, - position_current_{std::numeric_limits::min(), std::numeric_limits::min()}, - position_previous_{std::numeric_limits::min(), std::numeric_limits::min()}, - position_clicked_{std::numeric_limits::min(), std::numeric_limits::min()}, - down_{false}, - clicked_{false}, - double_clicked_{false}, - pad_{false}, - down_duration_{0}, - click_duration_{0} - { - std::ignore = pad_; - } - - auto Mouse::position() const noexcept -> point_type - { - return position_current_; - } - - auto Mouse::position_delta() const noexcept -> extent_type - { - const auto delta = position_current_ - position_previous_; - - return delta.to(); - } - - auto Mouse::down() const noexcept -> bool - { - return down_; - } - - auto Mouse::clicked() const noexcept -> bool - { - return clicked_; - } - - auto Mouse::double_clicked() const noexcept -> bool - { - return double_clicked_; - } - - auto Mouse::move(const point_type position) noexcept -> void - { - position_current_ = position; - } - - auto Mouse::tick(const time_type tick_time) noexcept -> void - { - position_previous_ = position_current_; - - clicked_ = false; - double_clicked_ = false; - if (down_) - { - if (down_duration_ > 0) - { - down_duration_ += tick_time; - } - else - { - down_duration_ = 0; - clicked_ = true; - } - } - else - { - down_duration_ = std::numeric_limits::min(); - } - if (clicked_) - { - if (0 - click_duration_ < double_click_interval_threshold_) - { - if (position_current_.distance(position_clicked_) < double_click_distance_threshold_) - { - double_clicked_ = true; - } - click_duration_ = std::numeric_limits::min(); - } - else - { - click_duration_ = 0; - position_clicked_ = position_current_; - } - } - } -} diff --git a/src/draw/mouse.hpp b/src/draw/mouse.hpp deleted file mode 100644 index be82a3e..0000000 --- a/src/draw/mouse.hpp +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -namespace gal::prometheus::draw -{ - class Context; - - class Mouse final - { - friend Context; - - public: - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using value_type = point_type::value_type; - - using time_type = float; - - private: - // ================================== - // static - // ================================== - - time_type double_click_interval_threshold_; - value_type double_click_distance_threshold_; - - // ================================== - // dynamic - // ================================== - - point_type position_current_; - point_type position_previous_; - point_type position_clicked_; - - bool down_; - bool clicked_; - bool double_clicked_; - bool pad_; - - time_type down_duration_; - time_type click_duration_; - - Mouse(time_type double_click_interval_threshold, value_type double_click_distance_threshold) noexcept; - - public: - [[nodiscard]] auto position() const noexcept -> point_type; - - [[nodiscard]] auto position_delta() const noexcept -> extent_type; - - [[nodiscard]] auto down() const noexcept -> bool; - - [[nodiscard]] auto clicked() const noexcept -> bool; - - [[nodiscard]] auto double_clicked() const noexcept -> bool; - - private: - auto move(point_type position) noexcept -> void; - - auto tick(time_type tick_time) noexcept -> void; - }; -} diff --git a/src/draw/shared_data.cpp b/src/draw/shared_data.cpp deleted file mode 100644 index cd7bb84..0000000 --- a/src/draw/shared_data.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include -#include - -#include - -#include - -namespace -{ - using namespace gal::prometheus; - using namespace draw; - - constexpr auto circle_segments_min = DrawListSharedData::circle_segments_min; - constexpr auto circle_segments_max = DrawListSharedData::circle_segments_max; - - // @see https://stackoverflow.com/a/2244088/15194693 - // Number of segments (N) is calculated using equation: - // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r - [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto - { - constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto - { - return (v + 1) / 2 * 2; - }; - - return std::ranges::clamp( - circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), - circle_segments_min, - circle_segments_max - ); - } - - [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto - { - return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); - } - - [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto - { - return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; - } - - template - [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> DrawListSharedData::vertex_sample_points_type - { - return [](std::index_sequence) noexcept -> DrawListSharedData::vertex_sample_points_type - { - constexpr auto make_point = []() noexcept -> DrawListSharedData::point_type - { - const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; - return {math::cos(a), -math::sin(a)}; - }; - - return {{make_point.template operator()()...}}; - }(std::make_index_sequence{}); - } -} - -namespace gal::prometheus::draw -{ - [[nodiscard]] auto range_of_arc_quadrant(const DrawArcFlag quadrant) noexcept -> std::pair - { - static_assert(DrawListSharedData::vertex_sample_points_count % 12 == 0); - constexpr auto factor = static_cast(DrawListSharedData::vertex_sample_points_count / 12); - - switch (quadrant) - { - case DrawArcFlag::Q1: { return std::make_pair(0 * factor, 3 * factor); } - case DrawArcFlag::Q2: { return std::make_pair(3 * factor, 6 * factor); } - case DrawArcFlag::Q3: { return std::make_pair(6 * factor, 9 * factor); } - case DrawArcFlag::Q4: { return std::make_pair(9 * factor, 12 * factor); } - case DrawArcFlag::TOP: { return std::make_pair(0 * factor, 6 * factor); } - case DrawArcFlag::BOTTOM: { return std::make_pair(6 * factor, 12 * factor); } - case DrawArcFlag::LEFT: { return std::make_pair(3 * factor, 9 * factor); } - case DrawArcFlag::RIGHT: { return std::make_pair(9 * factor, 15 * factor); } - case DrawArcFlag::ALL: { return std::make_pair(0 * factor, 12 * factor); } - case DrawArcFlag::Q1_CLOCK_WISH: { return std::make_pair(3 * factor, 0 * factor); } - case DrawArcFlag::Q2_CLOCK_WISH: { return std::make_pair(6 * factor, 3 * factor); } - case DrawArcFlag::Q3_CLOCK_WISH: { return std::make_pair(9 * factor, 6 * factor); } - case DrawArcFlag::Q4_CLOCK_WISH: { return std::make_pair(12 * factor, 9 * factor); } - case DrawArcFlag::TOP_CLOCK_WISH: { return std::make_pair(6 * factor, 0 * factor); } - case DrawArcFlag::BOTTOM_CLOCK_WISH: { return std::make_pair(12 * factor, 6 * factor); } - case DrawArcFlag::LEFT_CLOCK_WISH: { return std::make_pair(9 * factor, 3 * factor); } - case DrawArcFlag::RIGHT_CLOCK_WISH: { return std::make_pair(15 * factor, 9 * factor); } - case DrawArcFlag::ALL_CLOCK_WISH: { return std::make_pair(12 * factor, 0 * factor); } - default: { GAL_PROMETHEUS_ERROR_UNREACHABLE(); } - } - } - - DrawListSharedData::DrawListSharedData() noexcept - : - circle_segment_counts_{}, - vertex_sample_points_{vertex_sample_points_calc()}, - circle_segment_max_error_{}, - arc_fast_radius_cutoff_{}, - curve_tessellation_tolerance_{1.25f} - { - set_circle_tessellation_max_error(.3f); - } - - auto DrawListSharedData::get_circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type - { - // ceil to never reduce accuracy - if (const auto radius_index = static_cast(radius + .999999f); - radius_index < circle_segment_counts_.size()) - { - return circle_segment_counts_[radius_index]; - } - return static_cast(circle_segments_calc(radius, circle_segment_max_error_)); - } - - auto DrawListSharedData::get_vertex_sample_point(const std::size_t index) const noexcept -> const point_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points_.size()); - - return vertex_sample_points_[index]; - } - - auto DrawListSharedData::get_circle_tessellation_max_error() const noexcept -> float - { - return circle_segment_max_error_; - } - - auto DrawListSharedData::get_arc_fast_radius_cutoff() const noexcept -> float - { - return arc_fast_radius_cutoff_; - } - - auto DrawListSharedData::get_curve_tessellation_tolerance() const noexcept -> float - { - return curve_tessellation_tolerance_; - } - - auto DrawListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); - - if (circle_segment_max_error_ == max_error) // NOLINT(clang-diagnostic-float-equal) - { - return; - } - - for (decltype(circle_segment_counts_.size()) i = 0; i < circle_segment_counts_.size(); ++i) - { - const auto radius = static_cast(i); - circle_segment_counts_[i] = static_cast(circle_segments_calc(radius, max_error)); - } - circle_segment_max_error_ = max_error; - arc_fast_radius_cutoff_ = circle_segments_calc_radius(vertex_sample_points_count, max_error); - } - - auto DrawListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); - - curve_tessellation_tolerance_ = tolerance; - } -} diff --git a/src/draw/shared_data.hpp b/src/draw/shared_data.hpp deleted file mode 100644 index 8fbd866..0000000 --- a/src/draw/shared_data.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -namespace gal::prometheus::draw -{ - class DrawListSharedData final - { - public: - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using circle_segment_count_type = std::uint8_t; - constexpr static std::size_t circle_segment_counts_count = 64; - using circle_segment_counts_type = std::array; - - constexpr static std::uint32_t circle_segments_min = 4; - constexpr static std::uint32_t circle_segments_max = 512; - - constexpr static std::size_t vertex_sample_points_count = 48; - using vertex_sample_points_type = std::array; - - private: - circle_segment_counts_type circle_segment_counts_; - vertex_sample_points_type vertex_sample_points_; - - // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. - // Decrease for higher quality but more geometry. - float circle_segment_max_error_; - // Cutoff radius after which arc drawing will fall back to slower `path_arc` - float arc_fast_radius_cutoff_; - // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. - // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float curve_tessellation_tolerance_; - - public: - DrawListSharedData() noexcept; - - // -------------------------------------------------- - - [[nodiscard]] auto get_circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; - - [[nodiscard]] auto get_vertex_sample_point(std::size_t index) const noexcept -> const point_type&; - - [[nodiscard]] auto get_circle_tessellation_max_error() const noexcept -> float; - - [[nodiscard]] auto get_arc_fast_radius_cutoff() const noexcept -> float; - - [[nodiscard]] auto get_curve_tessellation_tolerance() const noexcept -> float; - - // -------------------------------------------------- - - auto set_circle_tessellation_max_error(float max_error) noexcept -> void; - - auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; - }; -} diff --git a/src/draw/theme.cpp b/src/draw/theme.cpp deleted file mode 100644 index ccd36c0..0000000 --- a/src/draw/theme.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. -#include - -namespace -{ - using namespace gal::prometheus; - using colors_type = draw::Theme::colors_type; - - [[nodiscard]] constexpr auto default_colors() noexcept -> colors_type - { - using draw::ThemeCategory; - - colors_type colors{}; - - colors[static_cast(ThemeCategory::TEXT)] = primitive::colors::black; - colors[static_cast(ThemeCategory::BORDER)] = primitive::colors::magenta; - - colors[static_cast(ThemeCategory::WINDOW_BACKGROUND)] = primitive::colors::gains_boro; - - colors[static_cast(ThemeCategory::WIDGET_BACKGROUND)] = primitive::colors::white; - colors[static_cast(ThemeCategory::WIDGET_ACTIVATED)] = primitive::colors::dark_salmon; - - colors[static_cast(ThemeCategory::TITLE_BAR)] = primitive::colors::light_coral; - colors[static_cast(ThemeCategory::TITLE_BAR_COLLAPSED)] = primitive::colors::dark_khaki; - - colors[static_cast(ThemeCategory::SLIDER)] = primitive::colors::light_blue; - colors[static_cast(ThemeCategory::SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; - - colors[static_cast(ThemeCategory::BUTTON)] = primitive::colors::sienna; - colors[static_cast(ThemeCategory::BUTTON_HOVERED)] = primitive::colors::slate_gray; - colors[static_cast(ThemeCategory::BUTTON_ACTIVATED)] = primitive::colors::steel_blue; - - colors[static_cast(ThemeCategory::RESIZE_GRIP)] = primitive::colors::gold; - colors[static_cast(ThemeCategory::RESIZE_GRIP_HOVERED)] = primitive::colors::peru; - colors[static_cast(ThemeCategory::RESIZE_GRIP_ACTIVATED)] = primitive::colors::powder_blue; - - colors[static_cast(ThemeCategory::TOOLTIP_BACKGROUND)] = primitive::colors::black; - colors[static_cast(ThemeCategory::TOOLTIP_TEXT)] = primitive::colors::red; - - return colors; - } - - [[nodiscard]] constexpr auto another_colors_for_test() noexcept -> colors_type - { - using draw::ThemeCategory; - - colors_type colors{}; - - colors[static_cast(ThemeCategory::TEXT)] = primitive::colors::black; - colors[static_cast(ThemeCategory::BORDER)] = primitive::colors::magenta; - - colors[static_cast(ThemeCategory::WINDOW_BACKGROUND)] = primitive::colors::pink; - - colors[static_cast(ThemeCategory::WIDGET_BACKGROUND)] = primitive::colors::white; - colors[static_cast(ThemeCategory::WIDGET_ACTIVATED)] = primitive::colors::dark_salmon; - - colors[static_cast(ThemeCategory::TITLE_BAR)] = primitive::colors::light_coral; - colors[static_cast(ThemeCategory::TITLE_BAR_COLLAPSED)] = primitive::colors::dark_khaki; - - colors[static_cast(ThemeCategory::SLIDER)] = primitive::colors::light_blue; - colors[static_cast(ThemeCategory::SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; - - colors[static_cast(ThemeCategory::BUTTON)] = primitive::colors::sienna; - colors[static_cast(ThemeCategory::BUTTON_HOVERED)] = primitive::colors::slate_gray; - colors[static_cast(ThemeCategory::BUTTON_ACTIVATED)] = primitive::colors::steel_blue; - - colors[static_cast(ThemeCategory::RESIZE_GRIP)] = primitive::colors::red; - colors[static_cast(ThemeCategory::RESIZE_GRIP_HOVERED)] = primitive::colors::yellow; - colors[static_cast(ThemeCategory::RESIZE_GRIP_ACTIVATED)] = primitive::colors::blue; - - colors[static_cast(ThemeCategory::TOOLTIP_BACKGROUND)] = primitive::colors::black; - colors[static_cast(ThemeCategory::TOOLTIP_TEXT)] = primitive::colors::blue; - - return colors; - } -} - -namespace gal::prometheus::draw -{ - auto Theme::default_theme() noexcept -> Theme - { - return - { - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - .font_path = R"(C:\Windows\Fonts\msyh.ttc)", - .font_size = 18, - #else - #error "fixme" - #endif - .title_bar_height = 20, - .window_rounding = 0, - .window_padding = {8, 8}, - .window_min_size = {640, 480}, - .resize_grip_size = {20, 20}, - .frame_padding = {4, 4}, - .item_spacing = {10, 5}, - .item_inner_spacing = {5, 5}, - .colors = default_colors() - }; - } - - auto Theme::another_theme_for_test() noexcept -> Theme - { - return - { - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - .font_path = R"(C:\Windows\Fonts\msyh.ttc)", - .font_size = 24, - #else - #error "fixme" - #endif - .title_bar_height = 40, - .window_rounding = 5, - .window_padding = {15, 15}, - .window_min_size = {1280, 960}, - .resize_grip_size = {40, 40}, - .frame_padding = {8, 8}, - .item_spacing = {20, 10}, - .item_inner_spacing = {10, 10}, - .colors = another_colors_for_test() - }; - } -} diff --git a/src/draw/theme.hpp b/src/draw/theme.hpp deleted file mode 100644 index e3a71bb..0000000 --- a/src/draw/theme.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include - -namespace gal::prometheus::draw -{ - class [[nodiscard]] Theme final - { - public: - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using value_type = point_type::value_type; - - using color_type = DrawListDef::color_type; - using colors_type = std::array; - - // ----------------------------------------------- - // FONT - - std::string font_path; - value_type font_size; - - // ----------------------------------------------- - // WINDOW - - value_type title_bar_height; - value_type window_rounding; - extent_type window_padding; - extent_type window_min_size; - extent_type resize_grip_size; - - // ----------------------------------------------- - // WINDOW CANVAS LAYOUT - - extent_type frame_padding; - extent_type item_spacing; - extent_type item_inner_spacing; - - // ----------------------------------------------- - // WINDOW WIDGET COLOR - - colors_type colors; - - template - requires (std::to_underlying(Category) < theme_category_count) - [[nodiscard]] constexpr auto color() const noexcept -> color_type - { - return colors[static_cast(Category)]; - } - - [[nodiscard]] static auto default_theme() noexcept -> Theme; - - [[nodiscard]] static auto another_theme_for_test() noexcept -> Theme; - }; -} diff --git a/src/draw/window.cpp b/src/draw/window.cpp deleted file mode 100644 index 8bfdb79..0000000 --- a/src/draw/window.cpp +++ /dev/null @@ -1,648 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. -#include - -#include - -namespace -{ - using namespace gal::prometheus; - - constexpr std::string_view window_widget_name_move{"@WINDOW::MOVE@"}; - constexpr std::string_view window_widget_name_close{"@WINDOW::CLOSE@"}; - constexpr std::string_view window_widget_name_resize{"@WINDOW::RESIZE@"}; -} - -namespace gal::prometheus::draw -{ - auto Window::get_id(const std::string_view name) const noexcept -> id_type - { - return functional::hash_combine_2(id_, functional::hash(name)); - } - - auto Window::rect_of_title_bar() const noexcept -> rect_type - { - const auto& context = Context::instance(); - const auto& theme = context.theme(); - - const auto point = rect_.left_top(); - const auto size = extent_type{rect_.width(), theme.title_bar_height}; - return {point, size}; - } - - auto Window::rect_of_close_button() const noexcept -> rect_type - { - const auto& context = Context::instance(); - const auto& theme = context.theme(); - - // RIGHT-TOP-CORNER - const auto point = rect_.right_top() - extent_type{theme.title_bar_height, 0}; - const auto size = extent_type{theme.title_bar_height, theme.title_bar_height}; - return {point, size}; - } - - auto Window::rect_of_resize_grip() const noexcept -> rect_type - { - const auto& context = Context::instance(); - const auto& theme = context.theme(); - - // RIGHT-BOTTOM-CORNER - const auto point = rect_.right_bottom() - theme.resize_grip_size; - const auto size = theme.resize_grip_size; - return {point, size}; - } - - auto Window::rect_of_canvas() const noexcept -> rect_type - { - // const auto& context = Context::instance(); - // const auto& theme = context.theme(); - - return rect_; - } - - auto Window::make_canvas() noexcept -> bool - { - auto& window = *this; - - auto& canvas = window.canvas_; - auto& draw_list = window.draw_list_; - const auto flag_value = std::to_underlying(window.flag_); - - auto& context = Context::instance(); - const auto& theme = context.theme(); - const auto& mouse = context.mouse(); - - // ------------------------------ - // TEST TITLE BAR - auto close_button_color = theme.color(); - auto close_button_pressed = false; - { - if (flag_value & std::to_underlying(WindowFlag::NO_TITLE_BAR)) - { - window.collapse_ = false; - } - else if (mouse.double_clicked() and window.rect_of_title_bar().includes(mouse.position())) - { - window.collapse_ = not window.collapse_; - } - - if (flag_value & std::to_underlying(WindowFlag::NO_CLOSE)) - { - // - } - else - { - const auto id = get_id(window_widget_name_close); - const auto close_button_rect = window.rect_of_close_button(); - - const auto status = context.test_widget_status( - id, - close_button_rect, - false - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - , - std::format("Test Window({})'s close-button({}).", window.name_, close_button_rect) - #endif - ); - - if (status.hovered) - { - if (status.keeping) - { - close_button_color = theme.color(); - } - else - { - close_button_color = theme.color(); - } - } - - if (status.pressed) - { - close_button_pressed = true; - } - } - - if (flag_value & std::to_underlying(WindowFlag::NO_MOVE)) - { - // - } - else - { - if (const auto id = get_id(window_widget_name_move); - context.is_widget_activated(id)) - { - if (mouse.down()) - { - window.rect_.point += mouse.position_delta(); - } - else - { - context.invalidate_widget_activated( - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - std::format("{} is not a movable window.", window.name_) - #endif - ); - } - } - } - } - - // ------------------------------ - // TEST RESIZE GRIP - auto resize_grip_color = theme.color(); - { - if (flag_value & std::to_underlying(WindowFlag::NO_RESIZE)) - { - // - } - else - { - if (not window.collapse_) - { - const auto id = get_id(window_widget_name_resize); - const auto resize_grip_rect = window.rect_of_resize_grip(); - - const auto status = context.test_widget_status( - id, - resize_grip_rect, - false - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - , - std::format("Test Window({})'s resize-grip({}).", window.name_, resize_grip_rect) - #endif - ); - - if (status.keeping) - { - const auto target_size = window.rect_.size() + mouse.position_delta(); - const auto min_size = theme.window_min_size; - window.rect_.extent = {std::ranges::max(target_size.width, min_size.width), std::ranges::max(target_size.height, min_size.height)}; - resize_grip_color = theme.color(); - } - else if (status.hovered) - { - resize_grip_color = theme.color(); - } - } - } - } - - // ------------------------------ - // INIT CANVAS - { - if (flag_value & std::to_underlying(WindowFlag::NO_TITLE_BAR)) - { - canvas.cursor_start_line = (theme.window_padding + extent_type{0, 0}).to(); - } - else - { - canvas.cursor_start_line = (theme.window_padding + extent_type{0, theme.title_bar_height}).to(); - } - canvas.cursor_current_line = canvas.cursor_start_line; - canvas.cursor_previous_line = canvas.cursor_current_line; - canvas.height_current_line = 0; - canvas.height_previous_line = 0; - canvas.item_width.resize(0); - canvas.item_width.push_back(window.default_item_width_); - } - - // ------------------------------ - // DRAW CANVAS BACKGROUND - { - const auto canvas_rect = window.rect_of_canvas(); - - if (window.collapse_) - { - // - } - else - { - draw_list.rect_filled(canvas_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); - if (flag_value & std::to_underlying(WindowFlag::BORDERED)) - { - draw_list.rect(canvas_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); - } - } - } - - // ------------------------------ - // DRAW TITTLE BAR - { - const auto title_bar_rect = window.rect_of_title_bar(); - - if (window.collapse_) - { - draw_list.rect_filled(title_bar_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); - - if (flag_value & std::to_underlying(WindowFlag::BORDERED)) - { - draw_list.rect(title_bar_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); - } - } - else - { - draw_list.rect_filled(title_bar_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_TOP); - } - - // todo: position - const auto text_point = title_bar_rect.left_top() + extent_type{theme.item_inner_spacing.width, 0}; - draw_list.text( - theme.font_size, - text_point, - theme.color(), - window.name_ - ); - - const auto close_button_rect = window.rect_of_close_button(); - if (flag_value & std::to_underlying(WindowFlag::NO_CLOSE)) - { - // - } - else - { - const auto center = close_button_rect.center(); - const auto radius = close_button_rect.width() / 2; - - const auto r = radius / std::numbers::sqrt2_v; - - const auto line1_from = center + extent_type{-r, -r}; - const auto line1_to = center + extent_type{r, r}; - - const auto line2_from = center + extent_type{-r, r}; - const auto line2_to = center + extent_type{r, -r}; - - draw_list.circle_filled(close_button_rect.center(), close_button_rect.width() / 2, close_button_color); - draw_list.line(line1_from, line1_to, theme.color()); - draw_list.line(line2_from, line2_to, theme.color()); - } - } - - // ------------------------------ - // DRAW RESIZE GRIP - { - const auto resize_grip_rect = window.rect_of_resize_grip(); - - if (window.collapse_ or (flag_value & std::to_underlying(WindowFlag::NO_RESIZE))) - { - // - } - else - { - // todo: rounding? - draw_list.triangle_filled(resize_grip_rect.left_bottom(), resize_grip_rect.right_bottom(), resize_grip_rect.right_top(), resize_grip_color); - } - } - - return close_button_pressed; - } - - auto Window::cursor_abs_position() const noexcept -> point_type - { - auto& window = *this; - - const auto& canvas = window.canvas_; - // auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - // const auto& context = Context::instance(); - // const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - return window.rect_.left_top() + canvas.cursor_current_line; - } - - auto Window::cursor_remaining_width() const noexcept -> value_type - { - auto& window = *this; - - const auto& canvas = window.canvas_; - // auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - // const auto& context = Context::instance(); - // const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - return window.rect_.width() - canvas.cursor_current_line.x; - } - - auto Window::adjust_item_size(const extent_type& size) noexcept -> void - { - auto& window = *this; - - auto& canvas = window.canvas_; - // auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - const auto& context = Context::instance(); - const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - if (window.collapse_) - { - return; - } - - const auto line_height = std::ranges::max(canvas.height_current_line, size.height); - - // Always align ourselves on pixel boundaries - canvas.cursor_previous_line = {canvas.cursor_current_line.x + size.width, canvas.cursor_current_line.y}; - canvas.cursor_current_line = {theme.window_padding.width, canvas.cursor_current_line.y + line_height + theme.item_spacing.height}; - - canvas.height_previous_line = line_height; - canvas.height_current_line = 0; - } - - auto Window::draw_widget_frame(const rect_type& rect, const color_type& color) noexcept -> void - { - auto& window = *this; - - // auto& canvas = window.canvas_; - auto& draw_list = window.draw_list_; - const auto flag_value = std::to_underlying(window.flag_); - - const auto& context = Context::instance(); - // const auto& font = context.font(); - const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - draw_list.rect_filled(rect, color); - if (flag_value & std::to_underlying(WindowFlag::BORDERED)) - { - draw_list.rect(rect, theme.color()); - } - } - - Window::Window(const std::string_view name, const WindowFlag flag, const rect_type& rect) noexcept - : name_{name}, - id_{functional::hash(name_)}, - flag_{flag}, - rect_{rect}, - default_item_width_{0}, - visible_{true}, - collapse_{false} {} - - auto Window::make(const std::string_view name, const WindowFlag flag, const rect_type& rect) noexcept -> Window - { - Window window{name, flag, rect}; - window.draw_list_.reset(); - // todo - std::ignore = window.make_canvas(); - - return window; - } - - auto Window::name() const noexcept -> std::string_view - { - return name_; - } - - auto Window::rect() const noexcept -> const rect_type& - { - return rect_; - } - - auto Window::hovered(const point_type mouse) const noexcept -> bool - { - // todo - return rect_.includes(mouse); - } - - auto Window::layout_same_line(const value_type column_width, value_type spacing_width) noexcept -> void - { - auto& window = *this; - - auto& canvas = window.canvas_; - // auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - const auto& context = Context::instance(); - // const auto& font = context.font(); - const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - if (window.collapse_) - { - return; - } - - canvas.height_current_line = canvas.height_previous_line; - canvas.cursor_current_line = canvas.cursor_previous_line; - - if (column_width <= 0) - { - if (spacing_width <= 0) - { - spacing_width = theme.item_spacing.width; - } - - canvas.cursor_current_line.x += spacing_width; - } - else - { - spacing_width = std::ranges::max(spacing_width, static_cast(0)); - - canvas.cursor_current_line.x = column_width + spacing_width; - } - } - - auto Window::draw_text(const std::string_view utf8_text) noexcept -> void - { - auto& window = *this; - - // const auto& canvas = window.canvas_; - auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - const auto& context = Context::instance(); - const auto& font = context.font(); - const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - if (window.collapse_) - { - return; - } - - const auto text_context_size = font.text_size(utf8_text, theme.font_size, cursor_remaining_width()); - - const auto text_point = cursor_abs_position(); - const auto text_size = text_context_size; - const rect_type text_rect{text_point, text_size}; - adjust_item_size(text_size); - - draw_list.text( - font, - theme.font_size, - text_rect.left_top(), - theme.color(), - utf8_text, - text_rect.width() - ); - } - - auto Window::draw_button(std::string_view utf8_text, extent_type size) noexcept -> bool - { - auto& window = *this; - - // const auto& canvas = window.canvas_; - auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - auto& context = Context::instance(); - const auto& font = context.font(); - const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - if (window.collapse_) - { - return false; - } - - const auto text_context_size = font.text_size(utf8_text, theme.font_size, cursor_remaining_width()); - - if (size.width <= 0) - { - size.width = text_context_size.width; - } - if (size.height <= 0) - { - size.height = text_context_size.height; - } - - // todo - const auto text_point = cursor_abs_position() + extent_type{theme.item_inner_spacing.width, theme.frame_padding.height}; - - const auto button_point = cursor_abs_position(); - const auto button_size = size + theme.frame_padding * 2; - const rect_type button_rect{button_point, button_size}; - adjust_item_size(button_size); - - const auto id = get_id(utf8_text); - const auto status = context.test_widget_status( - id, - button_rect, - false - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - , - std::format("Test Window({})'s button[{}]({}).", window.name_, utf8_text, button_rect) - #endif - ); - - color_type button_color = theme.color(); - { - if (status.keeping or status.pressed) - { - button_color = theme.color(); - } - else if (status.hovered) - { - button_color = theme.color(); - } - } - draw_widget_frame(button_rect, button_color); - - draw_list.text( - font, - theme.font_size, - text_point, - theme.color(), - utf8_text, - button_rect.width() - ); - - return status.pressed; - } - - auto Window::draw_checkbox(std::string_view utf8_text, bool checked, extent_type size) noexcept -> bool - { - auto& window = *this; - - // const auto& canvas = window.canvas_; - auto& draw_list = window.draw_list_; - // const auto flag_value = std::to_underlying(window.flag_); - - auto& context = Context::instance(); - const auto& font = context.font(); - const auto& theme = context.theme(); - // const auto& mouse = context.mouse(); - - if (window.collapse_) - { - return false; - } - - const auto text_context_size = font.text_size(utf8_text, theme.font_size, cursor_remaining_width()); - - if (size.width <= 0) - { - size.width = text_context_size.width; - } - if (size.height <= 0) - { - size.height = text_context_size.height; - } - - // □ + text - - // □, side length equals string rect height - const auto check_point = cursor_abs_position(); - const auto check_size = extent_type{size.height + theme.frame_padding.height * 2, size.height + theme.frame_padding.height * 2}; - const rect_type check_rect{check_point, check_size}; - adjust_item_size(check_size); - - // □ text - layout_same_line(0, theme.item_inner_spacing.width); - - // text - const auto text_point = cursor_abs_position() + extent_type{0, theme.frame_padding.height}; - const auto text_size = size; - const rect_type text_rect{text_point, text_size}; - adjust_item_size(text_size); - - draw_widget_frame(check_rect, theme.color()); - - const auto id = get_id(utf8_text); - const auto status = context.test_widget_status( - id, - check_rect, - false - #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) - , - std::format("Test Window({})'s checkbox[{}]({}).", window.name_, utf8_text, check_rect) - #endif - ); - - if (status.pressed) - { - checked = not checked; - } - - if (checked) - { - const auto check_fill_point = check_point + theme.item_inner_spacing; - const auto check_fill_size = check_size - theme.item_inner_spacing * 2; - const rect_type check_fill_rect{check_fill_point, check_fill_size}; - draw_list.rect_filled(check_fill_rect, theme.color()); - } - - draw_list.text( - font, - theme.font_size, - text_rect.left_top(), - theme.color(), - utf8_text, - text_rect.width() - ); - - return checked; - } - - auto Window::render() noexcept -> DrawList& - { - return draw_list_; - } -} diff --git a/src/draw/window.hpp b/src/draw/window.hpp deleted file mode 100644 index 07f3222..0000000 --- a/src/draw/window.hpp +++ /dev/null @@ -1,151 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -#include -#include -#include -#include - -namespace gal::prometheus::draw -{ - class [[nodiscard]] Window final - { - public: - template - using container_type = DrawListDef::container_type; - - using rect_type = DrawListDef::rect_type; - using point_type = DrawListDef::point_type; - using extent_type = DrawListDef::extent_type; - - using value_type = point_type::value_type; - - using color_type = DrawListDef::color_type; - - using id_type = functional::hash_result_type; - constexpr static auto invalid_id = std::numeric_limits::max(); - - private: - struct canvas_type - { - point_type cursor_start_line{0}; - point_type cursor_current_line{0}; - point_type cursor_previous_line{0}; - - value_type height_current_line{0}; - value_type height_previous_line{0}; - - container_type item_width{}; - }; - - std::string name_; - id_type id_; - WindowFlag flag_; - rect_type rect_; - - DrawList draw_list_; - - canvas_type canvas_; - - value_type default_item_width_; - - bool visible_; - bool collapse_; - - // ----------------------------------- - // ID - - [[nodiscard]] auto get_id(std::string_view name) const noexcept -> id_type; - - // ----------------------------------- - // CANVAS - - [[nodiscard]] auto rect_of_title_bar() const noexcept -> rect_type; - - [[nodiscard]] auto rect_of_close_button() const noexcept -> rect_type; - - [[nodiscard]] auto rect_of_resize_grip() const noexcept -> rect_type; - - [[nodiscard]] auto rect_of_canvas() const noexcept -> rect_type; - - [[nodiscard]] auto make_canvas() noexcept -> bool; - - // ----------------------------------- - // CANVAS CONTEXT - - [[nodiscard]] auto cursor_abs_position() const noexcept -> point_type; - - [[nodiscard]] auto cursor_remaining_width() const noexcept -> value_type; - - auto adjust_item_size(const extent_type& size) noexcept -> void; - - auto draw_widget_frame(const rect_type& rect, const color_type& color) noexcept -> void; - - // ----------------------------------- - // INITIALIZE - - Window(std::string_view name, WindowFlag flag, const rect_type& rect) noexcept; - - public: - static auto make(std::string_view name, WindowFlag flag, const rect_type& rect) noexcept -> Window; - - // --------------------------------------------- - // INFO - - [[nodiscard]] auto name() const noexcept -> std::string_view; - - [[nodiscard]] auto rect() const noexcept -> const rect_type&; - - // --------------------------------------------- - // STATUS - - [[nodiscard]] auto hovered(point_type mouse) const noexcept -> bool; - - // --------------------------------------------- - // LAYOUT - - auto layout_same_line(value_type column_width = 0, value_type spacing_width = 0) noexcept -> void; - - // --------------------------------------------- - // WIDGET - - auto draw_text(std::string_view utf8_text) noexcept -> void; - - /** - * @return Whether the button is pressed or not. - */ - [[nodiscard]] auto draw_button(std::string_view utf8_text, extent_type size = {0, 0}) noexcept -> bool; - - /** - * @return Whether the checkbox state is toggled (checked to unchecked, or vice versa) - */ - [[nodiscard]] auto draw_checkbox(std::string_view utf8_text, bool checked, extent_type size = {0, 0}) noexcept -> bool; - - // --------------------------------------------- - // RENDER - - auto render() noexcept -> DrawList&; - - // --------------------------------------------- - // for test only - - Window(const std::string_view name, const rect_type& rect) noexcept - : Window{name, WindowFlag::NONE, rect} {} - - auto test_init() noexcept -> void - { - std::ignore = make_canvas(); - } - - [[nodiscard]] auto test_draw_list() noexcept -> DrawList& - { - return draw_list_; - } - }; -} diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp new file mode 100644 index 0000000..45674cb --- /dev/null +++ b/src/gui/gui.hpp @@ -0,0 +1,462 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include + +namespace gal::prometheus +{ + namespace gui + { + using rect_type = primitive::basic_rect_2d; + using point_type = rect_type::point_type; + using extent_type = rect_type::extent_type; + + using uv_type = primitive::basic_point_2d; + using color_type = primitive::basic_color; + + using vertex_type = primitive::basic_vertex; + using index_type = std::uint16_t; + + using texture_id_type = std::uintptr_t; + using time_type = float; + + //------------------------------------------------------------------ + // CONTEXT + //------------------------------------------------------------------ + + class Context; + + // Create context + [[nodiscard]] auto create_context() noexcept -> Context*; + // Destroy context + auto destroy_context(Context& context) noexcept -> void; + // Destroy context, for smart pointer + auto destroy_context(Context* context) noexcept -> void; + + //------------------------------------------------------------------ + // FONT + //------------------------------------------------------------------ + + class FontOption + { + public: + using value_type = extent_type::value_type; + // todo: char32_t ? + using char_type = char16_t; + + using glyph_value_type = i18n::RangeBuilder::value_type; + using glyph_ranges_type = i18n::RangeBuilder::ranges_type; + + // todo + constexpr static std::uint32_t default_baked_line_max_width = 63; + + std::string font_path{}; + glyph_ranges_type glyph_ranges{}; + std::uint32_t pixel_height{}; + + std::uint32_t baked_line_max_width{default_baked_line_max_width}; + value_type scale{1.f}; + char_type fallback_char{u'?'}; + extent_type display_offset{.0f, .0f}; + point_type white_pixel_uv{-1, -1}; + }; + + class [[nodiscard]] Texture final + { + public: + using size_type = std::uint32_t; + // size.width * size.height (RGBA) + using data_type = std::unique_ptr; + + size_type width; + size_type height; + data_type data; + std::reference_wrapper id; + + explicit Texture(texture_id_type& texture_id) noexcept; + + Texture(const Texture& other) = delete; + Texture(Texture&& other) noexcept = default; + auto operator=(const Texture& other) -> Texture& = delete; + auto operator=(Texture&& other) noexcept -> Texture& = default; + + ~Texture() noexcept; + + [[nodiscard]] auto valid() const noexcept -> bool; + + auto bind(texture_id_type texture_id) noexcept -> void; + }; + + [[nodiscard]] auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture; + + [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture; + auto pop_font(Context& context) noexcept -> void; + + //------------------------------------------------------------------ + // THEME + //------------------------------------------------------------------ + + enum class ThemeCategory : std::uint8_t + { + TEXT = 0, + + BORDER, + BORDER_SHADOW, + + WINDOW_BACKGROUND, + + TITLEBAR, + TITLEBAR_COLLAPSED, + + RESIZE_GRIP, + RESIZE_GRIP_HOVERED, + RESIZE_GRIP_ACTIVATED, + + SCROLLBAR_BACKGROUND, + SCROLLBAR_GRAB, + SCROLLBAR_GRAB_HOVERED, + SCROLLBAR_GRAB_ACTIVATED, + + CLOSE_BUTTON, + CLOSE_BUTTON_HOVERED, + CLOSE_BUTTON_ACTIVATED, + + TOOLTIP_BACKGROUND, + TOOLTIP_TEXT, + + SLIDER, + SLIDER_ACTIVATED, + + BUTTON, + BUTTON_HOVERED, + BUTTON_ACTIVATED, + + // ------------------------------- + INTERNAL_COUNT + }; + + constexpr auto theme_category_count = static_cast(ThemeCategory::INTERNAL_COUNT); + + class Theme final + { + public: + using value_type = extent_type::value_type; + + using color_type = primitive::basic_color; + using alpha_type = value_type; + + using colors_type = std::array; + + // < 0 + constexpr static alpha_type window_fill_alpha_not_set{-.99999f}; + + //----------------- + // WINDOW + //----------------- + + // Default alpha of window background + value_type window_background_alpha; + // Height of the window titlebar + value_type window_titlebar_height; + // Rounding of the window corners + value_type window_corner_rounding; + // Minimum size of the window + extent_type window_min_size; + // Size of the window resize grip + extent_type window_resize_grip_size; + // width: Minimum distance of the first element of each line from the left border of the window + // height: Minimum distance of all elements of the first line from the upper border of the window (excluding the title bar) + extent_type window_padding; + // The extra padding space after adapting the window to the contents of the window (i.e., shrinking the window to just fit all the contents) + extent_type window_auto_fit_padding; + // Width of the vertical scrollbar + value_type window_vertical_scrollbar_width; + + //----------------- + // WINDOW CANVAS LAYOUT + //----------------- + + // The factor of the line (window width) that an element takes up by default + value_type item_default_width_factor; + // Extra padding size for elements with borders (e.g. horizontal and vertical fill for text in button boxes) + extent_type item_frame_padding; + // Filling distance between two different elements when they are on the `same line` + extent_type item_spacing; + // If an element consists of more than one sub-element, the distance between these sub-elements is filled + extent_type item_inner_spacing; + + //----------------- + // WINDOW WIDGET COLOR + //----------------- + + // Default alpha of all widget + alpha_type alpha; + colors_type colors; + + //----------------- + // DRAW + //----------------- + + // Maximum error (in pixels) allowed when using @c DrawList::circle and @c DrawList::circle_filled or drawing rounded corner rectangles with no explicit segment count specified + // Decrease for higher quality but more geometry + value_type circle_segment_max_error; + // Tessellation tolerance when using @c DrawList::path_bezier_curve without a specific number of segments + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality + value_type draw_curve_tessellation_tolerance; + }; + + // todo + [[nodiscard]] auto test_theme() noexcept -> Theme; + + auto set_default_theme(Context& context, const Theme& theme) noexcept -> void; + + auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme(Context& context) noexcept -> void; + + //------------------------------------------------------------------ + // IO + //------------------------------------------------------------------ + + enum class MouseKey : std::uint8_t + { + LEFT = 0, + MIDDLE, + RIGHT, + X1, + X2, + + // ------------------------------- + INTERNAL_COUNT + }; + + constexpr auto mouse_key_count = static_cast(MouseKey::INTERNAL_COUNT); + + // ReSharper disable once CppInconsistentNaming + class IO final + { + public: + using value_type = extent_type::value_type; + + // Update every frame + // Current window (viewport) size + extent_type display_size{0, 0}; + // Update every frame + // Time elapsed since last frame, in seconds + // FPS = 60 ==> time_delta = 1/60 + time_type delta_time{1.f / 60.f}; + + //----------------- + // MOUSE + //----------------- + + struct mouse_button_state_type + { + std::array state{}; + + [[nodiscard]] constexpr auto operator[](const MouseKey key) noexcept -> bool& + { + return state[static_cast(key)]; + } + + [[nodiscard]] constexpr auto operator[](const MouseKey key) const noexcept -> bool + { + return state[static_cast(key)]; + } + }; + + // Update every frame + // Current mouse position (this frame) + point_type mouse_position{0, 0}; + // Update every frame + // Current mouse wheel value + value_type mouse_wheel{0}; + // Update every frame + // Current mouse button state (this frame) + mouse_button_state_type mouse_button_state{}; + + // Update once (it can also be updated every frame basis if desired) + // The interval between two clicks is less than this threshold to be considered as a double click, in seconds + time_type mouse_double_click_interval_threshold{.3f}; + // Update once (it can also be updated every frame basis if desired) + // The mouse position delta between two clicks is less than this threshold to be considered as a double click + value_type mouse_double_click_distance_threshold{6}; + // Update once (it can also be updated every frame basis if desired) + // When holding a button, time before it starts repeating, in seconds + time_type mouse_repeat_click_delay{.275f}; + // Update once (it can also be updated every frame basis if desired) + // When holding a button, rate at which it repeats, in seconds + time_type mouse_repeat_click_rate{.05f}; + + //----------------- + // KEYBOARD + //----------------- + }; + + [[nodiscard]] auto get_io(Context& context) noexcept -> IO&; + + //------------------------------------------------------------------ + // DRAW + //------------------------------------------------------------------ + + enum class DrawListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + }; + + class DrawData final + { + public: + using size_type = std::uint32_t; + + struct [[nodiscard]] command_type + { + rect_type clip_rect; + texture_id_type texture_id; + + // ======================= + + // set by DrawList::index_list.size() + // start offset in @c DrawList::index_list + size_type index_offset; + // set by DrawList::draw_xxx + // number of indices (multiple of 3) to be rendered as triangles + size_type element_count; + }; + + using vertex_list_type = std::vector; + using index_list_type = std::vector; + using command_list_type = std::vector; + + std::reference_wrapper vertex_list; + std::reference_wrapper index_list; + std::reference_wrapper command_list; + }; + + auto set_default_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; + + auto push_draw_list_flag(Context& context, DrawListFlag new_flag) noexcept -> void; + auto pop_draw_list_flag(Context& context) noexcept -> void; + + auto new_frame(Context& context) noexcept -> void; + auto end_frame(Context& context) noexcept -> void; + auto render(Context& context) noexcept -> void; + + [[nodiscard]] auto get_draw_data(Context& context) noexcept -> std::vector; + + //------------------------------------------------------------------ + // WIDGET + //------------------------------------------------------------------ + + enum class WindowFlag : std::uint16_t + { + NONE = 0, + + BORDERED = 1 << 0, + + NO_TITLEBAR = 1 << 1, + NO_CLOSE = 1 << 2, + NO_RESIZE = 1 << 3, + NO_MOVE = 1 << 4, + NO_SCROLLBAR = 1 << 5, + NO_SCROLLBAR_WITH_MOUSE = 1 << 6, + + AUTO_RESIZE = 1 << 7, + }; + + auto begin_window( + Context& context, + std::string_view name, + const extent_type& size = {0, 0}, + Theme::value_type fill_alpha = Theme::window_fill_alpha_not_set, + WindowFlag flag = WindowFlag::NONE + ) noexcept -> bool; + auto end_window(Context& context) noexcept -> void; + } + + namespace meta::user_defined + { + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + } +} + +namespace gal::prometheus::gui +{ + //------------------------------------------------------------------ + // CONTEXT + //------------------------------------------------------------------ + + auto set_current_context(Context& context) noexcept -> void; + auto get_current_context() noexcept -> Context&; + + auto create_current_context() noexcept -> void; + + //------------------------------------------------------------------ + // FONT + //------------------------------------------------------------------ + + [[nodiscard]] auto set_default_font(const FontOption& option) noexcept -> Texture; + + [[nodiscard]] auto push_font(const FontOption& option) noexcept -> Texture; + auto pop_font() noexcept -> void; + + //------------------------------------------------------------------ + // THEME + //------------------------------------------------------------------ + + auto set_default_theme(const Theme& theme) noexcept -> void; + + auto push_theme(ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme() noexcept -> void; + + //------------------------------------------------------------------ + // IO + //------------------------------------------------------------------ + + [[nodiscard]] auto get_io() noexcept -> IO&; + + //------------------------------------------------------------------ + // DRAW + //------------------------------------------------------------------ + + auto set_default_draw_list_flag(DrawListFlag flag) noexcept -> void; + + auto push_draw_list_flag(DrawListFlag new_flag) noexcept -> void; + auto pop_draw_list_flag() noexcept -> void; + + auto new_frame() noexcept -> void; + auto end_frame() noexcept -> void; + auto render() noexcept -> void; + + [[nodiscard]] auto get_draw_data() noexcept -> std::vector; + + //------------------------------------------------------------------ + // WIDGET + //------------------------------------------------------------------ + + auto begin_window( + std::string_view name, + const extent_type& size = {0, 0}, + Theme::value_type fill_alpha = Theme::window_fill_alpha_not_set, + WindowFlag flag = WindowFlag::NONE + ) noexcept -> bool; + auto end_window() noexcept -> void; +} diff --git a/src/gui/internal/common.cpp b/src/gui/internal/common.cpp new file mode 100644 index 0000000..ba10969 --- /dev/null +++ b/src/gui/internal/common.cpp @@ -0,0 +1,166 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace gui; + using namespace internal; + + // @see https://stackoverflow.com/a/2244088/15194693 + // Number of segments (N) is calculated using equation: + // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r + [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto + { + constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto + { + return (v + 1) / 2 * 2; + }; + + return std::ranges::clamp( + circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), + DrawListSharedData::circle_segments_min, + DrawListSharedData::circle_segments_max + ); + } + + [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto + { + return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); + } + + [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto + { + return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; + } + + template + [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> DrawListSharedData::vertex_sample_points_type + { + return [](std::index_sequence) noexcept -> DrawListSharedData::vertex_sample_points_type + { + constexpr auto make_point = []() noexcept -> point_type + { + const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; + return {math::cos(a), -math::sin(a)}; + }; + + return {{make_point.template operator()()...}}; + }(std::make_index_sequence{}); + } +} + +namespace gal::prometheus::gui::internal +{ + DrawListSharedData::DrawListSharedData() noexcept + : circle_segment_counts{}, + vertex_sample_points{vertex_sample_points_calc()}, + circle_segment_max_error{0}, + arc_fast_radius_cutoff{0}, + curve_tessellation_tolerance{1.25f} + { + set_circle_tessellation_max_error(.3f); + } + + auto DrawListSharedData::circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type + { + // ceil to never reduce accuracy + if (const auto radius_index = static_cast(radius + .999999f); + radius_index < circle_segment_counts.size()) + { + return circle_segment_counts[radius_index]; + } + return static_cast(circle_segments_calc(radius, circle_segment_max_error)); + } + + auto DrawListSharedData::vertex_sample_point(const std::size_t index) const noexcept -> const point_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points.size()); + + return vertex_sample_points[index]; + } + + auto DrawListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); + + if (circle_segment_max_error == max_error) // NOLINT(clang-diagnostic-float-equal) + { + return; + } + + for (auto [index, count]: circle_segment_counts | std::views::enumerate) + { + const auto radius = static_cast(index); + count = static_cast(circle_segments_calc(radius, max_error)); + } + + circle_segment_max_error = max_error; + arc_fast_radius_cutoff = circle_segments_calc_radius(vertex_sample_points_count, max_error); + } + + auto DrawListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); + + curve_tessellation_tolerance = tolerance; + } + + PrimitiveAppender::PrimitiveAppender( + command_type& command, + vertex_list_type& vertex_list, + index_list_type& index_list + ) noexcept + : element_count_{command.element_count}, + vertex_list_{vertex_list}, + index_list_{index_list} {} + + auto PrimitiveAppender::vertex_count() const noexcept -> size_type + { + const auto& list = vertex_list_.get(); + const auto size = list.size(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size < std::numeric_limits::max(), "Too many vertex!"); + + return static_cast(size); + } + + auto PrimitiveAppender::reserve(const size_type vertex_count, const size_type index_count) noexcept -> void + { + auto& element_count = element_count_.get(); + auto& vertex = vertex_list_.get(); + auto& index = index_list_.get(); + + element_count += index_count; + vertex.reserve(vertex.size() + vertex_count); + index.reserve(index.size() + index_count); + } + + auto PrimitiveAppender::reserve(const std::size_t vertex_count, const std::size_t index_count) noexcept -> void + { + reserve(static_cast(vertex_count), static_cast(index_count)); + } + + auto PrimitiveAppender::add_vertex(const point_type& point, const uv_type& uv, const color_type color) noexcept -> void + { + auto& list = vertex_list_.get(); + + list.emplace_back(point, uv, color); + } + + auto PrimitiveAppender::add_index(const index_type a, const index_type b, const index_type c) noexcept -> void + { + auto& list = index_list_.get(); + + list.push_back(a); + list.push_back(b); + list.push_back(c); + } +} diff --git a/src/gui/internal/common.hpp b/src/gui/internal/common.hpp new file mode 100644 index 0000000..e42a5b1 --- /dev/null +++ b/src/gui/internal/common.hpp @@ -0,0 +1,105 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include + +#include +#include + +namespace gal::prometheus::gui::internal +{ + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; + + using frame_count_type = std::uint32_t; + // The number of frames is always counted from 1(call new_frame first), + // so we can set the number of uninitialized frames to the maximum value, + // and its +1 overflows to 0, then we can compare it with current frame count normally + constexpr static auto frame_count_uninitialized{std::numeric_limits::max()}; + + using widget_id_type = functional::hash_result_type; + constexpr auto invalid_widget_id = std::numeric_limits::max(); + + class [[nodiscard]] DrawListSharedData final + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; + + circle_segment_counts_type circle_segment_counts; + vertex_sample_points_type vertex_sample_points; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + DrawListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; + + class [[nodiscard]] PrimitiveAppender final + { + public: + using size_type = DrawData::size_type; + + using command_type = DrawData::command_type; + + using vertex_list_type = DrawData::vertex_list_type; + using index_list_type = DrawData::index_list_type; + + private: + // command_type::element_count + memory::RefWrapper element_count_; + // DrawList::vertex_list + memory::RefWrapper vertex_list_; + // DrawList::index_list_; + memory::RefWrapper index_list_; + + public: + PrimitiveAppender( + command_type& command, + vertex_list_type& vertex_list, + index_list_type& index_list + ) noexcept; + + [[nodiscard]] auto vertex_count() const noexcept -> size_type; + + auto reserve(size_type vertex_count, size_type index_count) noexcept -> void; + + auto reserve(std::size_t vertex_count, std::size_t index_count) noexcept -> void; + + auto add_vertex(const point_type& point, const uv_type& uv, color_type color) noexcept -> void; + + auto add_index(index_type a, index_type b, index_type c) noexcept -> void; + }; +} diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp new file mode 100644 index 0000000..eaae8a6 --- /dev/null +++ b/src/gui/internal/context.cpp @@ -0,0 +1,862 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include +#include + +#include +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace gui; + + /** + * @brief Finds the window with the given name + * @return Returns the corresponding window if it exists, otherwise it returns a null pointer + */ + [[nodiscard]] auto find_window(Context& context, const std::string_view name) noexcept -> internal::Window* + { + auto view = context.window_list | std::views::all; + + const auto it = std::ranges::find_if( + view, + [name](const auto& window) noexcept -> bool + { + return name == window->name(); + } + ); + + if (it == std::ranges::end(view)) + { + return nullptr; + } + + return it.operator*(); + } + + /** + * @brief Find the last possible parent (not child) window from the available windows in this frame + * @return If it is not found (if and only if there are no currently available windows, i.e. the window to be created is the first one), then the null pointer is returned + */ + [[nodiscard]] auto find_root_window(Context& context) noexcept -> internal::Window* + { + auto view = context.window_current_stack | std::views::all; + + const auto it = std::ranges::find_if( + view, + [](const auto& window) noexcept -> bool + { + return not window->flag().template is(); + } + ); + + if (it == std::ranges::end(view)) + { + return nullptr; + } + + return it.operator*(); + } + + [[nodiscard]] auto find_or_create_window(Context& context, const std::string_view name, const extent_type& size, const internal::Window::Flag flag) noexcept -> internal::Window& + { + [[maybe_unused]] const auto is_child_window = flag.is(); + + if (auto* window = find_window(context, name); + window == nullptr) + { + // find root + auto* root = find_root_window(context); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(is_child_window == (root != nullptr)); + + // fixme: load cached settings? + auto temp = memory::make_unique(name, flag, context.window_default_spawn_position, size, root); + + auto& ref = context.window_hive.emplace_back(std::move(temp)); + + context.window_list.emplace_back(ref.get()); + context.window_current_stack.emplace_back(ref.get()); + } + else + { + window->reset(flag); + context.window_current_stack.emplace_back(window); + } + + return *context.window_current_stack.back(); + } + + /** + * @brief Find the first (more top-level) window that contains the location of the given point + */ + [[nodiscard]] auto find_hovered_window(Context& context, const point_type& position, bool parent_only) noexcept -> internal::Window* + { + auto view = context.window_list | std::views::reverse; + + const auto it = std::ranges::find_if( + view, + [position, parent_only](const auto& window) noexcept -> bool + { + if (not window->visible()) + { + return false; + } + + if (parent_only and window->flag().template is()) + { + return false; + } + + const auto rect = window->rect(); + return rect.includes(position); + } + ); + + if (it == std::ranges::end(view)) + { + return nullptr; + } + + return it.operator*(); + } + + Context* g_context = nullptr; +} + +namespace gal::prometheus::gui +{ + auto create_context() noexcept -> Context* + { + auto* p = new ::Context{ + .initialized = false, + .draw_list_flag_stack = {}, + .draw_list_shared_data_stack = {}, + .font_stack = {}, + .draw_list_flag_current = Context::stack_pointer_default, + .draw_list_shared_data_current = Context::stack_pointer_default, + .font_current = Context::stack_pointer_default, + .theme = {}, + .theme_mod_stack = {}, + .io = {}, + .time_total = 0, + .frame_count = 0, + .frame_count_rendered = 0, + .mouse = {}, + .window_default_spawn_position = {50, 50}, + .window_hive = {}, + .window_list = {}, + .window_current_stack = {}, + .window_hovered = nullptr, + .window_hovered_root = nullptr, + .window_focused = nullptr, + .widget_hovered = internal::invalid_widget_id, + .widget_activated = internal::invalid_widget_id, + .widget_activated_previous_frame = internal::invalid_widget_id, + .widget_activated_still_alive = false, + .draw_lists = {} + }; + + // todo + p->initialized = true; + + return p; + } + + auto destroy_context(Context& context) noexcept -> void + { + delete std::addressof(context); + } + + auto destroy_context(Context* context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context != nullptr); + destroy_context(*context); + } + + auto test_theme() noexcept -> Theme + { + constexpr auto default_colors = []() noexcept -> Theme::colors_type + { + using enum ThemeCategory; + + Theme::colors_type colors{}; + + colors[static_cast(TEXT)] = primitive::colors::black; + + colors[static_cast(BORDER)] = primitive::colors::magenta; + colors[static_cast(BORDER_SHADOW)] = primitive::colors::red; + + colors[static_cast(WINDOW_BACKGROUND)] = primitive::colors::gains_boro; + + colors[static_cast(TITLEBAR)] = primitive::colors::light_coral; + colors[static_cast(TITLEBAR_COLLAPSED)] = primitive::colors::dark_khaki; + + colors[static_cast(RESIZE_GRIP)] = primitive::colors::gold; + colors[static_cast(RESIZE_GRIP_HOVERED)] = primitive::colors::peru; + colors[static_cast(RESIZE_GRIP_ACTIVATED)] = primitive::colors::powder_blue; + + colors[static_cast(SCROLLBAR_BACKGROUND)] = primitive::colors::white; + colors[static_cast(SCROLLBAR_GRAB)] = primitive::colors::dark_salmon; + colors[static_cast(SCROLLBAR_GRAB_HOVERED)] = primitive::colors::dark_green; + colors[static_cast(SCROLLBAR_GRAB_ACTIVATED)] = primitive::colors::dark_goldenrod; + + colors[static_cast(CLOSE_BUTTON)] = primitive::colors::red; + colors[static_cast(CLOSE_BUTTON_HOVERED)] = primitive::colors::violet_red; + colors[static_cast(CLOSE_BUTTON_ACTIVATED)] = primitive::colors::white; + + colors[static_cast(TOOLTIP_BACKGROUND)] = primitive::colors::black; + colors[static_cast(TOOLTIP_TEXT)] = primitive::colors::red; + + colors[static_cast(SLIDER)] = primitive::colors::light_blue; + colors[static_cast(SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; + + colors[static_cast(BUTTON)] = primitive::colors::sienna; + colors[static_cast(BUTTON_HOVERED)] = primitive::colors::slate_gray; + colors[static_cast(BUTTON_ACTIVATED)] = primitive::colors::steel_blue; + + return colors; + }; + + return + { + .window_background_alpha = .65f, + .window_titlebar_height = 20, + .window_corner_rounding = 0, + .window_min_size = {640, 480}, + .window_resize_grip_size = {20, 20}, + .window_padding = {8, 8}, + .window_auto_fit_padding = {8, 8}, + .window_vertical_scrollbar_width = 8, + .item_default_width_factor = .65f, + .item_frame_padding = {4, 4}, + .item_spacing = {10, 5}, + .item_inner_spacing = {5, 5}, + .alpha = 1, + .colors = default_colors(), + .circle_segment_max_error = .3f, + .draw_curve_tessellation_tolerance = 1.25f, + }; + } + + auto set_default_theme(Context& context, const Theme& theme) noexcept -> void + { + context.theme = theme; + } + + auto push_theme(Context& context, const ThemeCategory category, const Theme::color_type new_color) noexcept -> void + { + internal::push_theme(context, category, new_color); + } + + auto pop_theme(Context& context) noexcept -> void + { + internal::pop_theme(context); + } + + auto get_io(Context& context) noexcept -> IO& + { + return context.io; + } + + auto set_default_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void + { + if (context.draw_list_flag_current == Context::stack_pointer_default) + { + internal::push_draw_list_flag(context, flag); + } + else + { + context.draw_list_flag_stack[0] = flag; + } + } + + auto push_draw_list_flag(Context& context, const DrawListFlag new_flag) noexcept -> void + { + internal::push_draw_list_flag(context, new_flag); + } + + auto pop_draw_list_flag(Context& context) noexcept -> void + { + internal::pop_draw_list_flag(context); + } + + auto new_frame(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.io.display_size.width > 0 and context.io.display_size.height > 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.io.delta_time > 0); + + context.time_total += context.io.delta_time; + context.frame_count += 1; + + // Update mouse + { + context.mouse.tick(context); + } + + const auto mouse_position = context.mouse.position_current; + + // Update widget + { + // Clear reference to active widget if the widget isn't alive anymore + context.widget_hovered = internal::invalid_widget_id; + if ( + not context.widget_activated_still_alive and + context.widget_activated_previous_frame == context.widget_activated and + context.widget_activated != internal::invalid_widget_id + ) + { + context.widget_activated_previous_frame = context.widget_activated; + } + context.widget_activated_still_alive = false; + } + + // Update window + { + context.window_hovered = find_hovered_window(context, mouse_position, false); + context.window_hovered_root = find_hovered_window(context, mouse_position, true); + + // No window should be open at the beginning of the frame + // But in order to allow the user to call `new_frame` multiple times without calling `render`, we are doing an explicit clear + context.window_current_stack.clear(); + } + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto end_frame(Context& context) noexcept -> void + { + std::ignore = context; + } + + auto render(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + const auto& theme = internal::current_theme(context); + + const auto is_first_render_this_frame = context.frame_count_rendered != context.frame_count; + context.frame_count_rendered = context.frame_count; + + if (is_first_render_this_frame) + { + // Sort the window list so that all child windows are after their parent + // We cannot do that on `focus` because children may not exist yet + } + + context.draw_lists.clear(); + + if (theme.alpha > .0f) + { + // gather windows to render + std::ranges::for_each( + context.window_list, + [&context](auto* window) noexcept -> void + { + if (window->visible()) + { + window->render(context); + } + } + ); + } + } + + auto get_draw_data(Context& context) noexcept -> std::vector + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + std::vector data; + data.reserve(context.draw_lists.size()); + + std::ranges::transform( + context.draw_lists, + std::back_inserter(data), + [](const auto& draw_list_ref) noexcept -> DrawData + { + auto& draw_list = draw_list_ref.get(); + + return + { + .vertex_list = draw_list.vertex_list(), + .index_list = draw_list.index_list(), + .command_list = draw_list.command_list() + }; + } + ); + + return data; + } + + auto begin_window( + Context& context, + const std::string_view name, + const extent_type& size, + Theme::value_type fill_alpha, + const WindowFlag flag + ) noexcept -> bool + { + const auto& theme = internal::current_theme(context); + + auto& window = find_or_create_window(context, name, size, flag); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.window_current_stack.back() == std::addressof(window)); + + // alpha + static_assert(Context::window_fill_alpha_not_set < 0); + if (fill_alpha < 0) + { + fill_alpha = theme.window_background_alpha; + } + + const auto is_child_window = window.flag().is(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(is_child_window == (context.window_current_stack.size() > 1)); + + auto* parent = is_child_window ? context.window_current_stack[context.window_current_stack.size() - 2] : nullptr; + + return window.begin_draw(context, fill_alpha, parent); + } + + auto end_window(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + + window.end_draw(context); + + // Select window for move/focus when we're done with all our widgets (we only consider non-children windows here) + if (const auto rect = window.rect(); + context.widget_activated == internal::invalid_widget_id and + context.widget_hovered == internal::invalid_widget_id and + context.window_hovered_root == std::addressof(window) and + window.hovered(context, rect) and + context.mouse.is_clicked(context, MouseKey::LEFT) + ) + { + context.widget_activated = window.id_of_move(context); + } + + context.window_current_stack.pop_back(); + } + + auto set_current_context(Context& context) noexcept -> void + { + g_context = std::addressof(context); + } + + auto get_current_context() noexcept -> Context& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return *g_context; + } + + auto create_current_context() noexcept -> void + { + g_context = create_context(); + } + + auto set_default_font(const FontOption& option) noexcept -> Texture + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return set_default_font(*g_context, option); + } + + auto push_font(const FontOption& option) noexcept -> Texture + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return push_font(*g_context, option); + } + + auto pop_font() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + pop_font(*g_context); + } + + auto set_default_theme(const Theme& theme) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + set_default_theme(*g_context, theme); + } + + auto push_theme(const ThemeCategory category, const Theme::color_type new_color) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + push_theme(*g_context, category, new_color); + } + + auto pop_theme() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + pop_theme(*g_context); + } + + auto get_io() noexcept -> IO& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return get_io(*g_context); + } + + auto set_default_draw_list_flag(const DrawListFlag flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + set_default_draw_list_flag(*g_context, flag); + } + + auto push_draw_list_flag(const DrawListFlag new_flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + push_draw_list_flag(*g_context, new_flag); + } + + auto pop_draw_list_flag() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + pop_draw_list_flag(*g_context); + } + + auto new_frame() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return new_frame(*g_context); + } + + auto end_frame() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return end_frame(*g_context); + } + + auto render() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return render(*g_context); + } + + auto get_draw_data() noexcept -> std::vector + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return get_draw_data(*g_context); + } + + auto begin_window( + const std::string_view name, + const extent_type& size, + const Theme::value_type fill_alpha, + const WindowFlag flag + ) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return begin_window(*g_context, name, size, fill_alpha, flag); + } + + auto end_window() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return end_window(*g_context); + } + + namespace internal + { + auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + return context.draw_list_flag_stack[context.draw_list_flag_current]; + } + + auto push_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + context.draw_list_flag_current == Context::stack_pointer_default or + context.draw_list_flag_current < Context::draw_list_flag_stack_size, + "DrawListFlag stack overflow" + ); + + static_assert( + static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == + static_cast<::Context::stack_pointer_type>(0) + ); + + context.draw_list_flag_current += 1; + context.draw_list_flag_stack[context.draw_list_flag_current] = flag; + } + + auto pop_draw_list_flag(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + context.draw_list_flag_current != Context::stack_pointer_default, + "Unable to popup the default DrawListFlag!" + ); + + static_assert( + static_cast(static_cast(0) - 1) == + Context::stack_pointer_default + ); + + context.draw_list_flag_stack[context.draw_list_flag_current] = DrawListFlag::NONE; + context.draw_list_flag_current -= 1; + } + + auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + return context.draw_list_shared_data_stack[context.draw_list_shared_data_current]; + } + + auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + context.draw_list_shared_data_current == Context::stack_pointer_default or + context.draw_list_shared_data_current < Context::draw_list_shared_data_stack_size, + "DrawListSharedData stack overflow" + ); + + static_assert( + static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == + static_cast<::Context::stack_pointer_type>(0) + ); + + context.draw_list_shared_data_current += 1; + context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = shared_data; + } + + auto pop_draw_list_shared_data(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + context.draw_list_shared_data_current != Context::stack_pointer_default, + "Unable to popup the default DrawListSharedData!" + ); + + static_assert( + static_cast(static_cast(0) - 1) == + Context::stack_pointer_default + ); + + context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = {}; + context.draw_list_shared_data_current -= 1; + } + + auto current_font(const Context& context) noexcept -> const Font& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + const auto& font = *context.font_stack[context.font_current]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font.loaded(), "Invalid font!"); + + return font; + } + + auto push_font(Context& context, memory::UniquePointer font) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + context.font_current == Context::stack_pointer_default or + context.font_current < Context::font_stack_size, + "Font stack overflow" + ); + + static_assert( + static_cast(Context::stack_pointer_default + 1) == + static_cast(0) + ); + + context.font_current += 1; + context.font_stack[context.font_current] = std::move(font); + } + + auto pop_font(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + context.font_current != Context::stack_pointer_default, + "Unable to popup the default Font!" + ); + + static_assert( + static_cast(static_cast(0) - 1) == + Context::stack_pointer_default + ); + + context.font_stack[context.font_current].reset(); + context.font_current -= 1; + } + + auto current_theme(const Context& context) noexcept -> const Theme& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + return context.theme; + } + + auto push_theme(Context& context, const ThemeCategory category, const Theme::color_type new_color) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + const auto old_color = color_of(context, category); + context.theme_mod_stack.emplace_back(category, old_color); + + context.theme.colors[static_cast(category)] = new_color; + } + + auto pop_theme(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + + const auto [category, old_color] = context.theme_mod_stack.back(); + context.theme_mod_stack.pop_back(); + context.theme.colors[static_cast(category)] = old_color; + } + + auto color_of(const Theme& theme, const ThemeCategory category, const Theme::value_type factor) noexcept -> Theme::color_type + { + const auto index = static_cast(category); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < theme_category_count); + auto color = theme.colors[static_cast(category)]; + + color.alpha = static_cast(theme.alpha * 255 * factor); + + return color; + } + + auto color_of(const Context& context, const ThemeCategory category, const Theme::value_type factor) noexcept -> Theme::color_type + { + const auto& theme = current_theme(context); + + return color_of(theme, category, factor); + } + + auto test_mouse(Context& context, const widget_id_type id, const rect_type& area, const bool repeat) noexcept -> mouse_state_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + + const auto hovered = + // window + context.window_hovered_root == std::addressof(window) and + // new + context.widget_hovered == invalid_widget_id and + // mouse + window.hovered(context, area); + + mouse_state_type state{.hovered = hovered, .pressed = false, .keeping = false}; + if (hovered) + { + context.widget_hovered = id; + if (context.mouse.is_clicked(context, MouseKey::LEFT, false)) + { + context.widget_activated = id; + } + else if ( + repeat and + context.widget_activated != invalid_widget_id and + context.mouse.is_clicked(context, MouseKey::LEFT, true) + ) + { + state.pressed = true; + } + } + + if (context.widget_activated == id) + { + if (context.mouse.is_down(context, MouseKey::LEFT)) + { + state.keeping = true; + } + else + { + if (hovered) + { + state.pressed = true; + } + + context.widget_activated = invalid_widget_id; + } + } + + return state; + } + + auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool + { + return context.window_hovered == std::addressof(window); + } + + auto focus_window(Context& context, Window& window) noexcept -> void + { + context.window_focused = std::addressof(window); + + const auto it = std::ranges::find(context.window_list, std::addressof(window)); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != context.window_list.end()); + + const auto p = *it; + context.window_list.erase(it); + context.window_list.push_back(p); + } + + auto is_widget_hovered(const Context& context, const widget_id_type id) noexcept -> bool + { + return context.widget_hovered == id; + } + + auto is_widget_activated(const Context& context, const widget_id_type id) noexcept -> bool + { + return context.widget_activated == id; + } + + auto mark_widget_alive(Context& context, const widget_id_type id) noexcept -> bool + { + if (is_widget_activated(context, id)) + { + context.widget_activated_still_alive = true; + + return true; + } + + return false; + } + + auto mark_widget_dead(Context& context, const widget_id_type id) noexcept -> void + { + context.widget_activated = id; + } + } +} diff --git a/src/gui/internal/context.hpp b/src/gui/internal/context.hpp new file mode 100644 index 0000000..e9e6706 --- /dev/null +++ b/src/gui/internal/context.hpp @@ -0,0 +1,174 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include +#include + +namespace gal::prometheus::gui +{ + namespace internal + { + class Font; + class DrawList; + class Window; + } + + class Context + { + public: + using value_type = extent_type::value_type; + + struct theme_color_mod + { + ThemeCategory category; + Theme::color_type old_color; + }; + + using window_type = internal::Window; + using widget_id_type = internal::widget_id_type; + + // < 0 + constexpr static value_type window_fill_alpha_not_set{-.99999f}; + + bool initialized; + + // ---------------------------------------------------------------------- + // DrawListFlag + DrawListSharedData + Font + Theme + + constexpr static std::size_t draw_list_flag_stack_size = 8; + constexpr static std::size_t draw_list_shared_data_stack_size = 8; + constexpr static std::size_t font_stack_size = 8; + + using stack_pointer_type = std::uint8_t; + constexpr static auto stack_pointer_default = std::numeric_limits::max(); + + std::array draw_list_flag_stack; + std::array draw_list_shared_data_stack; + std::array, font_stack_size> font_stack; + + stack_pointer_type draw_list_flag_current; + stack_pointer_type draw_list_shared_data_current; + stack_pointer_type font_current; + + Theme theme; + std::vector theme_mod_stack; + + // ---------------------------------------------------------------------- + // IO + + IO io; + // time elapsed since program launch, in seconds + time_type time_total; + std::uint32_t frame_count; + std::uint32_t frame_count_rendered; + + internal::Mouse mouse; + // todo: keyboard + + // ---------------------------------------------------------------------- + // WINDOW + + point_type window_default_spawn_position; + + // all created windows + std::vector> window_hive; + + // all created windows, but sorted (child windows are next to their parents, the most recently used window is always moved to the tail) + std::vector window_list; + std::vector window_current_stack; + + // catch mouse + window_type* window_hovered; + // catch mouse (for focus/move only) + window_type* window_hovered_root; + // catch keyboard + window_type* window_focused; + + // ---------------------------------------------------------------------- + // WIDGET + + widget_id_type widget_hovered; + widget_id_type widget_activated; + widget_id_type widget_activated_previous_frame; + + bool widget_activated_still_alive; + + // ---------------------------------------------------------------------- + // DRAW LIST + + std::vector> draw_lists; + }; + + namespace internal + { + // ---------------------------------------------------------------------- + // DrawListFlag + + auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag; + auto push_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; + auto pop_draw_list_flag(Context& context) noexcept -> void; + + // ---------------------------------------------------------------------- + // DrawListSharedData + + auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData&; + auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void; + auto pop_draw_list_shared_data(Context& context) noexcept -> void; + + // ---------------------------------------------------------------------- + // Font + + auto current_font(const Context& context) noexcept -> const Font&; + auto push_font(Context& context, memory::UniquePointer font) noexcept -> void; + auto pop_font(Context& context) noexcept -> void; + + // ---------------------------------------------------------------------- + // Theme + + auto current_theme(const Context& context) noexcept -> const Theme&; + auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme(Context& context) noexcept -> void; + + [[nodiscard]] auto color_of(const Theme& theme, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; + [[nodiscard]] auto color_of(const Context& context, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; + + // ---------------------------------------------------------------------- + // IO + + struct mouse_state_type + { + bool hovered; + bool pressed; + bool keeping; + }; + + [[nodiscard]] auto test_mouse(Context& context, widget_id_type id, const rect_type& area, bool repeat = false) noexcept -> mouse_state_type; + + // ---------------------------------------------------------------------- + // WINDOW + + auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool; + + auto focus_window(Context& context, Window& window) noexcept -> void; + + // ---------------------------------------------------------------------- + // WIDGET + + auto is_widget_hovered(const Context& context, widget_id_type id) noexcept -> bool; + auto is_widget_activated(const Context& context, widget_id_type id) noexcept -> bool; + + /** + * @return @c is_widget_activated + */ + auto mark_widget_alive(Context& context, widget_id_type id) noexcept -> bool; + auto mark_widget_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + } +} diff --git a/src/gui/internal/draw_list.cpp b/src/gui/internal/draw_list.cpp new file mode 100644 index 0000000..71d9bd8 --- /dev/null +++ b/src/gui/internal/draw_list.cpp @@ -0,0 +1,1970 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace gal::prometheus; + using namespace gui; + using namespace internal; + + [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const DrawFlag flag) noexcept -> DrawFlag + { + using enum DrawFlag; + + if ((flag & ROUND_CORNER_MASK) == NONE) + { + return ROUND_CORNER_ALL | flag; + } + + return flag; + } + + [[nodiscard]] constexpr auto to_fixed_normal(const float x, const float y) noexcept -> std::pair + { + if (const auto d = math::pow(x, 2) + math::pow(y, 2); + d > 1e-6f) + { + // fixme + const auto inv_len = [d] + { + // #if defined(__AVX512F__) + // __m512 d_v = _mm512_set1_ps(d); + // __m512 inv_len_v = _mm512_rcp14_ps(d_v); + // return _mm512_cvtss_f32(inv_len_v); + // #elif defined(__AVX__) + // __m256 d_v = _mm256_set1_ps(d); + // __m256 inv_len_v = _mm256_rcp_ps(d_v); + // return _mm256_cvtss_f32(inv_len_v); + // #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) + // __m128 d_v = _mm_set_ss(d); + // __m128 inv_len_v = _mm_rcp_ss(d_v); + // return _mm_cvtss_f32(inv_len_v); + // #else + return 1.0f / d; + // #endif + }(); + + return {x * inv_len, y * inv_len}; + } + + return {x, y}; + } + + // fixme + constexpr std::size_t bezier_curve_casteljau_max_level = 10; + + constexpr auto bezier_cubic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 3); + const auto w2 = 3 * math::pow(u, 2) * tolerance; + const auto w3 = 3 * u * math::pow(tolerance, 2); + const auto w4 = math::pow(tolerance, 3); + + return + { + p1.x * w1 + p2.x * w2 + p3.x * w3 + p4.x * w4, + p1.y * w1 + p2.y * w2 + p3.y * w3 + p4.y * w4 + }; + }; + + constexpr auto bezier_quadratic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 2); + const auto w2 = 2 * u * tolerance; + const auto w3 = math::pow(tolerance, 2); + + return + { + p1.x * w1 + p2.x * w2 + p3.x * w3, + p1.y * w1 + p2.y * w2 + p3.y * w3 + }; + }; +} + +namespace gal::prometheus::gui::internal +{ + auto range_of_arc(const DrawArcFlag flag) noexcept -> std::pair + { + static_assert(DrawListSharedData::vertex_sample_points_count % 12 == 0); + constexpr auto factor = static_cast(DrawListSharedData::vertex_sample_points_count / 12); + + switch (flag) + { + case DrawArcFlag::Q1: { return std::make_pair(0 * factor, 3 * factor); } + case DrawArcFlag::Q2: { return std::make_pair(3 * factor, 6 * factor); } + case DrawArcFlag::Q3: { return std::make_pair(6 * factor, 9 * factor); } + case DrawArcFlag::Q4: { return std::make_pair(9 * factor, 12 * factor); } + case DrawArcFlag::TOP: { return std::make_pair(0 * factor, 6 * factor); } + case DrawArcFlag::BOTTOM: { return std::make_pair(6 * factor, 12 * factor); } + case DrawArcFlag::LEFT: { return std::make_pair(3 * factor, 9 * factor); } + case DrawArcFlag::RIGHT: { return std::make_pair(9 * factor, 15 * factor); } + case DrawArcFlag::ALL: { return std::make_pair(0 * factor, 12 * factor); } + case DrawArcFlag::Q1_CLOCK_WISH: { return std::make_pair(3 * factor, 0 * factor); } + case DrawArcFlag::Q2_CLOCK_WISH: { return std::make_pair(6 * factor, 3 * factor); } + case DrawArcFlag::Q3_CLOCK_WISH: { return std::make_pair(9 * factor, 6 * factor); } + case DrawArcFlag::Q4_CLOCK_WISH: { return std::make_pair(12 * factor, 9 * factor); } + case DrawArcFlag::TOP_CLOCK_WISH: { return std::make_pair(6 * factor, 0 * factor); } + case DrawArcFlag::BOTTOM_CLOCK_WISH: { return std::make_pair(12 * factor, 6 * factor); } + case DrawArcFlag::LEFT_CLOCK_WISH: { return std::make_pair(9 * factor, 3 * factor); } + case DrawArcFlag::RIGHT_CLOCK_WISH: { return std::make_pair(15 * factor, 9 * factor); } + case DrawArcFlag::ALL_CLOCK_WISH: { return std::make_pair(12 * factor, 0 * factor); } + default: { GAL_PROMETHEUS_ERROR_UNREACHABLE(); } // NOLINT(clang-diagnostic-covered-switch-default) + } + } + + class DrawList::Drawer + { + public: + memory::RefWrapper self; + + using path_list_type = std::vector; + + path_list_type path_list; + + [[nodiscard]] auto make_appender() noexcept -> PrimitiveAppender + { + auto& draw_list = self.get(); + + return {draw_list.command_list_.back(), draw_list.vertex_list_, draw_list.index_list_}; + } + + auto draw_polygon_line(const color_type color, const DrawFlag draw_flag, const float thickness) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& font = current_font(*draw_list.context_); + auto appender = make_appender(); + + const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + + const auto vertex_count = segments_count * 4; + const auto index_count = segments_count * 6; + appender.reserve(vertex_count, index_count); + + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + + const auto& p1 = path_point[i]; + const auto& p2 = path_point[n]; + + auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); + normalized_x *= (thickness * .5f); + normalized_y *= (thickness * .5f); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = font.white_pixel_uv; + + appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + } + + auto draw_polygon_line_aa(const color_type color, const DrawFlag draw_flag, float thickness) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& font = current_font(*draw_list.context_); + const auto draw_list_flag = self.get().draw_list_flag_; + auto appender = make_appender(); + + const auto& opaque_uv = font.white_pixel_uv; + const auto transparent_color = color.transparent(); + + const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + const auto is_thick_line = thickness > 1.f; + + thickness = std::ranges::max(thickness, 1.f); + const auto thickness_integer = static_cast(thickness); + const auto thickness_fractional = thickness - static_cast(thickness_integer); + + const auto is_use_texture = + ( + (draw_list_flag & DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + (thickness_integer < font.baked_line_max_width) and + (thickness_fractional <= .00001f) + ); + + const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); + const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); + appender.reserve(vertex_cont, index_count); + + // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; + + // Calculate normals (tangents) for each line segment + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + + if (not is_closed) + { + temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + } + + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + if (is_use_texture or not is_thick_line) + { + // [PATH 1] Texture-based lines (thick or non-thick) + + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA + const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; + } + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + // closed + (first_point_of_segment + 1) == path_point_count + ? current_vertex_index + : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + ); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // dm_x, dm_y are offset to the outer edge of the AA area + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= half_draw_size; + dm_y *= half_draw_size; + + // Add temporary vertexes for the outer edges + temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; + temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; + + if (is_use_texture) + { + // Add indices for two triangles + + // right + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); + // left + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); + } + else + { + // Add indexes for four triangles + + // right 1 + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); + // right 2 + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); + // left 1 + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // left 2 + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + } + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertexes for each point on the line + if (is_use_texture) + { + GAL_PROMETHEUS_ERROR_ASSUME(not font.baked_line_uv.empty(), "draw::FontAtlasFlag::NO_BAKED_LINE"); + + const auto& uv = font.baked_line_uv[thickness_integer]; + + const auto uv0 = uv.left_top(); + const auto uv1 = uv.right_bottom(); + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); + } + } + else + { + // If we're not using a texture, we need the center vertex as well + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // center of line + appender.add_vertex(path_point[i], opaque_uv, color); + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); + } + } + } + else + { + // [PATH 2] Non-texture-based lines (non-thick) + + // we need to draw the solid line core and thus require four vertices per point + const auto half_inner_thickness = (thickness - 1.f) * .5f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + const auto point_last = path_point_count - 1; + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + } + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + (first_point_of_segment + 1) == path_point_count + ? current_vertex_index + : (vertex_index_for_start + 4) + ); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); + const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); + const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); + const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); + + // Add temporary vertices + temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; + temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + + // Add indexes + appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); + appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertices + for (std::decay_t i = 0; i < path_point_count; ++i) + { + appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); + appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); + } + } + } + + auto draw_convex_polygon_line_filled(const color_type color) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& font = current_font(*draw_list.context_); + auto appender = make_appender(); + + const auto vertex_count = path_point_count; + const auto index_count = (path_point_count - 2) * 3; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = font.white_pixel_uv; + + std::ranges::for_each( + path_point, + [&](const point_type& point) noexcept -> void + { + appender.add_vertex(point, opaque_uv, color); + } + ); + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); + + appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); + } + } + + auto draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& font = current_font(*draw_list.context_); + auto appender = make_appender(); + + const auto& opaque_uv = font.white_pixel_uv; + const auto transparent_color = color.transparent(); + + const auto vertex_count = path_point_count * 2; + const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_inner_index = static_cast(appender.vertex_count()); + const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); + + // Add indexes for fill + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + + appender.add_index( + current_vertex_inner_index + 0, + current_vertex_inner_index + static_cast((i - 1) << 1), + current_vertex_inner_index + static_cast(i << 1) + ); + } + + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + // Average normals + const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= .5f; + dm_y *= .5f; + + // inner + appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); + // outer + appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + + // Add indexes for fringes + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + + appender.add_index( + current_vertex_inner_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(i << 1) + ); + appender.add_index( + current_vertex_outer_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(n << 1) + ); + } + } + + auto draw_rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& font = current_font(*draw_list.context_); + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto& opaque_uv = font.white_pixel_uv; + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(rect.left_top(), opaque_uv, color_left_top); + appender.add_vertex(rect.right_top(), opaque_uv, color_right_top); + appender.add_vertex(rect.right_bottom(), opaque_uv, color_right_bottom); + appender.add_vertex(rect.left_bottom(), opaque_uv, color_left_bottom); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + + auto draw_text( + const Font& font, + const float font_size, + const point_type& p, + const color_type color, + const std::string_view utf8_text, + const float wrap_width + ) noexcept -> void + { + auto& draw_list = self.get(); + + const auto new_texture = draw_list.this_command_texture_id_ != font.texture_id; + + if (new_texture) + { + draw_list.push_texture_id(font.texture_id); + } + + text_draw(font, utf8_text, font_size, wrap_width, p, color, make_appender()); + + if (new_texture) + { + draw_list.pop_texture_id(); + } + } + + auto draw_image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void + { + auto& draw_list = self.get(); + + const auto new_texture = draw_list.this_command_texture_id_ != texture_id; + + if (new_texture) + { + draw_list.push_texture_id(texture_id); + } + + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(display_p1, uv_p1, color); + appender.add_vertex(display_p2, uv_p2, color); + appender.add_vertex(display_p3, uv_p3, color); + appender.add_vertex(display_p4, uv_p4, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + + if (new_texture) + { + draw_list.pop_texture_id(); + } + } + + auto draw_image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color, + float rounding, + DrawFlag flag + ) noexcept -> void + { + // @see `path_rect` + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = + (flag & DrawFlag::ROUND_CORNER_TOP) == DrawFlag::ROUND_CORNER_TOP or + (flag & DrawFlag::ROUND_CORNER_BOTTOM) == DrawFlag::ROUND_CORNER_BOTTOM; + const auto h = + (flag & DrawFlag::ROUND_CORNER_LEFT) == DrawFlag::ROUND_CORNER_LEFT or + (flag & DrawFlag::ROUND_CORNER_RIGHT) == DrawFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) + { + draw_image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + else + { + auto& draw_list = self.get(); + + const auto new_texture = draw_list.this_command_texture_id_ != texture_id; + + if (new_texture) + { + draw_list.push_texture_id(texture_id); + } + + const auto rounding_left_top = (flag & DrawFlag::ROUND_CORNER_LEFT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & DrawFlag::ROUND_CORNER_RIGHT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & DrawFlag::ROUND_CORNER_LEFT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + + path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, DrawArcFlag::Q2_CLOCK_WISH); + path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, DrawArcFlag::Q1_CLOCK_WISH); + path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, DrawArcFlag::Q4_CLOCK_WISH); + path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, DrawArcFlag::Q3_CLOCK_WISH); + + const auto before_vertex_count = draw_list.vertex_list_.size(); + // draw + path_stroke(color); + const auto after_vertex_count = draw_list.vertex_list_.size(); + + // set uv manually + + const auto display_size = display_rect.size(); + const auto uv_size = uv_rect.size(); + const auto scale = uv_size / display_size; + + auto it = draw_list.vertex_list_.begin() + static_cast(before_vertex_count); + const auto end = draw_list.vertex_list_.begin() + static_cast(after_vertex_count); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == draw_list.vertex_list_.end()); + + // note: linear uv + const auto uv_min = uv_rect.left_top(); + // const auto uv_max = uv_rect.right_bottom(); + while (it != end) + { + const auto v = uv_min + (it->position - display_rect.left_top()) * scale; + + it->uv = + { + // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + v.x, + // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + v.y + }; + it += 1; + } + + if (new_texture) + { + draw_list.pop_texture_id(); + } + } + } + + auto path_clear() noexcept -> void + { + path_list.clear(); + } + + auto path_reserve(const std::size_t size) noexcept -> void + { + path_list.reserve(size); + } + + auto path_reserve_extra(const std::size_t size) noexcept -> void + { + path_reserve(path_list.size() + size); + } + + auto path_pin(const point_type& point) noexcept -> void + { + path_list.push_back(point); + } + + auto path_stroke(const color_type color, const DrawFlag flag, const float thickness) noexcept -> void + { + if (const auto draw_list_flag = self.get().draw_list_flag_; + (draw_list_flag & DrawListFlag::ANTI_ALIASED_LINE) != DrawListFlag::NONE) + { + draw_polygon_line_aa(color, flag, thickness); + } + else + { + draw_polygon_line(color, flag, thickness); + } + + path_clear(); + } + + auto path_stroke(const color_type color) noexcept -> void + { + if (const auto draw_list_flag = self.get().draw_list_flag_; + (draw_list_flag & DrawListFlag::ANTI_ALIASED_FILL) != DrawListFlag::NONE) + { + draw_convex_polygon_line_filled_aa(color); + } + else + { + draw_convex_polygon_line_filled(color); + } + + path_clear(); + } + + auto path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void + { + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + + // Calculate arc auto segment step size + auto step = DrawListSharedData::vertex_sample_points_count / draw_list_shared_data.circle_auto_segment_count(radius); + // Make sure we never do steps larger than one quarter of the circle + step = std::clamp(step, static_cast(1), DrawListSharedData::vertex_sample_points_count / 4); + + const auto sample_range = math::abs(to - from); + const auto next_step = step; + + auto extra_max_sample = false; + if (step > 1) + { + const auto overstep = sample_range % step; + if (overstep > 0) + { + extra_max_sample = true; + + // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + step -= (step - overstep) / 2; + } + + path_reserve_extra(sample_range / step + 1 + (overstep > 0)); + } + else + { + path_reserve_extra(sample_range + 1); + } + + auto sample_index = from; + if (sample_index < 0 or std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) + { + sample_index = sample_index % static_cast(DrawListSharedData::vertex_sample_points_count); + if (sample_index < 0) + { + sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); + } + } + + if (to >= from) + { + for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) + { + sample_index -= static_cast(DrawListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = draw_list_shared_data.vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + else + { + for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + { + sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = draw_list_shared_data.vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + + if (extra_max_sample) + { + auto normalized_max_sample_index = to % static_cast(DrawListSharedData::vertex_sample_points_count); + if (normalized_max_sample_index < 0) + { + normalized_max_sample_index += DrawListSharedData::vertex_sample_points_count; + } + + const auto& sample_point = draw_list_shared_data.vertex_sample_point(normalized_max_sample_index); + + path_pin({center + sample_point * radius}); + } + } + + auto path_arc_fast(const circle_type& circle, const DrawArcFlag flag) noexcept -> void + { + const auto [from, to] = range_of_arc(flag); + + return path_arc_fast(circle, from, to); + } + + auto path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); + + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); + } + } + + auto path_arc(const circle_type& circle, const float from, const float to) noexcept -> void + { + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + + // Automatic segment count + if (radius <= draw_list_shared_data.arc_fast_radius_cutoff) + { + const auto is_reversed = to < from; + + // We are going to use precomputed values for mid-samples. + // Determine first and last sample in lookup table that belong to the arc + const auto sample_from_f = DrawListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); + const auto sample_to_f = DrawListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); + + const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); + const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); + const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); + + const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; + const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; + + const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; + const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; + + if (emit_start) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); + } + if (sample_mid > 0) + { + path_arc_fast(circle, sample_from, sample_to); + } + if (emit_end) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); + } + } + else + { + const auto arc_length = to - from; + const auto circle_segment_count = draw_list_shared_data.circle_auto_segment_count(radius); + const auto arc_segment_count = std::ranges::max( + static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + static_cast(std::numbers::pi_v * 2 / arc_length) + ); + path_arc_n(circle, from, to, arc_segment_count); + } + } + + auto path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + const auto& [center, radius, rotation] = ellipse; + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + const auto offset = point_type{math::cos(a), math::sin(a)} * radius; + const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; + const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; + path_pin({center + point_type{prime_x, prime_y}}); + } + } + + auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void + { + path_pin(p1); + path_pin(p2); + path_pin(p3); + path_pin(p4); + } + + auto path_rect(const rect_type& rect, float rounding, DrawFlag flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = + (flag & DrawFlag::ROUND_CORNER_TOP) == DrawFlag::ROUND_CORNER_TOP or + (flag & DrawFlag::ROUND_CORNER_BOTTOM) == DrawFlag::ROUND_CORNER_BOTTOM; + const auto h = + (flag & DrawFlag::ROUND_CORNER_LEFT) == DrawFlag::ROUND_CORNER_LEFT or + (flag & DrawFlag::ROUND_CORNER_RIGHT) == DrawFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) + { + path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + } + else + { + const auto rounding_left_top = (flag & DrawFlag::ROUND_CORNER_LEFT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & DrawFlag::ROUND_CORNER_RIGHT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & DrawFlag::ROUND_CORNER_LEFT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + + path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, DrawArcFlag::Q2_CLOCK_WISH); + path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, DrawArcFlag::Q1_CLOCK_WISH); + path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, DrawArcFlag::Q4_CLOCK_WISH); + path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, DrawArcFlag::Q3_CLOCK_WISH); + } + } + + auto path_bezier_cubic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + { + const auto dx = p4.x - p1.x; + const auto dy = p4.y - p1.y; + const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); + const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); + + if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p4); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_34 = (p3 + p4) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + const auto p_234 = (p_23 + p_34) * .5f; + const auto p_1234 = (p_123 + p_234) * .5f; + + path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); + path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); + } + } + + auto path_bezier_quadratic_curve_casteljau(const point_type& p1, const point_type& p2, const point_type& p3, const float tessellation_tolerance, const std::size_t level) noexcept -> void + { + const auto dx = p3.x - p1.x; + const auto dy = p3.y - p1.y; + const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; + + if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p3); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + + path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); + path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); + } + } + + auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void + { + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data.curve_tessellation_tolerance > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, draw_list_shared_data.curve_tessellation_tolerance, 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); + } + } + } + + auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void + { + const auto& draw_list = self.get(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); + const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data.curve_tessellation_tolerance > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_quadratic_curve_casteljau(p1, p2, p3, draw_list_shared_data.curve_tessellation_tolerance, 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + } + } + } + }; + + auto DrawList::push_command() noexcept -> void + { + // fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_command_texture_id_ != Font::invalid_texture_id); + + command_list_.emplace_back( + command_type + { + .clip_rect = this_command_clip_rect_, + .texture_id = this_command_texture_id_, + .index_offset = static_cast(index_list_.size()), + // set by draw_xxx + .element_count = 0 + } + ); + } + + auto DrawList::on_element_changed(const ChangedElement element) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); + + const auto command_count = command_list_.size(); + const auto& [current_clip_rect, current_texture_id, current_index_offset, current_element_count] = command_list_.back(); + + if (current_element_count != 0) + { + if (element == ChangedElement::CLIP_RECT) + { + if (current_clip_rect != this_command_clip_rect_) + { + push_command(); + + return; + } + } + else if (element == ChangedElement::TEXTURE_ID) + { + if (current_texture_id != this_command_texture_id_) + { + push_command(); + + return; + } + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + } + + // try to merge with previous command if it matches, else use current command + if (command_count > 1) + { + if ( + const auto& [previous_clip_rect, previous_texture, previous_index_offset, previous_element_count] = command_list_[command_count - 2]; + current_element_count == 0 and + ( + this_command_clip_rect_ == previous_clip_rect and + this_command_texture_id_ == previous_texture + ) and + // sequential + current_index_offset == previous_index_offset + previous_element_count + ) + { + command_list_.pop_back(); + return; + } + } + + if (element == ChangedElement::CLIP_RECT) + { + command_list_.back().clip_rect = this_command_clip_rect_; + } + else if (element == ChangedElement::TEXTURE_ID) + { + command_list_.back().texture_id = this_command_texture_id_; + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + } + + DrawList::DrawList() noexcept + : + context_{nullptr}, + draw_list_flag_{DrawListFlag::NONE}, + this_command_clip_rect_{0, 0, 0, 0}, + this_command_texture_id_{Font::invalid_texture_id} {} + + auto DrawList::bind_context(Context& context) noexcept -> void + { + context_ = std::addressof(context); + + draw_list_flag_ = current_draw_list_flag(context); + } + + auto DrawList::reset() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); + const auto& font = current_font(*context_); + + command_list_.clear(); + vertex_list_.clear(); + index_list_.clear(); + + // we don't know the size of the clip rect, so we need the user to set it + this_command_clip_rect_ = {}; + // the first texture is always the (default) font texture + this_command_texture_id_ = font.texture_id; + + // we always have a command ready in the buffer + command_list_.emplace_back( + command_type + { + .clip_rect = this_command_clip_rect_, + .texture_id = this_command_texture_id_, + .index_offset = static_cast(index_list_.size()), + // set by subsequent draw_xxx + .element_count = 0 + } + ); + } + + auto DrawList::command_list() const noexcept -> const command_list_type& + { + return command_list_; + } + + auto DrawList::vertex_list() const noexcept -> const vertex_list_type& + { + return vertex_list_; + } + + auto DrawList::index_list() const noexcept -> const index_list_type& + { + return index_list_; + } + + auto DrawList::push_clip_rect(const rect_type& rect, const bool intersect_with_current_clip_rect) noexcept -> rect_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); + + const auto& [current_clip_rect, current_texture, current_index_offset, current_element_count] = command_list_.back(); + + this_command_clip_rect_ = intersect_with_current_clip_rect ? rect.combine_min(current_clip_rect) : rect; + + on_element_changed(ChangedElement::CLIP_RECT); + return command_list_.back().clip_rect; + } + + auto DrawList::pop_clip_rect() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list_.size() > 1); + this_command_clip_rect_ = command_list_[command_list_.size() - 2].clip_rect; + + on_element_changed(ChangedElement::CLIP_RECT); + } + + auto DrawList::push_texture_id(const texture_id_type texture) noexcept -> void + { + this_command_texture_id_ = texture; + + on_element_changed(ChangedElement::TEXTURE_ID); + } + + auto DrawList::pop_texture_id() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list_.size() > 1); + this_command_texture_id_ = command_list_[command_list_.size() - 2].texture_id; + + on_element_changed(ChangedElement::TEXTURE_ID); + } + + auto DrawList::line( + const point_type& from, + const point_type& to, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_pin(from); + drawer.path_pin(to); + + drawer.path_stroke(color, DrawFlag::NONE, thickness); + } + + auto DrawList::triangle( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_pin(a); + drawer.path_pin(b); + drawer.path_pin(c); + + drawer.path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_pin(a); + drawer.path_pin(b); + drawer.path_pin(c); + + drawer.path_stroke(color); + } + + auto DrawList::rect( + const rect_type& rect, + const color_type color, + const float rounding, + const DrawFlag flag, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_rect(rect, rounding, flag); + + drawer.path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::rect( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const DrawFlag flag, + const float thickness + ) noexcept -> void + { + return rect({left_top, right_bottom}, color, rounding, flag, thickness); + } + + auto DrawList::rect_filled( + const rect_type& rect, + const color_type color, + const float rounding, + const DrawFlag flag + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) + { + drawer.draw_rect_filled(rect, color, color, color, color); + } + else + { + drawer.path_rect(rect, rounding, flag); + drawer.path_stroke(color); + } + } + + auto DrawList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const DrawFlag flag + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color, rounding, flag); + } + + auto DrawList::rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto DrawList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto DrawList::quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_quadrilateral(p1, p2, p3, p4); + + drawer.path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_quadrilateral(p1, p2, p3, p4); + + drawer.path_stroke(color); + } + + auto DrawList::circle_n( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::circle_n( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle_n({center, radius}, color, segments, thickness); + } + + auto DrawList::ellipse_n( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color, DrawFlag::CLOSED, thickness); + } + + void DrawList::ellipse_n( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + { + return ellipse_n({center, radius, rotation}, color, segments, thickness); + } + + auto DrawList::circle_n_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color); + } + + auto DrawList::circle_n_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return circle_n_filled({center, radius}, color, segments); + } + + auto DrawList::ellipse_n_filled( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color); + } + + auto DrawList::ellipse_n_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_n_filled({center, radius, rotation}, color, segments); + } + + void DrawList::circle( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); + + drawer.path_stroke(color, DrawFlag::CLOSED, thickness); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, DrawListSharedData::circle_segments_min, DrawListSharedData::circle_segments_max); + + circle_n(circle, color, clamped_segments, thickness); + } + } + + auto DrawList::circle( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle({center, radius}, color, segments, thickness); + } + + auto DrawList::circle_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); + + drawer.path_stroke(color); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, DrawListSharedData::circle_segments_min, DrawListSharedData::circle_segments_max); + + circle_n_filled(circle, color, clamped_segments); + } + } + + auto DrawList::circle_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + circle_filled({center, radius}, color, segments); + } + + auto DrawList::ellipse( + const ellipse_type& ellipse, + const color_type color, + std::uint32_t segments, + const float thickness + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); + const auto& draw_list_shared_data = current_draw_list_shared_data(*context_); + + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + if (segments == 0) + { + // fixme + segments = draw_list_shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n(ellipse, color, segments, thickness); + } + + auto DrawList::ellipse( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return ellipse({center, radius, rotation}, color, segments, thickness); + } + + auto DrawList::ellipse_filled( + const ellipse_type& ellipse, + const color_type color, + std::uint32_t segments + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); + const auto& draw_list_shared_data = current_draw_list_shared_data(*context_); + + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + if (segments == 0) + { + // fixme + segments = draw_list_shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n_filled(ellipse, color, segments); + } + + auto DrawList::ellipse_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_filled({center, radius, rotation}, color, segments); + } + + auto DrawList::bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_bezier_curve(p1, p2, p3, p4, segments); + + drawer.path_stroke(color, DrawFlag::NONE, thickness); + } + + auto DrawList::bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_bezier_quadratic_curve(p1, p2, p3, segments); + + drawer.path_stroke(color, DrawFlag::NONE, thickness); + } + + auto DrawList::text( + const Font& font, + const float font_size, + const point_type& point, + const color_type color, + const std::string_view utf8_text, + float wrap_width + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + if (wrap_width == text_wrap_width_not_set) // NOLINT(clang-diagnostic-float-equal) + { + wrap_width = Font::no_auto_wrap; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_text(font, font_size, point, color, utf8_text, wrap_width); + } + + auto DrawList::text( + const float font_size, + const point_type& point, + const color_type color, + const std::string_view utf8_text, + const float wrap_width + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); + const auto& font = current_font(*context_); + + text(font, font_size, point, color, utf8_text, wrap_width); + } + + auto DrawList::image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); + } + + auto DrawList::image( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void + { + image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + + auto DrawList::image( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); + } + + auto DrawList::image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const float rounding, + const DrawFlag flag, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); + } + + auto DrawList::image_rounded( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const float rounding, + const DrawFlag flag, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + } +} diff --git a/src/gui/internal/draw_list.hpp b/src/gui/internal/draw_list.hpp new file mode 100644 index 0000000..353942a --- /dev/null +++ b/src/gui/internal/draw_list.hpp @@ -0,0 +1,492 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus +{ + namespace gui::internal + { + enum class DrawFlag : std::uint8_t + { + NONE = 0, + // specify that shape should be closed + // @see DrawList::draw_polygon_line + // @see DrawList::draw_polygon_line_aa + // @see DrawList::path_stroke + CLOSED = 1 << 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + }; + + enum class DrawArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + }; + } + + namespace meta::user_defined + { + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + } + + namespace gui::internal + { + [[nodiscard]] auto range_of_arc(DrawArcFlag flag) noexcept -> std::pair; + + class Font; + + class DrawList + { + public: + using size_type = DrawData::size_type; + + using command_type = DrawData::command_type; + + using vertex_list_type = DrawData::vertex_list_type; + using index_list_type = DrawData::index_list_type; + using command_list_type = DrawData::command_list_type; + + // not set ==> Font::no_auto_wrap + constexpr static float text_wrap_width_not_set = -0.f; + + private: + class Drawer; + + Context* context_; + + DrawListFlag draw_list_flag_; + + // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) + // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 + // command_list: + // 0: .clip_rect = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) + // 1: .clip_rect = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) + // 2: .clip_rect = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) + command_list_type command_list_; + vertex_list_type vertex_list_; + index_list_type index_list_; + + rect_type this_command_clip_rect_; + texture_id_type this_command_texture_id_; + + auto push_command() noexcept -> void; + + enum class ChangedElement : std::uint8_t + { + CLIP_RECT, + TEXTURE_ID, + }; + + auto on_element_changed(ChangedElement element) noexcept -> void; + + public: + DrawList() noexcept; + + // ---------------------------------------------------------------------------- + // CONTEXT + + auto bind_context(Context& context) noexcept -> void; + + // ---------------------------------------------------------------------------- + // RESET + + auto reset() noexcept -> void; + + // ---------------------------------------------------------------------------- + // DRAW DATA + + [[nodiscard]] auto command_list() const noexcept -> const command_list_type&; + + [[nodiscard]] auto vertex_list() const noexcept -> const vertex_list_type&; + + [[nodiscard]] auto index_list() const noexcept -> const index_list_type&; + + // ---------------------------------------------------------------------------- + // CLIP RECT & TEXTURE + + auto push_clip_rect(const rect_type& rect, bool intersect_with_current_clip_rect) noexcept -> rect_type&; + + auto pop_clip_rect() noexcept -> void; + + auto push_texture_id(texture_id_type texture) noexcept -> void; + + auto pop_texture_id() noexcept -> void; + + // ---------------------------------------------------------------------------- + // PRIMITIVE + + auto line( + const point_type& from, + const point_type& to, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color + ) noexcept -> void; + + auto rect( + const rect_type& rect, + color_type color, + float rounding = .0f, + DrawFlag flag = DrawFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const point_type& left_top, + const point_type& right_bottom, + color_type color, + float rounding = .0f, + DrawFlag flag = DrawFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color, + float rounding = .0f, + DrawFlag flag = DrawFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color, + float rounding = .0f, + DrawFlag flag = DrawFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color + ) noexcept -> void; + + auto circle_n( + const circle_type& circle, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle_n_filled( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle( + const circle_type& circle, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto circle_filled( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse_filled( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // TEXT + + auto text( + const Font& font, + float font_size, + const point_type& point, + color_type color, + std::string_view utf8_text, + float wrap_width = text_wrap_width_not_set + ) noexcept -> void; + + auto text( + float font_size, + const point_type& point, + color_type color, + std::string_view utf8_text, + float wrap_width = text_wrap_width_not_set + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // IMAGE + + // p1________ p2 + // | | + // | | + // p4|_______| p3 + auto image( + texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1 = {0, 0}, + const uv_type& uv_p2 = {1, 0}, + const uv_type& uv_p3 = {1, 1}, + const uv_type& uv_p4 = {0, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + }; + } +} diff --git a/src/gui/internal/font.cpp b/src/gui/internal/font.cpp new file mode 100644 index 0000000..0c05692 --- /dev/null +++ b/src/gui/internal/font.cpp @@ -0,0 +1,553 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H // +// #include FT_MODULE_H // +// #include FT_GLYPH_H // +// #include FT_SYNTHESIS_H // + +#define STB_RECT_PACK_IMPLEMENTATION +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#include + +namespace +{ + struct ft_type + { + FT_Library library; + FT_Face face; + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return library and face; + } + }; + + [[nodiscard]] auto create_ft(const std::string_view font_path, const std::uint32_t pixel_height) noexcept -> ft_type + { + FT_Library ft_library; + if (FT_Init_FreeType(&ft_library)) + { + // Could not initialize FreeType library + return {.library = nullptr, .face = nullptr}; + } + + FT_Face ft_face; + if (FT_New_Face(ft_library, font_path.data(), 0, &ft_face)) + { + FT_Done_FreeType(ft_library); + // Could not load font + return {.library = nullptr, .face = nullptr}; + } + + FT_Set_Pixel_Sizes(ft_face, 0, pixel_height); + return {.library = ft_library, .face = ft_face}; + } + + auto destroy_ft(const ft_type ft) noexcept -> void + { + auto [ft_library, ft_face] = ft; + + FT_Done_Face(ft_face); + FT_Done_FreeType(ft_library); + } + + using namespace gal::prometheus; + using namespace gui; + + auto do_load_font(const FontOption& option, internal::Font& font) noexcept -> Texture + { + font.font_path = std::format("{}-{}px", option.font_path, option.pixel_height); + font.pixel_height = option.pixel_height; + if (option.baked_line_max_width == 0 or option.baked_line_max_width > FontOption::default_baked_line_max_width) + { + font.baked_line_max_width = FontOption::default_baked_line_max_width; + } + else + { + font.baked_line_max_width = option.baked_line_max_width; + } + font.scale = option.scale; + font.display_offset = option.display_offset; + font.white_pixel_uv = option.white_pixel_uv; + + Texture texture{font.texture_id}; + + auto ft = create_ft(option.font_path, option.pixel_height); + if (not ft.valid()) + { + return texture; + } + + auto [library, face] = ft; + + // =============================== + + std::vector rects; + + // baked line + constexpr auto id_baked_line = std::numeric_limits::min() + 0; + { + const auto baked_line_uv_width = font.baked_line_max_width + 1; + const auto baked_line_uv_height = font.baked_line_max_width + 2; + + font.baked_line_uv.reserve(baked_line_uv_height); + + rects.emplace_back( + stbrp_rect + { + .id = id_baked_line, + .w = static_cast(baked_line_uv_width), + .h = static_cast(baked_line_uv_height), + .x = 0, + .y = 0, + .was_packed = 0 + } + ); + } + + // =============================== + + std::ranges::for_each( + option.glyph_ranges, + [&face, &rects](const auto& pair) noexcept -> void + { + const auto [from, to] = pair; + + for (auto c = from; c <= to; ++c) + { + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + { + continue; + } + + const auto& g = face->glyph; + rects.emplace_back( + stbrp_rect + { + .id = std::bit_cast(c), + .w = static_cast(g->bitmap.width), + .h = static_cast(g->bitmap.rows), + .x = static_cast(g->bitmap_left), + .y = static_cast(g->bitmap_top), + .was_packed = 0 + } + ); + } + } + ); + + // =============================== + + // todo: texture size? + const auto size = [&rects]() + { + Texture::size_type total_area = 0; + Texture::size_type max_width = 0; + Texture::size_type max_height = 0; + + for (const auto& [id, w, h, x, y, was_packed]: rects) + { + total_area += w * h; + max_width = std::ranges::max(max_width, static_cast(w)); + max_height = std::ranges::max(max_height, static_cast(h)); + } + + const auto min_side = static_cast(std::sqrt(total_area)); + const auto max_side = std::ranges::max(max_width, max_height); + + return std::bit_ceil(std::ranges::max(min_side, max_side)); + }(); + auto atlas_width = size; + auto atlas_height = size; + + stbrp_context rp_context; + std::vector nodes{atlas_width}; + while (true) + { + stbrp_init_target(&rp_context, static_cast(atlas_width), static_cast(atlas_height), nodes.data(), static_cast(nodes.size())); + if (stbrp_pack_rects(&rp_context, rects.data(), static_cast(rects.size()))) + { + break; + } + + atlas_width *= 2; + atlas_height *= 2; + nodes.resize(atlas_width); + } + + // =============================== + + // note: We don't necessarily overwrite all the memory, but it doesn't matter. + // auto texture_data = std::make_unique(static_cast(atlas_width * atlas_height)); + auto texture_data = std::make_unique_for_overwrite(static_cast(atlas_width * atlas_height)); + + const extent_type texture_uv_scale{1.f / static_cast(atlas_width), 1.f / static_cast(atlas_height)}; + + // =============================== + + for (const auto& [id, rect_width, rect_height, rect_x, rect_y, was_packed]: rects) + { + if (id == id_baked_line) + { + using value_type = point_type::value_type; + constexpr std::uint32_t white_color = 0xff'ff'ff'ff; + + if (font.white_pixel_uv.x < 0 or font.white_pixel_uv.y < 0) + { + // hacky: baked line rect area, one pixel + { + const auto x = rect_x + rect_y * atlas_width; + texture_data[x] = white_color; + + const auto uv_x = static_cast(static_cast(rect_x) + .5f) * texture_uv_scale.width; + const auto uv_y = static_cast(static_cast(rect_y) + .5f) * texture_uv_scale.height; + + font.white_pixel_uv = point_type{uv_x, uv_y}; + } + } + + // ◿ + for (stbrp_coord y = 0; y < rect_height; ++y) + { + const auto line_width = y; + const auto offset_y = (rect_y + y) * atlas_width; + + for (stbrp_coord x = line_width; x > 0; --x) + { + const auto offset_x = rect_x + (rect_width - x); + const auto index = offset_x + offset_y; + texture_data[index] = white_color; + } + + const auto p_x = rect_x + (rect_width - line_width); + const auto p_y = rect_y + y; + const auto width = line_width; + constexpr auto height = .5f; + + const auto uv_x = static_cast(p_x) * texture_uv_scale.width; + const auto uv_y = static_cast(p_y) * texture_uv_scale.height; + const auto uv_width = static_cast(width) * texture_uv_scale.width; + const auto uv_height = static_cast(height) * texture_uv_scale.height; + + font.baked_line_uv.emplace_back(uv_x, uv_y, uv_width, uv_height); + } + + continue; + } + + const auto c = std::bit_cast(id); + + // todo + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(c <= std::numeric_limits::max()); + + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + { + continue; + } + + const auto& g = face->glyph; + + for (std::uint32_t y = 0; y < g->bitmap.rows; ++y) + { + for (std::uint32_t x = 0; x < g->bitmap.width; ++x) + { + const auto index = rect_x + x + (rect_y + y) * atlas_width; + const auto a = g->bitmap.buffer[x + y * g->bitmap.pitch] << 24; + const auto color = + // A + a | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + texture_data[index] = color; + } + } + + const auto p_x = static_cast(g->bitmap_left); + const auto p_y = static_cast(g->bitmap_top); + const auto p_width = static_cast(g->bitmap.width); + const auto p_height = static_cast(g->bitmap.rows); + + const auto uv_x = static_cast(rect_x) * texture_uv_scale.width; + const auto uv_y = static_cast(rect_y) * texture_uv_scale.height; + const auto uv_width = static_cast(g->bitmap.width) * texture_uv_scale.width; + const auto uv_height = static_cast(g->bitmap.rows) * texture_uv_scale.height; + + const internal::Font::glyph_type glyph{ + .rect = {p_x, p_y, p_width, p_height}, + .uv = {uv_x, uv_y, uv_width, uv_height}, + .advance_x = static_cast(g->advance.x) / 64.f + }; + + font.glyphs.insert_or_assign(static_cast(c), glyph); + } + + font.fallback_glyph = font.glyphs[option.fallback_char]; + + // =============================== + + destroy_ft(ft); + + texture.width = atlas_width; + texture.height = atlas_height; + texture.data = std::move(texture_data); + return texture; + } +} + +namespace gal::prometheus::gui +{ + auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture + { + if (context.font_current == Context::stack_pointer_default) + { + return push_font(context, option); + } + + auto font = memory::make_unique(); + auto texture = do_load_font(option, *font); + + context.font_stack[0] = std::move(font); + + return texture; + } + + [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture + { + auto font = memory::make_unique(); + auto texture = do_load_font(option, *font); + + internal::push_font(context, std::move(font)); + + return texture; + } + + auto pop_font(Context& context) noexcept -> void + { + internal::pop_font(context); + } + + Texture::Texture(texture_id_type& texture_id) noexcept + : width{0}, + height{0}, + data{nullptr}, + id{texture_id} {} + + Texture::~Texture() noexcept + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + id != internal::Font::invalid_texture_id, + "Texture is not bound to a GPU resource id!" + ); + } + + auto Texture::valid() const noexcept -> bool + { + return data != nullptr; + } + + // ReSharper disable once CppMemberFunctionMayBeConst + auto Texture::bind(const texture_id_type texture_id) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + this->valid(), + "Only valid textures can be bound to a GPU resource id" + ); + + this->id.get() = texture_id; + } + + namespace internal + { + Font::Font() noexcept + : + pixel_height{std::numeric_limits::min()}, + baked_line_max_width{std::numeric_limits::min()}, + fallback_glyph{}, + scale{1}, + display_offset{0, 0}, + texture_id{invalid_texture_id} {} + + auto Font::loaded() const noexcept -> bool + { + return not glyphs.empty() and texture_id != invalid_texture_id; + } + + auto text_size( + const Font& font, + const std::basic_string_view utf8_text, + const float font_size, + const float wrap_width, + std::basic_string& out_text + ) noexcept -> extent_type + { + // todo + auto utf16_text = chars::convert(utf8_text); + static_assert(std::is_same_v); + + const auto line_height = font_size; + const auto scale = line_height / static_cast(font.pixel_height) * font.scale; + const auto& glyphs = font.glyphs; + const auto& fallback_glyph = font.fallback_glyph; + + float max_width = 0; + float current_width = 0; + float total_height = line_height; + + auto it_input_current = utf16_text.begin(); + const auto it_input_end = utf16_text.end(); + + while (it_input_current != it_input_end) + { + const auto this_char = *it_input_current; + it_input_current += 1; + + if (this_char == u'\n') + { + max_width = std::ranges::max(max_width, current_width); + current_width = 0; + total_height += line_height; + } + else + { + const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { + if (const auto it = glyphs.find(c); + it != glyphs.end()) + { + return it->second; + } + + return fallback_glyph; + }(this_char); + + if (const auto advance_x = glyph_advance_x * scale; + current_width + advance_x > wrap_width) + { + max_width = std::ranges::max(max_width, current_width); + current_width = advance_x; + total_height += line_height; + } + else + { + current_width += advance_x; + } + } + } + + max_width = std::ranges::max(max_width, current_width); + out_text = std::move(utf16_text); + + return {max_width, total_height}; + } + + auto text_size( + const Font& font, + const std::basic_string_view utf8_text, + const float font_size, + const float wrap_width + ) noexcept -> extent_type + { + std::basic_string out; + return text_size(font, utf8_text, font_size, wrap_width, out); + } + + auto text_draw( + const Font& font, + const std::basic_string_view utf8_text, + const float font_size, + const float wrap_width, + const point_type point, + const Theme::color_type color, + PrimitiveAppender appender + ) noexcept -> void + { + // todo + auto utf16_text = chars::convert(utf8_text); + static_assert(std::is_same_v); + + const auto newline_count = std::ranges::count(utf16_text, u'\n'); + const auto text_size_exclude_newline = utf16_text.size() - newline_count; + + const auto vertex_count = 4 * text_size_exclude_newline; + const auto index_count = 6 * text_size_exclude_newline; + appender.reserve(vertex_count, index_count); + + const auto line_height = font_size; + const auto scale = line_height / static_cast(font.pixel_height) * font.scale; + const auto& glyphs = font.glyphs; + const auto& fallback_glyph = font.fallback_glyph; + + auto cursor = point + point_type{0, line_height}; + + auto it_input_current = utf16_text.begin(); + const auto it_input_end = utf16_text.end(); + + while (it_input_current != it_input_end) + { + const auto this_char = *it_input_current; + it_input_current += 1; + + if (this_char == u'\n') + { + cursor.x = point.x; + cursor.y += line_height; + continue; + } + + const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { + if (const auto it = glyphs.find(c); + it != glyphs.end()) + { + return it->second; + } + + return fallback_glyph; + }(this_char); + + const auto advance_x = glyph_advance_x * scale; + if (cursor.x + advance_x > point.x + wrap_width) + { + cursor.x = point.x; + cursor.y += line_height; + } + + const rect_type char_rect + { + cursor + point_type{glyph_rect.left_top().x, -glyph_rect.left_top().y} * scale, + glyph_rect.size() * scale + }; + cursor.x += advance_x; + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(char_rect.left_top(), glyph_uv.left_top(), color); + appender.add_vertex(char_rect.right_top(), glyph_uv.right_top(), color); + appender.add_vertex(char_rect.right_bottom(), glyph_uv.right_bottom(), color); + appender.add_vertex(char_rect.left_bottom(), glyph_uv.left_bottom(), color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + } + } +} diff --git a/src/gui/internal/font.hpp b/src/gui/internal/font.hpp new file mode 100644 index 0000000..3ff6760 --- /dev/null +++ b/src/gui/internal/font.hpp @@ -0,0 +1,97 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +namespace gal::prometheus::gui::internal +{ + class Font final + { + public: + using value_type = FontOption::value_type; + using char_type = FontOption::char_type; + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + // Variable '%1$s' is uninitialized. Always initialize a member variable (type.6). + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(26495) + #endif + + struct glyph_type + { + rect_type rect; + rect_type uv; + float advance_x; + }; + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + #endif + + using glyphs_type = std::unordered_map; + + using glyph_value_type = FontOption::glyph_value_type; + using glyph_ranges_type = FontOption::glyph_ranges_type; + + using baked_line_uv_type = std::vector; + + constexpr static texture_id_type invalid_texture_id = 0; + + // Line breaks only when a line break ('\n') is encountered (this means that text beyond the content area will be clipped) + constexpr static float no_auto_wrap = 99999999.f; + + std::string font_path; + std::uint32_t pixel_height; + std::uint32_t baked_line_max_width; + + glyphs_type glyphs; + glyph_type fallback_glyph; + + value_type scale; + extent_type display_offset; + point_type white_pixel_uv; + baked_line_uv_type baked_line_uv; + + texture_id_type texture_id; + + Font() noexcept; + + // --------------------------------------------------------- + + [[nodiscard]] auto loaded() const noexcept -> bool; + }; + + // ========================================= + // DRAW TEXT + + [[nodiscard]] auto text_size( + const Font& font, + std::basic_string_view utf8_text, + float font_size, + float wrap_width, + std::basic_string& out_text + ) noexcept -> extent_type; + + [[nodiscard]] auto text_size( + const Font& font, + std::basic_string_view utf8_text, + float font_size, + float wrap_width + ) noexcept -> extent_type; + + auto text_draw( + const Font& font, + std::basic_string_view utf8_text, + float font_size, + float wrap_width, + point_type point, + Theme::color_type color, + PrimitiveAppender appender + ) noexcept -> void; +} diff --git a/src/gui/internal/mouse.cpp b/src/gui/internal/mouse.cpp new file mode 100644 index 0000000..1e54ab8 --- /dev/null +++ b/src/gui/internal/mouse.cpp @@ -0,0 +1,143 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include +#include + +namespace gal::prometheus::gui::internal +{ + auto Mouse::is_down(const Context& context, MouseKey key) const noexcept -> bool + { + std::ignore = context; + + const auto index = static_cast(key); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < mouse_key_count); + + return key_statuses[index].down; + } + + auto Mouse::is_clicked(const Context& context, MouseKey key, const bool repeat) const noexcept -> bool + { + const auto index = static_cast(key); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < mouse_key_count); + + const auto& key_status = key_statuses[index]; + const auto down_time = key_status.down_time; + + if (down_time == Mouse::time_just_start) // NOLINT(clang-diagnostic-float-equal) + { + return true; + } + + if (repeat and down_time > context.io.mouse_repeat_click_delay) + { + const auto v1 = std::fmodf(down_time - context.io.mouse_repeat_click_delay, context.io.mouse_repeat_click_rate) > context.io.mouse_repeat_click_rate * .5f; + const auto v2 = std::fmodf(down_time - context.io.delta_time, context.io.mouse_repeat_click_rate) > context.io.mouse_repeat_click_rate * .5f; + + return v1 != v2; + } + + return false; + } + + auto Mouse::is_double_clicked(const Context& context, MouseKey key) const noexcept -> bool + { + std::ignore = context; + + const auto index = static_cast(key); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < mouse_key_count); + + const auto& key_status = key_statuses[index]; + + return key_status.double_clicked; + } + + auto Mouse::tick(const Context& context) noexcept -> void + { + // ---------------------- + // read context + const auto& io = context.io; + + position_current = io.mouse_position; + wheel = io.mouse_wheel; + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(key_statuses.size() == io.mouse_button_state.state.size()); + key_statuses[static_cast(MouseKey::LEFT)].down = io.mouse_button_state[MouseKey::LEFT]; + key_statuses[static_cast(MouseKey::MIDDLE)].down = io.mouse_button_state[MouseKey::MIDDLE]; + key_statuses[static_cast(MouseKey::RIGHT)].down = io.mouse_button_state[MouseKey::RIGHT]; + key_statuses[static_cast(MouseKey::X1)].down = io.mouse_button_state[MouseKey::X1]; + key_statuses[static_cast(MouseKey::X2)].down = io.mouse_button_state[MouseKey::X2]; + + // ---------------------- + // update mouse state + + if (position_current.x < 0 and position_current.y < 0) + { + position_current = position_unknown; + } + + if (position_current == position_unknown and position_previous == position_unknown) + { + // mouse just appeared or disappeared + position_delta = {0, 0}; + } + else + { + position_delta = (position_current - position_previous).to(); + } + position_previous = position_current; + + static_assert(time_not_start < 0); + std::ranges::for_each( + key_statuses, + [this, &context](auto& key_status) noexcept -> void + { + if (key_status.down) + { + if (key_status.down_time < 0) + { + // not start + key_status.down_time = time_just_start; + } + else + { + key_status.down_time += context.io.delta_time; + } + } + else + { + key_status.down_time = time_not_start; + } + + // just start + key_status.clicked = key_status.down_time == time_just_start; // NOLINT(clang-diagnostic-float-equal) + key_status.double_clicked = false; + + if (key_status.clicked) + { + // time_not_start < 0 + if (context.time_total - key_status.click_time < context.io.mouse_double_click_interval_threshold) + { + if (position_current.distance(key_status.click_position) < context.io.mouse_double_click_distance_threshold) + { + key_status.double_clicked = true; + } + + // so the third click isn't turned into a double click + key_status.click_time = time_not_start; + } + else + { + key_status.click_time = context.time_total; + key_status.click_position = position_current; + } + } + } + ); + } +} diff --git a/src/gui/internal/mouse.hpp b/src/gui/internal/mouse.hpp new file mode 100644 index 0000000..e62b8b2 --- /dev/null +++ b/src/gui/internal/mouse.hpp @@ -0,0 +1,62 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gui::internal +{ + class Mouse final + { + public: + using value_type = extent_type::value_type; + + // < 0 + constexpr static point_type position_unknown{-.999999f, -.999999f}; + // < 0 + constexpr static auto time_not_start = time_type{-.999999f}; + constexpr static auto time_just_start = time_type{0.f}; + + struct key_status_type + { + bool down{false}; + bool pad{false}; + + bool clicked{false}; + bool double_clicked{false}; + + // Duration of the "pressed" state of the key + time_type down_time{time_not_start}; + + // Time of last press + time_type click_time{time_not_start}; + // Position of last press + point_type click_position{position_unknown}; + }; + + point_type position_current{position_unknown}; + point_type position_previous{position_unknown}; + extent_type position_delta{0, 0}; + + std::array key_statuses{}; + + value_type wheel{0}; + + // --------------------------------------------- + // STATE + + [[nodiscard]] auto is_down(const Context& context, MouseKey key) const noexcept -> bool; + + [[nodiscard]] auto is_clicked(const Context& context, MouseKey key, bool repeat = false) const noexcept -> bool; + + [[nodiscard]] auto is_double_clicked(const Context& context, MouseKey key) const noexcept -> bool; + + // --------------------------------------------- + // UPDATE (PER FRAME) + + auto tick(const Context& context) noexcept -> void; + }; +} diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp new file mode 100644 index 0000000..c1a6d96 --- /dev/null +++ b/src/gui/internal/window.cpp @@ -0,0 +1,1152 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include +#include + +namespace gal::prometheus::gui::internal +{ + class Window::IdMaker final + { + public: + memory::RefWrapper self; + + private: + [[nodiscard]] static auto make(const std::string_view string) noexcept -> widget_id_type + { + return functional::hash(string); + } + + [[nodiscard]] static auto make(const widget_id_type seed, const std::string_view string) noexcept -> widget_id_type + { + return functional::hash_combine_2(seed, make(string)); + } + + [[nodiscard]] static auto make(const void* pointer) noexcept -> widget_id_type + { + return static_cast(reinterpret_cast(pointer)); + } + + [[nodiscard]] static auto make(const widget_id_type seed, const void* pointer) noexcept -> widget_id_type + { + return functional::hash_combine_2(seed, make(pointer)); + } + + [[nodiscard]] static auto make(const widget_id_type value) noexcept -> widget_id_type + { + const auto* pointer = reinterpret_cast(static_cast(value)); // NOLINT(performance-no-int-to-ptr) + return make(pointer); + } + + [[nodiscard]] static auto make(const widget_id_type seed, const widget_id_type value) noexcept -> widget_id_type + { + return functional::hash_combine_2(seed, make(value)); + } + + public: + auto make_id() noexcept -> void + { + auto& window = self.get(); + + const auto id = make(window.name_); + window.id_stack_.push_back(id); + } + + [[nodiscard]] auto make_id(Context& context, const std::string_view string) const noexcept -> widget_id_type + { + auto& window = self.get(); + + // id_stack.front() -> window id + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.id_stack_.empty()); + + const auto seed = window.id_stack_.back(); + const auto id = make(seed, string); + + mark_widget_alive(context, id); + + return id; + } + + [[nodiscard]] auto make_id(Context& context, const void* pointer) const noexcept -> widget_id_type + { + auto& window = self.get(); + + // id_stack.front() -> window id + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.id_stack_.empty()); + + const auto seed = window.id_stack_.back(); + const auto id = make(seed, pointer); + + mark_widget_alive(context, id); + + return id; + } + + [[nodiscard]] auto make_id(Context& context, const widget_id_type value) const noexcept -> widget_id_type + { + auto& window = self.get(); + + // id_stack.front() -> window id + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.id_stack_.empty()); + + const auto seed = window.id_stack_.back(); + const auto id = make(seed, value); + + mark_widget_alive(context, id); + + return id; + } + }; + + class Window::Drawer final + { + public: + memory::RefWrapper self; + + // ----------------------------------- + // FONT + + [[nodiscard]] auto font_size(const Context& context) const noexcept -> value_type + { + std::ignore = this; + + const auto& font = current_font(context); + + // todo: scale? + return static_cast(font.pixel_height) * font.scale; + } + + // ----------------------------------- + // PADDING + + [[nodiscard]] auto window_padding(const Context& context) const noexcept -> extent_type + { + const auto& window = self.get(); + + const auto& theme = current_theme(context); + + if (window.flag_.is() and not window.flag_.is()) + { + return {1, 1}; + } + + return theme.window_padding; + } + + // ----------------------------------- + // TITLEBAR + + [[nodiscard]] auto titlebar_height(const Context& context) const noexcept -> value_type + { + const auto& window = self.get(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); + + const auto& theme = current_theme(context); + + return font_size(context) + theme.item_frame_padding.height * 2; + } + + [[nodiscard]] auto titlebar_rect(const Context& context) const noexcept -> rect_type + { + const auto& window = self.get(); + + return {window.point_, window.size_full_.width, titlebar_height(context)}; + } + + [[nodiscard]] auto titlebar_rect(const Context& context, const value_type height) const noexcept -> rect_type + { + std::ignore = context; + + const auto& window = self.get(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag_.is()); + + return {window.point_, window.size_full_.width, height}; + } + + // ----------------------------------- + // RESIZE-GRIP + + [[nodiscard]] auto resize_grip_size(const Context& context) const noexcept -> extent_type + { + std::ignore = this; + + const auto& theme = current_theme(context); + + return theme.window_resize_grip_size; + } + + [[nodiscard]] auto resize_grip_rect(const Context& context) const noexcept -> rect_type + { + const auto& window = self.get(); + + const auto s = resize_grip_size(context); + + // RIGHT-BOTTOM-CORNER + const point_type p{window.point_.x + window.size_.width - s.width, window.point_.y + window.size_.height - s.height}; + return {p, s}; + } + + // ----------------------------------- + // CLOSE BUTTON + + [[nodiscard]] auto close_button_size(const Context& context) const noexcept -> extent_type + { + const auto& window = self.get(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); + + const auto height = titlebar_height(context); + + return {height - 4, height - 4}; + } + + [[nodiscard]] auto close_button_rect(const Context& context) const noexcept -> rect_type + { + const auto& window = self.get(); + + const auto s = close_button_size(context); + + // LEFT-TOP-CORNER + const point_type p{window.point_.x + window.size_.width - s.width - 3, window.point_.y + 2}; + return {p, s}; + } + }; + + Window::Window( + const std::string_view name, + const Flag flag, + const point_type& point, + const extent_type& size, + Window* root + ) noexcept + : canvas_ + { + .cursor_start_line = {0, 0}, + .cursor_current_line = {0, 0}, + .cursor_previous_line = {0, 0}, + .height_current_line = 0, + .height_previous_line = 0, + .item_width = {}, + .text_wrap_width = {} + }, + name_{name}, + flag_{flag}, + root_{root}, + point_{point}, + size_{size}, + size_full_{size}, + size_of_content_{0, 0}, + default_item_width_{0}, + scroll_y_{0}, + scroll_next_y_{0}, + scroll_y_visible_{false}, + visible_{false}, + collapsed_{false}, + skip_item_{true}, + accessed_{false}, + auto_fit_only_grows_{false}, + auto_fit_frames_{-1}, + last_drawn_frame_{frame_count_uninitialized} + { + // window id + IdMaker{.self = *this}.make_id(); + + if (flag.is()) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(root != nullptr); + } + else + { + root_ = this; + } + + // auto fit + if (size.width < .001f or size.height < .001f) + { + auto_fit_only_grows_ = true; + auto_fit_frames_ = 2; + } + } + + auto Window::reset(const Flag flag) noexcept -> void + { + flag_ = flag; + } + + auto Window::begin_draw( + Context& context, + value_type fill_alpha, + Window* parent + ) noexcept -> bool + { + // This function can be called multiple times per frame, + // but is only initialized the first time it is called (after that we can just append the contents) + + const auto current_frame_count = context.frame_count; + const auto is_first_draw_this_frame = current_frame_count != last_drawn_frame_; + + const auto display_size = context.io.display_size; + + // --------------------------------- + // initialize the window, set the window's clip rect + { + if (is_first_draw_this_frame) + { + // bind context + draw_list_.bind_context(context); + // clear render data + draw_list_.reset(); + // show + visible_ = true; + // clear clip rect + clip_rect_stack_.clear(); + // clear all ids + id_stack_.clear(); + // generate a new id + IdMaker{.self = *this}.make_id(); + + if (flag_.is()) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parent != nullptr); + + parent->child_stack_.push_back(this); + + // moves the child window to the current cursor position of the parent window + point_ = parent->canvas_.cursor_current_line; + + // the viewing area of the child window must not exceed that of the parent window + push_clip_rect(context, parent->clip_rect_stack_.back()); + } + else + { + // entire display area + push_clip_rect(context, {0, 0, display_size}); + } + } + else + { + // the viewing area of the child window must not exceed that of the parent window + if (flag_.is()) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parent != nullptr); + + // the viewing area of the child window must not exceed that of the parent window + push_clip_rect(context, parent->clip_rect_stack_.back()); + } + else + { + // entire display area + push_clip_rect(context, {0, 0, display_size}); + } + } + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 1); + + // --------------------------------- + // initialize the canvas, set the canvas's clip rect + bool close_button_pressed = false; + { + auto drawer = Drawer{*this}; + + const auto& mouse = context.mouse; + const auto& font = current_font(context); + const auto& theme = current_theme(context); + + const auto has_titlebar = not flag_.is(); + + const auto is_child_window = flag_.is(); + const auto is_tooltip_window = flag_.is(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(has_titlebar != is_child_window, "The child window is not allowed to contain a titlebar!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(has_titlebar != is_tooltip_window, "The tootip window is not allowed to contain a titlebar!"); + + if (is_first_draw_this_frame) + { + if (last_drawn_frame_ + 1 < current_frame_count) + { + // The current window was not drawn in the last frame (or even many frames before that), this is usually because the window was just created, or the window was not visible before + // Focus on the current window + focus_window(context, *this); + + if (is_tooltip_window) + { + // In order to ensure that all the contents of the tooltip are visible, we delay the display of its contents by one frame + visible_ = false; + auto_fit_only_grows_ = false; + auto_fit_frames_ = 2; + } + } + last_drawn_frame_ = current_frame_count; + + // Follows the mouse if it's a tooltip window + if (is_tooltip_window) + { + // todo: offset + point_ = mouse.position_current + extent_type{32, 16} - theme.item_frame_padding * 2; + } + else + { + // If the user drags the window, we determine the new position of the window before the frame is drawn to avoid lag + if (const auto activated = mark_widget_alive(context, id_of_move(context)); + activated) + { + if (mouse.is_down(context, MouseKey::LEFT)) + { + // select current window + focus_window(context, *this); + + if (not flag_.is()) + { + const auto delta = mouse.position_delta; + + // dragging + point_ += delta; + } + } + else + { + // No widgets are active + mark_widget_dead(context); + } + } + } + + if (not is_child_window) + { + const auto s = drawer.font_size(context); + const auto pad = extent_type{s * 2.f, s * 2.f}; + + // Limit the current window from moving outside the program's visual area + point_ = point_.clamp( + (pad - size_).to(), + (display_size - pad).to() + ); + + // Limit the current window to be not too small + size_full_ = size_full_.combine_max(pad); + } + + // set default item width + if ( + size_.width > 0 and + not is_tooltip_window and + not flag_.is() + ) + { + default_item_width_ = size_.width * theme.item_default_width_factor; + } + else + { + // todo: default item width + default_item_width_ = theme.window_min_size.width * theme.item_default_width_factor; + } + + // apply and clamp scrolling + { + scroll_y_ = std::ranges::max(scroll_next_y_, .0f); + + if (not collapsed_ and not skip_item_) + { + const float max_scroll_y = std::ranges::max(.0f, size_of_content_.height - size_full_.height); + scroll_y_ = std::ranges::min(scroll_y_, max_scroll_y); + } + + scroll_next_y_ = scroll_y_; + } + + if (has_titlebar) + { + // `double left-click` on the `titlebar` of the `current window` to collapse the current window + if (is_window_hovered(context, *this)) + { + if (const auto rect = drawer.titlebar_rect(context); + hovered(context, rect) and + mouse.is_double_clicked(context, MouseKey::LEFT) + ) + { + // select current window + focus_window(context, *this); + + // collapse window by double-clicking on titlebar + collapsed_ = not collapsed_; + } + } + } + else + { + // Windows without a titlebar are not allowed to collapse + collapsed_ = false; + } + + if (collapsed_) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(has_titlebar); + + // titlebar only + const auto rect = drawer.titlebar_rect(context); + + draw_list_.rect_filled( + rect, + color_of(theme, ThemeCategory::TITLEBAR_COLLAPSED), + theme.window_corner_rounding + ); + + if (flag_.is()) + { + constexpr auto offset = extent_type{1, 1}; + + draw_list_.rect( + {rect.point + offset, rect.extent}, + color_of(theme, ThemeCategory::BORDER), + theme.window_corner_rounding + ); + draw_list_.rect( + rect, + color_of(theme, ThemeCategory::BORDER), + theme.window_corner_rounding + ); + } + } + else + { + size_ = size_full_; + + auto resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP); + + // The tooltip window always adapts to the size of the content + if (is_tooltip_window) + { + if (auto_fit_frames_ > 0) + { + size_full_ = size_of_content_ + theme.window_padding - extent_type{0, theme.item_spacing.height}; + } + } + else + { + const auto auto_fit_size = + (size_of_content_ + theme.window_auto_fit_padding).clamp( + theme.window_min_size, + display_size - theme.window_auto_fit_padding + ); + + if (flag_.is()) + { + size_full_ = auto_fit_size; + } + else if (auto_fit_frames_ > 0) + { + // Auto-fit only grows during the first few frames + if (auto_fit_only_grows_) + { + size_full_ = size_full_.combine_max(auto_fit_size); + } + else + { + size_full_ = auto_fit_size; + } + } + else if (not flag_.is()) + { + // resize grip + const auto rect = drawer.resize_grip_rect(context); + const auto id = id_of_resize(context); + + const auto [hovered, pressed, keeping] = test_mouse(context, id, rect); + if (keeping) + { + resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP_ACTIVATED); + } + else if (hovered) + { + resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP_HOVERED); + } + + if (keeping) + { + // `double left-click` on the `resize-grip` to auto-fit the current window + // allows the mouse to be offset from the current window area when resizing + if (mouse.is_double_clicked(context, MouseKey::LEFT)) + { + [[maybe_unused]] const auto old_size = size_; + + // auto-fit + size_ = auto_fit_size; + size_full_ = auto_fit_size; + } + else + { + const auto delta = mouse.position_delta; + [[maybe_unused]] const auto old_size = size_; + + // resize + size_ = theme.window_min_size.combine_max(size_full_ + delta); + size_full_ = size_; + } + } + } + } + + // At this point, we can already determine the size of the window + const auto current_titlebar_rect = [&]() noexcept -> rect_type + { + if (has_titlebar) + { + return drawer.titlebar_rect(context); + } + + return drawer.titlebar_rect(context, 0); + }(); + + // background rect + if (fill_alpha > 0) + { + draw_list_.rect_filled( + {point_, size_}, + color_of(theme, ThemeCategory::WINDOW_BACKGROUND, fill_alpha), + theme.window_corner_rounding + ); + + // border + if (flag_.is()) + { + constexpr auto offset = extent_type{1, 1}; + + draw_list_.rect( + {point_ + offset, size_}, + color_of(theme, ThemeCategory::BORDER_SHADOW), + theme.window_corner_rounding + ); + draw_list_.rect( + {point_, size_}, + color_of(theme, ThemeCategory::BORDER), + theme.window_corner_rounding + ); + } + } + + // titlebar rect + if (has_titlebar) + { + draw_list_.rect_filled( + current_titlebar_rect, + color_of(theme, ThemeCategory::TITLEBAR), + theme.window_corner_rounding, + DrawFlag::ROUND_CORNER_TOP + ); + + // border + if (flag_.is()) + { + draw_list_.line( + current_titlebar_rect.left_bottom(), + current_titlebar_rect.right_bottom(), + color_of(theme, ThemeCategory::BORDER) + ); + } + } + + // scrollbar + if ( + // no scrollbar + flag_.is() or + // window space is greater than the space required for content + size_.height > size_of_content_.height) + { + scroll_y_visible_ = false; + } + else + { + scroll_y_visible_ = true; + + const auto window_rect = rect(); + + const auto right_top = window_rect.right_top(); + const auto right_bottom = window_rect.right_bottom(); + + const auto background_y1 = right_top.y + current_titlebar_rect.height() + 1; + const auto background_y2 = right_bottom.y - 1; + const auto background_height = background_y2 - background_y1; + + // scrollbar background + const point_type background_point{right_bottom.x - theme.window_vertical_scrollbar_width, background_y1}; + const extent_type background_size{theme.window_vertical_scrollbar_width, background_height}; + const rect_type background_rect{background_point, background_size}; + draw_list_.rect_filled( + background_rect, + color_of(theme, ThemeCategory::SCROLLBAR_BACKGROUND) + ); + + // scrollbar grab + constexpr extent_type grab_offset{3, 3}; + const auto grab_point = background_point + grab_offset; + const auto grab_size = background_size - grab_offset * 2; + const rect_type grab_rect{grab_point, grab_size}; + + const auto grab_size_y_normalized = std::ranges::clamp( + size_.height / std::ranges::max(size_of_content_.height, size_.height), + .0f, + 1.f + ); + const auto grab_size_y = grab_size.height * grab_size_y_normalized; + + auto grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB); + if (grab_size_y_normalized < 1.f) + { + const auto id = id_of_scrollbar(context); + + if (const auto [hovered, pressed, keeping] = test_mouse(context, id, grab_rect); + keeping) + { + grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_ACTIVATED); + + const auto y_normalized = std::ranges::clamp( + (mouse.position_current.y - (grab_point.y + grab_size_y * .5f)) / (grab_size.height - grab_size_y) * (1.f - grab_size_y_normalized), + .0f, + 1.f + ); + + scroll_y_ = size_of_content_.height * y_normalized; + scroll_next_y_ = scroll_y_; + } + else if (hovered) + { + grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_HOVERED); + } + } + + // Normalized height of the grab + const auto y_normalized = std::ranges::clamp( + scroll_y_ / std::ranges::max(.0f, size_of_content_.height), + .0f, + 1.f + ); + const auto y1 = std::lerp(grab_point.y, grab_point.y + grab_size.height, y_normalized); + const auto y2 = std::lerp(grab_point.y, grab_point.y + grab_size.height, y_normalized + grab_size_y_normalized); + + draw_list_.rect_filled( + {grab_point.x, y1}, + {grab_point.x + grab_size.width, y2}, + grab_color + ); + } + + // resize-grip + if (not flag_.is()) + { + const auto rect = drawer.resize_grip_rect(context); + // if (const auto rounding = theme.window_corner_rounding; + // rounding == 0) // NOLINT(clang-diagnostic-float-equal) + // { + // draw_list_.triangle_filled( + // rect.right_bottom(), + // rect.left_bottom(), + // rect.right_top(), + // resize_grip_color + // ); + // } + // else + // { + // todo + draw_list_.triangle_filled( + rect.right_bottom(), + rect.left_bottom(), + rect.right_top(), + resize_grip_color + ); + // } + } + } + + // reset contents size for auto-fitting + size_of_content_ = {0, 0}; + if (auto_fit_frames_ > 0) + { + auto_fit_frames_ -= 1; + } + + // titlebar context + if (has_titlebar) + { + if (not flag_.is()) + { + const auto rect = drawer.close_button_rect(context); + const auto id = id_of_close(context); + + const auto [hovered, pressed, keeping] = test_mouse(context, id, rect); + + auto close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON); + if (hovered) + { + if (keeping) + { + close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON_ACTIVATED); + } + else + { + close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON_HOVERED); + } + } + + const auto center = rect.center(); + + // circle + draw_list_.circle_filled( + center, + std::ranges::max(2.f, rect.height() * .5f), + close_button_color, + 16 + ); + + // × + if (hovered) + { + const auto x = rect.extent.width * .5f * .667f - 1.f; + const auto y = rect.extent.height * .5f * .667f - 1.f; + + draw_list_.line( + center + extent_type{x, y}, + center + extent_type{-x, -y}, + color_of(theme, ThemeCategory::TEXT) + ); + draw_list_.line( + center + extent_type{x, -y}, + center + extent_type{-x, y}, + color_of(theme, ThemeCategory::TEXT) + ); + } + + close_button_pressed = pressed; + } + + // title text + const auto font_size = drawer.font_size(context); + const auto text_point = point_ + theme.item_frame_padding; + const auto text_size = internal::text_size(font, name_, font_size, Font::no_auto_wrap); + + const auto max_width = size_.width - theme.item_frame_padding.width * 2; + const auto max_height = size_.height; + + if (const auto do_clip = text_size.width > max_width or text_size.height > max_height; + do_clip) + { + const auto width = std::ranges::min(text_size.width, max_width); + const auto height = std::ranges::min(text_size.height, max_height); + + push_clip_rect(context, {text_point, width, height}); + draw_list_.text( + font, + font_size, + text_point, + color_of(theme, ThemeCategory::TEXT), + name_, + width + ); + pop_clip_rect(context); + } + else + { + draw_list_.text( + font, + font_size, + text_point, + color_of(theme, ThemeCategory::TEXT), + name_, + text_size.width + ); + } + } + + // init canvas + { + const auto current_titlebar_rect = [&]() noexcept -> rect_type + { + if (has_titlebar) + { + return drawer.titlebar_rect(context); + } + + return drawer.titlebar_rect(context, 0); + }(); + + canvas_.cursor_start_line = + // window start point + point_ + + // X: columns + // Y: titlebar + padding + extent_type{0, current_titlebar_rect.height() + drawer.window_padding(context).height} - + // Y: scrollbar + extent_type{0, scroll_y_}; + canvas_.cursor_current_line = canvas_.cursor_start_line; + canvas_.cursor_previous_line = canvas_.cursor_current_line; + + canvas_.height_current_line = 0; + canvas_.height_previous_line = 0; + + canvas_.item_width.clear(); + canvas_.item_width.push_back(default_item_width_); + + canvas_.text_wrap_width.clear(); + canvas_.text_wrap_width.push_back(DrawList::text_wrap_width_not_set); + } + } + else + { + // + } + + const auto window_rect = rect(); + const auto current_window_padding = drawer.window_padding(context); + const auto current_titlebar_rect = [&]() noexcept -> rect_type + { + if (has_titlebar) + { + return drawer.titlebar_rect(context); + } + + return drawer.titlebar_rect(context, 0); + }(); + + const point_type clip_rect_point + { + current_titlebar_rect.left_bottom().x + current_window_padding.width * .5f + .5f, + current_titlebar_rect.left_bottom().y + .5f + }; + const extent_type clip_rect_size + { + current_titlebar_rect.width() - current_window_padding.width - (scroll_y_visible_ ? theme.window_vertical_scrollbar_width : 0), + window_rect.height() - current_titlebar_rect.height() - 2 + }; + const rect_type clip_rect{clip_rect_point, clip_rect_size}; + + // Inner clipping rectangle (canvas area) + push_clip_rect(context, clip_rect); + + if (is_first_draw_this_frame) + { + accessed_ = false; + } + + // > Limit the current window from moving outside the program's visual area (if it is not a child window) (see code above) + // If it is a child window, it may move out of the visual area because the parent window moves, and we collapse it (so that we can skip the widgets drawn on it earlier) + if (is_child_window) + { + // todo + collapsed_ = not clip_rect.valid(); + + // We also hide the window from rendering because we've already added its border to the command list + // (we could perform the check earlier in the function, but it is simpler at this point) + if (collapsed_) + { + visible_ = false; + } + } + + if (theme.alpha <= 0) + { + visible_ = false; + } + + skip_item_ = collapsed_ or (not visible_ and auto_fit_frames_ == 0); + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); + + return close_button_pressed; + } + + auto Window::end_draw(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); + + // canvas rect + pop_clip_rect(context); + + // window rect + pop_clip_rect(context); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.empty()); + + // window_data.root = nullptr; + } + + auto Window::render(Context& context) const noexcept -> void + { + context.draw_lists.emplace_back(draw_list_); + } + + auto Window::name() const noexcept -> std::string_view + { + return name_; + } + + auto Window::flag() const noexcept -> Flag + { + return flag_; + } + + auto Window::position() const noexcept -> point_type + { + return point_; + } + + auto Window::width() const noexcept -> value_type + { + return size_.width; + } + + auto Window::height() const noexcept -> value_type + { + return size_.height; + } + + auto Window::size() const noexcept -> extent_type + { + return size_; + } + + auto Window::rect() const noexcept -> rect_type + { + return {point_, size_}; + } + + auto Window::visible() const noexcept -> bool + { + return visible_; + } + + auto Window::collapsed() const noexcept -> bool + { + return collapsed_; + } + + auto Window::root() const noexcept -> const Window& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(root_ != nullptr); + + return *root_; + } + + auto Window::hovered(const Context& context, const rect_type& rect) const noexcept -> bool + { + const auto clipped = [&]() noexcept -> rect_type + { + if (not clip_rect_stack_.empty()) + { + const auto& last = clip_rect_stack_.back(); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(last.includes(rect)); + + return rect.combine_min(last); + } + + return rect; + }(); + + return clipped.includes(context.mouse.position_current); + } + + auto Window::show() noexcept -> void + { + visible_ = true; + } + + auto Window::hide() noexcept -> void + { + visible_ = false; + } + + auto Window::id_of_move(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::MOVE@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::id_of_close(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::CLOSE@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::id_of_resize(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::RESIZE@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::id_of_scrollbar(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::SCROLLBAR@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::push_id(Context& context, const std::string_view string) noexcept -> void + { + const auto id = IdMaker{.self = *this}.make_id(context, string); + id_stack_.push_back(id); + } + + auto Window::push_id(Context& context, const void* pointer) noexcept -> void + { + const auto id = IdMaker{.self = *this}.make_id(context, pointer); + id_stack_.push_back(id); + } + + auto Window::push_id(Context& context, const widget_id_type value) noexcept -> void + { + const auto id = IdMaker{.self = *this}.make_id(context, value); + id_stack_.push_back(id); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::pop_id(Context& context) noexcept -> void + { + std::ignore = context; + + id_stack_.pop_back(); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::push_clip_rect(Context& context, const rect_type& rect, const bool clipped) noexcept -> void + { + std::ignore = context; + + const auto clip_rect = [&]() noexcept -> rect_type + { + if (clipped and not clip_rect_stack_.empty()) + { + // clip to a new rect + const auto last = clip_rect_stack_.back(); + return last.combine_min(rect); + } + + return rect; + }(); + + clip_rect_stack_.push_back(clip_rect); + draw_list_.push_clip_rect(clip_rect, false); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::pop_clip_rect(Context& context) noexcept -> void + { + clip_rect_stack_.pop_back(); + + if (clip_rect_stack_.empty()) + { + const auto size = context.io.display_size; + draw_list_.push_clip_rect({0, 0, size}, false); + } + else + { + draw_list_.push_clip_rect(clip_rect_stack_.back(), false); + } + } +} diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp new file mode 100644 index 0000000..8476edd --- /dev/null +++ b/src/gui/internal/window.hpp @@ -0,0 +1,299 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include + +namespace gal::prometheus +{ + namespace gui::internal + { + enum class WindowInternalFlag : std::uint16_t + { + NONE = 0, + + CHILD_WINDOW = 1 << 0, + CHILD_WINDOW_AUTO_FIT_X = 1 << 1, + CHILD_WINDOW_AUTO_FIT_Y = 1 << 2, + + CATEGORY_TOOLTIP = 1 << 3, + }; + } + + namespace meta::user_defined + { + template<> + struct enum_is_flag : std::true_type {}; + } + + namespace gui::internal + { + class Window final + { + public: + using value_type = extent_type::value_type; + + class [[nodiscard]] Flag final + { + WindowFlag flag_; + WindowInternalFlag internal_flag_; + + public: + constexpr explicit (false) Flag(const WindowFlag flag) noexcept + : flag_{flag}, + internal_flag_{WindowInternalFlag::NONE} {} + + constexpr explicit Flag(const WindowFlag flag, const WindowInternalFlag internal_flag) noexcept + : flag_{flag}, + internal_flag_{internal_flag} {} + + [[nodiscard]] constexpr auto flag() const noexcept -> WindowFlag + { + return flag_; + } + + [[nodiscard]] constexpr auto internal_flag() const noexcept -> WindowInternalFlag + { + return internal_flag_; + } + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + + template + struct workaround + { + T flag; + + constexpr explicit (false) workaround(const T flag) noexcept + : flag{flag} {} + }; + + template Flag> + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(flag_) & Flag.flag; + } + + template Flag> + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(internal_flag_) & Flag.flag; + } + + #else + + template + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(flag_) & Flag; + } + + template + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(internal_flag_) & Flag; + } + + #endif + }; + + static_assert(sizeof(Flag) == sizeof(std::uint32_t)); + + // <= 0 + constexpr static auto auto_size = value_type{-.999999f}; + + private: + class IdMaker; + class Drawer; + + struct canvas_type + { + point_type cursor_start_line; + point_type cursor_current_line; + point_type cursor_previous_line; + + value_type height_current_line; + value_type height_previous_line; + + std::vector item_width; + std::vector text_wrap_width; + }; + + // ================== + // CANVAS + + canvas_type canvas_; + + // ================== + // DRAW LIST + + DrawList draw_list_; + + // ================== + // WINDOW INFO + + std::string name_; + Flag flag_; + Window* root_; + + point_type point_; + // size_full / collapsed titlebar + extent_type size_; + extent_type size_full_; + // size of contents, extents reach by the drawing cursor + extent_type size_of_content_; + + value_type default_item_width_; + + value_type scroll_y_; + value_type scroll_next_y_; + bool scroll_y_visible_; + + bool visible_; + // Set when collapsing window to become only titlebar + bool collapsed_; + // visible and not collapsed + bool skip_item_; + + // Set to true when any widget access the current window + bool accessed_; + + bool auto_fit_only_grows_; + // 2 boolean, avoid padding + std::int16_t auto_fit_frames_; + + frame_count_type last_drawn_frame_; + + std::vector child_stack_; + std::vector id_stack_; + std::vector clip_rect_stack_; + + public: + // ----------------------------------- + // ctor & reset + + /** + * @brief Create a new window + * @param name window name + * @param flag window flag + * @param point window position + * @param size window size + * @param root the root of the window (parent window), if the current window is not a child window, + * this parameter must be a null pointer (while the root of the window is itself) + */ + Window( + std::string_view name, + Flag flag, + const point_type& point, + const extent_type& size, + Window* root + ) noexcept; + + /** + * @brief If a window has already been created, each "re-creation" only resets its flag (if necessary) + * @param flag window flag + */ + auto reset(Flag flag) noexcept -> void; + + + // ----------------------------------- + // DRAW BEGIN + + /** + * @brief Creating a canvas for the window + * @param context + * @param fill_alpha + * @param parent If the current window is a child window, then @c parent is its parent, otherwise this parameter must be a null pointer + * @return Whether the window is visible (not closed) + * @note Widgets can be drawn in the window if and only if its canvas has been created + */ + [[nodiscard]] auto begin_draw( + Context& context, + value_type fill_alpha, + Window* parent + ) noexcept -> bool; + + // ----------------------------------- + // WIDGETS + + // todo + + // ----------------------------------- + // DRAW END + + auto end_draw(Context& context) noexcept -> void; + + // ----------------------------------- + // RENDER + + auto render(Context& context) const noexcept -> void; + + // ----------------------------------- + // STATES + + [[nodiscard]] auto name() const noexcept -> std::string_view; + + [[nodiscard]] auto flag() const noexcept -> Flag; + + [[nodiscard]] auto position() const noexcept -> point_type; + + [[nodiscard]] auto width() const noexcept -> value_type; + + [[nodiscard]] auto height() const noexcept -> value_type; + + [[nodiscard]] auto size() const noexcept -> extent_type; + + [[nodiscard]] auto rect() const noexcept -> rect_type; + + [[nodiscard]] auto visible() const noexcept -> bool; + + [[nodiscard]] auto collapsed() const noexcept -> bool; + + [[nodiscard]] auto root() const noexcept -> const Window&; + + /** + * @brief Test if mouse cursor is hovering given rect + * @note @c rect is clipped by current clip rect setting + */ + [[nodiscard]] auto hovered(const Context& context, const rect_type& rect) const noexcept -> bool; + + // ----------------------------------- + // + + auto show() noexcept -> void; + auto hide() noexcept -> void; + + // ----------------------------------- + // ID + + [[nodiscard]] auto id_of_move(Context& context) const noexcept -> widget_id_type; + + [[nodiscard]] auto id_of_close(Context& context) const noexcept -> widget_id_type; + + [[nodiscard]] auto id_of_resize(Context& context) const noexcept -> widget_id_type; + + [[nodiscard]] auto id_of_scrollbar(Context& context) const noexcept -> widget_id_type; + + auto push_id(Context& context, std::string_view string) noexcept -> void; + + auto push_id(Context& context, const void* pointer) noexcept -> void; + + auto push_id(Context& context, widget_id_type value) noexcept -> void; + + auto pop_id(Context& context) noexcept -> void; + + // ----------------------------------- + // CLIP RECT + + auto push_clip_rect(Context& context, const rect_type& rect, bool clipped = true) noexcept -> void; + + auto pop_clip_rect(Context& context) noexcept -> void; + }; + } +} From a3d9942735ab07ef0e64bef277da048fdb516556 Mon Sep 17 00:00:00 2001 From: life4gal Date: Thu, 10 Apr 2025 15:18:49 +0800 Subject: [PATCH 02/54] =?UTF-8?q?`feat`:=20(point/extent/rect).(combine=5F?= =?UTF-8?q?max/combine=5Fmin/clamp).=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/primitive/extent.hpp | 76 +++++++++++++++++++++++++++++ src/primitive/point.hpp | 103 ++++++++++++++++++++------------------- src/primitive/rect.hpp | 32 ++++-------- 3 files changed, 138 insertions(+), 73 deletions(-) diff --git a/src/primitive/extent.hpp b/src/primitive/extent.hpp index ee0b410..bb0090c 100644 --- a/src/primitive/extent.hpp +++ b/src/primitive/extent.hpp @@ -69,6 +69,42 @@ namespace gal::prometheus { return {.width = width, .height = height, .depth = value_type{0}}; } + + template U = value_type> + [[nodiscard]] constexpr auto combine_max(const basic_extent<2, U>& other) const noexcept -> basic_extent + { + return + { + std::ranges::max(width, other.width), + std::ranges::max(height, other.height) + }; + } + + template U = value_type> + [[nodiscard]] constexpr auto combine_min(const basic_extent<2, U>& other) const noexcept -> basic_extent + { + return + { + std::ranges::min(width, other.width), + std::ranges::min(height, other.height) + }; + } + + template Low = value_type, std::convertible_to High = value_type> + [[nodiscard]] constexpr auto clamp( + const basic_extent<2, Low>& low, + const basic_extent<2, High>& high + ) const noexcept -> basic_extent + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.width < high.width); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.height < high.height); + + return + { + std::ranges::clamp(width, low.width, high.width), + std::ranges::clamp(height, low.height, high.height) + }; + } }; template @@ -124,6 +160,46 @@ namespace gal::prometheus { return {.width = width, .height = height}; } + + template U = value_type> + [[nodiscard]] constexpr auto combine_max(const basic_extent<3, U>& other) const noexcept -> basic_extent + { + return + { + std::ranges::max(width, other.width), + std::ranges::max(height, other.height), + std::ranges::max(depth, other.depth) + }; + } + + template U = value_type> + [[nodiscard]] constexpr auto combine_min(const basic_extent<3, U>& other) const noexcept -> basic_extent + { + return + { + std::ranges::min(width, other.width), + std::ranges::min(height, other.height), + std::ranges::min(depth, other.depth) + }; + } + + template Low = value_type, std::convertible_to High = value_type> + [[nodiscard]] constexpr auto clamp( + const basic_extent<3, Low>& low, + const basic_extent<3, High>& high + ) const noexcept -> basic_extent + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.width < high.width); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.height < high.height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.depth < high.depth); + + return + { + std::ranges::clamp(width, low.width, high.width), + std::ranges::clamp(height, low.height, high.height), + std::ranges::clamp(depth, low.depth, high.depth) + }; + } }; template diff --git a/src/primitive/point.hpp b/src/primitive/point.hpp index b54daa7..a92268b 100644 --- a/src/primitive/point.hpp +++ b/src/primitive/point.hpp @@ -77,40 +77,40 @@ namespace gal::prometheus return math::hypot(x - static_cast(other.x), y - static_cast(other.y)); } - template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] constexpr auto clamp( - const basic_point<2, Low>& low, - const basic_point<2, High>& high - ) noexcept -> basic_point& + template U = value_type> + [[nodiscard]] constexpr auto combine_max(const basic_point<2, U>& other) const noexcept -> basic_point { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - x = std::ranges::min(std::ranges::max(x, static_cast(low.x)), static_cast(high.x)); - y = std::ranges::min(std::ranges::max(y, static_cast(low.y)), static_cast(high.y)); - } - else + return { - x = std::ranges::clamp(x, low.x, high.x); - y = std::ranges::clamp(y, low.y, high.y); - } + std::ranges::max(x, other.x), + std::ranges::max(y, other.y) + }; + } - return *this; + template U = value_type> + [[nodiscard]] constexpr auto combine_min(const basic_point<2, U>& other) const noexcept -> basic_point + { + return + { + std::ranges::min(x, other.x), + std::ranges::min(y, other.y) + }; } template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] friend constexpr auto clamp( - const basic_point& point, + [[nodiscard]] constexpr auto clamp( const basic_point<2, Low>& low, const basic_point<2, High>& high - ) noexcept -> basic_point + ) const noexcept -> basic_point { - auto result{point}; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); - result.clamp(low, high); - return result; + return + { + std::ranges::clamp(x, low.x, high.x), + std::ranges::clamp(y, low.y, high.y) + }; } template T1 = value_type, std::convertible_to T2 = value_type> @@ -207,43 +207,44 @@ namespace gal::prometheus return math::hypot(x - static_cast(other.x), y - static_cast(other.y), z - static_cast(other.z)); } - template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] constexpr auto clamp( - const basic_point<3, Low>& low, - const basic_point<3, High>& high - ) noexcept -> basic_point& + template U = value_type> + [[nodiscard]] constexpr auto combine_max(const basic_point<3, U>& other) const noexcept -> basic_point { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.z < high.z); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - x = std::ranges::min(std::ranges::max(x, static_cast(low.x)), static_cast(high.x)); - y = std::ranges::min(std::ranges::max(y, static_cast(low.y)), static_cast(high.y)); - z = std::ranges::min(std::ranges::max(z, static_cast(low.z)), static_cast(high.z)); - } - else + return { - x = std::ranges::clamp(x, low.x, high.x); - y = std::ranges::clamp(y, low.y, high.y); - z = std::ranges::clamp(z, low.z, high.z); - } + std::ranges::max(x, other.x), + std::ranges::max(y, other.y), + std::ranges::max(z, other.z) + }; + } - return *this; + template U = value_type> + [[nodiscard]] constexpr auto combine_min(const basic_point<3, U>& other) const noexcept -> basic_point + { + return + { + std::ranges::min(x, other.x), + std::ranges::min(y, other.y), + std::ranges::min(z, other.z) + }; } template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] friend constexpr auto clamp( - const basic_point& point, + [[nodiscard]] constexpr auto clamp( const basic_point<3, Low>& low, const basic_point<3, High>& high - ) noexcept -> basic_point + ) const noexcept -> basic_point { - auto result{point}; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.z < high.z); - result.clamp(low, high); - return result; + return + { + std::ranges::clamp(x, low.x, high.x), + std::ranges::clamp(y, low.y, high.y), + std::ranges::clamp(z, low.z, high.z) + }; } template T1 = value_type, std::convertible_to T2 = value_type> diff --git a/src/primitive/rect.hpp b/src/primitive/rect.hpp index 0bd3ce7..05fca88 100644 --- a/src/primitive/rect.hpp +++ b/src/primitive/rect.hpp @@ -171,7 +171,7 @@ namespace gal::prometheus { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size().exact_greater_than(rect.size())); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(width() > rect.width() and height() > rect.height()); return rect.point.x >= point.x and @@ -197,10 +197,8 @@ namespace gal::prometheus { return { - std::ranges::min(point.x, rect.point.x), - std::ranges::min(point.y, rect.point.y), - std::ranges::max(point.x + width(), rect.point.x + rect.width()), - std::ranges::max(point.y + height(), rect.point.y + rect.height()) + point.combine_min(rect.point), + extent.combine_max(rect.extent) }; } @@ -208,10 +206,8 @@ namespace gal::prometheus { return { - std::ranges::max(point.x, rect.point.x), - std::ranges::max(point.y, rect.point.y), - std::ranges::min(point.x + width(), rect.point.x + rect.width()), - std::ranges::min(point.y + height(), rect.point.y + rect.height()) + point.combine_max(rect.point), + extent.combine_min(rect.extent) }; } }; @@ -393,7 +389,7 @@ namespace gal::prometheus { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size().exact_greater_than(rect.size())); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(width() > rect.width() and height() > rect.height() and depth() > rect.depth()); return rect.point.x >= point.x and @@ -423,12 +419,8 @@ namespace gal::prometheus { return { - std::ranges::min(point.x, rect.point.x), - std::ranges::min(point.y, rect.point.y), - std::ranges::min(point.z, rect.point.z), - std::ranges::max(point.x + width(), rect.point.x + rect.width()), - std::ranges::max(point.y + height(), rect.point.y + rect.height()), - std::ranges::max(point.z + depth(), rect.point.z + rect.depth()) + point.combine_min(rect.point), + extent.combine_max(rect.extent) }; } @@ -436,12 +428,8 @@ namespace gal::prometheus { return { - std::ranges::max(point.x, rect.point.x), - std::ranges::max(point.y, rect.point.y), - std::ranges::max(point.z, rect.point.z), - std::ranges::min(point.x + width(), rect.point.x + rect.width()), - std::ranges::min(point.y + height(), rect.point.y + rect.height()), - std::ranges::min(point.z + depth(), rect.point.z + rect.depth()) + point.combine_max(rect.point), + extent.combine_min(rect.extent) }; } }; From 057350dfa331b7852549c97e01fda8dcc4f212c9 Mon Sep 17 00:00:00 2001 From: life4gal Date: Thu, 10 Apr 2025 15:20:58 +0800 Subject: [PATCH 03/54] =?UTF-8?q?`feat`:=20Add=20a=20wrapper=20for=20`std:?= =?UTF-8?q?:unique=5Fptr`=20and=20`std::reference=5Fwrapper`,=20but=20prop?= =?UTF-8?q?agating=20const.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memory/reference_wrapper.hpp | 183 ++++++++++ src/memory/unique_ptr.hpp | 590 +++++++++++++++++++++++++++++++ 2 files changed, 773 insertions(+) create mode 100644 src/memory/reference_wrapper.hpp create mode 100644 src/memory/unique_ptr.hpp diff --git a/src/memory/reference_wrapper.hpp b/src/memory/reference_wrapper.hpp new file mode 100644 index 0000000..470b576 --- /dev/null +++ b/src/memory/reference_wrapper.hpp @@ -0,0 +1,183 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::memory +{ + namespace reference_wrapper_detail + { + template + // ReSharper disable once CppFunctionIsNotImplemented + auto ctor(std::type_identity_t) noexcept -> void; + + template + auto ctor(std::type_identity_t) noexcept -> void = delete; + + template + struct ref_wrap_has_ctor_from : std::bool_constant< + requires + { + reference_wrapper_detail::ctor(std::declval()); + } + > {}; + } + + /** + * @brief A wrapper for std::reference_wrapper, but propagating const. + */ + template + class RefWrapper + { + public: + static_assert(std::is_object_v || std::is_function_v, "reference_wrapper requires T to be an object type or a function type."); + + using type = T; + + private: + type* pointer_; + + public: + template + requires std::conjunction_v< + std::negation, RefWrapper>>, + reference_wrapper_detail::ref_wrap_has_ctor_from + > + constexpr explicit(false) RefWrapper(U&& value) noexcept(noexcept(reference_wrapper_detail::ctor(std::declval()))) + : pointer_{nullptr} + { + auto& ref = std::forward(value); + pointer_ = std::addressof(ref); + } + + // ReSharper disable once CppNonExplicitConversionOperator + constexpr explicit(false) operator type&() noexcept + { + return *pointer_; + } + + // ReSharper disable once CppNonExplicitConversionOperator + constexpr explicit (false) operator const type&() noexcept // + requires (not std::is_const_v) // avoid redefinition (RefWrapper) + { + return *pointer_; + } + + [[nodiscard]] constexpr auto get() noexcept -> type& + { + return *pointer_; + } + + [[nodiscard]] constexpr auto get() const noexcept -> const type& + { + return *pointer_; + } + + template + constexpr auto operator()(Args&&... args) // + noexcept(noexcept(std::invoke(*pointer_, std::forward(args)...))) // + -> decltype(std::invoke(*pointer_, std::forward(args)...)) // + { + return std::invoke(*pointer_, std::forward(args)...); + } + }; + + template + // ReSharper disable once CppInconsistentNaming + RefWrapper(T&) -> RefWrapper; + + template + [[nodiscard]] constexpr auto ref(T& value) noexcept -> RefWrapper + { + return RefWrapper{value}; + } + + template + [[nodiscard]] constexpr auto ref(const T&&) noexcept -> RefWrapper = delete; + + template + [[nodiscard]] constexpr auto ref(RefWrapper value) noexcept -> RefWrapper + { + return value; + } + + template + [[nodiscard]] constexpr auto ref(Ref value) noexcept -> RefWrapper // + requires std::is_same_v> + { + return RefWrapper{value.get()}; + } + + template + [[nodiscard]] constexpr auto cref(const T& value) noexcept -> RefWrapper + { + return RefWrapper{value}; + } + + template + [[nodiscard]] constexpr auto cref(const T&&) noexcept -> RefWrapper = delete; + + template + [[nodiscard]] constexpr auto cref(RefWrapper value) noexcept -> RefWrapper + { + return value; + } + + template + [[nodiscard]] constexpr auto cref(Ref value) noexcept -> RefWrapper // + requires std::is_same_v> + { + return RefWrapper{value.get()}; + } + + namespace reference_wrapper_detail + { + template + concept ref_wrapper_t = std::is_same_v>; + + // for std::basic_common_reference + template + concept ref_wrap_common_reference_exists_with = + ref_wrapper_t and + requires + { + typename std::common_reference_t; + } and + std::convertible_to>; + } +} + +namespace std +{ + // fixme: std::invoke + + template + struct unwrap_reference> // NOLINT(cert-dcl58-cpp) + { + using type = T&; + }; + + template typename RefWrapQ, template typename TypeQ> + requires ( + gal::prometheus::memory::reference_wrapper_detail::ref_wrap_common_reference_exists_with, TypeQ> and + not gal::prometheus::memory::reference_wrapper_detail::ref_wrap_common_reference_exists_with, RefWrapQ> + ) + struct basic_common_reference // NOLINT(cert-dcl58-cpp) + { + using type = std::common_reference_t>; + }; + + template typename TypeQ, template typename RefWrapQ> + requires ( + gal::prometheus::memory::reference_wrapper_detail::ref_wrap_common_reference_exists_with, TypeQ> and + not gal::prometheus::memory::reference_wrapper_detail::ref_wrap_common_reference_exists_with, RefWrapQ> + ) + struct basic_common_reference // NOLINT(cert-dcl58-cpp) + { + using type = std::common_reference_t>; + }; +} diff --git a/src/memory/unique_ptr.hpp b/src/memory/unique_ptr.hpp new file mode 100644 index 0000000..d3389a6 --- /dev/null +++ b/src/memory/unique_ptr.hpp @@ -0,0 +1,590 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +namespace gal::prometheus::memory +{ + namespace unique_ptr_detail + { + template + struct default_constructible_deleter : std::conjunction< + std::negation>, + std::is_constructible + > {}; + + template + constexpr auto default_constructible_deleter_v = default_constructible_deleter::value; + + template + concept default_constructible_deleter_t = default_constructible_deleter_v; + } + + template> + class UniquePointer; + + /** + * @brief A wrapper for std::unique_ptr, but propagating const. + */ + template + class UniquePointer final + { + template + friend class UniquePointer; + + using phantom_type = std::unique_ptr; + + public: + using pointer = typename phantom_type::pointer; + using element_type = typename phantom_type::element_type; + using deleter_type = typename phantom_type::deleter_type; + + private: + template + constexpr static auto enable_conversion = + std::conjunction_v< + std::negation>, + std::is_convertible::pointer, pointer>, + Trait + >; + + phantom_type phantom_; + + public: + // ===================================== + // UniquePointer p{}; + + constexpr UniquePointer() noexcept // + requires unique_ptr_detail::default_constructible_deleter_v + : phantom_{} {} + + // ===================================== + // UniquePointer p{nullptr}; + + constexpr explicit(false) UniquePointer(std::nullptr_t) noexcept // + requires unique_ptr_detail::default_constructible_deleter_v + : phantom_{} {} + + constexpr auto operator=(std::nullptr_t) noexcept -> UniquePointer& + { + phantom_ = nullptr; + return *this; + } + + // ===================================== + // UniquePointer p{new T{...}}; + + constexpr explicit UniquePointer(pointer ptr) noexcept // + requires unique_ptr_detail::default_constructible_deleter_v + : phantom_{ptr} {} + + constexpr UniquePointer(pointer ptr, const deleter_type& deleter) noexcept // + requires std::conjunction_v< + std::is_constructible + > + : phantom_{ptr, deleter} {} + + constexpr UniquePointer(pointer ptr, deleter_type&& deleter) noexcept // + requires std::conjunction_v< + std::negation>, + std::is_constructible + > + : phantom_{ptr, std::move(deleter)} {} + + constexpr UniquePointer(pointer, std::remove_reference_t&&) noexcept // + requires std::conjunction_v< + std::is_reference, + std::is_constructible> + > // + = delete; + + // ===================================== + // UniquePointer p1{...}; + // UniquePointer p2{p1}; + + constexpr UniquePointer(const UniquePointer&) noexcept = delete; + + constexpr auto operator=(const UniquePointer&) noexcept -> UniquePointer& = delete; + + // ===================================== + // UniquePointer p1{...}; + // UniquePointer p2{std::move(p1)}; + + constexpr UniquePointer(UniquePointer&& other) noexcept // + requires std::conjunction_v< + std::is_move_constructible + > + : phantom_{std::move(other.phantom_)} {} + + constexpr auto operator=(UniquePointer&& other) noexcept -> UniquePointer& // + requires std::conjunction_v< + std::is_move_assignable + > + { + phantom_ = std::move(other.phantom_); + return *this; + } + + template + requires enable_conversion, std::is_same, std::is_convertible>> + constexpr explicit(false) UniquePointer(UniquePointer&& other) noexcept + : phantom_{std::move(other)} {} + + template + requires enable_conversion> + constexpr auto operator=(UniquePointer&& other) noexcept -> UniquePointer& + { + phantom_ = std::move(other).phantom_; + return *this; + } + + constexpr ~UniquePointer() noexcept = default; + + constexpr auto swap(UniquePointer& other) noexcept -> void + { + phantom_.swap(other.phantom_); + } + + constexpr auto release() noexcept -> pointer + { + return phantom_.release(); + } + + constexpr auto reset(pointer ptr = nullptr) noexcept -> void + { + phantom_.reset(ptr); + } + + [[nodiscard]] constexpr auto get_deleter() noexcept -> deleter_type& + { + return phantom_.get_deleter(); + } + + [[nodiscard]] constexpr auto get_deleter() const noexcept -> const deleter_type& + { + return phantom_.get_deleter(); + } + + [[nodiscard]] constexpr auto operator*() noexcept(noexcept(*std::declval())) -> element_type& + { + return phantom_.operator*(); + } + + [[nodiscard]] constexpr auto operator*() const noexcept(noexcept(*std::declval())) -> const element_type& + { + return phantom_.operator*(); + } + + [[nodiscard]] constexpr auto operator->() noexcept -> pointer + { + return phantom_.operator->(); + } + + [[nodiscard]] constexpr auto operator->() const noexcept -> std::add_pointer_t> + { + return phantom_.operator->(); + } + + [[nodiscard]] constexpr auto get() noexcept -> pointer + { + return phantom_.get(); + } + + [[nodiscard]] constexpr auto get() const noexcept -> std::add_pointer_t> + { + return phantom_.get(); + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return static_cast(phantom_); + } + }; + + /** + * @brief A wrapper for std::unique_ptr, but propagating const. + */ + template + class UniquePointer final + { + template + friend class UniquePointer; + + using phantom_type = std::unique_ptr; + + public: + using pointer = typename phantom_type::pointer; + using element_type = typename phantom_type::element_type; + using deleter_type = typename phantom_type::deleter_type; + + private: + template> + constexpr static auto enable_ctor_reset = + std::disjunction_v< + IsNullptr, + std::is_same, + std::conjunction< + std::is_pointer, + std::is_same, + std::is_convertible(*)[], element_type(*)[]> + > + >; + + template + constexpr static auto enable_conversion = + std::conjunction_v< + std::is_array, + std::is_same, + std::is_same::pointer, typename UniquePointer::element_type*>, + std::is_convertible::element_type(*)[], element_type(*)[]>, + Trait + >; + + phantom_type phantom_; + + public: + // ===================================== + // UniquePointer p{}; + + constexpr UniquePointer() noexcept // + requires unique_ptr_detail::default_constructible_deleter_v + : phantom_{} {} + + // ===================================== + // UniquePointer p{nullptr}; + + constexpr explicit(false) UniquePointer(std::nullptr_t) noexcept // + requires unique_ptr_detail::default_constructible_deleter_v + : phantom_{} {} + + constexpr auto operator=(std::nullptr_t) noexcept -> UniquePointer& + { + phantom_ = nullptr; + return *this; + } + + // ===================================== + // UniquePointer p{new T{...}}; + + template + requires enable_ctor_reset + constexpr explicit UniquePointer(U ptr) noexcept // + requires unique_ptr_detail::default_constructible_deleter_v + : phantom_{ptr} {} + + template + requires enable_ctor_reset + constexpr UniquePointer(U ptr, const deleter_type& deleter) noexcept // + requires std::conjunction_v< + std::is_constructible + > + : phantom_{ptr, deleter} {} + + template + requires enable_ctor_reset + constexpr UniquePointer(U ptr, deleter_type&& deleter) noexcept // + requires std::conjunction_v< + std::negation>, + std::is_constructible + > + : phantom_{ptr, std::move(deleter)} {} + + template + constexpr UniquePointer(U pointer, std::remove_reference_t&&) noexcept // + requires std::conjunction_v< + std::is_reference, + std::is_constructible> + > // + = delete; + + // ===================================== + // UniquePointer p1{...}; + // UniquePointer p2{p1}; + + constexpr UniquePointer(const UniquePointer&) noexcept = delete; + + constexpr auto operator=(const UniquePointer&) noexcept -> UniquePointer& = delete; + + // ===================================== + // UniquePointer p1{...}; + // UniquePointer p2{std::move(p1)}; + + constexpr UniquePointer(UniquePointer&& other) noexcept // + requires std::conjunction_v< + std::is_move_constructible + > + : phantom_{std::move(other.phantom_)} {} + + constexpr auto operator=(UniquePointer&& other) noexcept -> UniquePointer& // + requires std::conjunction_v< + std::is_move_assignable + > + { + phantom_ = std::move(other.phantom_); + return *this; + } + + template + requires enable_conversion, std::is_same, std::is_convertible>> + constexpr explicit(false) UniquePointer(UniquePointer&& other) noexcept + : phantom_{std::move(other)} {} + + template + requires enable_conversion> + constexpr auto operator=(UniquePointer&& other) noexcept -> UniquePointer& + { + phantom_ = std::move(other).phantom_; + return *this; + } + + constexpr ~UniquePointer() noexcept = default; + + constexpr auto swap(UniquePointer& other) noexcept -> void + { + phantom_.swap(other.phantom_); + } + + constexpr auto release() noexcept -> pointer + { + return phantom_.release(); + } + + template + requires enable_ctor_reset + constexpr auto reset(U ptr) noexcept -> void + { + phantom_.reset(ptr); + } + + [[nodiscard]] constexpr auto get_deleter() noexcept -> deleter_type& + { + return phantom_.get_deleter(); + } + + [[nodiscard]] constexpr auto get_deleter() const noexcept -> const deleter_type& + { + return phantom_.get_deleter(); + } + + [[nodiscard]] constexpr auto operator[](const std::size_t index) noexcept -> element_type& + { + return phantom_[index]; + } + + [[nodiscard]] constexpr auto operator[](const std::size_t index) const noexcept -> const element_type& + { + return phantom_[index]; + } + + [[nodiscard]] constexpr auto get() noexcept -> pointer + { + return phantom_.get(); + } + + [[nodiscard]] constexpr auto get() const noexcept -> std::add_pointer_t> + { + return phantom_.get(); + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return static_cast(phantom_); + } + }; + + template + requires std::conjunction_v< + std::negation>, + std::is_constructible + > + [[nodiscard]] constexpr auto make_unique(Args&&... args) noexcept -> UniquePointer + { + // return UniquePointer{new T{std::forward(args)...}}; + return UniquePointer{new T(std::forward(args)...)}; + } + + template + requires std::conjunction_v< + std::is_array, + std::bool_constant == 0> + > + [[nodiscard]] constexpr auto make_unique(const std::size_t size) noexcept -> UniquePointer + { + using element_type = std::remove_extent_t; + // initialized + return UniquePointer{new element_type[size]{}}; + } + + template + requires std::conjunction_v< + std::bool_constant != 0> + > + [[nodiscard]] constexpr auto make_unique(Args&&...) noexcept -> UniquePointer = delete; + + template + requires std::conjunction_v< + std::negation>, + std::is_default_constructible + > + [[nodiscard]] constexpr auto make_unique_for_overwrite() noexcept -> UniquePointer + { + // unspecified value (for trivial type) + return UniquePointer{new T}; + } + + template + requires std::conjunction_v< + std::is_unbounded_array + > + [[nodiscard]] constexpr auto make_unique_for_overwrite(const std::size_t size) noexcept -> UniquePointer + { + using element_type = std::remove_extent_t; + // unspecified value (for trivial type) + return UniquePointer{new element_type[size]}; + } + + template + requires std::conjunction_v< + std::is_bounded_array + > + [[nodiscard]] constexpr auto make_unique_for_overwrite(Args&&...) noexcept = delete; + + template + requires std::conjunction_v< + std::is_swappable + > + constexpr auto swap(UniquePointer& lhs, UniquePointer& rhs) noexcept -> void + { + lhs.swap(rhs); + } + + template + [[nodiscard]] constexpr auto operator==(const UniquePointer& lhs, const UniquePointer& rhs) noexcept -> bool + { + return lhs.get() == rhs.get(); + } + + template + [[nodiscard]] constexpr auto operator<(const UniquePointer& lhs, const UniquePointer& rhs) noexcept -> bool + { + using p1 = typename UniquePointer::pointer; + using p2 = typename UniquePointer::pointer; + using common = std::common_type_t; + return std::less{}(lhs.get(), rhs.get()); + } + + template + [[nodiscard]] constexpr auto operator<=(const UniquePointer& lhs, const UniquePointer& rhs) noexcept -> bool + { + return not(rhs < lhs); + } + + template + [[nodiscard]] constexpr auto operator>(const UniquePointer& lhs, const UniquePointer& rhs) noexcept -> bool + { + return rhs < lhs; + } + + template + [[nodiscard]] constexpr auto operator>=(const UniquePointer& lhs, const UniquePointer& rhs) noexcept -> bool + { + return not(lhs < rhs); + } + + template + requires std::three_way_comparable_with< + typename UniquePointer::pointer, + typename UniquePointer::pointer + > + [[nodiscard]] constexpr auto operator<=>( + const UniquePointer& lhs, + const UniquePointer& rhs + ) noexcept -> std::compare_three_way_result_t::pointer, typename UniquePointer::pointer> + { + return lhs.get() <=> rhs.get(); + } + + template + [[nodiscard]] constexpr auto operator==(const UniquePointer& lhs, std::nullptr_t) noexcept -> bool + { + return not lhs; + } + + template + [[nodiscard]] constexpr auto operator<(const UniquePointer& lhs, std::nullptr_t) noexcept -> bool + { + using p = typename UniquePointer::pointer; + return std::less

{}(lhs.get(), nullptr); + } + + template + [[nodiscard]] constexpr auto operator<(std::nullptr_t, const UniquePointer& rhs) noexcept -> bool + { + using p = typename UniquePointer::pointer; + return std::less

{}(nullptr, rhs.get()); + } + + template + [[nodiscard]] constexpr auto operator<=(const UniquePointer& lhs, std::nullptr_t) noexcept -> bool + { + return not(nullptr < lhs); + } + + template + [[nodiscard]] constexpr auto operator<=(std::nullptr_t, const UniquePointer& rhs) noexcept -> bool + { + return not(rhs < nullptr); + } + + template + [[nodiscard]] constexpr auto operator>(const UniquePointer& lhs, std::nullptr_t) noexcept -> bool + { + return nullptr < lhs; + } + + template + constexpr auto operator>(std::nullptr_t, const UniquePointer& rhs) noexcept -> bool + { + return rhs < nullptr; + } + + template + [[nodiscard]] constexpr auto operator>=(const UniquePointer& lhs, std::nullptr_t) noexcept -> bool + { + return not(lhs < nullptr); + } + + template + [[nodiscard]] constexpr auto operator>=(std::nullptr_t, const UniquePointer& rhs) noexcept -> bool + { + return not(nullptr < rhs); + } + + template + requires std::three_way_comparable::pointer> + [[nodiscard]] constexpr auto operator<=>( + const UniquePointer& lhs, + std::nullptr_t // + ) noexcept -> std::compare_three_way_result_t::pointer> + { + return lhs.get() <=> static_cast::pointer>(nullptr); + } + + template + constexpr auto operator<<(std::basic_ostream& out, const UniquePointer& pointer) noexcept -> std::basic_ostream // + requires requires + { + out << pointer.get(); + } + { + out << pointer.get(); + return out; + } +} + +namespace std {} From 8f4def84d03ca58e1e4a1b123bd54088b25d3efa Mon Sep 17 00:00:00 2001 From: life4gal Date: Fri, 11 Apr 2025 14:05:25 +0800 Subject: [PATCH 04/54] `fix`: The grab of the scrollbar can no longer be dislodged from the scrolling area. `feat`: Add draw_text. --- src/gui/gui.hpp | 41 ++++++ src/gui/internal/context.cpp | 79 +++++++++- src/gui/internal/draw_list.hpp | 2 +- src/gui/internal/font.cpp | 2 +- src/gui/internal/font.hpp | 2 +- src/gui/internal/window.cpp | 254 ++++++++++++++++++++++++++++++--- src/gui/internal/window.hpp | 14 +- 7 files changed, 366 insertions(+), 28 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index 45674cb..7d6ba4d 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -386,6 +386,28 @@ namespace gal::prometheus WindowFlag flag = WindowFlag::NONE ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; + + auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + + //------------------------------------------------------------------ + // LAYOUT + //------------------------------------------------------------------ + + // < 0 + constexpr Theme::value_type layout_auto_size = -1; + + auto layout_same_line(const Context& context, Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; + + //------------------------------------------------------------------ + // WIDGET STATE + //------------------------------------------------------------------ + + // The available area of the current drawing unit + [[nodiscard]] auto get_content_region_max(const Context& context) noexcept -> extent_type; + // The available area of the current window + [[nodiscard]] auto get_window_content_region_min(const Context& context) noexcept -> extent_type; + // The available area of the current window + [[nodiscard]] auto get_window_content_region_max(const Context& context) noexcept -> extent_type; } namespace meta::user_defined @@ -459,4 +481,23 @@ namespace gal::prometheus::gui WindowFlag flag = WindowFlag::NONE ) noexcept -> bool; auto end_window() noexcept -> void; + + auto draw_text(std::string_view utf8_text) noexcept -> void; + + //------------------------------------------------------------------ + // LAYOUT + //------------------------------------------------------------------ + + auto layout_same_line(Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; + + //------------------------------------------------------------------ + // WIDGET STATE + //------------------------------------------------------------------ + + // The available area of the current drawing unit + [[nodiscard]] auto get_content_region_max() noexcept -> extent_type; + // The available area of the current window + [[nodiscard]] auto get_window_content_region_min() noexcept -> extent_type; + // The available area of the current window + [[nodiscard]] auto get_window_content_region_max() noexcept -> extent_type; } diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index eaae8a6..392ac56 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -231,11 +231,11 @@ namespace gal::prometheus::gui .window_background_alpha = .65f, .window_titlebar_height = 20, .window_corner_rounding = 0, - .window_min_size = {640, 480}, + .window_min_size = {64, 48}, .window_resize_grip_size = {20, 20}, .window_padding = {8, 8}, .window_auto_fit_padding = {8, 8}, - .window_vertical_scrollbar_width = 8, + .window_vertical_scrollbar_width = 10, .item_default_width_factor = .65f, .item_frame_padding = {4, 4}, .item_spacing = {10, 5}, @@ -447,6 +447,46 @@ namespace gal::prometheus::gui context.window_current_stack.pop_back(); } + auto draw_text(Context& context, const std::string_view utf8_text) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + window.draw_text(context, utf8_text); + } + + auto layout_same_line(const Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + window.same_line(context, column_width, spacing_width); + } + + auto get_content_region_max(const Context& context) noexcept -> extent_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + return window.content_region_max(context); + } + + auto get_window_content_region_min(const Context& context) noexcept -> extent_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + return window.window_content_region_min(context); + } + + auto get_window_content_region_max(const Context& context) noexcept -> extent_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + return window.window_content_region_max(context); + } + auto set_current_context(Context& context) noexcept -> void { g_context = std::addressof(context); @@ -581,6 +621,41 @@ namespace gal::prometheus::gui return end_window(*g_context); } + auto draw_text(const std::string_view utf8_text) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + draw_text(*g_context, utf8_text); + } + + auto layout_same_line(const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + layout_same_line(*g_context, column_width, spacing_width); + } + + auto get_content_region_max() noexcept -> extent_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return get_content_region_max(*g_context); + } + + auto get_window_content_region_min() noexcept -> extent_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return get_window_content_region_min(*g_context); + } + + auto get_window_content_region_max() noexcept -> extent_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + + return get_window_content_region_max(*g_context); + } + namespace internal { auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag diff --git a/src/gui/internal/draw_list.hpp b/src/gui/internal/draw_list.hpp index 353942a..1aa1c77 100644 --- a/src/gui/internal/draw_list.hpp +++ b/src/gui/internal/draw_list.hpp @@ -121,7 +121,7 @@ namespace gal::prometheus using command_list_type = DrawData::command_list_type; // not set ==> Font::no_auto_wrap - constexpr static float text_wrap_width_not_set = -0.f; + constexpr static float text_wrap_width_not_set = -1.f; private: class Drawer; diff --git a/src/gui/internal/font.cpp b/src/gui/internal/font.cpp index 0c05692..9348c89 100644 --- a/src/gui/internal/font.cpp +++ b/src/gui/internal/font.cpp @@ -406,7 +406,7 @@ namespace gal::prometheus::gui static_assert(std::is_same_v); const auto line_height = font_size; - const auto scale = line_height / static_cast(font.pixel_height) * font.scale; + const auto scale = line_height / (static_cast(font.pixel_height) * font.scale); const auto& glyphs = font.glyphs; const auto& fallback_glyph = font.fallback_glyph; diff --git a/src/gui/internal/font.hpp b/src/gui/internal/font.hpp index 3ff6760..910eb5d 100644 --- a/src/gui/internal/font.hpp +++ b/src/gui/internal/font.hpp @@ -44,7 +44,7 @@ namespace gal::prometheus::gui::internal constexpr static texture_id_type invalid_texture_id = 0; // Line breaks only when a line break ('\n') is encountered (this means that text beyond the content area will be clipped) - constexpr static float no_auto_wrap = 99999999.f; + constexpr static value_type no_auto_wrap = 99999999.f; std::string font_path; std::uint32_t pixel_height; diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index c1a6d96..97baac1 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -218,6 +218,37 @@ namespace gal::prometheus::gui::internal const point_type p{window.point_.x + window.size_.width - s.width - 3, window.point_.y + 2}; return {p, s}; } + + // ----------------------------------- + // CANVAS + + auto adjust_item_size(const Context& context, const extent_type& size) noexcept -> void + { + const auto& theme = current_theme(context); + + auto& window = self.get(); + auto& canvas = window.canvas_; + + const auto line_height = std::ranges::max(canvas.height_current_line, size.height); + + // Always align ourselves on pixel boundaries + canvas.cursor_previous_line = + // previous point + canvas.cursor_current_line + + // X: item width + // Y: 0 + extent_type{size.width, 0}; + canvas.cursor_current_line = + // todo X: start point X + columns offset + // Y: previous Y + item height + item spacing + {window.point_.x + 8, canvas.cursor_current_line.y + line_height + theme.item_spacing.height}; + // window.point_ + extent_type{8, canvas.cursor_current_line.y + line_height + theme.item_spacing.height}; + + canvas.height_previous_line = line_height; + canvas.height_current_line = 0; + + window.size_of_content_ = window.size_of_content_.combine_max(extent_type{canvas.cursor_previous_line.x, canvas.cursor_current_line.y + window.scroll_y_} - window.point_); + } }; Window::Window( @@ -685,34 +716,34 @@ namespace gal::prometheus::gui::internal color_of(theme, ThemeCategory::SCROLLBAR_BACKGROUND) ); - // scrollbar grab - constexpr extent_type grab_offset{3, 3}; - const auto grab_point = background_point + grab_offset; - const auto grab_size = background_size - grab_offset * 2; - const rect_type grab_rect{grab_point, grab_size}; + // scrollbar area + constexpr extent_type scrollbar_area_offset{3, 3}; + const auto scrollbar_area_point = background_point + scrollbar_area_offset; + const auto scrollbar_area_size = background_size - scrollbar_area_offset * 2; + const rect_type scrollbar_area_rect{scrollbar_area_point, scrollbar_area_size}; const auto grab_size_y_normalized = std::ranges::clamp( size_.height / std::ranges::max(size_of_content_.height, size_.height), .0f, 1.f ); - const auto grab_size_y = grab_size.height * grab_size_y_normalized; + const auto grab_size_y = scrollbar_area_size.height * grab_size_y_normalized; auto grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB); if (grab_size_y_normalized < 1.f) { const auto id = id_of_scrollbar(context); - if (const auto [hovered, pressed, keeping] = test_mouse(context, id, grab_rect); + if (const auto [hovered, pressed, keeping] = test_mouse(context, id, scrollbar_area_rect); keeping) { grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_ACTIVATED); const auto y_normalized = std::ranges::clamp( - (mouse.position_current.y - (grab_point.y + grab_size_y * .5f)) / (grab_size.height - grab_size_y) * (1.f - grab_size_y_normalized), - .0f, - 1.f - ); + (mouse.position_current.y - (scrollbar_area_point.y + grab_size_y * .5f)) / (scrollbar_area_size.height - grab_size_y), + .0f, + 1.f + ) * (1.f - grab_size_y_normalized); scroll_y_ = size_of_content_.height * y_normalized; scroll_next_y_ = scroll_y_; @@ -725,18 +756,16 @@ namespace gal::prometheus::gui::internal // Normalized height of the grab const auto y_normalized = std::ranges::clamp( - scroll_y_ / std::ranges::max(.0f, size_of_content_.height), + scroll_y_ / std::ranges::max(.00001f, size_of_content_.height), .0f, 1.f ); - const auto y1 = std::lerp(grab_point.y, grab_point.y + grab_size.height, y_normalized); - const auto y2 = std::lerp(grab_point.y, grab_point.y + grab_size.height, y_normalized + grab_size_y_normalized); - draw_list_.rect_filled( - {grab_point.x, y1}, - {grab_point.x + grab_size.width, y2}, - grab_color - ); + const auto grab_point = scrollbar_area_point + extent_type{0, scrollbar_area_size.height * y_normalized}; + const auto grab_size = extent_type{scrollbar_area_size.width, scrollbar_area_size.height * grab_size_y_normalized}; + const rect_type grab_rect{grab_point, grab_size}; + + draw_list_.rect_filled(grab_rect, grab_color); } // resize-grip @@ -841,6 +870,9 @@ namespace gal::prometheus::gui::internal const auto width = std::ranges::min(text_size.width, max_width); const auto height = std::ranges::min(text_size.height, max_height); + // If the titlebar is not large enough to accommodate the title content, + // simply discard the content that exceeds the space, + // rather than displaying the content on a new line push_clip_rect(context, {text_point, width, height}); draw_list_.text( font, @@ -880,11 +912,11 @@ namespace gal::prometheus::gui::internal canvas_.cursor_start_line = // window start point point_ + - // X: columns + // todo X: columns offset // Y: titlebar + padding - extent_type{0, current_titlebar_rect.height() + drawer.window_padding(context).height} - + extent_type{8, current_titlebar_rect.height() + drawer.window_padding(context).height} + // Y: scrollbar - extent_type{0, scroll_y_}; + extent_type{0, -scroll_y_}; canvas_.cursor_current_line = canvas_.cursor_start_line; canvas_.cursor_previous_line = canvas_.cursor_current_line; @@ -895,6 +927,7 @@ namespace gal::prometheus::gui::internal canvas_.item_width.push_back(default_item_width_); canvas_.text_wrap_width.clear(); + static_assert(DrawList::text_wrap_width_not_set < 0); canvas_.text_wrap_width.push_back(DrawList::text_wrap_width_not_set); } } @@ -963,6 +996,141 @@ namespace gal::prometheus::gui::internal return close_button_pressed; } + auto Window::draw_text(Context& context, const std::string_view utf8_text) noexcept -> void + { + if (skip_item_) + { + return; + } + accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = const_cast(*this)}; + + const auto font_size = drawer.font_size(context); + + static_assert(DrawList::text_wrap_width_not_set < 0); + const auto wrap_width = canvas_.text_wrap_width.back(); + const auto wrap_enabled = wrap_width >= .0f; + + if (constexpr std::size_t long_text_threshold = 1500; + utf8_text.size() > long_text_threshold and not wrap_enabled) + { + const auto clip_rect = clip_rect_stack_.back(); + const auto clip_rect_left_top = clip_rect.left_top(); + const auto clip_rect_left_bottom = clip_rect.left_bottom(); + + extent_type text_size{0, 0}; + + // Only text that is in the visible area is rendered + if (const auto visible_area_height = clip_rect_left_bottom.y - canvas_.cursor_current_line.y; + visible_area_height > 0) + { + const auto invisible_area_height = clip_rect_left_top.y - canvas_.cursor_current_line.y; + + const auto line_height = drawer.font_size(context); + const auto invisible_lines = static_cast(invisible_area_height / line_height - 1); + const auto visible_lines = static_cast(visible_area_height / line_height - 1); + + const auto has_invisible_lines = invisible_lines > 0; + + auto view = utf8_text | std::views::split('\n'); + auto visible_view = view | std::views::drop(has_invisible_lines ? invisible_lines : 0) | std::views::take(visible_lines); + + auto text_point = canvas_.cursor_current_line; + + if (has_invisible_lines) + { + text_point.y += static_cast(invisible_lines) * line_height; + } + + for (const auto sub: visible_view) + { + const std::string_view sub_string{sub}; + const auto this_line_text_size = internal::text_size( + font, + sub_string, + font_size, + Font::no_auto_wrap + ); + + // draw one line + draw_list_.text( + font, + font_size, + text_point, + color_of(theme, ThemeCategory::TEXT), + sub_string, + Font::no_auto_wrap + ); + + text_point.y += line_height; + text_size.width = std::ranges::max(text_size.width, this_line_text_size.width); + } + + text_size.height = static_cast(std::ranges::distance(view)) * line_height; + } + + drawer.adjust_item_size(context, text_size); + + // fixme: hovering text? + } + else + { + const auto this_wrap_width = wrap_enabled ? wrap_width : Font::no_auto_wrap; + + const auto text_point = canvas_.cursor_current_line; + const auto text_size = internal::text_size( + font, + utf8_text, + font_size, + this_wrap_width + ); + drawer.adjust_item_size(context, text_size); + + // fixme: hovering text? + + draw_list_.text( + font, + font_size, + text_point, + color_of(theme, ThemeCategory::TEXT), + utf8_text, + this_wrap_width + ); + } + } + + auto Window::same_line(const Context& context, const value_type column_width, value_type spacing_width) noexcept -> void + { + if (collapsed_) + { + return; + } + + const auto& theme = current_theme(context); + + canvas_.height_current_line = canvas_.height_previous_line; + canvas_.cursor_current_line = canvas_.cursor_previous_line; + + if (column_width < 0) + { + if (spacing_width < 0) + { + spacing_width = theme.item_spacing.width; + } + + canvas_.cursor_current_line.x += spacing_width; + } + else + { + spacing_width = std::ranges::max(spacing_width, static_cast(0)); + + canvas_.cursor_current_line.x = column_width + spacing_width; + } + } + auto Window::end_draw(Context& context) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); @@ -1035,6 +1203,48 @@ namespace gal::prometheus::gui::internal return *root_; } + auto Window::content_region_max(const Context& context) const noexcept -> extent_type + { + const auto& theme = current_theme(context); + const Drawer drawer{.self = const_cast(*this)}; + + auto size = size_ - drawer.window_padding(context); + + // todo + if (scroll_y_visible_) + { + size.width -= theme.window_vertical_scrollbar_width; + } + + return size; + } + + auto Window::window_content_region_min(const Context& context) const noexcept -> extent_type + { + const Drawer drawer{.self = const_cast(*this)}; + + // titlebar + padding + const auto titlebar_height = drawer.titlebar_height(context); + const auto padding = drawer.window_padding(context); + + return extent_type{0, titlebar_height} + padding; + } + + auto Window::window_content_region_max(const Context& context) const noexcept -> extent_type + { + const auto& theme = current_theme(context); + const Drawer drawer{.self = const_cast(*this)}; + + auto size = size_ - drawer.window_padding(context); + + if (scroll_y_visible_) + { + size.width -= theme.window_vertical_scrollbar_width; + } + + return size; + } + auto Window::hovered(const Context& context, const rect_type& rect) const noexcept -> bool { const auto clipped = [&]() noexcept -> rect_type diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index 8476edd..7ea3a39 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -122,6 +122,9 @@ namespace gal::prometheus value_type height_previous_line; std::vector item_width; + // <0(DrawList::text_wrap_width_not_set): disable + // =0: window.content_region_max().width + // >0: width std::vector text_wrap_width; }; @@ -222,7 +225,12 @@ namespace gal::prometheus // ----------------------------------- // WIDGETS - // todo + auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + + // ----------------------------------- + // LAYOUT + + auto same_line(const Context& context, value_type column_width = layout_auto_size, value_type spacing_width = layout_auto_size) noexcept -> void; // ----------------------------------- // DRAW END @@ -257,6 +265,10 @@ namespace gal::prometheus [[nodiscard]] auto root() const noexcept -> const Window&; + [[nodiscard]] auto content_region_max(const Context& context) const noexcept -> extent_type; + [[nodiscard]] auto window_content_region_min(const Context& context) const noexcept -> extent_type; + [[nodiscard]] auto window_content_region_max(const Context& context) const noexcept -> extent_type; + /** * @brief Test if mouse cursor is hovering given rect * @note @c rect is clipped by current clip rect setting From fa0c250b20c38031d0e0d6245ecd5d824373d8ed Mon Sep 17 00:00:00 2001 From: life4gal Date: Fri, 11 Apr 2025 16:10:14 +0800 Subject: [PATCH 05/54] `fix`: The window no longer unable to close. `feat`: Allows to scroll the contents of the window using the mouse wheel. --- src/gui/gui.hpp | 2 +- src/gui/internal/context.cpp | 63 ++++++++- src/gui/internal/context.hpp | 239 ++++++++++++++++++----------------- src/gui/internal/window.cpp | 36 ++++-- src/gui/internal/window.hpp | 4 + 5 files changed, 212 insertions(+), 132 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index 7d6ba4d..b084225 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -249,7 +249,7 @@ namespace gal::prometheus public: using value_type = extent_type::value_type; - // Update every frame + // Update once (it can also be updated every frame basis if desired) // Current window (viewport) size extent_type display_size{0, 0}; // Update every frame diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 392ac56..55ef061 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -325,9 +325,23 @@ namespace gal::prometheus::gui context.window_hovered = find_hovered_window(context, mouse_position, false); context.window_hovered_root = find_hovered_window(context, mouse_position, true); + // Mark all windows as not visible + std::ranges::for_each( + context.window_list, + [](auto* window) noexcept -> void + { + window->hide(); + } + ); + // No window should be open at the beginning of the frame // But in order to allow the user to call `new_frame` multiple times without calling `render`, we are doing an explicit clear context.window_current_stack.clear(); + + if (context.window_hovered != nullptr) + { + context.window_hovered->handle_inputs(context); + } } } @@ -350,6 +364,32 @@ namespace gal::prometheus::gui { // Sort the window list so that all child windows are after their parent // We cannot do that on `focus` because children may not exist yet + + std::vector sorted_windows{}; + sorted_windows.reserve(context.window_list.size()); + + std::ranges::for_each( + context.window_list, + [&](auto* window) noexcept -> void + { + // todo: child window + if (window->flag().template is() and window->visible()) + { + return; + } + + sorted_windows.push_back(window); + } + ); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(sorted_windows.size() == context.window_list.size()); + + context.window_list.swap(sorted_windows); + + // clear all data for new frame + context.io.delta_time = -1; + // context.io.mouse_position = {0, 0}; + context.io.mouse_wheel = 0; + // context.io.mouse_button_state.state.fill(false); } context.draw_lists.clear(); @@ -837,11 +877,12 @@ namespace gal::prometheus::gui return color_of(theme, category, factor); } - auto test_mouse(Context& context, const widget_id_type id, const rect_type& area, const bool repeat) noexcept -> mouse_state_type + auto test_mouse(Context& context, const widget_id_type id, const rect_type& area, const bool repeat) noexcept -> std::underlying_type_t { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); const auto& window = *context.window_current_stack.back(); + auto state = std::to_underlying(MouseState::NONE); const auto hovered = // window @@ -851,12 +892,16 @@ namespace gal::prometheus::gui // mouse window.hovered(context, area); - mouse_state_type state{.hovered = hovered, .pressed = false, .keeping = false}; if (hovered) { + state |= MouseState::HOVERED; + + // hovering widget context.widget_hovered = id; + if (context.mouse.is_clicked(context, MouseKey::LEFT, false)) { + // select widget context.widget_activated = id; } else if ( @@ -865,7 +910,7 @@ namespace gal::prometheus::gui context.mouse.is_clicked(context, MouseKey::LEFT, true) ) { - state.pressed = true; + state |= MouseState::PRESSED; } } @@ -873,15 +918,23 @@ namespace gal::prometheus::gui { if (context.mouse.is_down(context, MouseKey::LEFT)) { - state.keeping = true; + // select current widget, keep the left mouse button pressed + state |= MouseState::KEEPING; } else { if (hovered) { - state.pressed = true; + // select current widget, release the left mouse button on the widget + state |= MouseState::PRESSED; + } + else + { + // select current widget, did not release the left mouse button on the widget + // do nothing } + // the widget is no longer selected context.widget_activated = invalid_widget_id; } } diff --git a/src/gui/internal/context.hpp b/src/gui/internal/context.hpp index e9e6706..39b06aa 100644 --- a/src/gui/internal/context.hpp +++ b/src/gui/internal/context.hpp @@ -12,163 +12,174 @@ #include #include -namespace gal::prometheus::gui +namespace gal::prometheus { - namespace internal + namespace gui { - class Font; - class DrawList; - class Window; - } - - class Context - { - public: - using value_type = extent_type::value_type; + namespace internal + { + class Font; + class DrawList; + class Window; + } - struct theme_color_mod + class Context { - ThemeCategory category; - Theme::color_type old_color; - }; + public: + using value_type = extent_type::value_type; - using window_type = internal::Window; - using widget_id_type = internal::widget_id_type; + struct theme_color_mod + { + ThemeCategory category; + Theme::color_type old_color; + }; - // < 0 - constexpr static value_type window_fill_alpha_not_set{-.99999f}; + using window_type = internal::Window; + using widget_id_type = internal::widget_id_type; - bool initialized; + // < 0 + constexpr static value_type window_fill_alpha_not_set{-.99999f}; - // ---------------------------------------------------------------------- - // DrawListFlag + DrawListSharedData + Font + Theme + bool initialized; - constexpr static std::size_t draw_list_flag_stack_size = 8; - constexpr static std::size_t draw_list_shared_data_stack_size = 8; - constexpr static std::size_t font_stack_size = 8; + // ---------------------------------------------------------------------- + // DrawListFlag + DrawListSharedData + Font + Theme - using stack_pointer_type = std::uint8_t; - constexpr static auto stack_pointer_default = std::numeric_limits::max(); + constexpr static std::size_t draw_list_flag_stack_size = 8; + constexpr static std::size_t draw_list_shared_data_stack_size = 8; + constexpr static std::size_t font_stack_size = 8; - std::array draw_list_flag_stack; - std::array draw_list_shared_data_stack; - std::array, font_stack_size> font_stack; + using stack_pointer_type = std::uint8_t; + constexpr static auto stack_pointer_default = std::numeric_limits::max(); - stack_pointer_type draw_list_flag_current; - stack_pointer_type draw_list_shared_data_current; - stack_pointer_type font_current; + std::array draw_list_flag_stack; + std::array draw_list_shared_data_stack; + std::array, font_stack_size> font_stack; - Theme theme; - std::vector theme_mod_stack; + stack_pointer_type draw_list_flag_current; + stack_pointer_type draw_list_shared_data_current; + stack_pointer_type font_current; - // ---------------------------------------------------------------------- - // IO + Theme theme; + std::vector theme_mod_stack; - IO io; - // time elapsed since program launch, in seconds - time_type time_total; - std::uint32_t frame_count; - std::uint32_t frame_count_rendered; + // ---------------------------------------------------------------------- + // IO - internal::Mouse mouse; - // todo: keyboard + IO io; + // time elapsed since program launch, in seconds + time_type time_total; + std::uint32_t frame_count; + std::uint32_t frame_count_rendered; - // ---------------------------------------------------------------------- - // WINDOW + internal::Mouse mouse; + // todo: keyboard - point_type window_default_spawn_position; + // ---------------------------------------------------------------------- + // WINDOW - // all created windows - std::vector> window_hive; + point_type window_default_spawn_position; - // all created windows, but sorted (child windows are next to their parents, the most recently used window is always moved to the tail) - std::vector window_list; - std::vector window_current_stack; + // all created windows + std::vector> window_hive; - // catch mouse - window_type* window_hovered; - // catch mouse (for focus/move only) - window_type* window_hovered_root; - // catch keyboard - window_type* window_focused; + // all created windows, but sorted (child windows are next to their parents, the most recently used window is always moved to the tail) + std::vector window_list; + std::vector window_current_stack; - // ---------------------------------------------------------------------- - // WIDGET + // catch mouse + window_type* window_hovered; + // catch mouse (for focus/move only) + window_type* window_hovered_root; + // catch keyboard + window_type* window_focused; - widget_id_type widget_hovered; - widget_id_type widget_activated; - widget_id_type widget_activated_previous_frame; + // ---------------------------------------------------------------------- + // WIDGET - bool widget_activated_still_alive; + widget_id_type widget_hovered; + widget_id_type widget_activated; + widget_id_type widget_activated_previous_frame; - // ---------------------------------------------------------------------- - // DRAW LIST + bool widget_activated_still_alive; - std::vector> draw_lists; - }; + // ---------------------------------------------------------------------- + // DRAW LIST - namespace internal - { - // ---------------------------------------------------------------------- - // DrawListFlag + std::vector> draw_lists; + }; - auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag; - auto push_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; - auto pop_draw_list_flag(Context& context) noexcept -> void; + namespace internal + { + // ---------------------------------------------------------------------- + // DrawListFlag - // ---------------------------------------------------------------------- - // DrawListSharedData + auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag; + auto push_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; + auto pop_draw_list_flag(Context& context) noexcept -> void; - auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData&; - auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void; - auto pop_draw_list_shared_data(Context& context) noexcept -> void; + // ---------------------------------------------------------------------- + // DrawListSharedData - // ---------------------------------------------------------------------- - // Font + auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData&; + auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void; + auto pop_draw_list_shared_data(Context& context) noexcept -> void; - auto current_font(const Context& context) noexcept -> const Font&; - auto push_font(Context& context, memory::UniquePointer font) noexcept -> void; - auto pop_font(Context& context) noexcept -> void; + // ---------------------------------------------------------------------- + // Font - // ---------------------------------------------------------------------- - // Theme + auto current_font(const Context& context) noexcept -> const Font&; + auto push_font(Context& context, memory::UniquePointer font) noexcept -> void; + auto pop_font(Context& context) noexcept -> void; - auto current_theme(const Context& context) noexcept -> const Theme&; - auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; - auto pop_theme(Context& context) noexcept -> void; + // ---------------------------------------------------------------------- + // Theme - [[nodiscard]] auto color_of(const Theme& theme, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; - [[nodiscard]] auto color_of(const Context& context, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; + auto current_theme(const Context& context) noexcept -> const Theme&; + auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme(Context& context) noexcept -> void; - // ---------------------------------------------------------------------- - // IO + [[nodiscard]] auto color_of(const Theme& theme, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; + [[nodiscard]] auto color_of(const Context& context, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; - struct mouse_state_type - { - bool hovered; - bool pressed; - bool keeping; - }; + // ---------------------------------------------------------------------- + // IO + + enum class MouseState : std::uint8_t + { + NONE = 0, + + HOVERED = 1 << 0, + PRESSED = 1 << 1, + KEEPING = 1 << 2, + }; - [[nodiscard]] auto test_mouse(Context& context, widget_id_type id, const rect_type& area, bool repeat = false) noexcept -> mouse_state_type; + [[nodiscard]] auto test_mouse(Context& context, widget_id_type id, const rect_type& area, bool repeat = false) noexcept -> std::underlying_type_t; - // ---------------------------------------------------------------------- - // WINDOW + // ---------------------------------------------------------------------- + // WINDOW - auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool; + auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool; - auto focus_window(Context& context, Window& window) noexcept -> void; + auto focus_window(Context& context, Window& window) noexcept -> void; - // ---------------------------------------------------------------------- - // WIDGET + // ---------------------------------------------------------------------- + // WIDGET - auto is_widget_hovered(const Context& context, widget_id_type id) noexcept -> bool; - auto is_widget_activated(const Context& context, widget_id_type id) noexcept -> bool; + auto is_widget_hovered(const Context& context, widget_id_type id) noexcept -> bool; + auto is_widget_activated(const Context& context, widget_id_type id) noexcept -> bool; - /** - * @return @c is_widget_activated - */ - auto mark_widget_alive(Context& context, widget_id_type id) noexcept -> bool; - auto mark_widget_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + /** + * @return @c is_widget_activated + */ + auto mark_widget_alive(Context& context, widget_id_type id) noexcept -> bool; + auto mark_widget_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + } + } + + namespace meta::user_defined + { + template<> + struct enum_is_flag : std::true_type {}; } } diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 97baac1..c905ed3 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -312,6 +312,17 @@ namespace gal::prometheus::gui::internal flag_ = flag; } + auto Window::handle_inputs(const Context& context) noexcept -> void + { + // scroll + if (not flag_.is()) + { + // todo + constexpr auto scroll_weight = static_cast(5); + scroll_next_y_ -= context.mouse.wheel * Drawer{.self = *this}.font_size(context) * scroll_weight; + } + } + auto Window::begin_draw( Context& context, value_type fill_alpha, @@ -591,17 +602,17 @@ namespace gal::prometheus::gui::internal const auto rect = drawer.resize_grip_rect(context); const auto id = id_of_resize(context); - const auto [hovered, pressed, keeping] = test_mouse(context, id, rect); - if (keeping) + const auto state = test_mouse(context, id, rect); + if (state & MouseState::KEEPING) { resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP_ACTIVATED); } - else if (hovered) + else if (state & MouseState::HOVERED) { resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP_HOVERED); } - if (keeping) + if (state & MouseState::KEEPING) { // `double left-click` on the `resize-grip` to auto-fit the current window // allows the mouse to be offset from the current window area when resizing @@ -734,8 +745,8 @@ namespace gal::prometheus::gui::internal { const auto id = id_of_scrollbar(context); - if (const auto [hovered, pressed, keeping] = test_mouse(context, id, scrollbar_area_rect); - keeping) + if (const auto state = test_mouse(context, id, scrollbar_area_rect); + state & MouseState::KEEPING) { grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_ACTIVATED); @@ -748,7 +759,7 @@ namespace gal::prometheus::gui::internal scroll_y_ = size_of_content_.height * y_normalized; scroll_next_y_ = scroll_y_; } - else if (hovered) + else if (state & MouseState::HOVERED) { grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_HOVERED); } @@ -810,12 +821,12 @@ namespace gal::prometheus::gui::internal const auto rect = drawer.close_button_rect(context); const auto id = id_of_close(context); - const auto [hovered, pressed, keeping] = test_mouse(context, id, rect); + const auto state = test_mouse(context, id, rect); auto close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON); - if (hovered) + if (state & MouseState::HOVERED) { - if (keeping) + if (state & MouseState::KEEPING) { close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON_ACTIVATED); } @@ -836,7 +847,7 @@ namespace gal::prometheus::gui::internal ); // × - if (hovered) + if (state & MouseState::HOVERED) { const auto x = rect.extent.width * .5f * .667f - 1.f; const auto y = rect.extent.height * .5f * .667f - 1.f; @@ -853,7 +864,7 @@ namespace gal::prometheus::gui::internal ); } - close_button_pressed = pressed; + close_button_pressed = state & MouseState::PRESSED; } // title text @@ -1271,6 +1282,7 @@ namespace gal::prometheus::gui::internal auto Window::hide() noexcept -> void { visible_ = false; + accessed_ = false; } auto Window::id_of_move(Context& context) const noexcept -> widget_id_type diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index 7ea3a39..795664e 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -204,6 +204,10 @@ namespace gal::prometheus */ auto reset(Flag flag) noexcept -> void; + /** + * @brief Handles all input devices (mouse and keyboard) at the beginning of each frame (if necessary) + */ + auto handle_inputs(const Context& context) noexcept -> void; // ----------------------------------- // DRAW BEGIN From ecb7a56691cfe3b457845c5bf222a183300adf01 Mon Sep 17 00:00:00 2001 From: life4gal Date: Fri, 11 Apr 2025 16:21:50 +0800 Subject: [PATCH 06/54] `test`: Rename test.draw => test.gui. --- unit_test/CMakeLists.txt | 4 +- unit_test/src/draw/vulkan/main.cpp | 2222 ----------------- unit_test/src/{draw => gui}/CMakeLists.txt | 0 .../common/glfw_callback_handler.cpp | 207 +- .../src/{draw => gui}/common/print_time.hpp | 0 .../{draw/dx12 => gui/dx11}/CMakeLists.txt | 2 +- unit_test/src/{draw => gui}/dx11/backend.cpp | 303 +-- unit_test/src/{draw => gui}/dx11/main.cpp | 2 +- .../{draw/dx11 => gui/dx12}/CMakeLists.txt | 2 +- unit_test/src/{draw => gui}/dx12/backend.cpp | 251 +- unit_test/src/{draw => gui}/dx12/main.cpp | 2 +- unit_test/src/{draw => gui}/win/def.hpp | 8 +- 12 files changed, 393 insertions(+), 2610 deletions(-) delete mode 100644 unit_test/src/draw/vulkan/main.cpp rename unit_test/src/{draw => gui}/CMakeLists.txt (100%) rename unit_test/src/{draw => gui}/common/glfw_callback_handler.cpp (55%) rename unit_test/src/{draw => gui}/common/print_time.hpp (100%) rename unit_test/src/{draw/dx12 => gui/dx11}/CMakeLists.txt (96%) rename unit_test/src/{draw => gui}/dx11/backend.cpp (64%) rename unit_test/src/{draw => gui}/dx11/main.cpp (99%) rename unit_test/src/{draw/dx11 => gui/dx12}/CMakeLists.txt (96%) rename unit_test/src/{draw => gui}/dx12/backend.cpp (77%) rename unit_test/src/{draw => gui}/dx12/main.cpp (99%) rename unit_test/src/{draw => gui}/win/def.hpp (73%) diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt index df27a1e..7b2e3f4 100644 --- a/unit_test/CMakeLists.txt +++ b/unit_test/CMakeLists.txt @@ -76,5 +76,5 @@ add_test( # assets path set(ASSETS_PATH_PIC ${CMAKE_SOURCE_DIR}/assets/pic.jpg) -# DRAW -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/draw) +# GUI +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/gui) diff --git a/unit_test/src/draw/vulkan/main.cpp b/unit_test/src/draw/vulkan/main.cpp deleted file mode 100644 index 87ef6ed..0000000 --- a/unit_test/src/draw/vulkan/main.cpp +++ /dev/null @@ -1,2222 +0,0 @@ -#if GAL_PROMETHEUS_USE_MODULE -import gal.prometheus; -#else -#include -#endif - -#include -#include -#include -#include - -#include "font.hpp" - -#include -#include - -namespace -{ - using namespace gal::prometheus; - - using point_type = primitive::basic_point; - using rect_type = primitive::basic_rect; - using vertex_type = primitive::basic_vertex; - using vertex_index_type = std::uint16_t; - - using vertex_list_type = primitive::basic_vertex_list; - // todo - using vertex_index_list_type = std::vector; - - struct draw_list_type - { - vertex_list_type vertex_list; - vertex_index_list_type index_list; - }; - - struct draw_data_type - { - rect_type display_rect; - - std::vector draw_lists; - - [[nodiscard]] constexpr auto total_vertex_size() const noexcept -> std::size_t - { - return std::ranges::fold_left( - draw_lists, - static_cast(0), - [](const auto s, const auto& c) noexcept -> std::size_t { return s + c.vertex_list.size(); } - ); - } - - [[nodiscard]] constexpr auto total_index_size() const noexcept -> std::size_t - { - return std::ranges::fold_left( - draw_lists, - static_cast(0), - [](const auto s, const auto& c) noexcept -> std::size_t { return s + c.index_list.size(); } - ); - } - }; - - draw_data_type g_draw_data; - - namespace detail - { - struct frame_type - { - VkCommandPool command_pool; - VkCommandBuffer command_buffer; - VkFence fence; - VkImage back_buffer; - VkImageView back_buffer_view; - VkFramebuffer frame_buffer; - }; - - struct frame_semaphore_type - { - VkSemaphore image_acquired_semaphore; - VkSemaphore render_complete_semaphore; - }; - - auto destroy_frame(const VkDevice device, frame_type& frame, const VkAllocationCallbacks* allocation_callbacks) -> void - { - vkDestroyFence(device, frame.fence, allocation_callbacks); - vkFreeCommandBuffers(device, frame.command_pool, 1, &frame.command_buffer); - vkDestroyCommandPool(device, frame.command_pool, allocation_callbacks); - frame.fence = VK_NULL_HANDLE; - frame.command_buffer = VK_NULL_HANDLE; - frame.command_pool = VK_NULL_HANDLE; - - // vkDestroyImage(device, frame.back_buffer, &allocation_callbacks); - vkDestroyImageView(device, frame.back_buffer_view, allocation_callbacks); - vkDestroyFramebuffer(device, frame.frame_buffer, allocation_callbacks); - frame.back_buffer = VK_NULL_HANDLE; - frame.back_buffer_view = VK_NULL_HANDLE; - frame.frame_buffer = VK_NULL_HANDLE; - } - - auto destroy_frame_semaphore( - const VkDevice device, - frame_semaphore_type& frame_semaphore, - const VkAllocationCallbacks* allocation_callbacks - ) -> void - { - vkDestroySemaphore(device, frame_semaphore.image_acquired_semaphore, allocation_callbacks); - vkDestroySemaphore(device, frame_semaphore.render_complete_semaphore, allocation_callbacks); - frame_semaphore.image_acquired_semaphore = VK_NULL_HANDLE; - frame_semaphore.render_complete_semaphore = VK_NULL_HANDLE; - } - - struct frame_render_buffer_type - { - VkDeviceMemory vertex_buffer_memory{VK_NULL_HANDLE}; - VkDeviceSize vertex_count{0}; - VkBuffer vertex_buffer{VK_NULL_HANDLE}; - - VkDeviceMemory index_buffer_memory{VK_NULL_HANDLE}; - VkDeviceSize index_count{0}; - VkBuffer index_buffer{VK_NULL_HANDLE}; - }; - } - - GLFWwindow* g_window; - int g_window_width = 1280; - int g_window_height = 720; - int g_window_frame_buffer_width; - int g_window_frame_buffer_height; - - VkAllocationCallbacks* g_allocation_callbacks; - - VkSurfaceKHR g_window_surface; - VkSurfaceFormatKHR g_window_surface_format; - VkPresentModeKHR g_window_present_mode; - // assert(g_window_min_image_count >= 2) - std::uint32_t g_window_min_image_count{2}; - VkSwapchainKHR g_window_swap_chain; - bool g_window_swap_chain_rebuild_required; - - bool g_window_clear_enable{true}; - VkClearValue g_window_clear_value{.45f, .55f, .65f, 1.f}; - - std::unique_ptr g_window_frames; - std::uint32_t g_window_frame_current_index; - std::uint32_t g_window_frame_total_count; - constexpr auto g_window_frames_maker = [] - { - g_window_frames = std::make_unique(g_window_frame_total_count); - }; - constexpr auto g_window_frames_destroyer = [](const VkDevice device, const VkAllocationCallbacks* allocation_callbacks) - { - std::ranges::for_each( - std::ranges::subrange{g_window_frames.get(), g_window_frames.get() + g_window_frame_total_count}, - [&](detail::frame_type& frame) { destroy_frame(device, frame, allocation_callbacks); } - ); - - g_window_frames.reset(); - g_window_frame_current_index = 0; - g_window_frame_total_count = 0; - }; - - std::unique_ptr g_window_frame_semaphores; - std::uint32_t g_window_frame_semaphore_current_index; - std::uint32_t g_window_frame_semaphore_total_count; - constexpr auto g_window_frame_semaphores_maker = [] - { - g_window_frame_semaphores = std::make_unique(g_window_frame_semaphore_total_count); - }; - constexpr auto g_window_frame_semaphores_destroyer = [](const VkDevice device, const VkAllocationCallbacks* allocation_callbacks) - { - std::ranges::for_each( - std::ranges::subrange{g_window_frame_semaphores.get(), g_window_frame_semaphores.get() + g_window_frame_semaphore_total_count}, - [&](detail::frame_semaphore_type& frame_semaphore) { destroy_frame_semaphore(device, frame_semaphore, allocation_callbacks); } - ); - - g_window_frame_semaphores.reset(); - g_window_frame_semaphore_current_index = 0; - g_window_frame_semaphore_total_count = 0; - }; - - std::unique_ptr g_window_render_buffer; - std::uint32_t g_window_render_buffer_current_index; - std::uint32_t g_window_render_buffer_total_count; - constexpr auto g_window_render_buffer_maker = [] - { - g_window_render_buffer = std::make_unique(g_window_render_buffer_total_count); - }; - constexpr auto g_window_render_buffer_destroyer = [](const VkDevice device, const VkAllocationCallbacks* allocation_callbacks) - { - std::ranges::for_each( - std::ranges::subrange{g_window_render_buffer.get(), g_window_render_buffer.get() + g_window_render_buffer_total_count}, - [&](const detail::frame_render_buffer_type& frame) -> void - { - if (frame.vertex_buffer) - { - vkDestroyBuffer(device, frame.vertex_buffer, allocation_callbacks); - } - if (frame.vertex_buffer_memory) - { - vkFreeMemory(device, frame.vertex_buffer_memory, allocation_callbacks); - } - - if (frame.index_buffer) - { - vkDestroyBuffer(device, frame.index_buffer, allocation_callbacks); - } - if (frame.index_buffer_memory) - { - vkFreeMemory(device, frame.index_buffer_memory, allocation_callbacks); - } - } - ); - g_window_render_buffer.reset(); - g_window_render_buffer_current_index = 0; - g_window_render_buffer_total_count = 0; - }; - - VkInstance g_instance; - VkDebugReportCallbackEXT g_debug_report_callback; - - VkPhysicalDevice g_physical_device; - VkDevice g_device; - - std::uint32_t g_queue_family; - VkQueue g_queue; - - VkDescriptorPool g_descriptor_pool; - - VkSampler g_font_sampler; - VkDeviceMemory g_font_memory; - VkImage g_font_image; - VkImageView g_font_image_view; - VkDescriptorSet g_font_descriptor_set; - VkCommandPool g_font_command_pool; - VkCommandBuffer g_font_command_buffer; - - VkDescriptorSetLayout g_pipeline_descriptor_set_layout; - VkPipelineLayout g_pipeline_layout; - VkShaderModule g_pipeline_shader_module_vertex; - VkShaderModule g_pipeline_shader_module_fragment; - VkSampleCountFlagBits g_pipeline_rasterization_msaa{VK_SAMPLE_COUNT_1_BIT}; - VkPipelineCreateFlags g_pipeline_create_flags; - VkRenderPass g_pipeline_render_pass; - VkPipeline g_pipeline; - // optional - VkPipelineCache g_pipeline_cache; - std::uint32_t g_pipeline_sub_pass; - bool g_pipeline_use_dynamic_rendering; - VkPipelineRenderingCreateInfoKHR g_pipeline_rendering_create_info; - - auto glfw_error_callback(const int error, const char* message) -> void // - { - std::println(std::cerr, "GLFW Error {}: {}", error, message); - } - - auto vulkan_check_error(const VkResult result, const std::source_location& location = std::source_location::current()) -> void - { - if (result == VK_SUCCESS) - { - return; - } - - std::println(std::cerr, "VULKAN Error: {} -- at {}:{}\n", meta::name_of(result), location.file_name(), location.line()); - std::abort(); - } - - auto VKAPI_CALL vulkan_debug_report( - const VkDebugReportFlagsEXT flags, - const VkDebugReportObjectTypeEXT object_type, - const std::uint64_t object, - const std::size_t location, - const std::int32_t message_code, - const char* layer_prefix, - const char* message, - [[maybe_unused]] void* user_data - ) -> VkBool32 - { - std::println( - std::cerr, - "Vulkan debug report: \n\t flags({}) \n\t object_type({}) \n\t object(0x{:x}) \n\t location({}) \n\t message_code({}) \n\t " - "layer_prefix({}) \n\t message({})\n", - flags, - meta::name_of(object_type), - object, - location, - message_code, - layer_prefix, - message - ); - return VK_FALSE; - } - - auto vulkan_setup(std::vector& extensions) -> void; - - auto vulkan_create_or_resize_window() -> void; - - auto vulkan_setup_window() -> bool; - - auto init() -> void; - - auto new_frame() -> void; - - auto frame_render() -> void; - - auto frame_present() -> void; - - auto shutdown() -> void; - - auto vulkan_cleanup_window() -> void; - - auto vulkan_cleanup() -> void; -} - -int main(int, char**) -{ - glfwSetErrorCallback(glfw_error_callback); - if (not glfwInit()) - { - std::println(std::cerr, "GLFW: glfwInit failed"); - return -1; - } - - // Create window with Vulkan context - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - g_window = glfwCreateWindow(g_window_width, g_window_height, "Vulkan+GLFW GUI Playground", nullptr, nullptr); - if (not glfwVulkanSupported()) - { - std::println(std::cerr, "GLFW: Vulkan Not Supported"); - return -1; - } - - std::uint32_t extensions_count; - const char** extensions_raw = glfwGetRequiredInstanceExtensions(&extensions_count); - std::vector extensions{extensions_raw, extensions_raw + extensions_count}; - vulkan_setup(extensions); - - // Create Window Surface - vulkan_check_error(glfwCreateWindowSurface(g_instance, g_window, g_allocation_callbacks, &g_window_surface)); - - // Create frame buffer - glfwGetFramebufferSize(g_window, &g_window_frame_buffer_width, &g_window_frame_buffer_height); - if (not vulkan_setup_window()) - { - std::println(std::cerr, "Vulkan: vulkan_setup_window failed"); - return -1; - } - - // Setup Platform/Renderer backends - init(); - - // user data here - { - g_draw_data.display_rect = {0, 0, static_cast(g_window_width), static_cast(g_window_height)}; - auto& draw_list = g_draw_data.draw_lists.emplace_back(); - - constexpr vertex_type v1{{100, 100}, {.1f, .1f}, primitive::colors::red}; - constexpr vertex_type v2{{150, 150}, {.1f, .1f}, primitive::colors::green}; - constexpr vertex_type v3{{200, 200}, {.1f, .1f}, primitive::colors::blue}; - - draw_list.vertex_list.triangle(v1, v2, v3); - draw_list.index_list.push_back(0); - draw_list.index_list.push_back(1); - draw_list.index_list.push_back(2); - } - - // Main loop - while (not glfwWindowShouldClose(g_window)) - { - glfwPollEvents(); - - if (g_window_swap_chain_rebuild_required) - { - glfwGetFramebufferSize(g_window, &g_window_frame_buffer_width, &g_window_frame_buffer_height); - if (g_window_frame_buffer_width > 0 and g_window_frame_buffer_height > 0) - { - vulkan_create_or_resize_window(); - g_window_frame_current_index = 0; - g_window_swap_chain_rebuild_required = false; - } - } - - new_frame(); - - if (g_window_width > 0 and g_window_height > 0) - { - frame_render(); - frame_present(); - } - } - - // Cleanup - vulkan_check_error(vkDeviceWaitIdle(g_device)); - shutdown(); - - vulkan_cleanup_window(); - vulkan_cleanup(); - - glfwDestroyWindow(g_window); - glfwTerminate(); - - return 0; -} - -namespace -{ - [[nodiscard]] auto memory_type(const VkMemoryPropertyFlags property_flag, const std::uint32_t type_bits) -> std::uint32_t - { - VkPhysicalDeviceMemoryProperties memory_properties; - vkGetPhysicalDeviceMemoryProperties(g_physical_device, &memory_properties); - - for (std::uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) - { - if ((memory_properties.memoryTypes[i].propertyFlags & property_flag) == property_flag and type_bits & (1 << i)) - { - return i; - } - } - return std::numeric_limits::max(); - } - - auto vulkan_setup(std::vector& extensions) -> void - { - // Create Vulkan Instance - { - VkInstanceCreateInfo instance_create_info{ - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .pApplicationInfo = nullptr, - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, - .enabledExtensionCount = 0, - .ppEnabledExtensionNames = nullptr, - }; - - // Enumerate available extensions - std::uint32_t properties_count; - vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, nullptr); - std::vector properties; - properties.resize(properties_count); - vulkan_check_error(vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, properties.data())); - - // Enable required extensions - if (std::ranges::contains( - properties, - std::string_view{VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME}, - &VkExtensionProperties::extensionName - )) - { - extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - } - if (std::ranges::contains( - properties, - std::string_view{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME}, - &VkExtensionProperties::extensionName - )) - { - extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - instance_create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - } - - // Enabling validation layers - constexpr const char* layer_validation[]{"VK_LAYER_KHRONOS_validation"}; - instance_create_info.enabledLayerCount = 1; - instance_create_info.ppEnabledLayerNames = layer_validation; - extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); - - // Create Vulkan Instance - instance_create_info.enabledExtensionCount = static_cast(extensions.size()); - instance_create_info.ppEnabledExtensionNames = extensions.data(); - vulkan_check_error(vkCreateInstance(&instance_create_info, g_allocation_callbacks, &g_instance)); - - // Setup the debug report callback - const auto create_debug_report_callback = - reinterpret_cast( // NOLINT(clang-diagnostic-cast-function-type-strict) - vkGetInstanceProcAddr(g_instance, "vkCreateDebugReportCallbackEXT") - ); - assert(create_debug_report_callback); - VkDebugReportCallbackCreateInfoEXT debug_report_callback_create_info{ - .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, - .pNext = nullptr, - .flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT, - .pfnCallback = vulkan_debug_report, - .pUserData = nullptr - }; - vulkan_check_error( - create_debug_report_callback(g_instance, &debug_report_callback_create_info, g_allocation_callbacks, &g_debug_report_callback) - ); - } - - // Select Physical Device (GPU) - { - std::uint32_t gpu_count; - vulkan_check_error(vkEnumeratePhysicalDevices(g_instance, &gpu_count, nullptr)); - assert(gpu_count != 0); - std::vector gpus; - gpus.resize(gpu_count); - vulkan_check_error(vkEnumeratePhysicalDevices(g_instance, &gpu_count, gpus.data())); - - for (const VkPhysicalDevice& device: gpus) - { - VkPhysicalDeviceProperties properties; - vkGetPhysicalDeviceProperties(device, &properties); - - if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) - { - g_physical_device = device; - return; - } - } - - // Use first GPU (Integrated) is a Discrete one is not available. - g_physical_device = gpus.front(); - } - - // Select graphics queue family - { - std::uint32_t queue_family_properties_count; - vkGetPhysicalDeviceQueueFamilyProperties(g_physical_device, &queue_family_properties_count, nullptr); - std::vector queue_family_properties; - queue_family_properties.resize(queue_family_properties_count); - vkGetPhysicalDeviceQueueFamilyProperties(g_physical_device, &queue_family_properties_count, queue_family_properties.data()); - - for (std::uint32_t i = 0; i < queue_family_properties_count; ++i) - { - if (queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) - { - g_queue_family = i; - break; - } - } - } - - // Create Logical Device (with 1 queue) - { - std::vector device_extensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME}; - - // Enumerate physical device extension - std::uint32_t extension_properties_count; - vkEnumerateDeviceExtensionProperties(g_physical_device, nullptr, &extension_properties_count, nullptr); - std::vector extension_properties; - extension_properties.resize(extension_properties_count); - vkEnumerateDeviceExtensionProperties(g_physical_device, nullptr, &extension_properties_count, extension_properties.data()); - #if defined(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) - if (std::ranges::contains(extension_properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, &VkExtensionProperties::extensionName)) - { - extension_properties.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); - } - #endif - - constexpr float queue_priority[]{ - 1.f, - }; - const VkDeviceQueueCreateInfo device_queue_create_info{ - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueFamilyIndex = g_queue_family, - .queueCount = 1, - .pQueuePriorities = queue_priority - }; - const VkDeviceCreateInfo device_create_info{ - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &device_queue_create_info, - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, - .enabledExtensionCount = static_cast(device_extensions.size()), - .ppEnabledExtensionNames = device_extensions.data(), - .pEnabledFeatures = nullptr - }; - vulkan_check_error(vkCreateDevice(g_physical_device, &device_create_info, g_allocation_callbacks, &g_device)); - vkGetDeviceQueue(g_device, g_queue_family, 0, &g_queue); - } - - // Create Descriptor Pool - { - // a single combined image sampler descriptor for the font image and only uses one descriptor set (for that) - - constexpr VkDescriptorPoolSize descriptor_pool_size[]{ - {.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1}, - }; - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkDescriptorPoolCreateInfo descriptor_pool_create_info{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - .maxSets = 1, - .poolSizeCount = std::ranges::size(descriptor_pool_size), - .pPoolSizes = descriptor_pool_size - }; - - vulkan_check_error(vkCreateDescriptorPool(g_device, &descriptor_pool_create_info, g_allocation_callbacks, &g_descriptor_pool)); - } - } - - auto vulkan_create_or_resize_window() -> void - { - // swap-chain - { - auto old_swap_chain = std::exchange(g_window_swap_chain, VK_NULL_HANDLE); - vulkan_check_error(vkDeviceWaitIdle(g_device)); - - g_window_frames_destroyer(g_device, g_allocation_callbacks); - g_window_frame_semaphores_destroyer(g_device, g_allocation_callbacks); - - if (g_pipeline_render_pass != VK_NULL_HANDLE) - { - vkDestroyRenderPass(g_device, g_pipeline_render_pass, g_allocation_callbacks); - g_pipeline_render_pass = VK_NULL_HANDLE; - } - if (g_pipeline != VK_NULL_HANDLE) - { - vkDestroyPipeline(g_device, g_pipeline, g_allocation_callbacks); - g_pipeline = VK_NULL_HANDLE; - } - - // Create SwapChain - { - VkSurfaceCapabilitiesKHR surface_capabilities; - vulkan_check_error(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_physical_device, g_window_surface, &surface_capabilities)); - - // If min image count was not specified, request different count of images dependent on selected present mode - auto image_count = g_window_min_image_count; - if (image_count < surface_capabilities.minImageCount) - { - image_count = surface_capabilities.minImageCount; - } - if (surface_capabilities.maxImageCount != 0 and image_count > surface_capabilities.maxImageCount) - { - image_count = surface_capabilities.maxImageCount; - } - - if (surface_capabilities.currentExtent.width != std::numeric_limits::max()) - { - g_window_width = static_cast(surface_capabilities.currentExtent.width); - g_window_height = static_cast(surface_capabilities.currentExtent.height); - } - - VkSwapchainCreateInfoKHR swap_chain_create_info{ - .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - .pNext = nullptr, - .flags = 0, - .surface = g_window_surface, - .minImageCount = image_count, - .imageFormat = g_window_surface_format.format, - .imageColorSpace = g_window_surface_format.colorSpace, - .imageExtent = {.width = static_cast(g_window_width), .height = static_cast(g_window_height)}, - .imageArrayLayers = 1, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, - .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - .presentMode = g_window_present_mode, - .clipped = VK_TRUE, - .oldSwapchain = old_swap_chain - }; - vulkan_check_error(vkCreateSwapchainKHR(g_device, &swap_chain_create_info, g_allocation_callbacks, &g_window_swap_chain)); - - vulkan_check_error(vkGetSwapchainImagesKHR(g_device, g_window_swap_chain, &g_window_frame_total_count, nullptr)); - g_window_frame_semaphore_total_count = g_window_frame_total_count + 1; - - g_window_frames_maker(); - g_window_frame_semaphores_maker(); - - VkImage back_buffer[16]{}; - vulkan_check_error(vkGetSwapchainImagesKHR(g_device, g_window_swap_chain, &g_window_frame_total_count, back_buffer)); - for (std::uint32_t i = 0; i < g_window_frame_total_count; ++i) - { - g_window_frames[i].back_buffer = back_buffer[i]; - } - } - - if (old_swap_chain) - { - vkDestroySwapchainKHR(g_device, old_swap_chain, g_allocation_callbacks); - } - - // Create Render Pass - if (not g_pipeline_use_dynamic_rendering) - { - const VkAttachmentDescription attachment_description{ - .flags = 0, - .format = g_window_surface_format.format, - .samples = VK_SAMPLE_COUNT_1_BIT, - .loadOp = g_window_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR - }; - - constexpr VkAttachmentReference attachment_reference{.attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; - - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkSubpassDescription sub_pass_description{ - .flags = 0, - .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, - .inputAttachmentCount = 0, - .pInputAttachments = nullptr, - .colorAttachmentCount = 1, - .pColorAttachments = &attachment_reference, - .pResolveAttachments = nullptr, - .pDepthStencilAttachment = nullptr, - .preserveAttachmentCount = 0, - .pPreserveAttachments = nullptr - }; - - constexpr VkSubpassDependency sub_pass_dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - .dependencyFlags = 0 - }; - - const VkRenderPassCreateInfo render_pass_create_info{ - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .attachmentCount = 1, - .pAttachments = &attachment_description, - .subpassCount = 1, - .pSubpasses = &sub_pass_description, - .dependencyCount = 1, - .pDependencies = &sub_pass_dependency - }; - - vulkan_check_error(vkCreateRenderPass(g_device, &render_pass_create_info, g_allocation_callbacks, &g_pipeline_render_pass)); - } - - // Create Image Views - { - VkImageViewCreateInfo image_view_create_info{ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = VK_NULL_HANDLE, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = g_window_surface_format.format, - .components = - {.r = VK_COMPONENT_SWIZZLE_R, .g = VK_COMPONENT_SWIZZLE_G, .b = VK_COMPONENT_SWIZZLE_B, .a = VK_COMPONENT_SWIZZLE_A}, - .subresourceRange = - {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1} - }; - - std::ranges::for_each( - std::ranges::subrange{g_window_frames.get(), g_window_frames.get() + g_window_frame_total_count}, - [&](detail::frame_type& frame) - { - image_view_create_info.image = frame.back_buffer; - vulkan_check_error(vkCreateImageView(g_device, &image_view_create_info, g_allocation_callbacks, &frame.back_buffer_view)); - } - ); - } - - // Create Frame Buffer - if (not g_pipeline_use_dynamic_rendering) - { - VkFramebufferCreateInfo frame_buffer_create_info{ - .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .renderPass = g_pipeline_render_pass, - .attachmentCount = 1, - .pAttachments = VK_NULL_HANDLE, - .width = static_cast(g_window_width), - .height = static_cast(g_window_height), - .layers = 1 - }; - - std::ranges::for_each( - std::ranges::subrange{g_window_frames.get(), g_window_frames.get() + g_window_frame_total_count}, - [&](detail::frame_type& frame) - { - frame_buffer_create_info.pAttachments = &frame.back_buffer_view; - vulkan_check_error(vkCreateFramebuffer(g_device, &frame_buffer_create_info, g_allocation_callbacks, &frame.frame_buffer)); - } - ); - } - } - - // command buffer - { - std::ranges::for_each( - std::ranges::subrange{g_window_frames.get(), g_window_frames.get() + g_window_frame_total_count}, - [&](detail::frame_type& frame) - { - { - const VkCommandPoolCreateInfo command_pool_create_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueFamilyIndex = g_queue_family - }; - vulkan_check_error(vkCreateCommandPool(g_device, &command_pool_create_info, g_allocation_callbacks, &frame.command_pool)); - } - { - const VkCommandBufferAllocateInfo command_buffer_allocate_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .pNext = nullptr, - .commandPool = frame.command_pool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1 - }; - vulkan_check_error( - vkAllocateCommandBuffers(g_device, &command_buffer_allocate_info, &frame.command_buffer) - ); - } - { - constexpr VkFenceCreateInfo fence_create_info{ - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = VK_FENCE_CREATE_SIGNALED_BIT - }; - vulkan_check_error(vkCreateFence(g_device, &fence_create_info, g_allocation_callbacks, &frame.fence)); - } - } - ); - - std::ranges::for_each( - std::ranges::subrange{g_window_frame_semaphores.get(), g_window_frame_semaphores.get() + g_window_frame_semaphore_total_count}, - [&](detail::frame_semaphore_type& frame_semaphore) - { - constexpr VkSemaphoreCreateInfo semaphore_create_info{ - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = nullptr, .flags = 0 - }; - vulkan_check_error(vkCreateSemaphore( - g_device, - &semaphore_create_info, - g_allocation_callbacks, - &frame_semaphore.image_acquired_semaphore - )); - vulkan_check_error( - vkCreateSemaphore( - g_device, - &semaphore_create_info, - g_allocation_callbacks, - &frame_semaphore.render_complete_semaphore - )); - } - ); - } - } - - auto vulkan_setup_window() -> bool - { - // Check for WSI support - { - VkBool32 surface_support_result; - vkGetPhysicalDeviceSurfaceSupportKHR(g_physical_device, g_queue_family, g_window_surface, &surface_support_result); - if (surface_support_result != VK_TRUE) - { - std::println(std::cerr, "Vulkan Error: no WSI support on physical device"); - return false; - } - } - - // Select Surface Format - { - constexpr VkFormat request_surface_image_format[]{ - VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM - }; - constexpr VkColorSpaceKHR request_surface_color_space{VK_COLORSPACE_SRGB_NONLINEAR_KHR}; - - std::uint32_t surface_format_count; - vkGetPhysicalDeviceSurfaceFormatsKHR(g_physical_device, g_window_surface, &surface_format_count, nullptr); - std::vector surface_format; - surface_format.resize(surface_format_count); - vkGetPhysicalDeviceSurfaceFormatsKHR(g_physical_device, g_window_surface, &surface_format_count, surface_format.data()); - - // First check if only one format, VK_FORMAT_UNDEFINED, is available, which would imply that any format is available - if (surface_format_count == 1) - { - if (surface_format.front().format == VK_FORMAT_UNDEFINED) - { - g_window_surface_format.format = request_surface_image_format[0]; - g_window_surface_format.colorSpace = request_surface_color_space; - } - else - { - g_window_surface_format = surface_format.front(); - } - } - else - { - // Request several formats, the first found will be used - if (const auto result = [&]() -> std::optional - { - for (const VkFormat& request_format: request_surface_image_format) - { - if (const auto it = std::ranges::find_if( - surface_format, - [&](const VkSurfaceFormatKHR& format) noexcept -> bool - { - return format.format == request_format and format.colorSpace == request_surface_color_space; - } - ); - it != surface_format.end()) - { - return std::make_optional(*it); - } - } - - return std::nullopt; - }(); - result.has_value()) - { - g_window_surface_format = *result; - } - else - { - // If none of the requested image formats could be found, use the first available - g_window_surface_format = surface_format.front(); - } - } - } - - // Select Present Mode - { - // fixme: unlimited frame rate? - constexpr VkPresentModeKHR request_present_mode[] - { - #if 1 // NOLINT(readability-avoid-unconditional-preprocessor-if) - VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, - #endif - VK_PRESENT_MODE_FIFO_KHR - }; - - // Request a certain mode and confirm that it is available. If not use VK_PRESENT_MODE_FIFO_KHR which is mandatory - std::uint32_t surface_present_mode_count; - vkGetPhysicalDeviceSurfacePresentModesKHR(g_physical_device, g_window_surface, &surface_present_mode_count, nullptr); - std::vector surface_present_mode; - surface_present_mode.resize(surface_present_mode_count); - vkGetPhysicalDeviceSurfacePresentModesKHR(g_physical_device, g_window_surface, &surface_present_mode_count, surface_present_mode.data()); - - if (const auto mode_it = std::ranges::find_if( - request_present_mode, - [&](const VkPresentModeKHR& request_mode) noexcept -> bool { return std::ranges::contains(surface_present_mode, request_mode); } - ); - mode_it != std::ranges::end(request_present_mode)) - { - g_window_present_mode = *mode_it; - } - else - { - g_window_present_mode = VK_PRESENT_MODE_FIFO_KHR; - } - } - - vulkan_create_or_resize_window(); - return true; - } - - GLFWwindowfocusfun g_glfw_callback_window_focus; - GLFWcursorenterfun g_glfw_callback_window_cursor_enter; - GLFWcursorposfun g_glfw_callback_window_cursor_position; - GLFWmousebuttonfun g_glfw_callback_window_mouse_button; - GLFWscrollfun g_glfw_callback_window_scroll; - GLFWkeyfun g_glfw_callback_window_key; - GLFWcharfun g_glfw_callback_window_char; - GLFWmonitorfun g_glfw_callback_window_monitor; - - auto glfw_init() -> void - { - static auto callback_window_focus = [](GLFWwindow* window, const int focused) - { - std::println(std::cout, "callback_window_focus: window: 0x{:x}, focused: {}", reinterpret_cast(window), focused != 0); - - if (g_glfw_callback_window_focus) - { - g_glfw_callback_window_focus(window, focused); - } - }; - static auto callback_window_cursor_enter = [](GLFWwindow* window, const int entered) - { - std::println(std::cout, - "callback_window_cursor_enter: window: 0x{:x}, entered: {}", - reinterpret_cast(window), - entered != 0); - - if (g_glfw_callback_window_cursor_enter) - { - g_glfw_callback_window_cursor_enter(window, entered); - } - }; - static auto callback_window_cursor_position = [](GLFWwindow* window, const double x, const double y) - { - std::println(std::cout, "callback_window_cursor_position: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); - - if (g_glfw_callback_window_cursor_position) - { - g_glfw_callback_window_cursor_position(window, x, y); - } - }; - static auto callback_window_mouse_button = [](GLFWwindow* window, const int button, const int action, const int mods) - { - std::println( - std::cout, - "callback_window_mouse_button: window: 0x{:x}, button: {}, action: {}, mods: {}", - reinterpret_cast(window), - button, - action, - mods - ); - - if (g_glfw_callback_window_mouse_button) - { - g_glfw_callback_window_mouse_button(window, button, action, mods); - } - }; - static auto callback_window_scroll = [](GLFWwindow* window, const double x, const double y) - { - std::println(std::cout, "callback_window_scroll: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); - - if (g_glfw_callback_window_scroll) - { - g_glfw_callback_window_scroll(window, x, y); - } - }; - static auto callback_window_key = [](GLFWwindow* window, const int key_code, const int scan_code, const int action, const int mods) - { - std::println( - std::cout, - "callback_window_scroll: window: 0x{:x}, key_code: {}, scan_code: {}, action: {}, mods: {}", - reinterpret_cast(window), - key_code, - scan_code, - action, - mods - ); - - if (g_glfw_callback_window_key) - { - g_glfw_callback_window_key(window, key_code, scan_code, action, mods); - } - }; - static auto callback_window_char = [](GLFWwindow* window, const unsigned int codepoint) - { - std::println(std::cout, "callback_window_char: window: 0x{:x}, codepoint: 0x{:x}", reinterpret_cast(window), codepoint); - - if (g_glfw_callback_window_char) - { - g_glfw_callback_window_char(window, codepoint); - } - }; - static auto callback_window_monitor = [](GLFWmonitor* monitor, const int event) - { - std::println(std::cout, "callback_window_monitor: monitor: 0x{:x}, event: {}", reinterpret_cast(monitor), event); - - if (g_glfw_callback_window_monitor) - { - g_glfw_callback_window_monitor(monitor, event); - } - }; - - g_glfw_callback_window_focus = glfwSetWindowFocusCallback(g_window, callback_window_focus); - g_glfw_callback_window_cursor_enter = glfwSetCursorEnterCallback(g_window, callback_window_cursor_enter); - g_glfw_callback_window_cursor_position = glfwSetCursorPosCallback(g_window, callback_window_cursor_position); - g_glfw_callback_window_mouse_button = glfwSetMouseButtonCallback(g_window, callback_window_mouse_button); - g_glfw_callback_window_scroll = glfwSetScrollCallback(g_window, callback_window_scroll); - g_glfw_callback_window_key = glfwSetKeyCallback(g_window, callback_window_key); - g_glfw_callback_window_char = glfwSetCharCallback(g_window, callback_window_char); - g_glfw_callback_window_monitor = glfwSetMonitorCallback(callback_window_monitor); - } - - auto vulkan_init() -> void - { - if (g_font_sampler == VK_NULL_HANDLE) - { - constexpr VkSamplerCreateInfo sampler_create_info{ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .magFilter = VK_FILTER_LINEAR, - .minFilter = VK_FILTER_LINEAR, - .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, - .mipLodBias = .0f, - .anisotropyEnable = 0, - .maxAnisotropy = 1.f, - .compareEnable = VK_FALSE, - .compareOp = VK_COMPARE_OP_NEVER, - .minLod = -1000, - .maxLod = 1000, - .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, - .unnormalizedCoordinates = VK_FALSE - }; - vulkan_check_error(vkCreateSampler(g_device, &sampler_create_info, g_allocation_callbacks, &g_font_sampler)); - } - - if (g_pipeline_descriptor_set_layout == VK_NULL_HANDLE) - { - constexpr VkDescriptorSetLayoutBinding descriptor_set_layout_binding{ - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - .pImmutableSamplers = nullptr - }; - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .bindingCount = 1, - .pBindings = &descriptor_set_layout_binding - }; - vulkan_check_error(vkCreateDescriptorSetLayout( - g_device, - &descriptor_set_layout_create_info, - g_allocation_callbacks, - &g_pipeline_descriptor_set_layout - )); - } - - if (g_pipeline_layout == VK_NULL_HANDLE) - { - constexpr VkPushConstantRange push_constant_range{ - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, - .offset = sizeof(float) * 0, - .size = sizeof(float) * 4}; - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkPipelineLayoutCreateInfo pipeline_layout_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .setLayoutCount = 1, - .pSetLayouts = &g_pipeline_descriptor_set_layout, - .pushConstantRangeCount = 1, - .pPushConstantRanges = &push_constant_range - }; - vulkan_check_error(vkCreatePipelineLayout(g_device, &pipeline_layout_create_info, g_allocation_callbacks, &g_pipeline_layout)); - } - - if (g_pipeline_shader_module_vertex == VK_NULL_HANDLE) - { - // #version 450 core - // layout(location = 0) in vec2 aPos; - // layout(location = 1) in vec2 aUV; - // layout(location = 2) in vec4 aColor; - // layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; - // - // out gl_PerVertex { vec4 gl_Position; }; - // layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; - // - // void main() - // { - // Out.Color = aColor; - // Out.UV = aUV; - // gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); - // } - - constexpr static uint32_t shader_data[] = { - 0x07230203, 0x00010000, 0x00080001, 0x0000002e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, - 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x000a000f, 0x00000000, 0x00000004, 0x6e69616d, - 0x00000000, 0x0000000b, 0x0000000f, 0x00000015, 0x0000001b, 0x0000001c, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, - 0x00000004, 0x6e69616d, 0x00000000, 0x00030005, 0x00000009, 0x00000000, 0x00050006, 0x00000009, 0x00000000, 0x6f6c6f43, - 0x00000072, 0x00040006, 0x00000009, 0x00000001, 0x00005655, 0x00030005, 0x0000000b, 0x0074754f, 0x00040005, 0x0000000f, - 0x6c6f4361, 0x0000726f, 0x00030005, 0x00000015, 0x00565561, 0x00060005, 0x00000019, 0x505f6c67, 0x65567265, 0x78657472, - 0x00000000, 0x00060006, 0x00000019, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00030005, 0x0000001b, 0x00000000, - 0x00040005, 0x0000001c, 0x736f5061, 0x00000000, 0x00060005, 0x0000001e, 0x73755075, 0x6e6f4368, 0x6e617473, 0x00000074, - 0x00050006, 0x0000001e, 0x00000000, 0x61635375, 0x0000656c, 0x00060006, 0x0000001e, 0x00000001, 0x61725475, 0x616c736e, - 0x00006574, 0x00030005, 0x00000020, 0x00006370, 0x00040047, 0x0000000b, 0x0000001e, 0x00000000, 0x00040047, 0x0000000f, - 0x0000001e, 0x00000002, 0x00040047, 0x00000015, 0x0000001e, 0x00000001, 0x00050048, 0x00000019, 0x00000000, 0x0000000b, - 0x00000000, 0x00030047, 0x00000019, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00050048, 0x0000001e, - 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001e, 0x00000001, 0x00000023, 0x00000008, 0x00030047, 0x0000001e, - 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, - 0x00000007, 0x00000006, 0x00000004, 0x00040017, 0x00000008, 0x00000006, 0x00000002, 0x0004001e, 0x00000009, 0x00000007, - 0x00000008, 0x00040020, 0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, 0x00040015, - 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040020, 0x0000000e, 0x00000001, - 0x00000007, 0x0004003b, 0x0000000e, 0x0000000f, 0x00000001, 0x00040020, 0x00000011, 0x00000003, 0x00000007, 0x0004002b, - 0x0000000c, 0x00000013, 0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000008, 0x0004003b, 0x00000014, 0x00000015, - 0x00000001, 0x00040020, 0x00000017, 0x00000003, 0x00000008, 0x0003001e, 0x00000019, 0x00000007, 0x00040020, 0x0000001a, - 0x00000003, 0x00000019, 0x0004003b, 0x0000001a, 0x0000001b, 0x00000003, 0x0004003b, 0x00000014, 0x0000001c, 0x00000001, - 0x0004001e, 0x0000001e, 0x00000008, 0x00000008, 0x00040020, 0x0000001f, 0x00000009, 0x0000001e, 0x0004003b, 0x0000001f, - 0x00000020, 0x00000009, 0x00040020, 0x00000021, 0x00000009, 0x00000008, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, - 0x0004002b, 0x00000006, 0x00000029, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, - 0x00000005, 0x0004003d, 0x00000007, 0x00000010, 0x0000000f, 0x00050041, 0x00000011, 0x00000012, 0x0000000b, 0x0000000d, - 0x0003003e, 0x00000012, 0x00000010, 0x0004003d, 0x00000008, 0x00000016, 0x00000015, 0x00050041, 0x00000017, 0x00000018, - 0x0000000b, 0x00000013, 0x0003003e, 0x00000018, 0x00000016, 0x0004003d, 0x00000008, 0x0000001d, 0x0000001c, 0x00050041, - 0x00000021, 0x00000022, 0x00000020, 0x0000000d, 0x0004003d, 0x00000008, 0x00000023, 0x00000022, 0x00050085, 0x00000008, - 0x00000024, 0x0000001d, 0x00000023, 0x00050041, 0x00000021, 0x00000025, 0x00000020, 0x00000013, 0x0004003d, 0x00000008, - 0x00000026, 0x00000025, 0x00050081, 0x00000008, 0x00000027, 0x00000024, 0x00000026, 0x00050051, 0x00000006, 0x0000002a, - 0x00000027, 0x00000000, 0x00050051, 0x00000006, 0x0000002b, 0x00000027, 0x00000001, 0x00070050, 0x00000007, 0x0000002c, - 0x0000002a, 0x0000002b, 0x00000028, 0x00000029, 0x00050041, 0x00000011, 0x0000002d, 0x0000001b, 0x0000000d, 0x0003003e, - 0x0000002d, 0x0000002c, 0x000100fd, 0x00010038 - }; - - const VkShaderModuleCreateInfo shader_module_create_info{ - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .codeSize = sizeof(shader_data), - .pCode = shader_data - }; - vulkan_check_error(vkCreateShaderModule( - g_device, - &shader_module_create_info, - g_allocation_callbacks, - &g_pipeline_shader_module_vertex - ) - ); - } - if (g_pipeline_shader_module_fragment == VK_NULL_HANDLE) - { - // #version 450 core - // layout(location = 0) out vec4 fColor; - // layout(set=0, binding=0) uniform sampler2D sTexture; - // layout(location = 0) in struct { vec4 Color; vec2 UV; } In; - // void main() - // { - // fColor = In.Color * texture(sTexture, In.UV.st); - // } - constexpr static uint32_t shader_data[] = { - 0x07230203, 0x00010000, 0x00080001, 0x0000001e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, - 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, - 0x00000000, 0x00000009, 0x0000000d, 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, - 0x00000004, 0x6e69616d, 0x00000000, 0x00040005, 0x00000009, 0x6c6f4366, 0x0000726f, 0x00030005, 0x0000000b, 0x00000000, - 0x00050006, 0x0000000b, 0x00000000, 0x6f6c6f43, 0x00000072, 0x00040006, 0x0000000b, 0x00000001, 0x00005655, 0x00030005, - 0x0000000d, 0x00006e49, 0x00050005, 0x00000016, 0x78655473, 0x65727574, 0x00000000, 0x00040047, 0x00000009, 0x0000001e, - 0x00000000, 0x00040047, 0x0000000d, 0x0000001e, 0x00000000, 0x00040047, 0x00000016, 0x00000022, 0x00000000, 0x00040047, - 0x00000016, 0x00000021, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, - 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000007, 0x0004003b, - 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x0000000a, 0x00000006, 0x00000002, 0x0004001e, 0x0000000b, 0x00000007, - 0x0000000a, 0x00040020, 0x0000000c, 0x00000001, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040015, - 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, - 0x00000007, 0x00090019, 0x00000013, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, - 0x0003001b, 0x00000014, 0x00000013, 0x00040020, 0x00000015, 0x00000000, 0x00000014, 0x0004003b, 0x00000015, 0x00000016, - 0x00000000, 0x0004002b, 0x0000000e, 0x00000018, 0x00000001, 0x00040020, 0x00000019, 0x00000001, 0x0000000a, 0x00050036, - 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, - 0x0000000f, 0x0004003d, 0x00000007, 0x00000012, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, 0x00000016, 0x00050041, - 0x00000019, 0x0000001a, 0x0000000d, 0x00000018, 0x0004003d, 0x0000000a, 0x0000001b, 0x0000001a, 0x00050057, 0x00000007, - 0x0000001c, 0x00000017, 0x0000001b, 0x00050085, 0x00000007, 0x0000001d, 0x00000012, 0x0000001c, 0x0003003e, 0x00000009, - 0x0000001d, 0x000100fd, 0x00010038 - }; - - const VkShaderModuleCreateInfo shader_module_create_info{ - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .codeSize = sizeof(shader_data), - .pCode = shader_data - }; - vulkan_check_error(vkCreateShaderModule( - g_device, - &shader_module_create_info, - g_allocation_callbacks, - &g_pipeline_shader_module_fragment - ) - ); - } - - if (g_pipeline == VK_NULL_HANDLE) - { - const VkPipelineShaderStageCreateInfo pipeline_shader_stage_create_info_vertex{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .stage = VK_SHADER_STAGE_VERTEX_BIT, - .module = g_pipeline_shader_module_vertex, - .pName = "main", - .pSpecializationInfo = nullptr - }; - const VkPipelineShaderStageCreateInfo pipeline_shader_stage_create_info_fragment{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .stage = VK_SHADER_STAGE_FRAGMENT_BIT, - .module = g_pipeline_shader_module_fragment, - .pName = "main", - .pSpecializationInfo = nullptr - }; - const VkPipelineShaderStageCreateInfo pipeline_shader_stage_create_info[2] - { - pipeline_shader_stage_create_info_vertex, - pipeline_shader_stage_create_info_fragment - }; - - constexpr VkVertexInputBindingDescription vertex_input_binding_description{ - .binding = 0, .stride = sizeof(vertex_type), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX - }; - constexpr VkVertexInputAttributeDescription vertex_input_attribute_description_position{ - .location = 0, - .binding = vertex_input_binding_description.binding, - .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof(vertex_type, position) - }; - constexpr VkVertexInputAttributeDescription vertex_input_attribute_description_uv{ - .location = 1, - .binding = vertex_input_binding_description.binding, - .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof(vertex_type, uv) - }; - constexpr VkVertexInputAttributeDescription vertex_input_attribute_description_color{ - .location = 2, - .binding = vertex_input_binding_description.binding, - .format = VK_FORMAT_R8G8B8A8_UNORM, - .offset = offsetof(vertex_type, color) - }; - constexpr VkVertexInputAttributeDescription vertex_input_attribute_descriptions[3]{ - vertex_input_attribute_description_position, - vertex_input_attribute_description_uv, - vertex_input_attribute_description_color - }; - const VkPipelineVertexInputStateCreateInfo pipeline_vertex_input_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &vertex_input_binding_description, - .vertexAttributeDescriptionCount = 3, - .pVertexAttributeDescriptions = vertex_input_attribute_descriptions - }; - - constexpr VkPipelineInputAssemblyStateCreateInfo pipeline_input_assembly_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - .primitiveRestartEnable = VK_FALSE - }; - - constexpr VkPipelineViewportStateCreateInfo pipeline_viewport_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .viewportCount = 1, - .pViewports = nullptr, - .scissorCount = 1, - .pScissors = nullptr - }; - - constexpr VkPipelineRasterizationStateCreateInfo pipeline_rasterization_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .depthClampEnable = VK_FALSE, - .rasterizerDiscardEnable = VK_FALSE, - .polygonMode = VK_POLYGON_MODE_FILL, - .cullMode = VK_CULL_MODE_NONE, - .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, - .depthBiasEnable = VK_FALSE, - .depthBiasConstantFactor = .0f, - .depthBiasClamp = .0f, - .depthBiasSlopeFactor = .0f, - .lineWidth = 1.f - }; - - const VkPipelineMultisampleStateCreateInfo pipeline_multi_sample_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .rasterizationSamples = g_pipeline_rasterization_msaa, - .sampleShadingEnable = false, - .minSampleShading = .0f, - .pSampleMask = nullptr, - .alphaToCoverageEnable = VK_FALSE, - .alphaToOneEnable = VK_FALSE - }; - - constexpr VkPipelineDepthStencilStateCreateInfo pipeline_depth_stencil_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .depthTestEnable = VK_FALSE, - .depthWriteEnable = VK_FALSE, - .depthCompareOp = VK_COMPARE_OP_NEVER, - .depthBoundsTestEnable = VK_FALSE, - .stencilTestEnable = VK_FALSE, - .front = {}, - .back = {}, - .minDepthBounds = .0f, - .maxDepthBounds = .0f - }; - - constexpr VkPipelineColorBlendAttachmentState pipeline_color_blend_attachment_state{ - .blendEnable = VK_TRUE, - .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, - .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .colorBlendOp = VK_BLEND_OP_ADD, - .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, - .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .alphaBlendOp = VK_BLEND_OP_ADD, - .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT - }; - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkPipelineColorBlendStateCreateInfo pipeline_color_blend_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .logicOpEnable = VK_FALSE, - .logicOp = VK_LOGIC_OP_CLEAR, - .attachmentCount = 1, - .pAttachments = &pipeline_color_blend_attachment_state, - .blendConstants = {} - }; - - constexpr VkDynamicState dynamic_state[2]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkPipelineDynamicStateCreateInfo pipeline_dynamic_state_create_info{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .dynamicStateCount = std::ranges::size(dynamic_state), - .pDynamicStates = dynamic_state - }; - - VkGraphicsPipelineCreateInfo pipeline_create_info{ - .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = g_pipeline_create_flags, - .stageCount = 2, - .pStages = pipeline_shader_stage_create_info, - .pVertexInputState = &pipeline_vertex_input_state_create_info, - .pInputAssemblyState = &pipeline_input_assembly_state_create_info, - .pTessellationState = nullptr, - .pViewportState = &pipeline_viewport_state_create_info, - .pRasterizationState = &pipeline_rasterization_state_create_info, - .pMultisampleState = &pipeline_multi_sample_state_create_info, - .pDepthStencilState = &pipeline_depth_stencil_state_create_info, - .pColorBlendState = &pipeline_color_blend_state_create_info, - .pDynamicState = &pipeline_dynamic_state_create_info, - .layout = g_pipeline_layout, - .renderPass = g_pipeline_render_pass, - .subpass = g_pipeline_sub_pass, - .basePipelineHandle = VK_NULL_HANDLE, - .basePipelineIndex = 0 - }; - if (g_pipeline_use_dynamic_rendering) - { - assert(g_pipeline_rendering_create_info.sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR && - "pipeline_rendering_create_info sType must be VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR"); - assert(g_pipeline_rendering_create_info.pNext == nullptr && "pipeline_rendering_create_info.pNext must be NULL"); - - pipeline_create_info.pNext = &g_pipeline_rendering_create_info; - pipeline_create_info.renderPass = VK_NULL_HANDLE; - } - - vulkan_check_error(vkCreateGraphicsPipelines(g_device, g_pipeline_cache, 1, &pipeline_create_info, g_allocation_callbacks, &g_pipeline)); - } - } - - auto init() -> void - { - glfw_init(); - vulkan_init(); - } - - auto destroy_font_texture() -> void - { - if (g_font_descriptor_set != VK_NULL_HANDLE) - { - vkFreeDescriptorSets(g_device, g_descriptor_pool, 1, &g_font_descriptor_set); - g_font_descriptor_set = VK_NULL_HANDLE; - } - if (g_font_image_view != VK_NULL_HANDLE) - { - vkDestroyImageView(g_device, g_font_image_view, g_allocation_callbacks); - g_font_image_view = VK_NULL_HANDLE; - } - if (g_font_image != VK_NULL_HANDLE) - { - vkDestroyImage(g_device, g_font_image, g_allocation_callbacks); - g_font_image = VK_NULL_HANDLE; - } - if (g_font_memory != VK_NULL_HANDLE) - { - vkFreeMemory(g_device, g_font_memory, g_allocation_callbacks); - g_font_memory = VK_NULL_HANDLE; - } - } - - auto create_font_texture() -> void - { - if (g_font_descriptor_set != VK_NULL_HANDLE) - { - return; - } - - // Destroy existing texture (if any) - if (g_font_memory or g_font_image or g_font_image_view) - { - vkQueueWaitIdle(g_queue); - destroy_font_texture(); - } - - // Create command pool/buffer - if (g_font_command_pool == VK_NULL_HANDLE) - { - const VkCommandPoolCreateInfo command_pool_create_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, .flags = 0, - .queueFamilyIndex = g_queue_family - }; - vulkan_check_error(vkCreateCommandPool( - g_device, - &command_pool_create_info, - g_allocation_callbacks, - &g_font_command_pool - ) - ); - } - if (g_font_command_buffer == VK_NULL_HANDLE) - { - const VkCommandBufferAllocateInfo command_buffer_allocate_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .pNext = nullptr, - .commandPool = g_font_command_pool, - .commandBufferCount = 1 - }; - vulkan_check_error(vkAllocateCommandBuffers(g_device, &command_buffer_allocate_info, &g_font_command_buffer)); - } - - // Start command buffer - { - vulkan_check_error(vkResetCommandPool(g_device, g_font_command_pool, 0)); - - constexpr VkCommandBufferBeginInfo command_buffer_begin_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - .pInheritanceInfo = nullptr - }; - vulkan_check_error(vkBeginCommandBuffer(g_font_command_buffer, &command_buffer_begin_info)); - } - - // todo: RGBA[8+8+8+8] - const auto [pixels, width, height] = load_font(); - const auto upload_size = static_cast(width) * height * 4 * sizeof(unsigned char); - - // Create Image - { - const VkImageCreateInfo image_create_info{ - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .imageType = VK_IMAGE_TYPE_2D, - .format = VK_FORMAT_R8G8B8A8_UNORM, - .extent = {.width = static_cast(width), .height = static_cast(height), .depth = 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED - }; - vulkan_check_error(vkCreateImage(g_device, &image_create_info, g_allocation_callbacks, &g_font_image)); - - VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(g_device, g_font_image, &memory_requirements); - - const VkMemoryAllocateInfo memory_allocate_info{ - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = nullptr, - .allocationSize = memory_requirements.size, - .memoryTypeIndex = memory_type(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memory_requirements.memoryTypeBits) - }; - vulkan_check_error(vkAllocateMemory(g_device, &memory_allocate_info, g_allocation_callbacks, &g_font_memory)); - vulkan_check_error(vkBindImageMemory(g_device, g_font_image, g_font_memory, 0)); - } - - // Create Image View - { - const VkImageViewCreateInfo image_view_create_info{ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = g_font_image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = VK_FORMAT_R8G8B8A8_UNORM, - .components = {}, - .subresourceRange = - { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - } - }; - vulkan_check_error(vkCreateImageView(g_device, &image_view_create_info, g_allocation_callbacks, &g_font_image_view)); - } - - // Create Descriptor Set - { - const VkDescriptorSetAllocateInfo descriptor_set_allocate_info{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .pNext = nullptr, - .descriptorPool = g_descriptor_pool, - .descriptorSetCount = 1, - .pSetLayouts = &g_pipeline_descriptor_set_layout - }; - vulkan_check_error(vkAllocateDescriptorSets(g_device, &descriptor_set_allocate_info, &g_font_descriptor_set)); - - const VkDescriptorImageInfo descriptor_image_info{ - .sampler = g_font_sampler, .imageView = g_font_image_view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL - }; - const VkWriteDescriptorSet write_descriptor_set{ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = g_font_descriptor_set, - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = &descriptor_image_info, - .pBufferInfo = nullptr, - .pTexelBufferView = nullptr - }; - vkUpdateDescriptorSets(g_device, 1, &write_descriptor_set, 0, nullptr); - } - - // Create Upload Buffer - { - const VkBufferCreateInfo buffer_create_info{ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = upload_size, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr - }; - - VkBuffer upload_buffer; - vulkan_check_error(vkCreateBuffer(g_device, &buffer_create_info, g_allocation_callbacks, &upload_buffer)); - - VkMemoryRequirements memory_requirements; - vkGetBufferMemoryRequirements(g_device, upload_buffer, &memory_requirements); - - const VkMemoryAllocateInfo memory_allocate_info{ - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = nullptr, - .allocationSize = memory_requirements.size, - .memoryTypeIndex = memory_type(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memory_requirements.memoryTypeBits) - }; - - VkDeviceMemory upload_buffer_memory; - vulkan_check_error(vkAllocateMemory(g_device, &memory_allocate_info, g_allocation_callbacks, &upload_buffer_memory) - ); - vulkan_check_error(vkBindBufferMemory(g_device, upload_buffer, upload_buffer_memory, 0)); - - // Upload to Buffer - unsigned char* mapped_memory; - vulkan_check_error(vkMapMemory(g_device, upload_buffer_memory, 0, upload_size, 0, reinterpret_cast(&mapped_memory)) - ); - std::memcpy(mapped_memory, pixels, upload_size); - const VkMappedMemoryRange mapped_memory_range{ - .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, - .pNext = nullptr, - .memory = upload_buffer_memory, - .offset = 0, - .size = upload_size - }; - vulkan_check_error(vkFlushMappedMemoryRanges(g_device, 1, &mapped_memory_range)); - vkUnmapMemory(g_device, upload_buffer_memory); - - // Copy to Image - const VkImageMemoryBarrier copy_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = g_font_image, - .subresourceRange = - {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 - } - }; - vkCmdPipelineBarrier( - g_font_command_buffer, - VK_PIPELINE_STAGE_HOST_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, - 0, - nullptr, - 0, - nullptr, - 1, - ©_barrier - ); - - const VkBufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, - .imageOffset = {.x = 0, .y = 0, .z = 0}, - .imageExtent = {.width = static_cast(width), .height = static_cast(height), .depth = 1} - }; - vkCmdCopyBufferToImage( - g_font_command_buffer, - upload_buffer, - g_font_image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - ®ion - ); - - const VkImageMemoryBarrier use_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = g_font_image, - .subresourceRange = - {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 - } - }; - vkCmdPipelineBarrier( - g_font_command_buffer, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, - 0, - nullptr, - 0, - nullptr, - 1, - &use_barrier - ); - - // End command buffer - // ReSharper disable once CppVariableCanBeMadeConstexpr - const VkSubmitInfo submit_info{ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreCount = 0, - .pWaitSemaphores = nullptr, - .pWaitDstStageMask = nullptr, - .commandBufferCount = 1, - .pCommandBuffers = &g_font_command_buffer, - .signalSemaphoreCount = 0, - .pSignalSemaphores = nullptr - }; - vulkan_check_error(vkEndCommandBuffer(g_font_command_buffer)); - vulkan_check_error(vkQueueSubmit(g_queue, 1, &submit_info, VK_NULL_HANDLE)); - vulkan_check_error(vkQueueWaitIdle(g_queue)); - - vkDestroyBuffer(g_device, upload_buffer, g_allocation_callbacks); - vkFreeMemory(g_device, upload_buffer_memory, g_allocation_callbacks); - } - } - - auto new_frame() -> void - { - // checked by callee - create_font_texture(); - - glfwGetWindowSize(g_window, &g_window_width, &g_window_height); - glfwGetFramebufferSize(g_window, &g_window_frame_buffer_width, &g_window_frame_buffer_height); - } - - auto frame_render() -> void - { - const auto image_acquired_semaphore = g_window_frame_semaphores[g_window_frame_semaphore_current_index].image_acquired_semaphore; - const auto render_complete_semaphore = g_window_frame_semaphores[g_window_frame_semaphore_current_index].render_complete_semaphore; - - if (const auto result = vkAcquireNextImageKHR( - g_device, - g_window_swap_chain, - std::numeric_limits::max(), - image_acquired_semaphore, - VK_NULL_HANDLE, - &g_window_frame_current_index - ); - result == VK_ERROR_OUT_OF_DATE_KHR or result == VK_SUBOPTIMAL_KHR) - { - g_window_swap_chain_rebuild_required = true; - return; - } - else - { - vulkan_check_error(result); - } - - const auto& this_frame = g_window_frames[g_window_frame_current_index]; - { - vulkan_check_error(vkWaitForFences(g_device, 1, &this_frame.fence, VK_TRUE, std::numeric_limits::max())); - vulkan_check_error(vkResetFences(g_device, 1, &this_frame.fence)); - } - { - vulkan_check_error(vkResetCommandPool(g_device, this_frame.command_pool, 0)); - - constexpr VkCommandBufferBeginInfo command_buffer_begin_info{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - .pInheritanceInfo = nullptr - }; - vulkan_check_error(vkBeginCommandBuffer(this_frame.command_buffer, &command_buffer_begin_info)); - } - { - const VkRenderPassBeginInfo render_pass_begin_info{ - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .pNext = nullptr, - .renderPass = g_pipeline_render_pass, - .framebuffer = this_frame.frame_buffer, - .renderArea = - {.offset = {.x = 0, .y = 0}, - .extent = {.width = static_cast(g_window_width), .height = static_cast(g_window_height)}}, - .clearValueCount = 1, - .pClearValues = &g_window_clear_value - }; - vkCmdBeginRenderPass(this_frame.command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); - } - - if (g_window_render_buffer == nullptr) - { - g_window_render_buffer_total_count = g_window_frame_total_count; - g_window_render_buffer_maker(); - } - g_window_render_buffer_current_index = (g_window_render_buffer_current_index + 1) % g_window_render_buffer_total_count; - - auto& this_render_buffer = g_window_render_buffer[g_window_render_buffer_current_index]; - { - if (g_draw_data.total_vertex_size() > 0) - { - static auto resize_buffer = - [](VkBuffer& buffer, VkDeviceMemory& memory, const VkDeviceSize new_size, const VkBufferUsageFlagBits usage) - { - if (buffer != VK_NULL_HANDLE) - { - vkDestroyBuffer(g_device, buffer, g_allocation_callbacks); - } - if (memory != VK_NULL_HANDLE) - { - vkFreeMemory(g_device, memory, g_allocation_callbacks); - } - - const VkBufferCreateInfo buffer_create_info{ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = new_size, - .usage = static_cast(usage), - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr - }; - vulkan_check_error(vkCreateBuffer(g_device, &buffer_create_info, g_allocation_callbacks, &buffer)); - - VkMemoryRequirements memory_requirements; - vkGetBufferMemoryRequirements(g_device, buffer, &memory_requirements); - - const VkMemoryAllocateInfo memory_allocate_info{ - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = nullptr, - .allocationSize = memory_requirements.size, - .memoryTypeIndex = memory_type(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memory_requirements.memoryTypeBits) - }; - vulkan_check_error(vkAllocateMemory(g_device, &memory_allocate_info, g_allocation_callbacks, &memory)); - - vulkan_check_error(vkBindBufferMemory(g_device, buffer, memory, 0)); - }; - - // Create or resize the vertex/index buffers - const auto vertex_size = g_draw_data.total_vertex_size() * sizeof(vertex_type); - const auto index_size = g_draw_data.total_index_size() * sizeof(vertex_index_type); - - if (this_render_buffer.vertex_buffer == VK_NULL_HANDLE or this_render_buffer.vertex_count < vertex_size) - { - resize_buffer( - this_render_buffer.vertex_buffer, - this_render_buffer.vertex_buffer_memory, - vertex_size, - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT - ); - } - if (this_render_buffer.index_buffer == VK_NULL_HANDLE or this_render_buffer.index_count < index_size) - { - resize_buffer( - this_render_buffer.index_buffer, - this_render_buffer.index_buffer_memory, - index_size, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT - ); - } - - // Upload vertex/index data into a single contiguous GPU buffer - { - struct target_vertex_type - { - float position[2]; - float uv[2]; - std::uint32_t color; - }; - static_assert(sizeof(target_vertex_type) == sizeof(vertex_type)); - - target_vertex_type* mapped_vertex = nullptr; - vertex_index_type* mapped_index = nullptr; - vulkan_check_error(vkMapMemory( - g_device, - this_render_buffer.vertex_buffer_memory, - 0, - vertex_size, - 0, - reinterpret_cast(&mapped_vertex) - )); - vulkan_check_error( - vkMapMemory(g_device, this_render_buffer.index_buffer_memory, 0, index_size, 0, reinterpret_cast(&mapped_index)) - ); - - for (const auto& [vertex_list, index_list]: g_draw_data.draw_lists) - { - std::ranges::transform( - vertex_list.vertices(), - mapped_vertex, - [](const auto& vertex) -> target_vertex_type - { - return { - .position = {vertex.position.x, vertex.position.y}, - .uv = {vertex.uv.x, vertex.uv.y}, - // todo - // .color = vertex.color.to(primitive::color_format) - .color = vertex.color.to(primitive::color_format) - }; - } - ); - std::ranges::copy(index_list, mapped_index); - - mapped_vertex += vertex_list.size(); - mapped_index += index_list.size(); - } - - const VkMappedMemoryRange mapped_memory_range_vertex{ - .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, - .pNext = nullptr, - .memory = this_render_buffer.vertex_buffer_memory, - .offset = 0, - .size = VK_WHOLE_SIZE - }; - const VkMappedMemoryRange mapped_memory_range_index{ - .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, - .pNext = nullptr, - .memory = this_render_buffer.index_buffer_memory, - .offset = 0, - .size = VK_WHOLE_SIZE - }; - const VkMappedMemoryRange mapped_memory_range[2]{mapped_memory_range_vertex, mapped_memory_range_index}; - vulkan_check_error(vkFlushMappedMemoryRanges(g_device, 2, mapped_memory_range)); - - vkUnmapMemory(g_device, this_render_buffer.vertex_buffer_memory); - vkUnmapMemory(g_device, this_render_buffer.index_buffer_memory); - } - } - - // Setup desired Vulkan state - { - // Bind pipeline - vkCmdBindPipeline(this_frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pipeline); - - // Bind Vertex And Index Buffer - if (g_draw_data.total_vertex_size() > 0) - { - constexpr VkDeviceSize offset[1]{}; - vkCmdBindVertexBuffers(this_frame.command_buffer, 0, 1, &this_render_buffer.vertex_buffer, offset); - vkCmdBindIndexBuffer( - this_frame.command_buffer, - this_render_buffer.index_buffer, - 0, - // ReSharper disable once CppUnreachableCode - sizeof(vertex_index_type) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32 - ); - } - - // Setup viewport - { - const VkViewport viewport{ - .x = 0, - .y = 0, - .width = static_cast(g_window_frame_buffer_width), - .height = static_cast(g_window_frame_buffer_height), - .minDepth = .0f, - .maxDepth = 1.f - }; - vkCmdSetViewport(this_frame.command_buffer, 0, 1, &viewport); - } - - // Setup scale and translation - { - const float scale[2]{2.f / g_draw_data.display_rect.width(), 2.f / g_draw_data.display_rect.height()}; - const float translate[2]{ - -1.f - g_draw_data.display_rect.left_top().x * scale[0], -1.f - g_draw_data.display_rect.left_top().y * scale[1] - }; - - vkCmdPushConstants( - this_frame.command_buffer, - g_pipeline_layout, - VK_SHADER_STAGE_VERTEX_BIT, - sizeof(float) * 0, - sizeof(float) * 2, - scale - ); - vkCmdPushConstants( - this_frame.command_buffer, - g_pipeline_layout, - VK_SHADER_STAGE_VERTEX_BIT, - sizeof(float) * 2, - sizeof(float) * 2, - translate - ); - } - } - - // Render command lists - { - std::ranges::for_each( - g_draw_data.draw_lists, - [&this_frame, - display_rect = g_draw_data.display_rect, - offset_vertex = static_cast(0), - offset_index = static_cast(0) - ](const draw_list_type& draw_list) mutable - { - // todo: clip rect - const VkRect2D scissor{ - .offset = - {.x = static_cast(display_rect.left_top().x), - .y = static_cast(display_rect.left_top().y)}, - .extent = - {.width = static_cast(display_rect.width()), - .height = static_cast(display_rect.height())} - }; - vkCmdSetScissor(this_frame.command_buffer, 0, 1, &scissor); - - // Bind DescriptorSet with font - vkCmdBindDescriptorSets( - this_frame.command_buffer, - VK_PIPELINE_BIND_POINT_GRAPHICS, - g_pipeline_layout, - 0, - 1, - &g_font_descriptor_set, - 0, - nullptr - ); - - // Draw - vkCmdDrawIndexed(this_frame.command_buffer, - static_cast(draw_list.index_list.size()), - 1, - offset_index, - offset_vertex, - 0); - - offset_vertex += draw_list.vertex_list.size(); - offset_index += draw_list.index_list.size(); - } - ); - } - - const VkRect2D scissor = {.offset = {.x = 0, .y = 0}, - .extent = {.width = static_cast(g_window_frame_buffer_width), - .height = static_cast(g_window_frame_buffer_height)}}; - vkCmdSetScissor(this_frame.command_buffer, 0, 1, &scissor); - } - - // Submit command buffer - vkCmdEndRenderPass(this_frame.command_buffer); - { - constexpr VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - const VkSubmitInfo submit_info{ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &image_acquired_semaphore, - .pWaitDstStageMask = &wait_stage, - .commandBufferCount = 1, - .pCommandBuffers = &this_frame.command_buffer, - .signalSemaphoreCount = 1, - .pSignalSemaphores = &render_complete_semaphore - }; - - vulkan_check_error(vkEndCommandBuffer(this_frame.command_buffer)); - vulkan_check_error(vkQueueSubmit(g_queue, 1, &submit_info, this_frame.fence)); - } - } - - auto frame_present() -> void - { - if (g_window_swap_chain_rebuild_required) - { - return; - } - - const auto render_complete_semaphore = g_window_frame_semaphores[g_window_frame_semaphore_current_index].render_complete_semaphore; - const VkPresentInfoKHR present_info{ - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = nullptr, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &render_complete_semaphore, - .swapchainCount = 1, - .pSwapchains = &g_window_swap_chain, - .pImageIndices = &g_window_frame_current_index, - .pResults = nullptr - }; - - if (const auto result = vkQueuePresentKHR(g_queue, &present_info); result == VK_ERROR_OUT_OF_DATE_KHR or result == VK_SUBOPTIMAL_KHR) - { - g_window_swap_chain_rebuild_required = true; - return; - } - else - { - vulkan_check_error(result); - } - - g_window_frame_semaphore_current_index = (g_window_frame_semaphore_current_index + 1) % g_window_frame_semaphore_total_count; - } - - auto destroy_device_objects() -> void - { - // ============== - // render buffer - g_window_render_buffer_destroyer(g_device, g_allocation_callbacks); - - // ============== - // font - destroy_font_texture(); - if (g_font_sampler != VK_NULL_HANDLE) - { - vkDestroySampler(g_device, g_font_sampler, g_allocation_callbacks); - g_font_sampler = VK_NULL_HANDLE; - } - if (g_font_command_buffer != VK_NULL_HANDLE) - { - vkFreeCommandBuffers(g_device, g_font_command_pool, 1, &g_font_command_buffer); - g_font_command_buffer = VK_NULL_HANDLE; - } - if (g_font_command_pool != VK_NULL_HANDLE) - { - vkDestroyCommandPool(g_device, g_font_command_pool, g_allocation_callbacks); - g_font_command_pool = VK_NULL_HANDLE; - } - - // ============== - // pipeline - if (g_pipeline_shader_module_vertex != VK_NULL_HANDLE) - { - vkDestroyShaderModule(g_device, g_pipeline_shader_module_vertex, g_allocation_callbacks); - g_pipeline_shader_module_vertex = VK_NULL_HANDLE; - } - if (g_pipeline_shader_module_fragment != VK_NULL_HANDLE) - { - vkDestroyShaderModule(g_device, g_pipeline_shader_module_fragment, g_allocation_callbacks); - g_pipeline_shader_module_fragment = VK_NULL_HANDLE; - } - if (g_pipeline_layout != VK_NULL_HANDLE) - { - vkDestroyPipelineLayout(g_device, g_pipeline_layout, g_allocation_callbacks); - g_pipeline_layout = VK_NULL_HANDLE; - } - if (g_pipeline_descriptor_set_layout != VK_NULL_HANDLE) - { - vkDestroyDescriptorSetLayout(g_device, g_pipeline_descriptor_set_layout, g_allocation_callbacks); - g_pipeline_descriptor_set_layout = VK_NULL_HANDLE; - } - if (g_pipeline) - { - vkDestroyPipeline(g_device, g_pipeline, g_allocation_callbacks); - g_pipeline = VK_NULL_HANDLE; - } - } - - auto shutdown() -> void - { - destroy_device_objects(); - - glfwSetWindowFocusCallback(g_window, g_glfw_callback_window_focus); - glfwSetCursorEnterCallback(g_window, g_glfw_callback_window_cursor_enter); - glfwSetCursorPosCallback(g_window, g_glfw_callback_window_cursor_position); - glfwSetMouseButtonCallback(g_window, g_glfw_callback_window_mouse_button); - glfwSetScrollCallback(g_window, g_glfw_callback_window_scroll); - glfwSetKeyCallback(g_window, g_glfw_callback_window_key); - glfwSetCharCallback(g_window, g_glfw_callback_window_char); - glfwSetMonitorCallback(g_glfw_callback_window_monitor); - } - - auto vulkan_cleanup_window() -> void - { - vulkan_check_error(vkDeviceWaitIdle(g_device)); - - g_window_frames_destroyer(g_device, g_allocation_callbacks); - g_window_frame_semaphores_destroyer(g_device, g_allocation_callbacks); - - vkDestroyPipeline(g_device, g_pipeline, g_allocation_callbacks); - vkDestroyRenderPass(g_device, g_pipeline_render_pass, g_allocation_callbacks); - vkDestroySwapchainKHR(g_device, g_window_swap_chain, g_allocation_callbacks); - vkDestroySurfaceKHR(g_instance, g_window_surface, g_allocation_callbacks); - } - - auto vulkan_cleanup() -> void - { - vkDestroyDescriptorPool(g_device, g_descriptor_pool, g_allocation_callbacks); - - const auto destroy_debug_report_callback = - reinterpret_cast( // NOLINT(clang-diagnostic-cast-function-type-strict) - vkGetInstanceProcAddr(g_instance, "vkDestroyDebugReportCallbackEXT") - ); - destroy_debug_report_callback(g_instance, g_debug_report_callback, g_allocation_callbacks); - - vkDestroyDevice(g_device, g_allocation_callbacks); - vkDestroyInstance(g_instance, g_allocation_callbacks); - } -} diff --git a/unit_test/src/draw/CMakeLists.txt b/unit_test/src/gui/CMakeLists.txt similarity index 100% rename from unit_test/src/draw/CMakeLists.txt rename to unit_test/src/gui/CMakeLists.txt diff --git a/unit_test/src/draw/common/glfw_callback_handler.cpp b/unit_test/src/gui/common/glfw_callback_handler.cpp similarity index 55% rename from unit_test/src/draw/common/glfw_callback_handler.cpp rename to unit_test/src/gui/common/glfw_callback_handler.cpp index 1b162b0..33c7ef6 100644 --- a/unit_test/src/draw/common/glfw_callback_handler.cpp +++ b/unit_test/src/gui/common/glfw_callback_handler.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace { @@ -18,22 +19,22 @@ namespace GLFWcharfun g_glfw_callback_window_char; GLFWmonitorfun g_glfw_callback_window_monitor; - enum class MouseButton + enum class MouseButton : std::uint8_t { LEFT = GLFW_MOUSE_BUTTON_LEFT, RIGHT = GLFW_MOUSE_BUTTON_RIGHT, MIDDLE = GLFW_MOUSE_BUTTON_MIDDLE, - X1, - X2, + X1 = GLFW_MOUSE_BUTTON_4, + X2 = GLFW_MOUSE_BUTTON_5, }; - enum class MouseAction + enum class MouseAction : std::uint8_t { RELEASE = GLFW_RELEASE, PRESS = GLFW_PRESS, }; - enum class MouseMod + enum class MouseMod : std::uint8_t { NONE = 0, SHIFT = GLFW_MOD_SHIFT, @@ -44,7 +45,7 @@ namespace NUM_LOCK = GLFW_MOD_NUM_LOCK, }; - enum class KeyboardKeyCode + enum class KeyboardKeyCode : std::uint8_t { NUM_0 = GLFW_KEY_0, NUM_1 = GLFW_KEY_1, @@ -84,14 +85,14 @@ namespace Z = GLFW_KEY_Z, }; - enum class KeyboardAction + enum class KeyboardAction : std::uint8_t { RELEASE = GLFW_RELEASE, PRESS = GLFW_PRESS, REPEAT = GLFW_REPEAT, }; - enum class KeyboardMod + enum class KeyboardMod : std::uint8_t { NONE = 0, SHIFT = GLFW_MOD_SHIFT, @@ -139,135 +140,177 @@ struct meta::user_defined::enum_name_policy constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; }; -// io::DeviceEventQueue g_device_event_queue; +bool g_print_glfw_message = true; +bool g_print_glfw_message_focus = false; +bool g_print_glfw_message_cursor_enter = false; +bool g_print_glfw_message_cursor_position = false; +bool g_print_glfw_message_mouse_button = false; +bool g_print_glfw_message_scroll = false; +bool g_print_glfw_message_key = false; +bool g_print_glfw_message_char = false; +bool g_print_glfw_message_monitor = false; auto glfw_callback_setup(GLFWwindow& w) -> void { - static auto callback_window_focus = [](GLFWwindow* window, const int focused) + static auto callback_window_focus = [](GLFWwindow* window, const int focused) noexcept -> void { - std::println(stdout, "[FOCUS]: window: 0x{:x}, focused: {}", reinterpret_cast(window), focused != 0); + if (g_print_glfw_message and g_print_glfw_message_focus) + { + std::println(stdout, "[FOCUS]: window: 0x{:x}, focused: {}", reinterpret_cast(window), focused != 0); + } if (g_glfw_callback_window_focus) { g_glfw_callback_window_focus(window, focused); } }; - static auto callback_window_cursor_enter = [](GLFWwindow* window, const int entered) + static auto callback_window_cursor_enter = [](GLFWwindow* window, const int entered) noexcept -> void { - std::println( - stdout, - "[CURSOR]: window: 0x{:x}, entered: {}", - reinterpret_cast(window), - entered != 0 - ); + if (g_print_glfw_message and g_print_glfw_message_cursor_enter) + { + std::println( + stdout, + "[CURSOR]: window: 0x{:x}, entered: {}", + reinterpret_cast(window), + entered != 0 + ); + } if (g_glfw_callback_window_cursor_enter) { g_glfw_callback_window_cursor_enter(window, entered); } }; - static auto callback_window_cursor_position = [](GLFWwindow* window, const double x, const double y) + static auto callback_window_cursor_position = [](GLFWwindow* window, const double x, const double y) noexcept -> void { - std::println(stdout, "[CURSOR]: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); + if (g_print_glfw_message and g_print_glfw_message_cursor_position) + { + std::println(stdout, "[CURSOR]: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); + } + + auto& io = gui::get_io(); - // g_device_event_queue.mouse_move(static_cast(x), static_cast(y)); + io.mouse_position = {static_cast(x), static_cast(y)}; if (g_glfw_callback_window_cursor_position) { g_glfw_callback_window_cursor_position(window, x, y); } }; - static auto callback_window_mouse_button = [](GLFWwindow* window, const int button, const int action, const int mods) + static auto callback_window_mouse_button = [](GLFWwindow* window, const int button, const int action, const int mods) noexcept -> void { - std::println( - stdout, - "[MOUSE]: window: 0x{:x}, button: [{}], action: [{}], mods: [{}]", - reinterpret_cast(window), - meta::to_string(static_cast(button)), - meta::to_string(static_cast(action)), - meta::to_string(static_cast(mods)) - ); - - // const auto status = static_cast(action) == MouseAction::PRESS ? io::MouseButtonStatus::PRESS : io::MouseButtonStatus::RELEASE; - // switch (static_cast(button)) - // { - // case MouseButton::LEFT: - // { - // g_device_event_queue.mouse_button(io::MouseButton::LEFT, status); - // break; - // } - // case MouseButton::MIDDLE: - // { - // g_device_event_queue.mouse_button(io::MouseButton::MIDDLE, status); - // break; - // } - // case MouseButton::RIGHT: - // { - // g_device_event_queue.mouse_button(io::MouseButton::RIGHT, status); - // break; - // } - // case MouseButton::X1: - // { - // g_device_event_queue.mouse_button(io::MouseButton::X1, status); - // break; - // } - // case MouseButton::X2: - // { - // g_device_event_queue.mouse_button(io::MouseButton::X2, status); - // break; - // } - // default: - // { - // break; - // } - // } + if (g_print_glfw_message and g_print_glfw_message_mouse_button) + { + std::println( + stdout, + "[MOUSE]: window: 0x{:x}, button: [{}], action: [{}], mods: [{}]", + reinterpret_cast(window), + meta::to_string(static_cast(button)), + meta::to_string(static_cast(action)), + meta::to_string(static_cast(mods)) + ); + } + + auto& io = gui::get_io(); + const auto pressed = static_cast(action) == MouseAction::PRESS; + + switch (static_cast(button)) + { + case MouseButton::LEFT: + { + io.mouse_button_state[gui::MouseKey::LEFT] = pressed; + + break; + } + case MouseButton::MIDDLE: + { + io.mouse_button_state[gui::MouseKey::MIDDLE] = pressed; + + break; + } + case MouseButton::RIGHT: + { + io.mouse_button_state[gui::MouseKey::RIGHT] = pressed; + + break; + } + case MouseButton::X1: + { + io.mouse_button_state[gui::MouseKey::X1] = pressed; + + break; + } + case MouseButton::X2: + { + io.mouse_button_state[gui::MouseKey::X2] = pressed; + + break; + } + default: + { + break; + } + } if (g_glfw_callback_window_mouse_button) { g_glfw_callback_window_mouse_button(window, button, action, mods); } }; - static auto callback_window_scroll = [](GLFWwindow* window, const double x, const double y) + static auto callback_window_scroll = [](GLFWwindow* window, const double x, const double y) noexcept -> void { - std::println(stdout, "[MOUSE SCROLL]: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); + if (g_print_glfw_message and g_print_glfw_message_scroll) + { + std::println(stdout, "[MOUSE SCROLL]: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); + } - // g_device_event_queue.mouse_wheel(static_cast(x), static_cast(y)); + auto& io = gui::get_io(); + io.mouse_wheel = static_cast(y); if (g_glfw_callback_window_scroll) { g_glfw_callback_window_scroll(window, x, y); } }; - static auto callback_window_key = [](GLFWwindow* window, const int key_code, const int scan_code, const int action, const int mods) + static auto callback_window_key = [](GLFWwindow* window, const int key_code, const int scan_code, const int action, const int mods) noexcept -> void { - std::println( - stdout, - "[KEYBOARD]: window: 0x{:x}, key_code: [{}]({}), scan_code: {}, action: {}, mods: {}", - reinterpret_cast(window), - meta::to_string(static_cast(key_code)), - key_code, - scan_code, - meta::to_string(static_cast(action)), - meta::to_string(static_cast(mods)) - ); + if (g_print_glfw_message and g_print_glfw_message_key) + { + std::println( + stdout, + "[KEYBOARD]: window: 0x{:x}, key_code: [{}]({}), scan_code: {}, action: {}, mods: {}", + reinterpret_cast(window), + meta::to_string(static_cast(key_code)), + key_code, + scan_code, + meta::to_string(static_cast(action)), + meta::to_string(static_cast(mods)) + ); + } if (g_glfw_callback_window_key) { g_glfw_callback_window_key(window, key_code, scan_code, action, mods); } }; - static auto callback_window_char = [](GLFWwindow* window, const unsigned int codepoint) + static auto callback_window_char = [](GLFWwindow* window, const unsigned int codepoint) noexcept -> void { - std::println(stdout, "[KEYBOARD]: window: 0x{:x}, codepoint: 0x{:x}", reinterpret_cast(window), codepoint); + if (g_print_glfw_message and g_print_glfw_message_char) + { + std::println(stdout, "[KEYBOARD]: window: 0x{:x}, codepoint: 0x{:x}", reinterpret_cast(window), codepoint); + } if (g_glfw_callback_window_char) { g_glfw_callback_window_char(window, codepoint); } }; - static auto callback_window_monitor = [](GLFWmonitor* monitor, const int event) + static auto callback_window_monitor = [](GLFWmonitor* monitor, const int event) noexcept -> void { - std::println(stdout, "[MONITOR]: monitor: 0x{:x}, event: {}", reinterpret_cast(monitor), event); + if (g_print_glfw_message and g_print_glfw_message_monitor) + { + std::println(stdout, "[MONITOR]: monitor: 0x{:x}, event: {}", reinterpret_cast(monitor), event); + } if (g_glfw_callback_window_monitor) { diff --git a/unit_test/src/draw/common/print_time.hpp b/unit_test/src/gui/common/print_time.hpp similarity index 100% rename from unit_test/src/draw/common/print_time.hpp rename to unit_test/src/gui/common/print_time.hpp diff --git a/unit_test/src/draw/dx12/CMakeLists.txt b/unit_test/src/gui/dx11/CMakeLists.txt similarity index 96% rename from unit_test/src/draw/dx12/CMakeLists.txt rename to unit_test/src/gui/dx11/CMakeLists.txt index 4741bfc..66d6c2d 100644 --- a/unit_test/src/draw/dx12/CMakeLists.txt +++ b/unit_test/src/gui/dx11/CMakeLists.txt @@ -1,5 +1,5 @@ project( - prometheus-draw-dx12 + prometheus-gui-dx11 LANGUAGES CXX ) diff --git a/unit_test/src/draw/dx11/backend.cpp b/unit_test/src/gui/dx11/backend.cpp similarity index 64% rename from unit_test/src/draw/dx11/backend.cpp rename to unit_test/src/gui/dx11/backend.cpp index 2625887..dad3799 100644 --- a/unit_test/src/draw/dx11/backend.cpp +++ b/unit_test/src/gui/dx11/backend.cpp @@ -1,6 +1,5 @@ #include "../win/def.hpp" #include "../common/print_time.hpp" - #include #include #include @@ -24,8 +23,6 @@ extern double g_last_time; extern std::uint64_t g_frame_count; extern float g_fps; -// extern io::DeviceEventQueue g_device_event_queue; - namespace { struct render_buffer_type @@ -53,8 +50,6 @@ namespace ComPtr g_additional_picture_texture = nullptr; - draw::Window g_window{"Window 1", {100, 100, 640, 480}}; - [[nodiscard]] auto load_texture( const std::uint8_t* texture_data, const std::uint32_t texture_width, @@ -109,16 +104,6 @@ auto prometheus_init() -> void // { print_time(); - const auto glyph_range = i18n::RangeBuilder{}.simplified_chinese_common().range(); - const auto font_option = - draw::Font::option() - .path(R"(C:\Windows\Fonts\msyh.ttc)") - .glyph_ranges(i18n::RangeBuilder{}.simplified_chinese_common().range()) - .pixel_height(18); - - auto font = std::make_shared(); - auto font_texture = font->load(font_option); - // Create the blending setup { constexpr D3D11_RENDER_TARGET_BLEND_DESC render_target{ @@ -376,17 +361,29 @@ auto prometheus_init() -> void // check_hr_error(g_device->CreateSamplerState(&sampler_desc, g_font_sampler.ReleaseAndGetAddressOf())); } + gui::create_current_context(); + + set_default_theme(gui::test_theme()); + set_default_draw_list_flag(gui::DrawListFlag::ANTI_ALIASED_LINE | gui::DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | gui::DrawListFlag::ANTI_ALIASED_FILL); + + gui::FontOption font_option{}; + font_option.font_path = R"(C:\Windows\Fonts\msyh.ttc)"; + font_option.pixel_height = 18; + font_option.glyph_ranges = i18n::RangeBuilder{}.simplified_chinese_common().range(); + auto font_texture = set_default_font(font_option); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_texture.valid()); + // Load default font texture { [[maybe_unused]] const auto load_font_texture_result = load_texture( - reinterpret_cast(font_texture.data().get()), - font_texture.width(), - font_texture.height(), + reinterpret_cast(font_texture.data.get()), + font_texture.width, + font_texture.height, g_font_texture ); assert(load_font_texture_result); - font_texture.bind(reinterpret_cast(g_font_texture.Get())); + font_texture.bind(reinterpret_cast(g_font_texture.Get())); } // Load additional picture texture @@ -407,157 +404,98 @@ auto prometheus_init() -> void // stbi_image_free(data); } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font->loaded()); - - auto& context = draw::Context::instance(); - auto& draw_list = g_window.test_draw_list(); - - context.set_default_font(font); - context.test_set_window(g_window); - - draw_list.draw_list_flag(draw::DrawListFlag::ANTI_ALIASED_LINE | draw::DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | draw::DrawListFlag::ANTI_ALIASED_FILL); - draw_list.reset(); - - g_window.test_init(); } auto prometheus_new_frame() -> void // { - auto& draw_list = g_window.test_draw_list(); + auto& io = gui::get_io(); + + io.display_size = {static_cast(g_window_width), static_cast(g_window_height)}; + io.delta_time = 1.f / g_fps; - draw_list.reset(); - draw_list.push_clip_rect({0, 0}, {static_cast(g_window_width), static_cast(g_window_height)}, false); + gui::new_frame(); } auto prometheus_render() -> void { - g_window.test_init(); - if (g_window.draw_button("Button")) + if (static bool window_closed = false; + not window_closed) { - std::println(stdout, "Button!"); - } + window_closed = gui::begin_window("Window 1", {640, 480}); - g_window.draw_text("Hello1"); - g_window.draw_text("World1"); + gui::draw_text("Text:"); + { + gui::draw_text("Hello"); + gui::draw_text("World"); - g_window.draw_text("Hello2"); - g_window.layout_same_line(); - g_window.draw_text("World2"); + gui::draw_text("你好,"); + gui::layout_same_line(); + gui::draw_text("世界"); - { - static bool checked = false; - if (const auto new_status = g_window.draw_checkbox("Checkbox1", checked); - new_status != checked) - { - checked = new_status; - std::println(stdout, "Checkbox1!"); - } - } - { - static bool checked = true; - if (const auto new_status = g_window.draw_checkbox("Checkbox2", checked); - new_status != checked) - { - checked = new_status; - std::println(stdout, "Checkbox2!"); + std::string string{}; + string.resize(200); + for (int i = 0; i < 200; ++i) + { + if (i != 0 and i % 25 == 0) + { + string[i] = '\n'; + } + else + { + const auto c = 'a' + i % ('z' - 'a'); + string[i] = static_cast(c); + } + } + + gui::draw_text(string); + gui::draw_text(string); + gui::draw_text(string); } + gui::end_window(); } - // auto& draw_list = g_window.test_draw_list(); - - // draw_list.text(24.f, {10, 10}, primitive::colors::blue, std::format("FPS: {:.3f}", g_fps)); - // - // draw_list.text(18.f, {50, 50}, primitive::colors::red, "The quick brown fox jumps over the lazy dog.\nHello world!\n你好世界!\n"); - - // draw_list.line({200, 100}, {200, 300}, primitive::colors::red); - // draw_list.line({100, 200}, {300, 200}, primitive::colors::red); - // - // draw_list.rect({100, 100}, {300, 300}, primitive::colors::blue); - // draw_list.rect({150, 150}, {250, 250}, primitive::colors::blue, 30); - // - // draw_list.triangle({120, 120}, {120, 150}, {150, 120}, primitive::colors::green); - // draw_list.triangle_filled({130, 130}, {130, 150}, {150, 130}, primitive::colors::red); - // - // draw_list.rect_filled({300, 100}, {400, 200}, primitive::colors::pink); - // draw_list.rect_filled({300, 200}, {400, 300}, primitive::colors::pink, 20); - // draw_list.rect_filled({300, 300}, {400, 400}, primitive::colors::pink, primitive::colors::gold, primitive::colors::azure, primitive::colors::lavender); - // - // draw_list.quadrilateral({100, 500}, {200, 500}, {250, 550}, {50, 550}, primitive::colors::red); - // draw_list.quadrilateral_filled({100, 500}, {200, 500}, {250, 450}, {50, 450}, primitive::colors::red); - // - // draw_list.circle({100, 600}, 50, primitive::colors::green); - // draw_list.circle({200, 600}, 50, primitive::colors::red, 8); - // draw_list.circle_filled({100, 700}, 50, primitive::colors::green); - // draw_list.circle_filled({200, 700}, 50, primitive::colors::red, 8); - // - // draw_list.ellipse({500, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 8); - // draw_list.ellipse_filled({500, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 8); - // draw_list.ellipse({600, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 16); - // draw_list.ellipse_filled({600, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 16); - // draw_list.ellipse({700, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 24); - // draw_list.ellipse_filled({700, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 24); - // draw_list.ellipse({800, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red); - // draw_list.ellipse_filled({800, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red); - // - // draw_list.circle_filled({500, 300}, 5, primitive::colors::red); - // draw_list.circle_filled({600, 350}, 5, primitive::colors::red); - // draw_list.circle_filled({450, 500}, 5, primitive::colors::red); - // draw_list.circle_filled({550, 550}, 5, primitive::colors::red); - // draw_list.bezier_cubic({500, 300}, {600, 350}, {450, 500}, {550, 550}, primitive::colors::green); - // - // draw_list.circle_filled({600, 300}, 5, primitive::colors::red); - // draw_list.circle_filled({700, 350}, 5, primitive::colors::red); - // draw_list.circle_filled({550, 500}, 5, primitive::colors::red); - // draw_list.circle_filled({650, 550}, 5, primitive::colors::red); - // draw_list.bezier_cubic({600, 300}, {700, 350}, {550, 500}, {650, 550}, primitive::colors::green, 5); - // - // draw_list.circle_filled({500, 600}, 5, primitive::colors::red); - // draw_list.circle_filled({600, 650}, 5, primitive::colors::red); - // draw_list.circle_filled({450, 800}, 5, primitive::colors::red); - // draw_list.bezier_quadratic({500, 600}, {600, 650}, {450, 800}, primitive::colors::green); - // - // draw_list.circle_filled({600, 600}, 5, primitive::colors::red); - // draw_list.circle_filled({700, 650}, 5, primitive::colors::red); - // draw_list.circle_filled({550, 800}, 5, primitive::colors::red); - // draw_list.bezier_quadratic({600, 600}, {700, 650}, {550, 800}, primitive::colors::green, 5); - // - // // push bound - // // [800,350] => [1000, 550] (200 x 200) - // draw_list.push_clip_rect({800, 350}, {1000, 550}, true); - // draw_list.rect({800, 350}, {1000, 550}, primitive::colors::red); - // // out-of-bound - // draw_list.triangle_filled({700, 250}, {900, 400}, {850, 450}, primitive::colors::green); - // // in-bound - // draw_list.triangle_filled({900, 450}, {1000, 450}, {950, 550}, primitive::colors::blue); - // // pop bound - // draw_list.pop_clip_rect(); - // - // draw_list.triangle_filled({800, 450}, {700, 750}, {850, 800}, primitive::colors::gold); - // - // // font - // draw_list.image(draw::Context::instance().font().texture_id(), {900, 20, 300, 300}); - // // image - // draw_list.image_rounded(reinterpret_cast(g_additional_picture_texture.Get()), {900, 350, 300, 300}, 10); + gui::render(); } auto prometheus_draw() -> void { - auto& draw_list = g_window.test_draw_list(); + const auto& draw_datas = gui::get_draw_data(); auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = render_buffer; - const auto command_list = draw_list.command_list(); - const auto vertex_list = draw_list.vertex_list(); - const auto index_list = draw_list.index_list(); + const auto [total_vertex_count, total_index_count] = [&]() noexcept + { + struct sum + { + UINT vertex; + UINT index; + }; + + return std::ranges::fold_left( + draw_datas, + sum{.vertex = 0, .index = 0}, + [](const sum s, const gui::DrawData& draw_data) noexcept -> sum + { + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); - if (not this_frame_vertex_buffer or vertex_list.size() > this_frame_vertex_count) + return + { + .vertex = s.vertex + static_cast(vertex_list.size()), + .index = s.index + static_cast(index_list.size()) + }; + } + ); + }(); + + if (not this_frame_vertex_buffer or total_vertex_count > this_frame_vertex_count) { // todo: grow factor - this_frame_vertex_count = static_cast(vertex_list.size()) + 5000; + this_frame_vertex_count = total_vertex_count + 5000; - const D3D11_BUFFER_DESC buffer_desc{ - .ByteWidth = static_cast(this_frame_vertex_count * sizeof(draw::DrawList::vertex_type)), + const D3D11_BUFFER_DESC buffer_desc + { + .ByteWidth = static_cast(this_frame_vertex_count * sizeof(gui::vertex_type)), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_VERTEX_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, @@ -566,13 +504,14 @@ auto prometheus_draw() -> void }; check_hr_error(g_device->CreateBuffer(&buffer_desc, nullptr, this_frame_vertex_buffer.ReleaseAndGetAddressOf())); } - if (not this_frame_index_buffer or index_list.size() > this_frame_index_count) + if (not this_frame_index_buffer or total_index_count > this_frame_index_count) { // todo: grow factor - this_frame_index_count = static_cast(index_list.size()) + 10000; + this_frame_index_count = total_index_count + 10000; - const D3D11_BUFFER_DESC buffer_desc{ - .ByteWidth = static_cast(this_frame_index_count * sizeof(draw::DrawList::index_type)), + const D3D11_BUFFER_DESC buffer_desc + { + .ByteWidth = static_cast(this_frame_index_count * sizeof(gui::index_type)), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_INDEX_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, @@ -592,20 +531,29 @@ auto prometheus_draw() -> void auto* mapped_vertex = static_cast(mapped_vertex_resource.pData); auto* mapped_index = static_cast(mapped_index_resource.pData); - std::ranges::transform( - vertex_list, - mapped_vertex, - [](const draw::DrawList::vertex_type& vertex) -> d3d_vertex_type + std::ranges::for_each( + draw_datas, + [&](const gui::DrawData& draw_data) noexcept -> void { - // return { - // .position = {vertex.position.x, vertex.position.y}, - // .uv = {vertex.uv.x, vertex.uv.y}, - // .color = vertex.color.to(primitive::color_format) - // }; - return std::bit_cast(vertex); + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); + + std::ranges::transform( + vertex_list, + mapped_vertex, + [](const gui::vertex_type& vertex) -> d3d_vertex_type + { + // return { + // .position = {vertex.position.x, vertex.position.y}, + // .uv = {vertex.uv.x, vertex.uv.y}, + // .color = vertex.color.to(primitive::color_format) + // }; + return std::bit_cast(vertex); + } + ); + std::ranges::copy(index_list, mapped_index); } ); - std::ranges::copy(index_list, mapped_index); g_device_immediate_context->Unmap(this_frame_vertex_buffer.Get(), 0); g_device_immediate_context->Unmap(this_frame_index_buffer.Get(), 0); @@ -674,19 +622,32 @@ auto prometheus_draw() -> void g_device_immediate_context->OMSetDepthStencilState(g_depth_stencil_state.Get(), 0); g_device_immediate_context->RSSetState(g_rasterizer_state.Get()); - for (const auto& [clip_rect, texture, index_offset, element_count]: command_list) - { - const auto [point, extent] = clip_rect; - const D3D11_RECT rect{static_cast(point.x), static_cast(point.y), static_cast(point.x + extent.width), static_cast(point.y + extent.height)}; - g_device_immediate_context->RSSetScissorRects(1, &rect); - - assert(texture != 0 and "push_texture_id when create texture view"); - ID3D11ShaderResourceView* textures[]{reinterpret_cast(texture)}; // NOLINT(performance-no-int-to-ptr) - g_device_immediate_context->PSSetShaderResources(0, 1, textures); - - // g_device_immediate_context->DrawIndexed(static_cast(element_count), static_cast(index_offset), 0); - g_device_immediate_context->DrawIndexedInstanced(static_cast(element_count), 1, static_cast(index_offset), 0, 0); - } + std::ranges::for_each( + draw_datas, + [&](const gui::DrawData& draw_data) noexcept -> void + { + for (const auto& command_list = draw_data.command_list.get(); + const auto& [clip_rect, texture, index_offset, element_count]: command_list) + { + const auto [point, extent] = clip_rect; + const D3D11_RECT rect + { + static_cast(point.x), + static_cast(point.y), + static_cast(point.x + extent.width), + static_cast(point.y + extent.height) + }; + g_device_immediate_context->RSSetScissorRects(1, &rect); + + assert(texture != 0 and "push_texture_id when create texture view"); + ID3D11ShaderResourceView* textures[]{reinterpret_cast(texture)}; // NOLINT(performance-no-int-to-ptr) + g_device_immediate_context->PSSetShaderResources(0, 1, textures); + + // g_device_immediate_context->DrawIndexed(static_cast(element_count), static_cast(index_offset), 0); + g_device_immediate_context->DrawIndexedInstanced(static_cast(element_count), 1, static_cast(index_offset), 0, 0); + } + } + ); } auto prometheus_shutdown() -> void diff --git a/unit_test/src/draw/dx11/main.cpp b/unit_test/src/gui/dx11/main.cpp similarity index 99% rename from unit_test/src/draw/dx11/main.cpp rename to unit_test/src/gui/dx11/main.cpp index c68ea90..668c664 100644 --- a/unit_test/src/draw/dx11/main.cpp +++ b/unit_test/src/gui/dx11/main.cpp @@ -25,7 +25,7 @@ int g_window_height = 960; double g_last_time = 0; std::uint64_t g_frame_count = 0; -float g_fps = 0; +float g_fps = 60; extern auto glfw_callback_setup(GLFWwindow& w) -> void; diff --git a/unit_test/src/draw/dx11/CMakeLists.txt b/unit_test/src/gui/dx12/CMakeLists.txt similarity index 96% rename from unit_test/src/draw/dx11/CMakeLists.txt rename to unit_test/src/gui/dx12/CMakeLists.txt index 6594cbe..1603684 100644 --- a/unit_test/src/draw/dx11/CMakeLists.txt +++ b/unit_test/src/gui/dx12/CMakeLists.txt @@ -1,5 +1,5 @@ project( - prometheus-draw-dx11 + prometheus-gui-dx12 LANGUAGES CXX ) diff --git a/unit_test/src/draw/dx12/backend.cpp b/unit_test/src/gui/dx12/backend.cpp similarity index 77% rename from unit_test/src/draw/dx12/backend.cpp rename to unit_test/src/gui/dx12/backend.cpp index 928c5d3..32f85d9 100644 --- a/unit_test/src/draw/dx12/backend.cpp +++ b/unit_test/src/gui/dx12/backend.cpp @@ -26,8 +26,6 @@ extern double g_last_time; extern std::uint64_t g_frame_count; extern float g_fps; -// extern io::DeviceEventQueue g_device_event_queue; - namespace { struct render_buffer_type @@ -57,8 +55,6 @@ namespace ComPtr g_additional_picture_resource = nullptr; D3D12_GPU_DESCRIPTOR_HANDLE g_additional_picture_handle = {.ptr = 0}; - draw::DrawList g_draw_list; - [[nodiscard]] auto load_texture( const std::uint8_t* texture_data, const std::uint32_t texture_width, @@ -277,20 +273,6 @@ auto prometheus_init() -> void { print_time(); - const auto glyph_range = i18n::RangeBuilder{}.simplified_chinese_common().range(); - const auto font_option = - draw::Font::option() - .path(R"(C:\Windows\Fonts\msyh.ttc)") - .glyph_ranges(i18n::RangeBuilder{}.simplified_chinese_common().range()) - .pixel_height(18); - - auto font = std::make_shared(); - auto font_texture = font->load(font_option); - - draw::Context::instance().set_default_font(font); - - g_draw_list.draw_list_flag(draw::DrawListFlag::ANTI_ALIASED_LINE | draw::DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | draw::DrawListFlag::ANTI_ALIASED_FILL); - // Create the root signature { // [0] projection_matrix @@ -566,12 +548,24 @@ auto prometheus_init() -> void check_hr_error(g_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(g_shader_resource_view_descriptor_heap.GetAddressOf()))); } + gui::create_current_context(); + + set_default_theme(gui::test_theme()); + set_default_draw_list_flag(gui::DrawListFlag::ANTI_ALIASED_LINE | gui::DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | gui::DrawListFlag::ANTI_ALIASED_FILL); + + gui::FontOption font_option{}; + font_option.font_path = R"(C:\Windows\Fonts\msyh.ttc)"; + font_option.pixel_height = 18; + font_option.glyph_ranges = i18n::RangeBuilder{}.simplified_chinese_common().range(); + auto font_texture = set_default_font(font_option); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_texture.valid()); + // Load default font texture { [[maybe_unused]] const auto load_font_texture_result = load_texture( - reinterpret_cast(font_texture.data().get()), - font_texture.width(), - font_texture.height(), + reinterpret_cast(font_texture.data.get()), + font_texture.width, + font_texture.height, g_shader_resource_view_descriptor_heap, 0, g_font_handle, @@ -579,7 +573,7 @@ auto prometheus_init() -> void ); assert(load_font_texture_result); - font_texture.bind(static_cast(g_font_handle.ptr)); + font_texture.bind(static_cast(g_font_handle.ptr)); } // Load additional picture texture @@ -607,85 +601,53 @@ auto prometheus_init() -> void auto prometheus_new_frame() -> void // { - g_draw_list.reset(); - g_draw_list.push_clip_rect({0, 0}, {static_cast(g_window_width), static_cast(g_window_height)}, false); + auto& io = gui::get_io(); + + io.display_size = {static_cast(g_window_width), static_cast(g_window_height)}; + io.delta_time = 1.f / g_fps; + + gui::new_frame(); } auto prometheus_render() -> void { - g_draw_list.text(24.f, {10, 10}, primitive::colors::blue, std::format("FPS: {:.3f}", g_fps)); - - g_draw_list.text(18.f, {50, 50}, primitive::colors::red, "The quick brown fox jumps over the lazy dog.\nHello world!\n你好世界!\n"); - - g_draw_list.line({200, 100}, {200, 300}, primitive::colors::red); - g_draw_list.line({100, 200}, {300, 200}, primitive::colors::red); - - g_draw_list.rect({100, 100}, {300, 300}, primitive::colors::blue); - g_draw_list.rect({150, 150}, {250, 250}, primitive::colors::blue, 30); - - g_draw_list.triangle({120, 120}, {120, 150}, {150, 120}, primitive::colors::green); - g_draw_list.triangle_filled({130, 130}, {130, 150}, {150, 130}, primitive::colors::red); - - g_draw_list.rect_filled({300, 100}, {400, 200}, primitive::colors::pink); - g_draw_list.rect_filled({300, 200}, {400, 300}, primitive::colors::pink, 20); - g_draw_list.rect_filled({300, 300}, {400, 400}, primitive::colors::pink, primitive::colors::gold, primitive::colors::azure, primitive::colors::lavender); - - g_draw_list.quadrilateral({100, 500}, {200, 500}, {250, 550}, {50, 550}, primitive::colors::red); - g_draw_list.quadrilateral_filled({100, 500}, {200, 500}, {250, 450}, {50, 450}, primitive::colors::red); - - g_draw_list.circle({100, 600}, 50, primitive::colors::green); - g_draw_list.circle({200, 600}, 50, primitive::colors::red, 8); - g_draw_list.circle_filled({100, 700}, 50, primitive::colors::green); - g_draw_list.circle_filled({200, 700}, 50, primitive::colors::red, 8); - - g_draw_list.ellipse({500, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 8); - g_draw_list.ellipse_filled({500, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 8); - g_draw_list.ellipse({600, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 16); - g_draw_list.ellipse_filled({600, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 16); - g_draw_list.ellipse({700, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 24); - g_draw_list.ellipse_filled({700, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 24); - g_draw_list.ellipse({800, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red); - g_draw_list.ellipse_filled({800, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red); - - g_draw_list.circle_filled({500, 300}, 5, primitive::colors::red); - g_draw_list.circle_filled({600, 350}, 5, primitive::colors::red); - g_draw_list.circle_filled({450, 500}, 5, primitive::colors::red); - g_draw_list.circle_filled({550, 550}, 5, primitive::colors::red); - g_draw_list.bezier_cubic({500, 300}, {600, 350}, {450, 500}, {550, 550}, primitive::colors::green); - - g_draw_list.circle_filled({600, 300}, 5, primitive::colors::red); - g_draw_list.circle_filled({700, 350}, 5, primitive::colors::red); - g_draw_list.circle_filled({550, 500}, 5, primitive::colors::red); - g_draw_list.circle_filled({650, 550}, 5, primitive::colors::red); - g_draw_list.bezier_cubic({600, 300}, {700, 350}, {550, 500}, {650, 550}, primitive::colors::green, 5); - - g_draw_list.circle_filled({500, 600}, 5, primitive::colors::red); - g_draw_list.circle_filled({600, 650}, 5, primitive::colors::red); - g_draw_list.circle_filled({450, 800}, 5, primitive::colors::red); - g_draw_list.bezier_quadratic({500, 600}, {600, 650}, {450, 800}, primitive::colors::green); - - g_draw_list.circle_filled({600, 600}, 5, primitive::colors::red); - g_draw_list.circle_filled({700, 650}, 5, primitive::colors::red); - g_draw_list.circle_filled({550, 800}, 5, primitive::colors::red); - g_draw_list.bezier_quadratic({600, 600}, {700, 650}, {550, 800}, primitive::colors::green, 5); - - // push bound - // [800,350] => [1000, 550] (200 x 200) - g_draw_list.push_clip_rect({800, 350}, {1000, 550}, true); - g_draw_list.rect({800, 350}, {1000, 550}, primitive::colors::red); - // out-of-bound - g_draw_list.triangle_filled({700, 250}, {900, 400}, {850, 450}, primitive::colors::green); - // in-bound - g_draw_list.triangle_filled({900, 450}, {1000, 450}, {950, 550}, primitive::colors::blue); - // pop bound - g_draw_list.pop_clip_rect(); - - g_draw_list.triangle_filled({800, 450}, {700, 750}, {850, 800}, primitive::colors::gold); - - // font - g_draw_list.image(draw::Context::instance().font().texture_id(), {900, 20, 300, 300}); - // image - g_draw_list.image_rounded(static_cast(g_additional_picture_handle.ptr), {900, 350, 300, 300}, 10); + if (static bool window_closed = false; + not window_closed) + { + window_closed = gui::begin_window("Window 1", {640, 480}); + + gui::draw_text("Text:"); + { + gui::draw_text("Hello"); + gui::draw_text("World"); + + gui::draw_text("你好,"); + gui::layout_same_line(); + gui::draw_text("世界"); + + std::string string{}; + string.resize(200); + for (int i = 0; i < 200; ++i) + { + if (i != 0 and i % 25 == 0) + { + string[i] = '\n'; + } + else + { + const auto c = 'a' + i % ('z' - 'a'); + string[i] = static_cast(c); + } + } + + gui::draw_text(string); + gui::draw_text(string); + gui::draw_text(string); + } + gui::end_window(); + } + + gui::render(); } auto prometheus_draw() -> void @@ -695,9 +657,32 @@ auto prometheus_draw() -> void auto& this_frame = g_frame_resource[this_frame_index]; auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = this_frame; - const auto command_list = g_draw_list.command_list(); - const auto vertex_list = g_draw_list.vertex_list(); - const auto index_list = g_draw_list.index_list(); + const auto& draw_datas = gui::get_draw_data(); + + const auto [total_vertex_count, total_index_count] = [&]() noexcept + { + struct sum + { + UINT vertex; + UINT index; + }; + + return std::ranges::fold_left( + draw_datas, + sum{.vertex = 0, .index = 0}, + [](const sum s, const gui::DrawData& draw_data) noexcept -> sum + { + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); + + return + { + .vertex = s.vertex + static_cast(vertex_list.size()), + .index = s.index + static_cast(index_list.size()) + }; + } + ); + }(); constexpr D3D12_HEAP_PROPERTIES heap_properties{ .Type = D3D12_HEAP_TYPE_UPLOAD, @@ -707,10 +692,10 @@ auto prometheus_draw() -> void .VisibleNodeMask = 0 }; // Create and grow vertex/index buffers if needed - if (this_frame_vertex_buffer == nullptr or this_frame_vertex_count < vertex_list.size()) + if (this_frame_vertex_buffer == nullptr or this_frame_vertex_count < total_vertex_count) { // todo: grow factor - this_frame_vertex_count = static_cast(vertex_list.size()) + 5000; + this_frame_vertex_count = total_vertex_count + 5000; const D3D12_RESOURCE_DESC resource_desc{ .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, @@ -735,10 +720,10 @@ auto prometheus_draw() -> void ) ); } - if (this_frame_index_buffer == nullptr or this_frame_index_count < index_list.size()) + if (this_frame_index_buffer == nullptr or this_frame_index_count < total_index_count) { // todo: grow factor - this_frame_index_count = static_cast(index_list.size()) + 10000; + this_frame_index_count = total_index_count + 10000; const D3D12_RESOURCE_DESC resource_desc{ .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, @@ -776,20 +761,29 @@ auto prometheus_draw() -> void auto* mapped_vertex = static_cast(mapped_vertex_resource); auto* mapped_index = static_cast(mapped_index_resource); - std::ranges::transform( - vertex_list, - mapped_vertex, - [](const draw::DrawList::vertex_type& vertex) -> d3d_vertex_type + std::ranges::for_each( + draw_datas, + [&](const gui::DrawData& draw_data) noexcept -> void { - // return { - // .position = {vertex.position.x, vertex.position.y}, - // .uv = {vertex.uv.x, vertex.uv.y}, - // .color = vertex.color.to(primitive::color_format) - // }; - return std::bit_cast(vertex); + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); + + std::ranges::transform( + vertex_list, + mapped_vertex, + [](const gui::vertex_type& vertex) -> d3d_vertex_type + { + // return { + // .position = {vertex.position.x, vertex.position.y}, + // .uv = {vertex.uv.x, vertex.uv.y}, + // .color = vertex.color.to(primitive::color_format) + // }; + return std::bit_cast(vertex); + } + ); + std::ranges::copy(index_list, mapped_index); } ); - std::ranges::copy(index_list, mapped_index); this_frame_vertex_buffer->Unmap(0, &vertex_range); this_frame_index_buffer->Unmap(0, &index_range); @@ -857,18 +851,25 @@ auto prometheus_draw() -> void constexpr float blend_factor[4]{.0f, .0f, .0f, .0f}; g_command_list->OMSetBlendFactor(blend_factor); - for (const auto& [clip_rect, texture, index_offset, element_count]: command_list) - { - const auto [point, extent] = clip_rect; - const D3D12_RECT rect{static_cast(point.x), static_cast(point.y), static_cast(point.x + extent.width), static_cast(point.y + extent.height)}; - g_command_list->RSSetScissorRects(1, &rect); + std::ranges::for_each( + draw_datas, + [&](const gui::DrawData& draw_data) noexcept -> void + { + for (const auto& command_list = draw_data.command_list.get(); + const auto& [clip_rect, texture, index_offset, element_count]: command_list) + { + const auto [point, extent] = clip_rect; + const D3D12_RECT rect{static_cast(point.x), static_cast(point.y), static_cast(point.x + extent.width), static_cast(point.y + extent.height)}; + g_command_list->RSSetScissorRects(1, &rect); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture != 0, "push_texture_id when create texture view"); - const D3D12_GPU_DESCRIPTOR_HANDLE texture_handle{.ptr = static_cast(texture)}; - g_command_list->SetGraphicsRootDescriptorTable(1, texture_handle); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture != 0, "push_texture_id when create texture view"); + const D3D12_GPU_DESCRIPTOR_HANDLE texture_handle{.ptr = static_cast(texture)}; + g_command_list->SetGraphicsRootDescriptorTable(1, texture_handle); - g_command_list->DrawIndexedInstanced(static_cast(element_count), 1, static_cast(index_offset), 0, 0); - } + g_command_list->DrawIndexedInstanced(static_cast(element_count), 1, static_cast(index_offset), 0, 0); + } + } + ); } auto prometheus_shutdown() -> void diff --git a/unit_test/src/draw/dx12/main.cpp b/unit_test/src/gui/dx12/main.cpp similarity index 99% rename from unit_test/src/draw/dx12/main.cpp rename to unit_test/src/gui/dx12/main.cpp index fe864e5..9537715 100644 --- a/unit_test/src/draw/dx12/main.cpp +++ b/unit_test/src/gui/dx12/main.cpp @@ -37,7 +37,7 @@ int g_window_height = 960; double g_last_time = 0; std::uint64_t g_frame_count = 0; -float g_fps = 0; +float g_fps = 60; extern auto glfw_callback_setup(GLFWwindow& w) -> void; diff --git a/unit_test/src/draw/win/def.hpp b/unit_test/src/gui/win/def.hpp similarity index 73% rename from unit_test/src/draw/win/def.hpp rename to unit_test/src/gui/win/def.hpp index 0e64447..575723e 100644 --- a/unit_test/src/draw/win/def.hpp +++ b/unit_test/src/gui/win/def.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include @@ -15,11 +15,11 @@ struct d3d_vertex_type std::uint32_t color; }; -using d3d_index_type = gal::prometheus::draw::DrawList::index_type; +using d3d_index_type = gal::prometheus::gui::index_type; using d3d_projection_matrix_type = float[4][4]; -static_assert(sizeof(gal::prometheus::draw::DrawList::vertex_type) == sizeof(d3d_vertex_type)); -static_assert(sizeof(gal::prometheus::draw::DrawList::index_type) == sizeof(d3d_index_type)); +static_assert(sizeof(gal::prometheus::gui::vertex_type) == sizeof(d3d_vertex_type)); +static_assert(sizeof(gal::prometheus::gui::index_type) == sizeof(d3d_index_type)); template auto check_hr_error( From a7045ce5696c6a9531226e9a09a5cf6bee9d3d74 Mon Sep 17 00:00:00 2001 From: life4gal Date: Mon, 14 Apr 2025 11:19:05 +0800 Subject: [PATCH 07/54] `feat`: Add button/small_button/radio_button/checkbox. --- src/gui/gui.hpp | 405 +++++++++++++++++++++++--------- src/gui/internal/context.cpp | 440 +++++++++++++++++++---------------- src/gui/internal/context.hpp | 28 +-- src/gui/internal/font.cpp | 50 ++-- src/gui/internal/window.cpp | 320 +++++++++++++++++++++++++ src/gui/internal/window.hpp | 4 + 6 files changed, 891 insertions(+), 356 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index b084225..597c040 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -17,6 +17,10 @@ namespace gal::prometheus { + // ================================================== + // TYPE & FLAG + // ================================================== + namespace gui { using rect_type = primitive::basic_rect_2d; @@ -32,19 +36,6 @@ namespace gal::prometheus using texture_id_type = std::uintptr_t; using time_type = float; - //------------------------------------------------------------------ - // CONTEXT - //------------------------------------------------------------------ - - class Context; - - // Create context - [[nodiscard]] auto create_context() noexcept -> Context*; - // Destroy context - auto destroy_context(Context& context) noexcept -> void; - // Destroy context, for smart pointer - auto destroy_context(Context* context) noexcept -> void; - //------------------------------------------------------------------ // FONT //------------------------------------------------------------------ @@ -99,11 +90,6 @@ namespace gal::prometheus auto bind(texture_id_type texture_id) noexcept -> void; }; - [[nodiscard]] auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture; - - [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture; - auto pop_font(Context& context) noexcept -> void; - //------------------------------------------------------------------ // THEME //------------------------------------------------------------------ @@ -136,13 +122,22 @@ namespace gal::prometheus TOOLTIP_BACKGROUND, TOOLTIP_TEXT, - SLIDER, - SLIDER_ACTIVATED, - BUTTON, BUTTON_HOVERED, BUTTON_ACTIVATED, + // RADIO-BUTTON / CHECKBOX + FRAME_BACKGROUND, + + RADIO_BUTTON_HOVERED, + RADIO_BUTTON_ACTIVATED, + + CHECKBOX_HOVERED, + CHECKBOX_ACTIVATED, + + SLIDER, + SLIDER_ACTIVATED, + // ------------------------------- INTERNAL_COUNT }; @@ -217,14 +212,6 @@ namespace gal::prometheus value_type draw_curve_tessellation_tolerance; }; - // todo - [[nodiscard]] auto test_theme() noexcept -> Theme; - - auto set_default_theme(Context& context, const Theme& theme) noexcept -> void; - - auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; - auto pop_theme(Context& context) noexcept -> void; - //------------------------------------------------------------------ // IO //------------------------------------------------------------------ @@ -304,8 +291,6 @@ namespace gal::prometheus //----------------- }; - [[nodiscard]] auto get_io(Context& context) noexcept -> IO&; - //------------------------------------------------------------------ // DRAW //------------------------------------------------------------------ @@ -347,17 +332,6 @@ namespace gal::prometheus std::reference_wrapper command_list; }; - auto set_default_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; - - auto push_draw_list_flag(Context& context, DrawListFlag new_flag) noexcept -> void; - auto pop_draw_list_flag(Context& context) noexcept -> void; - - auto new_frame(Context& context) noexcept -> void; - auto end_frame(Context& context) noexcept -> void; - auto render(Context& context) noexcept -> void; - - [[nodiscard]] auto get_draw_data(Context& context) noexcept -> std::vector; - //------------------------------------------------------------------ // WIDGET //------------------------------------------------------------------ @@ -377,6 +351,81 @@ namespace gal::prometheus AUTO_RESIZE = 1 << 7, }; + } + + namespace meta::user_defined + { + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + } + + // ================================================== + // CONTEXT + API + // ================================================== + + namespace gui + { + //------------------------------------------------------------------ + // CONTEXT + //------------------------------------------------------------------ + + class Context; + + // Create context + [[nodiscard]] auto create_context() noexcept -> Context*; + // Destroy context + auto destroy_context(Context& context) noexcept -> void; + // Destroy context, for smart pointer + auto destroy_context(Context* context) noexcept -> void; + + //------------------------------------------------------------------ + // FONT + //------------------------------------------------------------------ + + [[nodiscard]] auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture; + + // [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture; + // auto pop_font(Context& context) noexcept -> void; + + //------------------------------------------------------------------ + // THEME + //------------------------------------------------------------------ + + // todo + [[nodiscard]] auto test_theme() noexcept -> Theme; + + auto set_default_theme(Context& context, const Theme& theme) noexcept -> void; + + auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme(Context& context) noexcept -> void; + + //------------------------------------------------------------------ + // IO + //------------------------------------------------------------------ + + [[nodiscard]] auto get_io(Context& context) noexcept -> IO&; + + //------------------------------------------------------------------ + // DRAW + //------------------------------------------------------------------ + + auto set_default_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; + + // auto push_draw_list_flag(Context& context, DrawListFlag new_flag) noexcept -> void; + // auto pop_draw_list_flag(Context& context) noexcept -> void; + + auto new_frame(Context& context) noexcept -> void; + auto end_frame(Context& context) noexcept -> void; + auto render(Context& context) noexcept -> void; + + [[nodiscard]] auto get_draw_data(Context& context) noexcept -> std::vector; + + //------------------------------------------------------------------ + // WIDGET + //------------------------------------------------------------------ auto begin_window( Context& context, @@ -387,8 +436,76 @@ namespace gal::prometheus ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; + /** + * @brief Draw a piece of text + */ auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + /** + * @brief Draw a button, if the specified size is smaller than the size occupied by the text (plus padding), the text will not be completely inside the box + * @return Whether the button is pressed or not + */ + auto draw_button(Context& context, std::string_view utf8_text, const extent_type& size = {0, 0}, bool repeat_when_held = false) noexcept -> bool; + + /** + * @brief Draw a button, it fits within text without additional spacing + * @return Whether the button is pressed or not + */ + auto draw_small_button(Context& context, std::string_view utf8_text, bool repeat_when_held = false) noexcept -> bool; + + /** + * @brief Draw a radio button, its initial state is required + * @return Whether the radio button is pressed (being pressed does not mean that the state is switched, it can also be a repeat selection) + */ + auto draw_radio_button(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + + /** + * @brief Draw a radio button, its initial state and referenced state is required + */ + template + auto draw_radio_button(Context& context, std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> void + { + const auto prev_selected = reference == identifier; + if (const auto pressed = gui::draw_radio_button(context, utf8_text, prev_selected); + pressed) + { + reference = identifier; + } + } + + /** + * @brief Draw a checkbox, its initial state is required + * @return Current state of the checkbox (checked or unchecked) + */ + auto draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + + /** + * @brief Draw a checkbox, its initial state and referenced state is required + */ + template + static auto draw_checkbox( + Context& context, + const std::string_view utf8_text, + T& reference, + const std::type_identity_t& checked_identifier, + const std::type_identity_t& unchecked_identifier + ) noexcept -> void + { + const auto prev_checked = reference == checked_identifier; + if (const auto checked = gui::draw_checkbox(context, utf8_text, prev_checked); + checked != prev_checked) + { + if (prev_checked) + { + reference = unchecked_identifier; + } + else + { + reference = checked_identifier; + } + } + } + //------------------------------------------------------------------ // LAYOUT //------------------------------------------------------------------ @@ -396,6 +513,16 @@ namespace gal::prometheus // < 0 constexpr Theme::value_type layout_auto_size = -1; + /** + * @brief Each element drawn will move the cursor to the next line of the canvas, + * this function forces the cursor to move to the end of the previous line (immediately after the previous element) + * @note @c column_width == @c auto_size and @c spacing_width == @c auto_size, + * the x-axis distance of the next drawn element from the previous element will depend on @c theme.item_spacing.width + * @note @c column_width == @c auto_size and @c spacing_width != @c auto_size, + * the x-axis distance of the next drawn element from the previous element will depend on @c spacing_width + * @note @c column_width != @c auto_size (@c spacing_width can be any value, force to 0 if less than 0), + * the x-axis distance of the next drawn element from the previous element will depend on @c column_width + @c spacing_width + */ auto layout_same_line(const Context& context, Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; //------------------------------------------------------------------ @@ -410,94 +537,154 @@ namespace gal::prometheus [[nodiscard]] auto get_window_content_region_max(const Context& context) noexcept -> extent_type; } - namespace meta::user_defined - { - template<> - struct enum_is_flag : std::true_type {}; + // ================================================== + // API + // ================================================== - template<> - struct enum_is_flag : std::true_type {}; - } -} - -namespace gal::prometheus::gui -{ - //------------------------------------------------------------------ - // CONTEXT - //------------------------------------------------------------------ + namespace gui + { + //------------------------------------------------------------------ + // CONTEXT + //------------------------------------------------------------------ - auto set_current_context(Context& context) noexcept -> void; - auto get_current_context() noexcept -> Context&; + auto set_current_context(Context& context) noexcept -> void; + auto get_current_context() noexcept -> Context&; - auto create_current_context() noexcept -> void; + auto create_current_context() noexcept -> void; + auto destroy_current_context() noexcept -> void; - //------------------------------------------------------------------ - // FONT - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // FONT + //------------------------------------------------------------------ - [[nodiscard]] auto set_default_font(const FontOption& option) noexcept -> Texture; + [[nodiscard]] auto set_default_font(const FontOption& option) noexcept -> Texture; - [[nodiscard]] auto push_font(const FontOption& option) noexcept -> Texture; - auto pop_font() noexcept -> void; + // [[nodiscard]] auto push_font(const FontOption& option) noexcept -> Texture; + // auto pop_font() noexcept -> void; - //------------------------------------------------------------------ - // THEME - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // THEME + //------------------------------------------------------------------ - auto set_default_theme(const Theme& theme) noexcept -> void; + auto set_default_theme(const Theme& theme) noexcept -> void; - auto push_theme(ThemeCategory category, Theme::color_type new_color) noexcept -> void; - auto pop_theme() noexcept -> void; + auto push_theme(ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme() noexcept -> void; - //------------------------------------------------------------------ - // IO - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // IO + //------------------------------------------------------------------ - [[nodiscard]] auto get_io() noexcept -> IO&; + [[nodiscard]] auto get_io() noexcept -> IO&; - //------------------------------------------------------------------ - // DRAW - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // DRAW + //------------------------------------------------------------------ - auto set_default_draw_list_flag(DrawListFlag flag) noexcept -> void; + auto set_default_draw_list_flag(DrawListFlag flag) noexcept -> void; - auto push_draw_list_flag(DrawListFlag new_flag) noexcept -> void; - auto pop_draw_list_flag() noexcept -> void; + // auto push_draw_list_flag(DrawListFlag new_flag) noexcept -> void; + // auto pop_draw_list_flag() noexcept -> void; - auto new_frame() noexcept -> void; - auto end_frame() noexcept -> void; - auto render() noexcept -> void; + auto new_frame() noexcept -> void; + auto end_frame() noexcept -> void; + auto render() noexcept -> void; - [[nodiscard]] auto get_draw_data() noexcept -> std::vector; + [[nodiscard]] auto get_draw_data() noexcept -> std::vector; - //------------------------------------------------------------------ - // WIDGET - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // WIDGET + //------------------------------------------------------------------ - auto begin_window( - std::string_view name, - const extent_type& size = {0, 0}, - Theme::value_type fill_alpha = Theme::window_fill_alpha_not_set, - WindowFlag flag = WindowFlag::NONE - ) noexcept -> bool; - auto end_window() noexcept -> void; + auto begin_window( + std::string_view name, + const extent_type& size = {0, 0}, + Theme::value_type fill_alpha = Theme::window_fill_alpha_not_set, + WindowFlag flag = WindowFlag::NONE + ) noexcept -> bool; + auto end_window() noexcept -> void; + + /** + * @brief Draw a piece of text + */ + auto draw_text(std::string_view utf8_text) noexcept -> void; + + /** + * @brief Draw a button, if the specified size is smaller than the size occupied by the text (plus padding), the text will not be completely inside the box + * @return Whether the button is pressed or not + */ + auto draw_button(std::string_view utf8_text, const extent_type& size = {0, 0}, bool repeat_when_held = false) noexcept -> bool; + + /** + * @brief Draw a button, it fits within text without additional spacing + * @return Whether the button is pressed or not + */ + auto draw_small_button(std::string_view utf8_text, bool repeat_when_held = false) noexcept -> bool; + + /** + * @brief Draw a radio button, its initial state is required + * @return Whether the radio button is pressed (being pressed does not mean that the state is switched, it can also be a repeat selection) + */ + auto draw_radio_button(std::string_view utf8_text, bool checked) noexcept -> bool; + + /** + * @brief Draw a radio button, its initial state and referenced state is required + */ + template + auto draw_radio_button(std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> void + { + auto& context = get_current_context(); + + gui::draw_radio_button(context, utf8_text, reference, identifier); + } + + /** + * @brief Draw a checkbox, its initial state is required + * @return Current state of the checkbox (checked or unchecked) + */ + auto draw_checkbox(std::string_view utf8_text, bool checked) noexcept -> bool; + + /** + * @brief Draw a checkbox, its initial state and referenced state is required + */ + template + static auto draw_checkbox( + const std::string_view utf8_text, + T& reference, + const std::type_identity_t& checked_identifier, + const std::type_identity_t& unchecked_identifier + ) noexcept -> void + { + auto& context = get_current_context(); - auto draw_text(std::string_view utf8_text) noexcept -> void; + gui::draw_checkbox(context, utf8_text, reference, checked_identifier, unchecked_identifier); + } - //------------------------------------------------------------------ - // LAYOUT - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // LAYOUT + //------------------------------------------------------------------ - auto layout_same_line(Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; + /** + * @brief Each element drawn will move the cursor to the next line of the canvas, + * this function forces the cursor to move to the end of the previous line (immediately after the previous element) + * @note @c column_width == @c auto_size and @c spacing_width == @c auto_size, + * the x-axis distance of the next drawn element from the previous element will depend on @c theme.item_spacing.width + * @note @c column_width == @c auto_size and @c spacing_width != @c auto_size, + * the x-axis distance of the next drawn element from the previous element will depend on @c spacing_width + * @note @c column_width != @c auto_size (@c spacing_width can be any value, force to 0 if less than 0), + * the x-axis distance of the next drawn element from the previous element will depend on @c column_width + @c spacing_width + */ + auto layout_same_line(Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; - //------------------------------------------------------------------ - // WIDGET STATE - //------------------------------------------------------------------ + //------------------------------------------------------------------ + // WIDGET STATE + //------------------------------------------------------------------ - // The available area of the current drawing unit - [[nodiscard]] auto get_content_region_max() noexcept -> extent_type; - // The available area of the current window - [[nodiscard]] auto get_window_content_region_min() noexcept -> extent_type; - // The available area of the current window - [[nodiscard]] auto get_window_content_region_max() noexcept -> extent_type; + // The available area of the current drawing unit + [[nodiscard]] auto get_content_region_max() noexcept -> extent_type; + // The available area of the current window + [[nodiscard]] auto get_window_content_region_min() noexcept -> extent_type; + // The available area of the current window + [[nodiscard]] auto get_window_content_region_max() noexcept -> extent_type; + } } diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 55ef061..c27f042 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -138,12 +138,9 @@ namespace gal::prometheus::gui { auto* p = new ::Context{ .initialized = false, - .draw_list_flag_stack = {}, - .draw_list_shared_data_stack = {}, - .font_stack = {}, - .draw_list_flag_current = Context::stack_pointer_default, - .draw_list_shared_data_current = Context::stack_pointer_default, - .font_current = Context::stack_pointer_default, + .draw_list_flag = DrawListFlag::NONE, + .draw_list_shared_data = {}, + .font = {}, .theme = {}, .theme_mod_stack = {}, .io = {}, @@ -216,13 +213,21 @@ namespace gal::prometheus::gui colors[static_cast(TOOLTIP_BACKGROUND)] = primitive::colors::black; colors[static_cast(TOOLTIP_TEXT)] = primitive::colors::red; - colors[static_cast(SLIDER)] = primitive::colors::light_blue; - colors[static_cast(SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; - colors[static_cast(BUTTON)] = primitive::colors::sienna; colors[static_cast(BUTTON_HOVERED)] = primitive::colors::slate_gray; colors[static_cast(BUTTON_ACTIVATED)] = primitive::colors::steel_blue; + colors[static_cast(FRAME_BACKGROUND)] = primitive::colors::steel_blue; + + colors[static_cast(RADIO_BUTTON_HOVERED)] = primitive::colors::powder_blue; + colors[static_cast(RADIO_BUTTON_ACTIVATED)] = primitive::colors::green_yellow; + + colors[static_cast(CHECKBOX_HOVERED)] = primitive::colors::powder_blue; + colors[static_cast(CHECKBOX_ACTIVATED)] = primitive::colors::green_yellow; + + colors[static_cast(SLIDER)] = primitive::colors::light_blue; + colors[static_cast(SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; + return colors; }; @@ -269,24 +274,7 @@ namespace gal::prometheus::gui auto set_default_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void { - if (context.draw_list_flag_current == Context::stack_pointer_default) - { - internal::push_draw_list_flag(context, flag); - } - else - { - context.draw_list_flag_stack[0] = flag; - } - } - - auto push_draw_list_flag(Context& context, const DrawListFlag new_flag) noexcept -> void - { - internal::push_draw_list_flag(context, new_flag); - } - - auto pop_draw_list_flag(Context& context) noexcept -> void - { - internal::pop_draw_list_flag(context); + context.draw_list_flag = flag; } auto new_frame(Context& context) noexcept -> void @@ -495,6 +483,38 @@ namespace gal::prometheus::gui window.draw_text(context, utf8_text); } + auto draw_button(Context& context, const std::string_view utf8_text, const extent_type& size, const bool repeat_when_held) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_button(context, utf8_text, size, repeat_when_held); + } + + auto draw_small_button(Context& context, const std::string_view utf8_text, const bool repeat_when_held) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_small_button(context, utf8_text, repeat_when_held); + } + + auto draw_radio_button(Context& context, const std::string_view utf8_text, const bool checked) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_radio_button(context, utf8_text, checked); + } + + auto draw_checkbox(Context& context, const std::string_view utf8_text, const bool checked) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_checkbox(context, utf8_text, checked); + } + auto layout_same_line(const Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -544,102 +564,81 @@ namespace gal::prometheus::gui g_context = create_context(); } - auto set_default_font(const FontOption& option) noexcept -> Texture + auto destroy_current_context() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return set_default_font(*g_context, option); + destroy_context(context); } - auto push_font(const FontOption& option) noexcept -> Texture - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); - - return push_font(*g_context, option); - } - - auto pop_font() noexcept -> void + auto set_default_font(const FontOption& option) noexcept -> Texture { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - pop_font(*g_context); + return set_default_font(context, option); } auto set_default_theme(const Theme& theme) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - set_default_theme(*g_context, theme); + set_default_theme(context, theme); } auto push_theme(const ThemeCategory category, const Theme::color_type new_color) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - push_theme(*g_context, category, new_color); + push_theme(context, category, new_color); } auto pop_theme() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - pop_theme(*g_context); + pop_theme(context); } auto get_io() noexcept -> IO& { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return get_io(*g_context); + return get_io(context); } auto set_default_draw_list_flag(const DrawListFlag flag) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); - - set_default_draw_list_flag(*g_context, flag); - } - - auto push_draw_list_flag(const DrawListFlag new_flag) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - push_draw_list_flag(*g_context, new_flag); - } - - auto pop_draw_list_flag() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); - - pop_draw_list_flag(*g_context); + set_default_draw_list_flag(context, flag); } auto new_frame() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return new_frame(*g_context); + return new_frame(context); } auto end_frame() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return end_frame(*g_context); + return end_frame(context); } auto render() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return render(*g_context); + return render(context); } auto get_draw_data() noexcept -> std::vector { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return get_draw_data(*g_context); + return get_draw_data(context); } auto begin_window( @@ -649,189 +648,220 @@ namespace gal::prometheus::gui const WindowFlag flag ) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return begin_window(*g_context, name, size, fill_alpha, flag); + return begin_window(context, name, size, fill_alpha, flag); } auto end_window() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return end_window(*g_context); + return end_window(context); } auto draw_text(const std::string_view utf8_text) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - draw_text(*g_context, utf8_text); + draw_text(context, utf8_text); } - auto layout_same_line(const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void + auto draw_button(const std::string_view utf8_text, const extent_type& size, const bool repeat_when_held) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - layout_same_line(*g_context, column_width, spacing_width); + return draw_button(context, utf8_text, size, repeat_when_held); } - auto get_content_region_max() noexcept -> extent_type + auto draw_small_button(const std::string_view utf8_text, const bool repeat_when_held) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return get_content_region_max(*g_context); + return draw_small_button(context, utf8_text, repeat_when_held); } - auto get_window_content_region_min() noexcept -> extent_type + auto draw_radio_button(const std::string_view utf8_text, const bool checked) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return get_window_content_region_min(*g_context); + return draw_radio_button(context, utf8_text, checked); } - auto get_window_content_region_max() noexcept -> extent_type + auto draw_checkbox(const std::string_view utf8_text, const bool checked) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_context != nullptr); + auto& context = get_current_context(); - return get_window_content_region_max(*g_context); + return draw_checkbox(context, utf8_text, checked); } - namespace internal + auto layout_same_line(const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { - auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - return context.draw_list_flag_stack[context.draw_list_flag_current]; - } - - auto push_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - context.draw_list_flag_current == Context::stack_pointer_default or - context.draw_list_flag_current < Context::draw_list_flag_stack_size, - "DrawListFlag stack overflow" - ); + const auto& context = get_current_context(); - static_assert( - static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == - static_cast<::Context::stack_pointer_type>(0) - ); - - context.draw_list_flag_current += 1; - context.draw_list_flag_stack[context.draw_list_flag_current] = flag; - } + layout_same_line(context, column_width, spacing_width); + } - auto pop_draw_list_flag(Context& context) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + auto get_content_region_max() noexcept -> extent_type + { + const auto& context = get_current_context(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - context.draw_list_flag_current != Context::stack_pointer_default, - "Unable to popup the default DrawListFlag!" - ); + return get_content_region_max(context); + } - static_assert( - static_cast(static_cast(0) - 1) == - Context::stack_pointer_default - ); + auto get_window_content_region_min() noexcept -> extent_type + { + const auto& context = get_current_context(); - context.draw_list_flag_stack[context.draw_list_flag_current] = DrawListFlag::NONE; - context.draw_list_flag_current -= 1; - } + return get_window_content_region_min(context); + } - auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + auto get_window_content_region_max() noexcept -> extent_type + { + const auto& context = get_current_context(); - return context.draw_list_shared_data_stack[context.draw_list_shared_data_current]; - } + return get_window_content_region_max(context); + } - auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void + namespace internal + { + auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - context.draw_list_shared_data_current == Context::stack_pointer_default or - context.draw_list_shared_data_current < Context::draw_list_shared_data_stack_size, - "DrawListSharedData stack overflow" - ); + // return context.draw_list_flag_stack[context.draw_list_flag_current]; + return context.draw_list_flag; + } + + // auto push_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // context.draw_list_flag_current == Context::stack_pointer_default or + // context.draw_list_flag_current < Context::draw_list_flag_stack_size, + // "DrawListFlag stack overflow" + // ); + // + // static_assert( + // static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == + // static_cast<::Context::stack_pointer_type>(0) + // ); + // + // context.draw_list_flag_current += 1; + // context.draw_list_flag_stack[context.draw_list_flag_current] = flag; + // } + // + // auto pop_draw_list_flag(Context& context) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // context.draw_list_flag_current != Context::stack_pointer_default, + // "Unable to popup the default DrawListFlag!" + // ); + // + // static_assert( + // static_cast(static_cast(0) - 1) == + // Context::stack_pointer_default + // ); + // + // context.draw_list_flag_stack[context.draw_list_flag_current] = DrawListFlag::NONE; + // context.draw_list_flag_current -= 1; + // } - static_assert( - static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == - static_cast<::Context::stack_pointer_type>(0) - ); - - context.draw_list_shared_data_current += 1; - context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = shared_data; - } - - auto pop_draw_list_shared_data(Context& context) noexcept -> void + auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData& { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - context.draw_list_shared_data_current != Context::stack_pointer_default, - "Unable to popup the default DrawListSharedData!" - ); - - static_assert( - static_cast(static_cast(0) - 1) == - Context::stack_pointer_default - ); - - context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = {}; - context.draw_list_shared_data_current -= 1; - } + // return context.draw_list_shared_data_stack[context.draw_list_shared_data_current]; + return context.draw_list_shared_data; + } + + // auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // context.draw_list_shared_data_current == Context::stack_pointer_default or + // context.draw_list_shared_data_current < Context::draw_list_shared_data_stack_size, + // "DrawListSharedData stack overflow" + // ); + // + // static_assert( + // static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == + // static_cast<::Context::stack_pointer_type>(0) + // ); + // + // context.draw_list_shared_data_current += 1; + // context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = shared_data; + // } + // + // auto pop_draw_list_shared_data(Context& context) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // context.draw_list_shared_data_current != Context::stack_pointer_default, + // "Unable to popup the default DrawListSharedData!" + // ); + // + // static_assert( + // static_cast(static_cast(0) - 1) == + // Context::stack_pointer_default + // ); + // + // context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = {}; + // context.draw_list_shared_data_current -= 1; + // } auto current_font(const Context& context) noexcept -> const Font& { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - const auto& font = *context.font_stack[context.font_current]; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font.loaded(), "Invalid font!"); - - return font; - } - - auto push_font(Context& context, memory::UniquePointer font) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - context.font_current == Context::stack_pointer_default or - context.font_current < Context::font_stack_size, - "Font stack overflow" - ); - - static_assert( - static_cast(Context::stack_pointer_default + 1) == - static_cast(0) - ); - - context.font_current += 1; - context.font_stack[context.font_current] = std::move(font); - } - - auto pop_font(Context& context) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - context.font_current != Context::stack_pointer_default, - "Unable to popup the default Font!" - ); - - static_assert( - static_cast(static_cast(0) - 1) == - Context::stack_pointer_default - ); - - context.font_stack[context.font_current].reset(); - context.font_current -= 1; - } + // const auto& font = *context.font_stack[context.font_current]; + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font.loaded(), "Invalid font!"); + // + // return font; + return context.font; + } + + // auto push_font(Context& context, memory::UniquePointer font) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // context.font_current == Context::stack_pointer_default or + // context.font_current < Context::font_stack_size, + // "Font stack overflow" + // ); + // + // static_assert( + // static_cast(Context::stack_pointer_default + 1) == + // static_cast(0) + // ); + // + // context.font_current += 1; + // context.font_stack[context.font_current] = std::move(font); + // } + // + // auto pop_font(Context& context) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // context.font_current != Context::stack_pointer_default, + // "Unable to popup the default Font!" + // ); + // + // static_assert( + // static_cast(static_cast(0) - 1) == + // Context::stack_pointer_default + // ); + // + // context.font_stack[context.font_current].reset(); + // context.font_current -= 1; + // } auto current_theme(const Context& context) noexcept -> const Theme& { diff --git a/src/gui/internal/context.hpp b/src/gui/internal/context.hpp index 39b06aa..5297a3c 100644 --- a/src/gui/internal/context.hpp +++ b/src/gui/internal/context.hpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace gal::prometheus @@ -45,20 +46,11 @@ namespace gal::prometheus // ---------------------------------------------------------------------- // DrawListFlag + DrawListSharedData + Font + Theme - constexpr static std::size_t draw_list_flag_stack_size = 8; - constexpr static std::size_t draw_list_shared_data_stack_size = 8; - constexpr static std::size_t font_stack_size = 8; + DrawListFlag draw_list_flag; - using stack_pointer_type = std::uint8_t; - constexpr static auto stack_pointer_default = std::numeric_limits::max(); + internal::DrawListSharedData draw_list_shared_data; - std::array draw_list_flag_stack; - std::array draw_list_shared_data_stack; - std::array, font_stack_size> font_stack; - - stack_pointer_type draw_list_flag_current; - stack_pointer_type draw_list_shared_data_current; - stack_pointer_type font_current; + internal::Font font; Theme theme; std::vector theme_mod_stack; @@ -115,22 +107,22 @@ namespace gal::prometheus // DrawListFlag auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag; - auto push_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; - auto pop_draw_list_flag(Context& context) noexcept -> void; + // auto push_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; + // auto pop_draw_list_flag(Context& context) noexcept -> void; // ---------------------------------------------------------------------- // DrawListSharedData auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData&; - auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void; - auto pop_draw_list_shared_data(Context& context) noexcept -> void; + // auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void; + // auto pop_draw_list_shared_data(Context& context) noexcept -> void; // ---------------------------------------------------------------------- // Font auto current_font(const Context& context) noexcept -> const Font&; - auto push_font(Context& context, memory::UniquePointer font) noexcept -> void; - auto pop_font(Context& context) noexcept -> void; + // auto push_font(Context& context, memory::UniquePointer font) noexcept -> void; + // auto pop_font(Context& context) noexcept -> void; // ---------------------------------------------------------------------- // Theme diff --git a/src/gui/internal/font.cpp b/src/gui/internal/font.cpp index 9348c89..cc0ef9e 100644 --- a/src/gui/internal/font.cpp +++ b/src/gui/internal/font.cpp @@ -319,33 +319,35 @@ namespace gal::prometheus::gui { auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture { - if (context.font_current == Context::stack_pointer_default) - { - return push_font(context, option); - } - - auto font = memory::make_unique(); - auto texture = do_load_font(option, *font); - - context.font_stack[0] = std::move(font); - + // if (context.font_current == Context::stack_pointer_default) + // { + // return push_font(context, option); + // } + // + // auto font = memory::make_unique(); + // auto texture = do_load_font(option, *font); + // + // context.font_stack[0] = std::move(font); + // + // return texture; + auto texture = do_load_font(option, context.font); return texture; } - [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture - { - auto font = memory::make_unique(); - auto texture = do_load_font(option, *font); - - internal::push_font(context, std::move(font)); - - return texture; - } - - auto pop_font(Context& context) noexcept -> void - { - internal::pop_font(context); - } + // [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture + // { + // auto font = memory::make_unique(); + // auto texture = do_load_font(option, *font); + // + // internal::push_font(context, std::move(font)); + // + // return texture; + // } + // + // auto pop_font(Context& context) noexcept -> void + // { + // internal::pop_font(context); + // } Texture::Texture(texture_id_type& texture_id) noexcept : width{0}, diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index c905ed3..89d4fb6 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -249,6 +249,49 @@ namespace gal::prometheus::gui::internal window.size_of_content_ = window.size_of_content_.combine_max(extent_type{canvas.cursor_previous_line.x, canvas.cursor_current_line.y + window.scroll_y_} - window.point_); } + + // ----------------------------------- + // FRAME + + auto draw_widget_frame(const Context& context, const rect_type& rect, const color_type& color) noexcept -> void + { + const auto& theme = current_theme(context); + + auto& window = self.get(); + + window.draw_list_.rect_filled(rect, color); + if (window.flag_.is()) + { + const point_type outer_point{rect.left_top() + extent_type{.5f, .5f}}; + const extent_type outer_size{rect.size() - extent_type{.5f, .5f}}; + + const point_type inner_point{rect.left_top() + extent_type{1.5f, 1.5f}}; + const extent_type inner_size{rect.size() - extent_type{.5f, .5f}}; + + window.draw_list_.rect({inner_point, inner_size}, color_of(theme, ThemeCategory::BORDER_SHADOW)); + window.draw_list_.rect({outer_point, outer_size}, color_of(theme, ThemeCategory::BORDER)); + } + } + + auto draw_widget_frame(const Context& context, const circle_type& circle, const color_type& color) noexcept -> void + { + const auto& theme = current_theme(context); + + auto& window = self.get(); + + window.draw_list_.circle_filled(circle, color); + if (window.flag_.is()) + { + const point_type outer_point{circle.center() + extent_type{.5f, .5f}}; + const auto outer_radius = circle.radius - .5f; + + const point_type inner_point{circle.center() + extent_type{1.5f, 1.5f}}; + const auto inner_radius = circle.radius - .5f; + + window.draw_list_.circle({inner_point, inner_radius}, color_of(theme, ThemeCategory::BORDER_SHADOW)); + window.draw_list_.circle({outer_point, outer_radius}, color_of(theme, ThemeCategory::BORDER)); + } + } }; Window::Window( @@ -1113,6 +1156,283 @@ namespace gal::prometheus::gui::internal } } + auto Window::draw_button(Context& context, const std::string_view utf8_text, extent_type size, const bool repeat_when_held) noexcept -> bool + { + if (skip_item_) + { + return false; + } + accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = const_cast(*this)}; + const IdMaker id_maker{.self = *this}; + + const auto id = id_maker.make_id(context, utf8_text); + const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + + if (size.width <= 0) + { + size.width = text_size.width; + } + if (size.height <= 0) + { + size.height = text_size.height; + } + + const auto button_point = canvas_.cursor_current_line; + const auto button_size = size + theme.item_frame_padding * 2; + const rect_type button_rect{button_point, button_size}; + drawer.adjust_item_size(context, button_size); + + const auto state = test_mouse(context, id, button_rect, repeat_when_held); + + color_type button_color = color_of(theme, ThemeCategory::BUTTON); + { + if (state & MouseState::KEEPING or state & MouseState::PRESSED) + { + button_color = color_of(theme, ThemeCategory::BUTTON_ACTIVATED); + } + else if (state & MouseState::HOVERED) + { + button_color = color_of(theme, ThemeCategory::BUTTON_HOVERED); + } + } + + // draw □ + drawer.draw_widget_frame(context, button_rect, button_color); + + const auto text_area_point = button_rect.left_top() + theme.item_frame_padding; + + // the given size may be smaller than the required size of the text, in which case the button text needs to be truncated + const auto clip = size.width < text_size.width or size.height < text_size.height; + if (clip) + { + const rect_type rect + { + text_area_point, + // Allow extra to draw over the horizontal padding to make it visible that text doesn't fit + button_rect.right_bottom() - extent_type{0, theme.item_frame_padding.height} + }; + + push_clip_rect(context, rect); + } + + const auto text_offset = (size - text_size).combine_max({0, 0}) * .5f; + const auto text_point = + text_area_point + + text_offset; + + // draw text + draw_list_.text( + font, + drawer.font_size(context), + text_point, + color_of(theme, ThemeCategory::TEXT), + utf8_text, + button_rect.width() + ); + + if (clip) + { + pop_clip_rect(context); + } + + return state & MouseState::PRESSED; + } + + auto Window::draw_small_button(Context& context, const std::string_view utf8_text, const bool repeat_when_held) noexcept -> bool + { + if (skip_item_) + { + return false; + } + accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = const_cast(*this)}; + const IdMaker id_maker{.self = *this}; + + const auto id = id_maker.make_id(context, utf8_text); + const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + + const auto button_point = canvas_.cursor_current_line; + const auto button_size = text_size + theme.item_frame_padding * 2; + const rect_type button_rect{button_point, button_size}; + drawer.adjust_item_size(context, button_size); + + const auto state = test_mouse(context, id, button_rect, repeat_when_held); + + color_type button_color = color_of(theme, ThemeCategory::BUTTON); + { + if (state & MouseState::KEEPING or state & MouseState::PRESSED) + { + button_color = color_of(theme, ThemeCategory::BUTTON_ACTIVATED); + } + else if (state & MouseState::HOVERED) + { + button_color = color_of(theme, ThemeCategory::BUTTON_HOVERED); + } + } + + // draw □ + drawer.draw_widget_frame(context, button_rect, button_color); + + const auto text_area_point = button_rect.left_top() + theme.item_frame_padding; + + // draw text + draw_list_.text( + font, + drawer.font_size(context), + text_area_point, + color_of(theme, ThemeCategory::TEXT), + utf8_text, + button_rect.width() + ); + + return state & MouseState::PRESSED; + } + + auto Window::draw_radio_button(Context& context, const std::string_view utf8_text, const bool checked) noexcept -> bool + { + if (skip_item_) + { + return false; + } + accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = const_cast(*this)}; + const IdMaker id_maker{.self = *this}; + + const auto id = id_maker.make_id(context, utf8_text); + const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + + // ○ + text + const auto width = text_size.height; + + // ○, diameter equals string rect height + const auto check_point = canvas_.cursor_current_line; + const auto check_size = extent_type{width + theme.item_frame_padding.height * 2, text_size.height + theme.item_frame_padding.height * 2}; + const rect_type check_rect{check_point, check_size}; + const circle_type check_circle{check_point + check_size / 2, check_size.width / 2}; + drawer.adjust_item_size(context, check_size); + + // ○ text + same_line(context, auto_size, theme.item_inner_spacing.width); + + // text + const auto text_point = canvas_.cursor_current_line + extent_type{0, theme.item_frame_padding.height}; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const auto state = test_mouse(context, id, check_rect, false); + + // draw ○ + if (state & MouseState::HOVERED) + { + drawer.draw_widget_frame(context, check_circle, color_of(theme, ThemeCategory::RADIO_BUTTON_HOVERED)); + } + else + { + drawer.draw_widget_frame(context, check_circle, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + } + + if (checked) + { + const auto check_fill_point = check_point + theme.item_inner_spacing; + const auto check_fill_size = check_size - theme.item_inner_spacing * 2; + const circle_type check_fill_circle{check_fill_point + check_fill_size / 2, check_fill_size.width / 2}; + draw_list_.circle_filled(check_fill_circle, color_of(theme, ThemeCategory::RADIO_BUTTON_ACTIVATED)); + } + + // draw text + draw_list_.text( + font, + drawer.font_size(context), + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + + return state & MouseState::PRESSED; + } + + auto Window::draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool + { + if (skip_item_) + { + return false; + } + accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = const_cast(*this)}; + const IdMaker id_maker{.self = *this}; + + const auto id = id_maker.make_id(context, utf8_text); + const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + + // □ + text + const auto width = text_size.height; + + // □, side length equals string rect height + const auto check_point = canvas_.cursor_current_line; + const auto check_size = extent_type{width + theme.item_frame_padding.height * 2, text_size.height + theme.item_frame_padding.height * 2}; + const rect_type check_rect{check_point, check_size}; + drawer.adjust_item_size(context, check_size); + + // □ text + same_line(context, auto_size, theme.item_inner_spacing.width); + + // text + const auto text_point = canvas_.cursor_current_line + extent_type{0, theme.item_frame_padding.height}; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const auto state = test_mouse(context, id, check_rect, false); + + // draw □ + if (state & MouseState::HOVERED) + { + drawer.draw_widget_frame(context, check_rect, color_of(theme, ThemeCategory::CHECKBOX_HOVERED)); + } + else + { + drawer.draw_widget_frame(context, check_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + } + + if (state & MouseState::PRESSED) + { + checked = not checked; + } + + if (checked) + { + const auto check_fill_point = check_point + theme.item_inner_spacing; + const auto check_fill_size = check_size - theme.item_inner_spacing * 2; + const rect_type check_fill_rect{check_fill_point, check_fill_size}; + draw_list_.rect_filled(check_fill_rect, color_of(theme, ThemeCategory::CHECKBOX_ACTIVATED)); + } + + // draw text + draw_list_.text( + font, + drawer.font_size(context), + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + + return checked; + } + auto Window::same_line(const Context& context, const value_type column_width, value_type spacing_width) noexcept -> void { if (collapsed_) diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index 795664e..f6a33e6 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -230,6 +230,10 @@ namespace gal::prometheus // WIDGETS auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + auto draw_button(Context& context, std::string_view utf8_text, extent_type size, bool repeat_when_held) noexcept -> bool; + auto draw_small_button(Context& context, std::string_view utf8_text, bool repeat_when_held) noexcept -> bool; + auto draw_radio_button(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + auto draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; // ----------------------------------- // LAYOUT From 16582e0efb5e91e36a30882db5b1efde1a572931 Mon Sep 17 00:00:00 2001 From: life4gal Date: Tue, 15 Apr 2025 15:20:18 +0800 Subject: [PATCH 08/54] `fix`: Allow `meta::basic_xxxx_string` to be implicitly converted to `std::basic_string_view`. --- src/meta/string.hpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/meta/string.hpp b/src/meta/string.hpp index 40a3264..3a3ed41 100644 --- a/src/meta/string.hpp +++ b/src/meta/string.hpp @@ -312,19 +312,19 @@ namespace gal::prometheus::meta // ================================================= template - requires(is_constructible_from_data_v) - [[nodiscard]] constexpr explicit operator StringType() const noexcept // + requires(is_constructible_from_view_v) + [[nodiscard]] constexpr explicit(not std::is_convertible_v, StringType>) operator StringType() const noexcept // requires(not lazy_is_static()) { - return StringType{rep_value(), rep_size()}; + return StringType{basic_string_view{rep_value(), rep_size()}}; } template - requires(is_constructible_from_view_v and not is_constructible_from_data_v) - [[nodiscard]] constexpr explicit(not std::is_convertible_v, StringType>) operator StringType() const noexcept // - requires(not lazy_is_static()) + requires(is_constructible_from_data_v) + [[nodiscard]] constexpr explicit operator StringType() const noexcept // + requires(not is_constructible_from_view_v and not lazy_is_static()) { - return StringType{basic_string_view{rep_value(), rep_size()}}; + return StringType{rep_value(), rep_size()}; } template @@ -687,13 +687,20 @@ namespace gal::prometheus::meta std::ranges::copy(std::ranges::begin(char_array), std::ranges::begin(char_array) + N, value); } - template + template requires std::is_same_v, value_type> constexpr explicit basic_fixed_string(const String& string) noexcept { std::ranges::copy(std::ranges::begin(string), std::ranges::begin(string) + N, value); } + template + requires std::is_same_v, value_type> + constexpr explicit basic_fixed_string(const Iterator begin, const Iterator end) noexcept + { + std::ranges::copy(begin, end, value); + } + [[nodiscard]] constexpr auto begin() noexcept -> pointer { return value; } [[nodiscard]] constexpr auto begin() const noexcept -> const_pointer { return value; } @@ -702,6 +709,10 @@ namespace gal::prometheus::meta [[nodiscard]] constexpr auto end() const noexcept -> const_pointer { return value + size; } + [[nodiscard]] constexpr auto operator[](const size_type index) noexcept -> value_type& { return value[index]; } + + [[nodiscard]] constexpr auto operator[](const size_type index) const noexcept -> value_type { return value[index]; } + // // basic_fixed_string <=> basic_fixed_string // template // [[nodiscard]] constexpr auto operator<=>(const basic_fixed_string& rhs) const noexcept -> auto From a0412207c1b529d623989f73746ad1a104e365e9 Mon Sep 17 00:00:00 2001 From: life4gal Date: Tue, 15 Apr 2025 15:31:30 +0800 Subject: [PATCH 09/54] `feat`: Use `meta::member_of_name` gets the corresponding member by name (supports chaining, e.g. a.b->c.d). --- src/meta/member.hpp | 134 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 15 deletions(-) diff --git a/src/meta/member.hpp b/src/meta/member.hpp index 653ef79..8425217 100644 --- a/src/meta/member.hpp +++ b/src/meta/member.hpp @@ -15,17 +15,17 @@ namespace gal::prometheus::meta { - template - struct extern_accessor + namespace member_detail { - [[nodiscard]] constexpr static auto make() noexcept -> T + template + struct extern_accessor { - return T{}; - } - }; + [[nodiscard]] constexpr static auto make() noexcept -> T + { + return T{}; + } + }; - namespace member_detail - { // note that this requires the target type to be default constructible template extern const auto extern_any = extern_accessor::make(); @@ -211,7 +211,10 @@ namespace gal::prometheus::meta } template - requires (known_member_t> and N < member_size>()) + requires ( + known_member_t> and + N < member_size>() + ) [[nodiscard]] constexpr auto member_of_index(T&& object) noexcept -> decltype(auto) { using bare_type = std::remove_cvref_t; @@ -249,7 +252,10 @@ namespace gal::prometheus::meta namespace member_detail { template - requires (member_detail::known_member_t> and N < member_size>()) + requires ( + member_detail::known_member_t> and + N < member_size>() + ) struct member_type_of_index { using type = std::decay_t(std::declval()))>; @@ -260,9 +266,30 @@ namespace gal::prometheus::meta using member_type_of_index = typename member_detail::member_type_of_index::type; template - requires (known_member_t> and N < member_size>()) + requires ( + known_member_t> and + N < member_size>() + ) [[nodiscard]] constexpr auto name_of_member() noexcept -> std::string_view { + // note: + // If you encounter compile errors here, such as: + // "No overloaded function matching `get_full_function_name` was found" + // "The result of `member_detail::visit` was not a (compile time) constant" + // then you should check your class definition about structured bindings at this point: + // class YourClass + // { + // public: + // template + // [[nodiscard]] constexpr auto get() const noexcept -> const value_type& // <-- see this + // + // template + // requires(Index < 2) + // [[nodiscard]] constexpr auto get() noexcept -> value_type&; + // }; + // + // Even if your value_type is a POD type, or can return a value directly (perhaps at less cost than a reference), it must be a constant reference here! + // constexpr auto full_function_name = get_full_function_name< member_detail::visit( [](Ts&&... args) noexcept -> auto // @@ -343,7 +370,8 @@ namespace gal::prometheus::meta { const auto f = []() noexcept { - if constexpr (name_of_member() == Name) + if constexpr (constexpr auto member_name = meta::name_of_member(); + member_name == Name.template as()) { return I; } @@ -361,7 +389,7 @@ namespace gal::prometheus::meta GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH GAL_PROMETHEUS_COMPILER_DISABLE_CLANG_WARNING(-Wunused-value) - (((index = f.template operator()()) == member_index_unknown) and ...); + std::ignore = (((index = f.template operator()()) == member_index_unknown) and ...); GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP @@ -376,7 +404,8 @@ namespace gal::prometheus::meta { const auto f = [name]() noexcept { - if (name_of_member() == name) + if (constexpr auto member_name = meta::name_of_member(); + member_name == name) { return I; } @@ -392,7 +421,7 @@ namespace gal::prometheus::meta GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH GAL_PROMETHEUS_COMPILER_DISABLE_CLANG_WARNING(-Wunused-value) - (((index = f.template operator()()) == member_index_unknown) and ...); + std::ignore = (((index = f.template operator()()) == member_index_unknown) and ...); GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP @@ -429,6 +458,81 @@ namespace gal::prometheus::meta return member_index(name) != member_index_unknown; } + namespace member_detail + { + template + [[nodiscard]] constexpr auto member_of_name(T&& object) noexcept -> decltype(auto) + { + constexpr auto begin = std::ranges::begin(Name); + constexpr auto end = std::ranges::end(Name); + constexpr auto size = std::ranges::size(Name); + + if constexpr (begin == end) + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + else + { + constexpr std::string_view sub{begin, end}; + + constexpr auto sub_ref_index = sub.find_first_of('.'); + constexpr auto sub_ptr_index = sub.find_first_of("->"); + + if constexpr (sub_ref_index == std::string_view::npos and sub_ptr_index == std::string_view::npos) + { + constexpr auto index = meta::member_index(); + + if constexpr (index == member_index_unknown) + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("If you see this message, you've passed in the wrong member name."); + } + else + { + return meta::member_of_index(std::forward(object)); + } + } + else + { + const auto make_sub_name = [](const auto it) noexcept + { + basic_fixed_string r{it, it + N}; + r[N] = '\0'; + return r; + }; + + if constexpr (sub_ref_index != std::string_view::npos) + { + constexpr auto name = make_sub_name.template operator()(begin); + auto&& ref = member_detail::member_of_name(std::forward(object)); + + // "." + constexpr auto next = make_sub_name.template operator()(begin + sub_ref_index + 1); + return member_detail::member_of_name(std::forward(ref)); + } + else if constexpr (sub_ptr_index != std::string_view::npos) + { + constexpr auto name = make_sub_name.template operator()(begin); + auto&& ptr = member_detail::member_of_name(std::forward(object)); + + // "->" + constexpr auto next = make_sub_name.template operator()(begin + sub_ptr_index + 2); + return member_detail::member_of_name(*std::forward(ptr)); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + } + } + } + + template + [[nodiscard]] constexpr auto member_of_name(T&& object) noexcept -> decltype(auto) + { + return member_detail::member_of_name(std::forward(object)); + } + namespace member_detail { enum class FoldCategory : std::uint8_t From c7211dcf86e080407ca3986ec40075194562b56f Mon Sep 17 00:00:00 2001 From: life4gal Date: Tue, 15 Apr 2025 15:32:55 +0800 Subject: [PATCH 10/54] `fix`: `get` needs to return a constant reference (otherwise meta::member_xxx will not be supported). --- src/primitive/extent.hpp | 4 ++-- src/primitive/point.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/primitive/extent.hpp b/src/primitive/extent.hpp index bb0090c..5c45655 100644 --- a/src/primitive/extent.hpp +++ b/src/primitive/extent.hpp @@ -49,7 +49,7 @@ namespace gal::prometheus template requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> value_type + [[nodiscard]] constexpr auto get() const noexcept -> const value_type& { if constexpr (Index == 0) { return width; } else if constexpr (Index == 1) { return height; } @@ -138,7 +138,7 @@ namespace gal::prometheus template requires(Index < 3) - [[nodiscard]] constexpr auto get() const noexcept -> value_type + [[nodiscard]] constexpr auto get() const noexcept -> const value_type& { if constexpr (Index == 0) { return width; } else if constexpr (Index == 1) { return height; } diff --git a/src/primitive/point.hpp b/src/primitive/point.hpp index a92268b..2c8b1ae 100644 --- a/src/primitive/point.hpp +++ b/src/primitive/point.hpp @@ -50,7 +50,7 @@ namespace gal::prometheus template requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> value_type + [[nodiscard]] constexpr auto get() const noexcept -> const value_type& { if constexpr (Index == 0) { return x; } else if constexpr (Index == 1) { return y; } @@ -178,7 +178,7 @@ namespace gal::prometheus template requires(Index < 3) - [[nodiscard]] constexpr auto get() const noexcept -> value_type + [[nodiscard]] constexpr auto get() const noexcept -> const value_type& { if constexpr (Index == 0) { return x; } else if constexpr (Index == 1) { return y; } From 21464a4af89f591cef010be7d0d24f35eec9c26c Mon Sep 17 00:00:00 2001 From: life4gal Date: Tue, 15 Apr 2025 17:20:17 +0800 Subject: [PATCH 11/54] `feat`: Add `gui::draw_slider`. --- src/gui/gui.hpp | 118 +++++++- src/gui/internal/context.cpp | 349 ++++++++++++++++------ src/gui/internal/window.cpp | 542 +++++++++++++++++++++++++++-------- src/gui/internal/window.hpp | 90 +++--- 4 files changed, 848 insertions(+), 251 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index 597c040..f43a37d 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -126,7 +126,7 @@ namespace gal::prometheus BUTTON_HOVERED, BUTTON_ACTIVATED, - // RADIO-BUTTON / CHECKBOX + // RADIO-BUTTON / CHECKBOX / SLIDER FRAME_BACKGROUND, RADIO_BUTTON_HOVERED, @@ -163,8 +163,6 @@ namespace gal::prometheus // Default alpha of window background value_type window_background_alpha; - // Height of the window titlebar - value_type window_titlebar_height; // Rounding of the window corners value_type window_corner_rounding; // Minimum size of the window @@ -394,9 +392,6 @@ namespace gal::prometheus // THEME //------------------------------------------------------------------ - // todo - [[nodiscard]] auto test_theme() noexcept -> Theme; - auto set_default_theme(Context& context, const Theme& theme) noexcept -> void; auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; @@ -427,6 +422,8 @@ namespace gal::prometheus // WIDGET //------------------------------------------------------------------ + auto set_next_window_point(Context& context, const point_type& point) noexcept -> void; + auto begin_window( Context& context, std::string_view name, @@ -441,6 +438,11 @@ namespace gal::prometheus */ auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + /** + * @brief Draw a piece of text with specified color + */ + auto draw_text_colored(Context& context, std::string_view utf8_text, Theme::color_type color) noexcept -> void; + /** * @brief Draw a button, if the specified size is smaller than the size occupied by the text (plus padding), the text will not be completely inside the box * @return Whether the button is pressed or not @@ -506,8 +508,46 @@ namespace gal::prometheus } } + auto draw_slider( + Context& context, + std::string_view utf8_text, + float& reference, + float min, + float max, + std::uint32_t decimal_precision = 3, + float power = 1 + ) noexcept -> bool; + + template + requires std::is_arithmetic_v + auto draw_slider( + Context& context, + T&& object, + const ValueType min, + const std::type_identity_t max, + const std::uint32_t decimal_precision = 3, + const float power = 1 + ) noexcept -> bool + { + auto& ref = meta::member_of_name(std::forward(object)); + auto v = static_cast(ref); + + const auto result = gui::draw_slider( + context, + MemberName, + v, + static_cast(min), + static_cast(max), + decimal_precision, + power + ); + ref = v; + + return result; + } + //------------------------------------------------------------------ - // LAYOUT + // WIDGET LAYOUT //------------------------------------------------------------------ // < 0 @@ -523,7 +563,17 @@ namespace gal::prometheus * @note @c column_width != @c auto_size (@c spacing_width can be any value, force to 0 if less than 0), * the x-axis distance of the next drawn element from the previous element will depend on @c column_width + @c spacing_width */ - auto layout_same_line(const Context& context, Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; + auto layout_same_line(Context& context, Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; + + //------------------------------------------------------------------ + // CANVAS LAYOUT + //------------------------------------------------------------------ + + auto push_item_width(Context& context, Theme::value_type new_item_width) noexcept -> void; + auto pop_item_width(Context& context) noexcept -> void; + + auto push_text_wrap_width(Context& context, Theme::value_type new_wrap_width) noexcept -> void; + auto pop_text_wrap_width(Context& context) noexcept -> void; //------------------------------------------------------------------ // WIDGET STATE @@ -596,6 +646,8 @@ namespace gal::prometheus // WIDGET //------------------------------------------------------------------ + auto set_next_window_point(const point_type& point) noexcept -> void; + auto begin_window( std::string_view name, const extent_type& size = {0, 0}, @@ -609,6 +661,11 @@ namespace gal::prometheus */ auto draw_text(std::string_view utf8_text) noexcept -> void; + /** + * @brief Draw a piece of text with specified color + */ + auto draw_text_colored(std::string_view utf8_text, Theme::color_type color) noexcept -> void; + /** * @brief Draw a button, if the specified size is smaller than the size occupied by the text (plus padding), the text will not be completely inside the box * @return Whether the button is pressed or not @@ -660,8 +717,32 @@ namespace gal::prometheus gui::draw_checkbox(context, utf8_text, reference, checked_identifier, unchecked_identifier); } + auto draw_slider( + std::string_view utf8_text, + float& reference, + float min, + float max, + std::uint32_t decimal_precision = 3, + float power = 1 + ) noexcept -> bool; + + template + requires std::is_arithmetic_v + auto draw_slider( + T& object, + const ValueType min, + const std::type_identity_t max, + const std::uint32_t decimal_precision = 3, + const float power = 1 + ) noexcept -> bool + { + auto& context = get_current_context(); + + return gui::draw_slider(context, object, min, max, decimal_precision, power); + } + //------------------------------------------------------------------ - // LAYOUT + // WIDGET LAYOUT //------------------------------------------------------------------ /** @@ -676,6 +757,16 @@ namespace gal::prometheus */ auto layout_same_line(Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; + //------------------------------------------------------------------ + // CANVAS LAYOUT + //------------------------------------------------------------------ + + auto push_item_width(Theme::value_type new_item_width) noexcept -> void; + auto pop_item_width() noexcept -> void; + + auto push_text_wrap_width(Theme::value_type new_wrap_width) noexcept -> void; + auto pop_text_wrap_width() noexcept -> void; + //------------------------------------------------------------------ // WIDGET STATE //------------------------------------------------------------------ @@ -686,5 +777,14 @@ namespace gal::prometheus [[nodiscard]] auto get_window_content_region_min() noexcept -> extent_type; // The available area of the current window [[nodiscard]] auto get_window_content_region_max() noexcept -> extent_type; + + //------------------------------------------------------------------ + // FOR TEST + //------------------------------------------------------------------ + + // todo + [[nodiscard]] auto test_theme() noexcept -> Theme; + + auto show_theme_editor() noexcept -> bool; } } diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index c27f042..1390231 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -70,13 +70,13 @@ namespace [[nodiscard]] auto find_or_create_window(Context& context, const std::string_view name, const extent_type& size, const internal::Window::Flag flag) noexcept -> internal::Window& { [[maybe_unused]] const auto is_child_window = flag.is(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((is_child_window == true) == (context.window_current_stack.size() > 1)); if (auto* window = find_window(context, name); window == nullptr) { // find root - auto* root = find_root_window(context); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(is_child_window == (root != nullptr)); + auto* root = is_child_window ? find_root_window(context) : nullptr; // fixme: load cached settings? auto temp = memory::make_unique(name, flag, context.window_default_spawn_position, size, root); @@ -98,22 +98,26 @@ namespace /** * @brief Find the first (more top-level) window that contains the location of the given point */ - [[nodiscard]] auto find_hovered_window(Context& context, const point_type& position, bool parent_only) noexcept -> internal::Window* + template + [[nodiscard]] auto find_hovered_window(Context& context, const point_type& position) noexcept -> internal::Window* { auto view = context.window_list | std::views::reverse; const auto it = std::ranges::find_if( view, - [position, parent_only](const auto& window) noexcept -> bool + [position](const auto& window) noexcept -> bool { if (not window->visible()) { return false; } - if (parent_only and window->flag().template is()) + if constexpr (ExcludesChildren) { - return false; + if (window->flag().template is()) + { + return false; + } } const auto rect = window->rect(); @@ -179,79 +183,6 @@ namespace gal::prometheus::gui destroy_context(*context); } - auto test_theme() noexcept -> Theme - { - constexpr auto default_colors = []() noexcept -> Theme::colors_type - { - using enum ThemeCategory; - - Theme::colors_type colors{}; - - colors[static_cast(TEXT)] = primitive::colors::black; - - colors[static_cast(BORDER)] = primitive::colors::magenta; - colors[static_cast(BORDER_SHADOW)] = primitive::colors::red; - - colors[static_cast(WINDOW_BACKGROUND)] = primitive::colors::gains_boro; - - colors[static_cast(TITLEBAR)] = primitive::colors::light_coral; - colors[static_cast(TITLEBAR_COLLAPSED)] = primitive::colors::dark_khaki; - - colors[static_cast(RESIZE_GRIP)] = primitive::colors::gold; - colors[static_cast(RESIZE_GRIP_HOVERED)] = primitive::colors::peru; - colors[static_cast(RESIZE_GRIP_ACTIVATED)] = primitive::colors::powder_blue; - - colors[static_cast(SCROLLBAR_BACKGROUND)] = primitive::colors::white; - colors[static_cast(SCROLLBAR_GRAB)] = primitive::colors::dark_salmon; - colors[static_cast(SCROLLBAR_GRAB_HOVERED)] = primitive::colors::dark_green; - colors[static_cast(SCROLLBAR_GRAB_ACTIVATED)] = primitive::colors::dark_goldenrod; - - colors[static_cast(CLOSE_BUTTON)] = primitive::colors::red; - colors[static_cast(CLOSE_BUTTON_HOVERED)] = primitive::colors::violet_red; - colors[static_cast(CLOSE_BUTTON_ACTIVATED)] = primitive::colors::white; - - colors[static_cast(TOOLTIP_BACKGROUND)] = primitive::colors::black; - colors[static_cast(TOOLTIP_TEXT)] = primitive::colors::red; - - colors[static_cast(BUTTON)] = primitive::colors::sienna; - colors[static_cast(BUTTON_HOVERED)] = primitive::colors::slate_gray; - colors[static_cast(BUTTON_ACTIVATED)] = primitive::colors::steel_blue; - - colors[static_cast(FRAME_BACKGROUND)] = primitive::colors::steel_blue; - - colors[static_cast(RADIO_BUTTON_HOVERED)] = primitive::colors::powder_blue; - colors[static_cast(RADIO_BUTTON_ACTIVATED)] = primitive::colors::green_yellow; - - colors[static_cast(CHECKBOX_HOVERED)] = primitive::colors::powder_blue; - colors[static_cast(CHECKBOX_ACTIVATED)] = primitive::colors::green_yellow; - - colors[static_cast(SLIDER)] = primitive::colors::light_blue; - colors[static_cast(SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; - - return colors; - }; - - return - { - .window_background_alpha = .65f, - .window_titlebar_height = 20, - .window_corner_rounding = 0, - .window_min_size = {64, 48}, - .window_resize_grip_size = {20, 20}, - .window_padding = {8, 8}, - .window_auto_fit_padding = {8, 8}, - .window_vertical_scrollbar_width = 10, - .item_default_width_factor = .65f, - .item_frame_padding = {4, 4}, - .item_spacing = {10, 5}, - .item_inner_spacing = {5, 5}, - .alpha = 1, - .colors = default_colors(), - .circle_segment_max_error = .3f, - .draw_curve_tessellation_tolerance = 1.25f, - }; - } - auto set_default_theme(Context& context, const Theme& theme) noexcept -> void { context.theme = theme; @@ -303,15 +234,21 @@ namespace gal::prometheus::gui context.widget_activated != internal::invalid_widget_id ) { - context.widget_activated_previous_frame = context.widget_activated; + context.widget_activated = internal::invalid_widget_id; } + context.widget_activated_previous_frame = context.widget_activated; context.widget_activated_still_alive = false; } // Update window { - context.window_hovered = find_hovered_window(context, mouse_position, false); - context.window_hovered_root = find_hovered_window(context, mouse_position, true); + context.window_hovered = find_hovered_window(context, mouse_position); + context.window_hovered_root = find_hovered_window(context, mouse_position); + + if (context.window_hovered != nullptr) + { + context.window_hovered->handle_inputs(context); + } // Mark all windows as not visible std::ranges::for_each( @@ -325,11 +262,6 @@ namespace gal::prometheus::gui // No window should be open at the beginning of the frame // But in order to allow the user to call `new_frame` multiple times without calling `render`, we are doing an explicit clear context.window_current_stack.clear(); - - if (context.window_hovered != nullptr) - { - context.window_hovered->handle_inputs(context); - } } } @@ -361,7 +293,10 @@ namespace gal::prometheus::gui [&](auto* window) noexcept -> void { // todo: child window - if (window->flag().template is() and window->visible()) + if ( + window->flag().template is() and + window->visible() + ) { return; } @@ -424,6 +359,12 @@ namespace gal::prometheus::gui return data; } + auto set_next_window_point(Context& context, const point_type& point) noexcept -> void + { + // todo + context.window_default_spawn_position = point; + } + auto begin_window( Context& context, const std::string_view name, @@ -445,8 +386,6 @@ namespace gal::prometheus::gui } const auto is_child_window = window.flag().is(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(is_child_window == (context.window_current_stack.size() > 1)); - auto* parent = is_child_window ? context.window_current_stack[context.window_current_stack.size() - 2] : nullptr; return window.begin_draw(context, fill_alpha, parent); @@ -465,7 +404,7 @@ namespace gal::prometheus::gui context.widget_activated == internal::invalid_widget_id and context.widget_hovered == internal::invalid_widget_id and context.window_hovered_root == std::addressof(window) and - window.hovered(context, rect) and + window.is_hovered(context, rect) and context.mouse.is_clicked(context, MouseKey::LEFT) ) { @@ -483,6 +422,13 @@ namespace gal::prometheus::gui window.draw_text(context, utf8_text); } + auto draw_text_colored(Context& context, const std::string_view utf8_text, const Theme::color_type color) noexcept -> void + { + push_theme(context, ThemeCategory::TEXT, color); + draw_text(context, utf8_text); + pop_theme(context); + } + auto draw_button(Context& context, const std::string_view utf8_text, const extent_type& size, const bool repeat_when_held) noexcept -> bool { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -515,7 +461,23 @@ namespace gal::prometheus::gui return window.draw_checkbox(context, utf8_text, checked); } - auto layout_same_line(const Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void + auto draw_slider( + Context& context, + const std::string_view utf8_text, + float& reference, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_slider(context, utf8_text, reference, min, max, decimal_precision, power); + } + + auto layout_same_line(Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -523,6 +485,38 @@ namespace gal::prometheus::gui window.same_line(context, column_width, spacing_width); } + auto push_item_width(Context& context, const Theme::value_type new_item_width) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + window.push_item_width(context, new_item_width); + } + + auto pop_item_width(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + window.pop_item_width(context); + } + + auto push_text_wrap_width(Context& context, const Theme::value_type new_wrap_width) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + window.push_text_wrap_width(context, new_wrap_width); + } + + auto pop_text_wrap_width(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + window.pop_text_wrap_width(context); + } + auto get_content_region_max(const Context& context) noexcept -> extent_type { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -641,6 +635,13 @@ namespace gal::prometheus::gui return get_draw_data(context); } + auto set_next_window_point(const point_type& point) noexcept -> void + { + auto& context = get_current_context(); + + set_next_window_point(context, point); + } + auto begin_window( const std::string_view name, const extent_type& size, @@ -667,6 +668,13 @@ namespace gal::prometheus::gui draw_text(context, utf8_text); } + auto draw_text_colored(const std::string_view utf8_text, const Theme::color_type color) noexcept -> void + { + auto& context = get_current_context(); + + draw_text_colored(context, utf8_text, color); + } + auto draw_button(const std::string_view utf8_text, const extent_type& size, const bool repeat_when_held) noexcept -> bool { auto& context = get_current_context(); @@ -695,13 +703,55 @@ namespace gal::prometheus::gui return draw_checkbox(context, utf8_text, checked); } + auto draw_slider( + const std::string_view utf8_text, + float& reference, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + auto& context = get_current_context(); + + return draw_slider(context, utf8_text, reference, min, max, decimal_precision, power); + } + auto layout_same_line(const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { - const auto& context = get_current_context(); + auto& context = get_current_context(); layout_same_line(context, column_width, spacing_width); } + auto push_item_width(const Theme::value_type new_item_width) noexcept -> void + { + auto& context = get_current_context(); + + push_item_width(context, new_item_width); + } + + auto pop_item_width() noexcept -> void + { + auto& context = get_current_context(); + + pop_item_width(context); + } + + auto push_text_wrap_width(const Theme::value_type new_wrap_width) noexcept -> void + { + auto& context = get_current_context(); + + push_text_wrap_width(context, new_wrap_width); + } + + auto pop_text_wrap_width() noexcept -> void + { + auto& context = get_current_context(); + + pop_text_wrap_width(context); + } + auto get_content_region_max() noexcept -> extent_type { const auto& context = get_current_context(); @@ -723,6 +773,112 @@ namespace gal::prometheus::gui return get_window_content_region_max(context); } + auto test_theme() noexcept -> Theme + { + constexpr auto default_colors = []() noexcept -> Theme::colors_type + { + using enum ThemeCategory; + + Theme::colors_type colors{}; + + colors[static_cast(TEXT)] = primitive::colors::black; + + colors[static_cast(BORDER)] = primitive::colors::magenta; + colors[static_cast(BORDER_SHADOW)] = primitive::colors::red; + + colors[static_cast(WINDOW_BACKGROUND)] = primitive::colors::gains_boro; + + colors[static_cast(TITLEBAR)] = primitive::colors::light_coral; + colors[static_cast(TITLEBAR_COLLAPSED)] = primitive::colors::dark_khaki; + + colors[static_cast(RESIZE_GRIP)] = primitive::colors::gold; + colors[static_cast(RESIZE_GRIP_HOVERED)] = primitive::colors::peru; + colors[static_cast(RESIZE_GRIP_ACTIVATED)] = primitive::colors::powder_blue; + + colors[static_cast(SCROLLBAR_BACKGROUND)] = primitive::colors::white; + colors[static_cast(SCROLLBAR_GRAB)] = primitive::colors::dark_salmon; + colors[static_cast(SCROLLBAR_GRAB_HOVERED)] = primitive::colors::dark_green; + colors[static_cast(SCROLLBAR_GRAB_ACTIVATED)] = primitive::colors::dark_goldenrod; + + colors[static_cast(CLOSE_BUTTON)] = primitive::colors::red; + colors[static_cast(CLOSE_BUTTON_HOVERED)] = primitive::colors::violet_red; + colors[static_cast(CLOSE_BUTTON_ACTIVATED)] = primitive::colors::white; + + colors[static_cast(TOOLTIP_BACKGROUND)] = primitive::colors::black; + colors[static_cast(TOOLTIP_TEXT)] = primitive::colors::red; + + colors[static_cast(BUTTON)] = primitive::colors::sienna; + colors[static_cast(BUTTON_HOVERED)] = primitive::colors::slate_gray; + colors[static_cast(BUTTON_ACTIVATED)] = primitive::colors::steel_blue; + + colors[static_cast(FRAME_BACKGROUND)] = primitive::colors::steel_blue; + + colors[static_cast(RADIO_BUTTON_HOVERED)] = primitive::colors::powder_blue; + colors[static_cast(RADIO_BUTTON_ACTIVATED)] = primitive::colors::green_yellow; + + colors[static_cast(CHECKBOX_HOVERED)] = primitive::colors::powder_blue; + colors[static_cast(CHECKBOX_ACTIVATED)] = primitive::colors::green_yellow; + + colors[static_cast(SLIDER)] = primitive::colors::light_blue; + colors[static_cast(SLIDER_ACTIVATED)] = primitive::colors::light_pink; + + return colors; + }; + + return + { + .window_background_alpha = .65f, + .window_corner_rounding = 0, + .window_min_size = {64, 48}, + .window_resize_grip_size = {20, 20}, + .window_padding = {8, 8}, + .window_auto_fit_padding = {8, 8}, + .window_vertical_scrollbar_width = 10, + .item_default_width_factor = .65f, + .item_frame_padding = {4, 4}, + .item_spacing = {10, 5}, + .item_inner_spacing = {5, 5}, + .alpha = 1, + .colors = default_colors(), + .circle_segment_max_error = .3f, + .draw_curve_tessellation_tolerance = 1.25f, + }; + } + + auto show_theme_editor() noexcept -> bool + { + auto& context = get_current_context(); + auto& theme = context.theme; + + const auto window_closed = begin_window(context, "ThemeEditor"); + + draw_slider<"window_background_alpha">(context, theme, 0, 1); + draw_slider<"window_corner_rounding">(context, theme, 0, 24); + draw_slider<"window_min_size.width">(context, theme, 64, 640); + draw_slider<"window_min_size.height">(context, theme, 48, 480); + draw_slider<"window_resize_grip_size.width">(context, theme, 10, 50); + draw_slider<"window_resize_grip_size.height">(context, theme, 10, 50); + draw_slider<"window_padding.width">(context, theme, 2, 20); + draw_slider<"window_padding.height">(context, theme, 2, 20); + draw_slider<"window_auto_fit_padding.width">(context, theme, 2, 20); + draw_slider<"window_auto_fit_padding.height">(context, theme, 2, 20); + draw_slider<"window_vertical_scrollbar_width">(context, theme, 6, 25); + draw_slider<"item_default_width_factor">(context, theme, .35f, .85f); + draw_slider<"item_frame_padding.width">(context, theme, 1, 10); + draw_slider<"item_frame_padding.height">(context, theme, 1, 10); + draw_slider<"item_spacing.width">(context, theme, 1, 10); + draw_slider<"item_spacing.height">(context, theme, 1, 10); + draw_slider<"alpha">(context, theme, 0, 1); + + // todo: update DrawListSharedData + draw_slider<"circle_segment_max_error">(context, theme, 0, 1); + draw_slider<"draw_curve_tessellation_tolerance">(context, theme, 0, 5); + + end_window(context); + + return window_closed; + } + namespace internal { auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag @@ -920,7 +1076,7 @@ namespace gal::prometheus::gui // new context.widget_hovered == invalid_widget_id and // mouse - window.hovered(context, area); + window.is_hovered(context, area); if (hovered) { @@ -979,6 +1135,11 @@ namespace gal::prometheus::gui auto focus_window(Context& context, Window& window) noexcept -> void { + if (context.window_focused == std::addressof(window)) + { + return; + } + context.window_focused = std::addressof(window); const auto it = std::ranges::find(context.window_list, std::addressof(window)); diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 89d4fb6..6569494 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -250,6 +250,26 @@ namespace gal::prometheus::gui::internal window.size_of_content_ = window.size_of_content_.combine_max(extent_type{canvas.cursor_previous_line.x, canvas.cursor_current_line.y + window.scroll_y_} - window.point_); } + auto test_last_item(const Context& context, const rect_type& rect) noexcept -> void + { + auto& window = self.get(); + auto& canvas = window.canvas_; + + canvas.last_item_rect = rect; + canvas.last_item_focused = false; + canvas.last_item_hovered = window.is_hovered(context, rect); + } + + [[nodiscard]] auto is_visible_area(const Context& context, const rect_type& rect) const noexcept -> bool + { + std::ignore = context; + + auto& window = self.get(); + + const auto& last = window.clip_rect_stack_.back(); + return last.intersects(rect); + } + // ----------------------------------- // FRAME @@ -308,6 +328,9 @@ namespace gal::prometheus::gui::internal .cursor_previous_line = {0, 0}, .height_current_line = 0, .height_previous_line = 0, + .last_item_rect = {0, 0, 0, 0}, + .last_item_hovered = false, + .last_item_focused = false, .item_width = {}, .text_wrap_width = {} }, @@ -446,6 +469,7 @@ namespace gal::prometheus::gui::internal const auto& font = current_font(context); const auto& theme = current_theme(context); + const auto font_size = drawer.font_size(context); const auto has_titlebar = not flag_.is(); const auto is_child_window = flag_.is(); @@ -507,8 +531,7 @@ namespace gal::prometheus::gui::internal if (not is_child_window) { - const auto s = drawer.font_size(context); - const auto pad = extent_type{s * 2.f, s * 2.f}; + const auto pad = extent_type{font_size * 2.f, font_size * 2.f}; // Limit the current window from moving outside the program's visual area point_ = point_.clamp( @@ -554,7 +577,7 @@ namespace gal::prometheus::gui::internal if (is_window_hovered(context, *this)) { if (const auto rect = drawer.titlebar_rect(context); - hovered(context, rect) and + is_hovered(context, rect) and mouse.is_double_clicked(context, MouseKey::LEFT) ) { @@ -911,7 +934,6 @@ namespace gal::prometheus::gui::internal } // title text - const auto font_size = drawer.font_size(context); const auto text_point = point_ + theme.item_frame_padding; const auto text_size = internal::text_size(font, name_, font_size, Font::no_auto_wrap); @@ -1050,6 +1072,26 @@ namespace gal::prometheus::gui::internal return close_button_pressed; } + auto Window::end_draw(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); + + // canvas rect + pop_clip_rect(context); + + // window rect + pop_clip_rect(context); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.empty()); + + // window_data.root = nullptr; + } + + auto Window::render(Context& context) const noexcept -> void + { + context.draw_lists.emplace_back(draw_list_); + } + auto Window::draw_text(Context& context, const std::string_view utf8_text) noexcept -> void { if (skip_item_) @@ -1126,9 +1168,11 @@ namespace gal::prometheus::gui::internal text_size.height = static_cast(std::ranges::distance(view)) * line_height; } - drawer.adjust_item_size(context, text_size); + const rect_type rect{canvas_.cursor_current_line, text_size}; - // fixme: hovering text? + drawer.adjust_item_size(context, text_size); + // todo: test visible? + drawer.test_last_item(context, rect); } else { @@ -1141,9 +1185,14 @@ namespace gal::prometheus::gui::internal font_size, this_wrap_width ); - drawer.adjust_item_size(context, text_size); + const rect_type rect{text_point, text_size}; - // fixme: hovering text? + drawer.adjust_item_size(context, text_size); + if (not drawer.is_visible_area(context, rect)) + { + return; + } + drawer.test_last_item(context, rect); draw_list_.text( font, @@ -1169,8 +1218,10 @@ namespace gal::prometheus::gui::internal Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; + const auto font_size = drawer.font_size(context); + const auto id = id_maker.make_id(context, utf8_text); - const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); if (size.width <= 0) { @@ -1184,7 +1235,14 @@ namespace gal::prometheus::gui::internal const auto button_point = canvas_.cursor_current_line; const auto button_size = size + theme.item_frame_padding * 2; const rect_type button_rect{button_point, button_size}; + drawer.adjust_item_size(context, button_size); + if (not drawer.is_visible_area(context, button_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, button_rect); const auto state = test_mouse(context, id, button_rect, repeat_when_held); @@ -1227,7 +1285,7 @@ namespace gal::prometheus::gui::internal // draw text draw_list_.text( font, - drawer.font_size(context), + font_size, text_point, color_of(theme, ThemeCategory::TEXT), utf8_text, @@ -1255,13 +1313,22 @@ namespace gal::prometheus::gui::internal Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; + const auto font_size = drawer.font_size(context); + const auto id = id_maker.make_id(context, utf8_text); - const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); const auto button_point = canvas_.cursor_current_line; const auto button_size = text_size + theme.item_frame_padding * 2; const rect_type button_rect{button_point, button_size}; + drawer.adjust_item_size(context, button_size); + if (not drawer.is_visible_area(context, button_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, button_rect); const auto state = test_mouse(context, id, button_rect, repeat_when_held); @@ -1285,7 +1352,7 @@ namespace gal::prometheus::gui::internal // draw text draw_list_.text( font, - drawer.font_size(context), + font_size, text_area_point, color_of(theme, ThemeCategory::TEXT), utf8_text, @@ -1308,15 +1375,16 @@ namespace gal::prometheus::gui::internal Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; + const auto font_size = drawer.font_size(context); + const auto id = id_maker.make_id(context, utf8_text); - const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); // ○ + text - const auto width = text_size.height; // ○, diameter equals string rect height const auto check_point = canvas_.cursor_current_line; - const auto check_size = extent_type{width + theme.item_frame_padding.height * 2, text_size.height + theme.item_frame_padding.height * 2}; + const auto check_size = extent_type{text_size.height + theme.item_frame_padding.height * 2 - 1, text_size.height + theme.item_frame_padding.height * 2 - 1}; const rect_type check_rect{check_point, check_size}; const circle_type check_circle{check_point + check_size / 2, check_size.width / 2}; drawer.adjust_item_size(context, check_size); @@ -1329,6 +1397,15 @@ namespace gal::prometheus::gui::internal const rect_type text_rect{text_point, text_size}; drawer.adjust_item_size(context, text_size); + const rect_type total_rect{check_rect.left_top(), text_rect.right_bottom()}; + if (not drawer.is_visible_area(context, total_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, total_rect); + + // fixme: test check_rect or total_rect? const auto state = test_mouse(context, id, check_rect, false); // draw ○ @@ -1352,7 +1429,7 @@ namespace gal::prometheus::gui::internal // draw text draw_list_.text( font, - drawer.font_size(context), + font_size, text_rect.left_top(), color_of(theme, ThemeCategory::TEXT), utf8_text, @@ -1362,7 +1439,7 @@ namespace gal::prometheus::gui::internal return state & MouseState::PRESSED; } - auto Window::draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool + auto Window::draw_checkbox(Context& context, const std::string_view utf8_text, bool checked) noexcept -> bool { if (skip_item_) { @@ -1375,8 +1452,10 @@ namespace gal::prometheus::gui::internal Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; + const auto font_size = drawer.font_size(context); + const auto id = id_maker.make_id(context, utf8_text); - const auto text_size = internal::text_size(font, utf8_text, drawer.font_size(context), Font::no_auto_wrap); + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); // □ + text const auto width = text_size.height; @@ -1395,6 +1474,15 @@ namespace gal::prometheus::gui::internal const rect_type text_rect{text_point, text_size}; drawer.adjust_item_size(context, text_size); + const rect_type total_rect{check_rect.left_top(), text_rect.right_bottom()}; + if (not drawer.is_visible_area(context, total_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, total_rect); + + // fixme: test check_rect or total_rect? const auto state = test_mouse(context, id, check_rect, false); // draw □ @@ -1423,7 +1511,7 @@ namespace gal::prometheus::gui::internal // draw text draw_list_.text( font, - drawer.font_size(context), + font_size, text_rect.left_top(), color_of(theme, ThemeCategory::TEXT), utf8_text, @@ -1433,7 +1521,206 @@ namespace gal::prometheus::gui::internal return checked; } - auto Window::same_line(const Context& context, const value_type column_width, value_type spacing_width) noexcept -> void + auto Window::draw_slider( + Context& context, + const std::string_view utf8_text, + float& reference, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + if (skip_item_) + { + return false; + } + accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = const_cast(*this)}; + const IdMaker id_maker{.self = *this}; + + const auto font_size = drawer.font_size(context); + + const auto id = id_maker.make_id(context, utf8_text); + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); + + // □ + text + const auto last_item_width = canvas_.item_width.back(); + + // □, height equals last item width + const auto slider_point = canvas_.cursor_current_line + theme.item_frame_padding; + const auto slider_size = extent_type{last_item_width, text_size.height}; + const rect_type slider_rect{slider_point, slider_size}; + + const auto frame_point = canvas_.cursor_current_line; + const auto frame_size = slider_size + theme.item_frame_padding * 2; + const rect_type frame_rect{frame_point, frame_size}; + + drawer.adjust_item_size(context, frame_size); + + // □ text + same_line(context, auto_size, theme.item_inner_spacing.width); + + // text + const auto text_point = canvas_.cursor_current_line + theme.item_frame_padding; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const rect_type total_rect{frame_rect.left_top(), text_rect.right_bottom()}; + if (not drawer.is_visible_area(context, total_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, total_rect); + + const auto state = test_mouse(context, id, slider_rect, false); + + // draw □ (frame + slider + text) + bool value_changed = false; + { + // frame + drawer.draw_widget_frame(context, frame_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + + // slider + + // todo + constexpr float grab_size_in_pixels = 10.f; + + const auto slider_effective_width = slider_size.width - grab_size_in_pixels; + const auto slider_effective_x1 = slider_point.x + grab_size_in_pixels * .5f; + const auto slider_effective_x2 = slider_point.x + slider_size.width - grab_size_in_pixels * .5f; + + const auto linear_zero_pos = [=]() noexcept -> float + { + if (min * max < 0) + { + // different sign + const auto linear_dist_min_to_0 = std::powf(std::abs(.0f - min), 1.f / power); + const auto linear_dist_max_to_0 = std::powf(std::abs(max - .0f), 1.f / power); + return linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0); + } + + // same sign + return min < 0 ? 1.f : .0f; + }(); + + if (state & MouseState::KEEPING) + { + const auto mouse_position = context.mouse.position_current; + const auto normalized_x = std::ranges::clamp((mouse_position.x - slider_effective_x1) / slider_effective_width, .0f, 1.f); + + // account for logarithmic scale on both sides of the zero + auto value = [=]() noexcept -> float + { + if (normalized_x < linear_zero_pos) + { + // rescale to the negative range before powering + auto v = 1.f - (normalized_x / linear_zero_pos); + v = std::powf(v, power); + return std::lerp(std::ranges::min(max, .0f), min, v); + } + + // rescale to the positive range before powering + auto v = normalized_x; + if (std::abs(linear_zero_pos - 1.f) > 1e-6) + { + v = (v - linear_zero_pos) / (1.f - linear_zero_pos); + } + v = std::powf(v, power); + return std::lerp(std::ranges::max(min, .0f), max, v); + }(); + + const auto min_step = 1.f / std::powf(10.f, static_cast(decimal_precision)); + const auto remainder = std::fmodf(value, min_step); + + if (remainder <= min_step * .5f) + { + value -= remainder; + } + else + { + value += (min_step - remainder); + } + + if (reference != value) // NOLINT(clang-diagnostic-float-equal) + { + reference = value; + value_changed = true; + } + } + + // grab + { + const auto v = [=]() noexcept -> float + { + const auto clamped = std::ranges::clamp(reference, min, max); + if (clamped < .0f) + { + const auto f = 1.f - (clamped - min) / (std::ranges::min(.0f, max) - min); + return (1.f - std::powf(f, 1.f / power)) * linear_zero_pos; + } + + const auto f = (clamped - std::ranges::max(0.f, min)) / (max - std::ranges::max(0.f, min)); + return linear_zero_pos + std::powf(f, 1.f / power) * (1.f - linear_zero_pos); + }(); + + const auto x = std::lerp(slider_effective_x1, slider_effective_x2, v); + const point_type grab_point{x - grab_size_in_pixels * .5f, frame_point.y + 2.f}; + const extent_type grab_size{grab_size_in_pixels, frame_size.height - 4.f}; + const rect_type grab_rect{grab_point, grab_size}; + + if (state & MouseState::PRESSED) + { + draw_list_.rect_filled( + grab_rect, + color_of(theme, ThemeCategory::SLIDER_ACTIVATED), + theme.window_corner_rounding, + DrawFlag::ROUND_CORNER_ALL + ); + } + else + { + draw_list_.rect_filled( + grab_rect, + color_of(theme, ThemeCategory::SLIDER), + theme.window_corner_rounding, + DrawFlag::ROUND_CORNER_ALL + ); + } + } + + // text + const auto value_text = std::format("{:.{}f}", reference, decimal_precision); + const auto value_text_size = internal::text_size(font, value_text, font_size, Font::no_auto_wrap); + const point_type value_text_point{slider_point.x + slider_size.width / 2 - value_text_size.width / 2, frame_point.y + theme.item_frame_padding.height}; + draw_list_.text( + font, + font_size, + value_text_point, + color_of(theme, ThemeCategory::TEXT), + value_text + ); + } + + // draw text + draw_list_.text( + font, + font_size, + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + + return value_changed; + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::same_line(Context& context, const value_type column_width, value_type spacing_width) noexcept -> void { if (collapsed_) { @@ -1462,24 +1749,123 @@ namespace gal::prometheus::gui::internal } } - auto Window::end_draw(Context& context) noexcept -> void + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::push_item_width(Context& context, const Theme::value_type new_item_width) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); + std::ignore = context; - // canvas rect - pop_clip_rect(context); + canvas_.item_width.push_back(new_item_width); + } - // window rect - pop_clip_rect(context); + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::pop_item_width(Context& context) noexcept -> void + { + std::ignore = context; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.empty()); + canvas_.item_width.pop_back(); + } - // window_data.root = nullptr; + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::push_text_wrap_width(Context& context, const Theme::value_type new_wrap_width) noexcept -> void + { + std::ignore = context; + + canvas_.text_wrap_width.push_back(new_wrap_width); } - auto Window::render(Context& context) const noexcept -> void + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::pop_text_wrap_width(Context& context) noexcept -> void { - context.draw_lists.emplace_back(draw_list_); + std::ignore = context; + + canvas_.text_wrap_width.pop_back(); + } + + auto Window::id_of_move(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::MOVE@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::id_of_close(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::CLOSE@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::id_of_resize(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::RESIZE@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::id_of_scrollbar(Context& context) const noexcept -> widget_id_type + { + constexpr std::string_view name{"@WINDOW::SCROLLBAR@"}; + return IdMaker{.self = const_cast(*this)}.make_id(context, name); + } + + auto Window::push_id(Context& context, const std::string_view string) noexcept -> void + { + const auto id = IdMaker{.self = *this}.make_id(context, string); + id_stack_.push_back(id); + } + + auto Window::push_id(Context& context, const void* pointer) noexcept -> void + { + const auto id = IdMaker{.self = *this}.make_id(context, pointer); + id_stack_.push_back(id); + } + + auto Window::push_id(Context& context, const widget_id_type value) noexcept -> void + { + const auto id = IdMaker{.self = *this}.make_id(context, value); + id_stack_.push_back(id); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::pop_id(Context& context) noexcept -> void + { + std::ignore = context; + + id_stack_.pop_back(); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::push_clip_rect(Context& context, const rect_type& rect, const bool clipped) noexcept -> void + { + std::ignore = context; + + const auto clip_rect = [&]() noexcept -> rect_type + { + if (clipped and not clip_rect_stack_.empty()) + { + // clip to a new rect + const auto last = clip_rect_stack_.back(); + return last.combine_min(rect); + } + + return rect; + }(); + + clip_rect_stack_.push_back(clip_rect); + draw_list_.push_clip_rect(clip_rect, false); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::pop_clip_rect(Context& context) noexcept -> void + { + clip_rect_stack_.pop_back(); + + if (clip_rect_stack_.empty()) + { + const auto size = context.io.display_size; + draw_list_.push_clip_rect({0, 0, size}, false); + } + else + { + draw_list_.push_clip_rect(clip_rect_stack_.back(), false); + } } auto Window::name() const noexcept -> std::string_view @@ -1576,14 +1962,13 @@ namespace gal::prometheus::gui::internal return size; } - auto Window::hovered(const Context& context, const rect_type& rect) const noexcept -> bool + auto Window::is_hovered(const Context& context, const rect_type& rect) const noexcept -> bool { const auto clipped = [&]() noexcept -> rect_type { if (not clip_rect_stack_.empty()) { const auto& last = clip_rect_stack_.back(); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(last.includes(rect)); return rect.combine_min(last); } @@ -1594,101 +1979,28 @@ namespace gal::prometheus::gui::internal return clipped.includes(context.mouse.position_current); } - auto Window::show() noexcept -> void - { - visible_ = true; - } - - auto Window::hide() noexcept -> void - { - visible_ = false; - accessed_ = false; - } - - auto Window::id_of_move(Context& context) const noexcept -> widget_id_type - { - constexpr std::string_view name{"@WINDOW::MOVE@"}; - return IdMaker{.self = const_cast(*this)}.make_id(context, name); - } - - auto Window::id_of_close(Context& context) const noexcept -> widget_id_type - { - constexpr std::string_view name{"@WINDOW::CLOSE@"}; - return IdMaker{.self = const_cast(*this)}.make_id(context, name); - } - - auto Window::id_of_resize(Context& context) const noexcept -> widget_id_type - { - constexpr std::string_view name{"@WINDOW::RESIZE@"}; - return IdMaker{.self = const_cast(*this)}.make_id(context, name); - } - - auto Window::id_of_scrollbar(Context& context) const noexcept -> widget_id_type + auto Window::is_item_hovered(const Context& context) const noexcept -> bool { - constexpr std::string_view name{"@WINDOW::SCROLLBAR@"}; - return IdMaker{.self = const_cast(*this)}.make_id(context, name); - } - - auto Window::push_id(Context& context, const std::string_view string) noexcept -> void - { - const auto id = IdMaker{.self = *this}.make_id(context, string); - id_stack_.push_back(id); - } - - auto Window::push_id(Context& context, const void* pointer) noexcept -> void - { - const auto id = IdMaker{.self = *this}.make_id(context, pointer); - id_stack_.push_back(id); - } + std::ignore = context; - auto Window::push_id(Context& context, const widget_id_type value) noexcept -> void - { - const auto id = IdMaker{.self = *this}.make_id(context, value); - id_stack_.push_back(id); + return canvas_.last_item_hovered; } - // ReSharper disable once CppParameterMayBeConstPtrOrRef - auto Window::pop_id(Context& context) noexcept -> void + auto Window::is_item_focused(const Context& context) const noexcept -> bool { std::ignore = context; - id_stack_.pop_back(); + return canvas_.last_item_focused; } - // ReSharper disable once CppParameterMayBeConstPtrOrRef - auto Window::push_clip_rect(Context& context, const rect_type& rect, const bool clipped) noexcept -> void + auto Window::show() noexcept -> void { - std::ignore = context; - - const auto clip_rect = [&]() noexcept -> rect_type - { - if (clipped and not clip_rect_stack_.empty()) - { - // clip to a new rect - const auto last = clip_rect_stack_.back(); - return last.combine_min(rect); - } - - return rect; - }(); - - clip_rect_stack_.push_back(clip_rect); - draw_list_.push_clip_rect(clip_rect, false); + visible_ = true; } - // ReSharper disable once CppParameterMayBeConstPtrOrRef - auto Window::pop_clip_rect(Context& context) noexcept -> void + auto Window::hide() noexcept -> void { - clip_rect_stack_.pop_back(); - - if (clip_rect_stack_.empty()) - { - const auto size = context.io.display_size; - draw_list_.push_clip_rect({0, 0, size}, false); - } - else - { - draw_list_.push_clip_rect(clip_rect_stack_.back(), false); - } + visible_ = false; + accessed_ = false; } } diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index f6a33e6..f8489f5 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -121,6 +121,10 @@ namespace gal::prometheus value_type height_current_line; value_type height_previous_line; + rect_type last_item_rect; + bool last_item_hovered; + bool last_item_focused; + std::vector item_width; // <0(DrawList::text_wrap_width_not_set): disable // =0: window.content_region_max().width @@ -226,29 +230,72 @@ namespace gal::prometheus Window* parent ) noexcept -> bool; + auto end_draw(Context& context) noexcept -> void; + + auto render(Context& context) const noexcept -> void; + // ----------------------------------- // WIDGETS auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + auto draw_button(Context& context, std::string_view utf8_text, extent_type size, bool repeat_when_held) noexcept -> bool; + auto draw_small_button(Context& context, std::string_view utf8_text, bool repeat_when_held) noexcept -> bool; + auto draw_radio_button(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + auto draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + auto draw_slider( + Context& context, + std::string_view utf8_text, + float& reference, + float min, + float max, + std::uint32_t decimal_precision, + float power + ) noexcept -> bool; + // ----------------------------------- - // LAYOUT + // WIDGET LAYOUT - auto same_line(const Context& context, value_type column_width = layout_auto_size, value_type spacing_width = layout_auto_size) noexcept -> void; + auto same_line(Context& context, value_type column_width = layout_auto_size, value_type spacing_width = layout_auto_size) noexcept -> void; // ----------------------------------- - // DRAW END + // CANVAS LAYOUT - auto end_draw(Context& context) noexcept -> void; + auto push_item_width(Context& context, Theme::value_type new_item_width) noexcept -> void; + auto pop_item_width(Context& context) noexcept -> void; + + auto push_text_wrap_width(Context& context, Theme::value_type new_wrap_width) noexcept -> void; + auto pop_text_wrap_width(Context& context) noexcept -> void; // ----------------------------------- - // RENDER + // ID - auto render(Context& context) const noexcept -> void; + [[nodiscard]] auto id_of_move(Context& context) const noexcept -> widget_id_type; + + [[nodiscard]] auto id_of_close(Context& context) const noexcept -> widget_id_type; + + [[nodiscard]] auto id_of_resize(Context& context) const noexcept -> widget_id_type; + + [[nodiscard]] auto id_of_scrollbar(Context& context) const noexcept -> widget_id_type; + + auto push_id(Context& context, std::string_view string) noexcept -> void; + + auto push_id(Context& context, const void* pointer) noexcept -> void; + + auto push_id(Context& context, widget_id_type value) noexcept -> void; + + auto pop_id(Context& context) noexcept -> void; + + // ----------------------------------- + // CLIP RECT + + auto push_clip_rect(Context& context, const rect_type& rect, bool clipped = true) noexcept -> void; + + auto pop_clip_rect(Context& context) noexcept -> void; // ----------------------------------- // STATES @@ -281,39 +328,16 @@ namespace gal::prometheus * @brief Test if mouse cursor is hovering given rect * @note @c rect is clipped by current clip rect setting */ - [[nodiscard]] auto hovered(const Context& context, const rect_type& rect) const noexcept -> bool; + [[nodiscard]] auto is_hovered(const Context& context, const rect_type& rect) const noexcept -> bool; + + [[nodiscard]] auto is_item_hovered(const Context& context) const noexcept -> bool; + [[nodiscard]] auto is_item_focused(const Context& context) const noexcept -> bool; // ----------------------------------- // auto show() noexcept -> void; auto hide() noexcept -> void; - - // ----------------------------------- - // ID - - [[nodiscard]] auto id_of_move(Context& context) const noexcept -> widget_id_type; - - [[nodiscard]] auto id_of_close(Context& context) const noexcept -> widget_id_type; - - [[nodiscard]] auto id_of_resize(Context& context) const noexcept -> widget_id_type; - - [[nodiscard]] auto id_of_scrollbar(Context& context) const noexcept -> widget_id_type; - - auto push_id(Context& context, std::string_view string) noexcept -> void; - - auto push_id(Context& context, const void* pointer) noexcept -> void; - - auto push_id(Context& context, widget_id_type value) noexcept -> void; - - auto pop_id(Context& context) noexcept -> void; - - // ----------------------------------- - // CLIP RECT - - auto push_clip_rect(Context& context, const rect_type& rect, bool clipped = true) noexcept -> void; - - auto pop_clip_rect(Context& context) noexcept -> void; }; } } From 7438348059de4e63572aeaf9d4a1373d8cda1812 Mon Sep 17 00:00:00 2001 From: life4gal Date: Tue, 15 Apr 2025 17:21:45 +0800 Subject: [PATCH 12/54] `fix`: When drawing multiple windows, later windows no longer overwrite the vertex/index data of earlier windows. --- unit_test/src/gui/dx11/backend.cpp | 110 ++++++++++++++++++++++++++-- unit_test/src/gui/dx12/backend.cpp | 113 +++++++++++++++++++++++++++-- 2 files changed, 208 insertions(+), 15 deletions(-) diff --git a/unit_test/src/gui/dx11/backend.cpp b/unit_test/src/gui/dx11/backend.cpp index dad3799..f1ea387 100644 --- a/unit_test/src/gui/dx11/backend.cpp +++ b/unit_test/src/gui/dx11/backend.cpp @@ -423,7 +423,16 @@ auto prometheus_render() -> void { window_closed = gui::begin_window("Window 1", {640, 480}); - gui::draw_text("Text:"); + static bool theme_window_closed = true; + theme_window_closed ^= gui::draw_button("OpenThemeEditor"); + + if (not theme_window_closed) + { + gui::set_next_window_point({100, 100}); + theme_window_closed = gui::show_theme_editor(); + } + + gui::draw_text_colored("Text:", primitive::colors::red); { gui::draw_text("Hello"); gui::draw_text("World"); @@ -447,10 +456,73 @@ auto prometheus_render() -> void } } + gui::push_text_wrap_width(150); gui::draw_text(string); + gui::pop_text_wrap_width(); + + gui::push_text_wrap_width(300); gui::draw_text(string); - gui::draw_text(string); + gui::pop_text_wrap_width(); + } + + gui::draw_text_colored("Button:", primitive::colors::red); + { + if (gui::draw_button("Button")) + { + std::println(stdout, "Press Button!"); + } + if (gui::draw_small_button("SmallButton")) + { + std::println(stdout, "Press SmallButton!"); + } + } + + gui::draw_text_colored("RadioButton:", primitive::colors::red); + { + struct radio_button + { + int value; + + [[nodiscard]] constexpr auto operator==(const radio_button& other) const noexcept -> bool + { + return value == other.value; + } + }; + static radio_button value{.value = 0}; + + gui::draw_radio_button("RadioButton1", value, radio_button{.value = 0}); + gui::layout_same_line(); + gui::draw_radio_button("RadioButton2", value, radio_button{.value = 1}); + gui::layout_same_line(); + gui::draw_radio_button("RadioButton3", value, radio_button{.value = 2}); + gui::layout_same_line(); + gui::draw_text(std::format("> Select: {}", value.value)); + } + + gui::draw_text_colored("Checkbox:", primitive::colors::red); + { + struct checkbox + { + int value; + + [[nodiscard]] constexpr auto operator==(const checkbox& other) const noexcept -> bool + { + return value == other.value; + } + }; + static checkbox value{.value = 0}; + + gui::draw_checkbox("Checkbox", value, checkbox{.value = 1}, checkbox{.value = 0}); + gui::layout_same_line(); + gui::draw_text(std::format("> Select: {}", value.value)); + } + + gui::draw_text_colored("Slider", primitive::colors::red); + { + static float v = 0; + gui::draw_slider("Slider", v, -1, 1); } + gui::end_window(); } @@ -531,6 +603,9 @@ auto prometheus_draw() -> void auto* mapped_vertex = static_cast(mapped_vertex_resource.pData); auto* mapped_index = static_cast(mapped_index_resource.pData); + UINT vertex_offset = 0; + UINT index_offset = 0; + std::ranges::for_each( draw_datas, [&](const gui::DrawData& draw_data) noexcept -> void @@ -540,8 +615,8 @@ auto prometheus_draw() -> void std::ranges::transform( vertex_list, - mapped_vertex, - [](const gui::vertex_type& vertex) -> d3d_vertex_type + mapped_vertex + vertex_offset, + [](const gui::vertex_type& vertex) noexcept -> d3d_vertex_type { // return { // .position = {vertex.position.x, vertex.position.y}, @@ -551,7 +626,17 @@ auto prometheus_draw() -> void return std::bit_cast(vertex); } ); - std::ranges::copy(index_list, mapped_index); + std::ranges::transform( + index_list, + mapped_index + index_offset, + [vertex_offset](const gui::index_type index) noexcept -> d3d_index_type + { + return static_cast(index + vertex_offset); + } + ); + + vertex_offset += static_cast(vertex_list.size()); + index_offset += static_cast(index_list.size()); } ); @@ -622,10 +707,14 @@ auto prometheus_draw() -> void g_device_immediate_context->OMSetDepthStencilState(g_depth_stencil_state.Get(), 0); g_device_immediate_context->RSSetState(g_rasterizer_state.Get()); + UINT total_index_offset = 0; std::ranges::for_each( draw_datas, - [&](const gui::DrawData& draw_data) noexcept -> void + [&total_index_offset](const gui::DrawData& draw_data) noexcept -> void { + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); + for (const auto& command_list = draw_data.command_list.get(); const auto& [clip_rect, texture, index_offset, element_count]: command_list) { @@ -643,9 +732,12 @@ auto prometheus_draw() -> void ID3D11ShaderResourceView* textures[]{reinterpret_cast(texture)}; // NOLINT(performance-no-int-to-ptr) g_device_immediate_context->PSSetShaderResources(0, 1, textures); - // g_device_immediate_context->DrawIndexed(static_cast(element_count), static_cast(index_offset), 0); - g_device_immediate_context->DrawIndexedInstanced(static_cast(element_count), 1, static_cast(index_offset), 0, 0); + const auto this_index_offset = static_cast(total_index_offset + index_offset); + // g_device_immediate_context->DrawIndexed(static_cast(element_count), this_index_offset, 0); + g_device_immediate_context->DrawIndexedInstanced(static_cast(element_count), 1, this_index_offset, 0, 0); } + + total_index_offset += static_cast(index_list.size()); } ); } @@ -653,4 +745,6 @@ auto prometheus_draw() -> void auto prometheus_shutdown() -> void { print_time(); + + gui::destroy_current_context(); } diff --git a/unit_test/src/gui/dx12/backend.cpp b/unit_test/src/gui/dx12/backend.cpp index 32f85d9..f4848fa 100644 --- a/unit_test/src/gui/dx12/backend.cpp +++ b/unit_test/src/gui/dx12/backend.cpp @@ -616,7 +616,15 @@ auto prometheus_render() -> void { window_closed = gui::begin_window("Window 1", {640, 480}); - gui::draw_text("Text:"); + static bool theme_window_closed = true; + theme_window_closed ^= gui::draw_button("OpenThemeEditor"); + + if (not theme_window_closed) + { + theme_window_closed = gui::show_theme_editor(); + } + + gui::draw_text_colored("Text:", primitive::colors::red); { gui::draw_text("Hello"); gui::draw_text("World"); @@ -640,10 +648,73 @@ auto prometheus_render() -> void } } + gui::push_text_wrap_width(150); gui::draw_text(string); + gui::pop_text_wrap_width(); + + gui::push_text_wrap_width(300); gui::draw_text(string); - gui::draw_text(string); + gui::pop_text_wrap_width(); + } + + gui::draw_text_colored("Button:", primitive::colors::red); + { + if (gui::draw_button("Button")) + { + std::println(stdout, "Press Button!"); + } + if (gui::draw_small_button("SmallButton")) + { + std::println(stdout, "Press SmallButton!"); + } + } + + gui::draw_text_colored("RadioButton:", primitive::colors::red); + { + struct radio_button + { + int value; + + [[nodiscard]] constexpr auto operator==(const radio_button& other) const noexcept -> bool + { + return value == other.value; + } + }; + static radio_button value{.value = 0}; + + gui::draw_radio_button("RadioButton1", value, radio_button{.value = 0}); + gui::layout_same_line(); + gui::draw_radio_button("RadioButton2", value, radio_button{.value = 1}); + gui::layout_same_line(); + gui::draw_radio_button("RadioButton3", value, radio_button{.value = 2}); + gui::layout_same_line(); + gui::draw_text(std::format("> Select: {}", value.value)); } + + gui::draw_text_colored("Checkbox:", primitive::colors::red); + { + struct checkbox + { + int value; + + [[nodiscard]] constexpr auto operator==(const checkbox& other) const noexcept -> bool + { + return value == other.value; + } + }; + static checkbox value{.value = 0}; + + gui::draw_checkbox("Checkbox", value, checkbox{.value = 1}, checkbox{.value = 0}); + gui::layout_same_line(); + gui::draw_text(std::format("> Select: {}", value.value)); + } + + gui::draw_text_colored("Slider", primitive::colors::red); + { + static float v = 0; + gui::draw_slider("Slider", v, -1, 1); + } + gui::end_window(); } @@ -761,6 +832,9 @@ auto prometheus_draw() -> void auto* mapped_vertex = static_cast(mapped_vertex_resource); auto* mapped_index = static_cast(mapped_index_resource); + UINT vertex_offset = 0; + UINT index_offset = 0; + std::ranges::for_each( draw_datas, [&](const gui::DrawData& draw_data) noexcept -> void @@ -770,7 +844,7 @@ auto prometheus_draw() -> void std::ranges::transform( vertex_list, - mapped_vertex, + mapped_vertex + vertex_offset, [](const gui::vertex_type& vertex) -> d3d_vertex_type { // return { @@ -781,7 +855,17 @@ auto prometheus_draw() -> void return std::bit_cast(vertex); } ); - std::ranges::copy(index_list, mapped_index); + std::ranges::transform( + index_list, + mapped_index + index_offset, + [vertex_offset](const gui::index_type index) noexcept -> d3d_index_type + { + return static_cast(index + vertex_offset); + } + ); + + vertex_offset += static_cast(vertex_list.size()); + index_offset += static_cast(index_list.size()); } ); @@ -851,23 +935,36 @@ auto prometheus_draw() -> void constexpr float blend_factor[4]{.0f, .0f, .0f, .0f}; g_command_list->OMSetBlendFactor(blend_factor); + UINT total_index_offset = 0; std::ranges::for_each( draw_datas, - [&](const gui::DrawData& draw_data) noexcept -> void + [&total_index_offset](const gui::DrawData& draw_data) noexcept -> void { + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); + for (const auto& command_list = draw_data.command_list.get(); const auto& [clip_rect, texture, index_offset, element_count]: command_list) { const auto [point, extent] = clip_rect; - const D3D12_RECT rect{static_cast(point.x), static_cast(point.y), static_cast(point.x + extent.width), static_cast(point.y + extent.height)}; + const D3D12_RECT rect + { + static_cast(point.x), + static_cast(point.y), + static_cast(point.x + extent.width), + static_cast(point.y + extent.height) + }; g_command_list->RSSetScissorRects(1, &rect); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture != 0, "push_texture_id when create texture view"); const D3D12_GPU_DESCRIPTOR_HANDLE texture_handle{.ptr = static_cast(texture)}; g_command_list->SetGraphicsRootDescriptorTable(1, texture_handle); - g_command_list->DrawIndexedInstanced(static_cast(element_count), 1, static_cast(index_offset), 0, 0); + const auto this_index_offset = static_cast(total_index_offset + index_offset); + g_command_list->DrawIndexedInstanced(static_cast(element_count), 1, this_index_offset, 0, 0); } + + total_index_offset += static_cast(index_list.size()); } ); } @@ -875,4 +972,6 @@ auto prometheus_draw() -> void auto prometheus_shutdown() -> void { print_time(); + + gui::destroy_current_context(); } From 29c91dbcdc8c84b1808410e3aad7229f24cc9f08 Mon Sep 17 00:00:00 2001 From: life4gal Date: Wed, 16 Apr 2025 14:21:30 +0800 Subject: [PATCH 13/54] `feat`: Add `gui::draw_slider_n`. --- src/gui/gui.hpp | 72 ++++- src/gui/internal/context.cpp | 30 +++ src/gui/internal/window.cpp | 507 ++++++++++++++++++++++------------- src/gui/internal/window.hpp | 11 + 4 files changed, 428 insertions(+), 192 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index f43a37d..bcc8b99 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -463,16 +464,19 @@ namespace gal::prometheus /** * @brief Draw a radio button, its initial state and referenced state is required + * @return Whether the radio button is pressed (being pressed does not mean that the state is switched, it can also be a repeat selection) */ template - auto draw_radio_button(Context& context, std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> void + auto draw_radio_button(Context& context, std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> bool { const auto prev_selected = reference == identifier; - if (const auto pressed = gui::draw_radio_button(context, utf8_text, prev_selected); - pressed) + const auto pressed = gui::draw_radio_button(context, utf8_text, prev_selected); + + if (pressed) { reference = identifier; } + return pressed; } /** @@ -483,6 +487,7 @@ namespace gal::prometheus /** * @brief Draw a checkbox, its initial state and referenced state is required + * @return Current state of the checkbox (checked or unchecked) */ template static auto draw_checkbox( @@ -491,11 +496,12 @@ namespace gal::prometheus T& reference, const std::type_identity_t& checked_identifier, const std::type_identity_t& unchecked_identifier - ) noexcept -> void + ) noexcept -> bool { const auto prev_checked = reference == checked_identifier; - if (const auto checked = gui::draw_checkbox(context, utf8_text, prev_checked); - checked != prev_checked) + const auto checked = gui::draw_checkbox(context, utf8_text, prev_checked); + + if (checked != prev_checked) { if (prev_checked) { @@ -506,6 +512,7 @@ namespace gal::prometheus reference = checked_identifier; } } + return checked; } auto draw_slider( @@ -518,6 +525,34 @@ namespace gal::prometheus float power = 1 ) noexcept -> bool; + template + requires std::is_arithmetic_v + auto draw_slider( + Context& context, + const std::string_view utf8_text, + T& reference, + const ValueType min, + const std::type_identity_t max, + const std::uint32_t decimal_precision = 3, + const float power = 1 + ) noexcept -> bool + { + auto v = static_cast(reference); + + const auto result = gui::draw_slider( + context, + utf8_text, + v, + static_cast(min), + static_cast(max), + decimal_precision, + power + ); + reference = v; + + return result; + } + template requires std::is_arithmetic_v auto draw_slider( @@ -546,6 +581,16 @@ namespace gal::prometheus return result; } + auto draw_slider_n( + Context& context, + std::string_view utf8_text, + std::span references, + float min, + float max, + std::uint32_t decimal_precision = 3, + float power = 1 + ) noexcept -> bool; + //------------------------------------------------------------------ // WIDGET LAYOUT //------------------------------------------------------------------ @@ -556,11 +601,11 @@ namespace gal::prometheus /** * @brief Each element drawn will move the cursor to the next line of the canvas, * this function forces the cursor to move to the end of the previous line (immediately after the previous element) - * @note @c column_width == @c auto_size and @c spacing_width == @c auto_size, + * @note @c column_width == @c layout_auto_size and @c spacing_width == @c layout_auto_size, * the x-axis distance of the next drawn element from the previous element will depend on @c theme.item_spacing.width - * @note @c column_width == @c auto_size and @c spacing_width != @c auto_size, + * @note @c column_width == @c layout_auto_size and @c spacing_width != @c layout_auto_size, * the x-axis distance of the next drawn element from the previous element will depend on @c spacing_width - * @note @c column_width != @c auto_size (@c spacing_width can be any value, force to 0 if less than 0), + * @note @c column_width != @c layout_auto_size (@c spacing_width can be any value, force to 0 if less than 0), * the x-axis distance of the next drawn element from the previous element will depend on @c column_width + @c spacing_width */ auto layout_same_line(Context& context, Theme::value_type column_width = layout_auto_size, Theme::value_type spacing_width = layout_auto_size) noexcept -> void; @@ -741,6 +786,15 @@ namespace gal::prometheus return gui::draw_slider(context, object, min, max, decimal_precision, power); } + auto draw_slider_n( + std::string_view utf8_text, + std::span references, + float min, + float max, + std::uint32_t decimal_precision = 3, + float power = 1 + ) noexcept -> bool; + //------------------------------------------------------------------ // WIDGET LAYOUT //------------------------------------------------------------------ diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 1390231..3acf213 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -477,6 +477,22 @@ namespace gal::prometheus::gui return window.draw_slider(context, utf8_text, reference, min, max, decimal_precision, power); } + auto draw_slider_n( + Context& context, + const std::string_view utf8_text, + const std::span references, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_slider_n(context, utf8_text, references, min, max, decimal_precision, power); + } + auto layout_same_line(Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -717,6 +733,20 @@ namespace gal::prometheus::gui return draw_slider(context, utf8_text, reference, min, max, decimal_precision, power); } + auto draw_slider_n( + const std::string_view utf8_text, + const std::span references, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + auto& context = get_current_context(); + + return draw_slider_n(context, utf8_text, references, min, max, decimal_precision, power); + } + auto layout_same_line(const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { auto& context = get_current_context(); diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 6569494..4e3ea57 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -314,6 +314,315 @@ namespace gal::prometheus::gui::internal } }; + class Window::Anonymous final + { + public: + memory::RefWrapper self; + + template + requires (std::is_same_v or std::is_same_v>) + auto draw_slider( + Context& context, + const std::string_view utf8_text, + T& reference, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + constexpr auto is_list = std::is_same_v>; + + auto& window = self.get(); + + if (window.skip_item_) + { + return false; + } + window.accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = window}; + const IdMaker id_maker{.self = window}; + + const auto font_size = drawer.font_size(context); + + const auto do_draw_slider = [&](const widget_id_type id, ValueType& ref_value, const rect_type& slider_rect, const rect_type& frame_rect) noexcept -> bool + { + const auto slider_point = slider_rect.left_top(); + const auto slider_size = slider_rect.size(); + const auto frame_point = frame_rect.left_top(); + const auto frame_size = frame_rect.size(); + + const auto state = test_mouse(context, id, slider_rect, false); + + // draw □ (frame + slider + text) + bool value_changed = false; + + // frame + drawer.draw_widget_frame(context, frame_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + + // slider + + // todo + constexpr float grab_size_in_pixels = 10.f; + + const auto slider_effective_width = slider_size.width - grab_size_in_pixels; + const auto slider_effective_x1 = slider_point.x + grab_size_in_pixels * .5f; + const auto slider_effective_x2 = slider_point.x + slider_size.width - grab_size_in_pixels * .5f; + + const auto linear_zero_pos = [=]() noexcept -> float + { + if (min * max < 0) + { + // different sign + const auto linear_dist_min_to_0 = std::powf(std::fabs(.0f - min), 1.f / power); + const auto linear_dist_max_to_0 = std::powf(std::fabs(max - .0f), 1.f / power); + return linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0); + } + + // same sign + return min < 0 ? 1.f : .0f; + }(); + + if (state & MouseState::KEEPING) + { + const auto mouse_position = context.mouse.position_current; + const auto normalized_x = std::ranges::clamp((mouse_position.x - slider_effective_x1) / slider_effective_width, .0f, 1.f); + + // account for logarithmic scale on both sides of the zero + auto value = [=]() noexcept -> float + { + if (normalized_x < linear_zero_pos) + { + // rescale to the negative range before powering + auto v = 1.f - (normalized_x / linear_zero_pos); + v = std::powf(v, power); + + const auto negative_part_max = std::ranges::min(max, .0f); + return std::lerp(negative_part_max, min, v); + } + + // rescale to the positive range before powering + auto v = normalized_x; + if (std::abs(linear_zero_pos - 1.f) > 1e-6) + { + v = (v - linear_zero_pos) / (1.f - linear_zero_pos); + } + v = std::powf(v, power); + + const auto positive_part_min = std::ranges::max(min, .0f); + return std::lerp(positive_part_min, max, v); + }(); + + const auto min_step = 1.f / std::powf(10.f, static_cast(decimal_precision)); + const auto remainder = std::fmodf(value, min_step); + + if (remainder <= min_step * .5f) + { + value -= remainder; + } + else + { + value += (min_step - remainder); + } + + // if (ref_value != value) // NOLINT(clang-diagnostic-float-equal) + if (std::fabs(ref_value - value) > 1e-6f) + { + ref_value = value; + value_changed = true; + } + } + + // grab + { + const auto v = [=]() noexcept -> float + { + const auto clamped = std::ranges::clamp(ref_value, min, max); + if (clamped < .0f) + { + const auto f = 1.f - (clamped - min) / (std::ranges::min(.0f, max) - min); + return (1.f - std::powf(f, 1.f / power)) * linear_zero_pos; + } + + const auto f = (clamped - std::ranges::max(0.f, min)) / (max - std::ranges::max(0.f, min)); + return linear_zero_pos + std::powf(f, 1.f / power) * (1.f - linear_zero_pos); + }(); + + const auto x = std::lerp(slider_effective_x1, slider_effective_x2, v); + const point_type grab_point{x - grab_size_in_pixels * .5f, frame_point.y + 2.f}; + const extent_type grab_size{grab_size_in_pixels, frame_size.height - 4.f}; + const rect_type grab_rect{grab_point, grab_size}; + + if (state & MouseState::PRESSED) + { + window.draw_list_.rect_filled( + grab_rect, + color_of(theme, ThemeCategory::SLIDER_ACTIVATED), + theme.window_corner_rounding, + DrawFlag::ROUND_CORNER_ALL + ); + } + else + { + window.draw_list_.rect_filled( + grab_rect, + color_of(theme, ThemeCategory::SLIDER), + theme.window_corner_rounding, + DrawFlag::ROUND_CORNER_ALL + ); + } + } + + // text + const auto value_text = std::format("{:.{}f}", ref_value, decimal_precision); + const auto value_text_size = internal::text_size(font, value_text, font_size, Font::no_auto_wrap); + const point_type value_text_point{slider_point.x + slider_size.width / 2 - value_text_size.width / 2, frame_point.y + theme.item_frame_padding.height}; + window.draw_list_.text( + font, + font_size, + value_text_point, + color_of(theme, ThemeCategory::TEXT), + value_text + ); + + return value_changed; + }; + + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); + + // □ + text + const auto last_item_width = window.canvas_.item_width.back(); + bool value_changed = false; + + if constexpr (is_list) + { + const auto list_size = reference.size(); + if (list_size == 1) + { + return this->draw_slider(context, utf8_text, reference[0], min, max, decimal_precision, power); + } + + const auto each_frame_width = (last_item_width - theme.item_inner_spacing.width * static_cast(list_size - 1)) / static_cast(list_size); + const auto each_slider_width = each_frame_width - theme.item_frame_padding.width * 2; + + const auto total_frame_point = window.canvas_.cursor_current_line; + const extent_type total_frame_size{last_item_width + theme.item_frame_padding.width * 2, text_size.height + theme.item_frame_padding.height * 2}; + const rect_type total_frame_rect{total_frame_point, total_frame_size}; + drawer.adjust_item_size(context, total_frame_size); + + // □ text + window.same_line(context, auto_size, theme.item_inner_spacing.width); + + // text + const auto text_point = window.canvas_.cursor_current_line + theme.item_frame_padding; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const rect_type total_rect{total_frame_rect.left_top(), text_rect.right_bottom()}; + if (not drawer.is_visible_area(context, total_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, total_rect); + + // draw all slider + // note: when drawing multiple sliders, the widget id is based on the slider index, not the label text + // note: to avoid different sliders getting the same id (since ids are based on index), the label text is pushed in here as the seed + window.push_id(context, utf8_text); + for (const auto view = reference | std::views::enumerate; + auto [index, ref_value]: view) + { + const auto id = id_maker.make_id(context, index); + + const point_type slider_point + { + (total_frame_point.x + theme.item_frame_padding.width) + ((each_frame_width + theme.item_inner_spacing.width) * static_cast(index)), + (total_frame_point.y + theme.item_frame_padding.height) + }; + const extent_type slider_size + { + each_slider_width, + text_size.height + }; + const rect_type slider_rect{slider_point, slider_size}; + + const point_type frame_point + { + (total_frame_point.x) + ((each_frame_width + theme.item_inner_spacing.width) * static_cast(index)), + (total_frame_point.y) + }; + const extent_type frame_size + { + each_frame_width, + text_size.height + theme.item_frame_padding.height * 2 + }; + const rect_type frame_rect{frame_point, frame_size}; + + value_changed |= do_draw_slider(id, ref_value, slider_rect, frame_rect); + } + window.pop_id(context); + + // draw text + window.draw_list_.text( + font, + font_size, + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + } + else + { + const auto id = id_maker.make_id(context, utf8_text); + + // □, height equals last item width + const auto slider_point = window.canvas_.cursor_current_line + theme.item_frame_padding; + const auto slider_size = extent_type{last_item_width, text_size.height}; + const rect_type slider_rect{slider_point, slider_size}; + + const auto frame_point = window.canvas_.cursor_current_line; + const auto frame_size = slider_size + theme.item_frame_padding * 2; + const rect_type frame_rect{frame_point, frame_size}; + drawer.adjust_item_size(context, frame_size); + + // □ text + window.same_line(context, auto_size, theme.item_inner_spacing.width); + + // text + const auto text_point = window.canvas_.cursor_current_line + theme.item_frame_padding; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const rect_type total_rect{frame_rect.left_top(), text_rect.right_bottom()}; + if (not drawer.is_visible_area(context, total_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, total_rect); + + value_changed |= do_draw_slider(id, reference, slider_rect, frame_rect); + + // draw text + window.draw_list_.text( + font, + font_size, + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + } + + return value_changed; + } + }; + Window::Window( const std::string_view name, const Flag flag, @@ -1531,192 +1840,24 @@ namespace gal::prometheus::gui::internal const float power ) noexcept -> bool { - if (skip_item_) - { - return false; - } - accessed_ = true; - - const auto& theme = current_theme(context); - const auto& font = current_font(context); - Drawer drawer{.self = const_cast(*this)}; - const IdMaker id_maker{.self = *this}; - - const auto font_size = drawer.font_size(context); - - const auto id = id_maker.make_id(context, utf8_text); - const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); - - // □ + text - const auto last_item_width = canvas_.item_width.back(); - - // □, height equals last item width - const auto slider_point = canvas_.cursor_current_line + theme.item_frame_padding; - const auto slider_size = extent_type{last_item_width, text_size.height}; - const rect_type slider_rect{slider_point, slider_size}; - - const auto frame_point = canvas_.cursor_current_line; - const auto frame_size = slider_size + theme.item_frame_padding * 2; - const rect_type frame_rect{frame_point, frame_size}; - - drawer.adjust_item_size(context, frame_size); - - // □ text - same_line(context, auto_size, theme.item_inner_spacing.width); - - // text - const auto text_point = canvas_.cursor_current_line + theme.item_frame_padding; - const rect_type text_rect{text_point, text_size}; - drawer.adjust_item_size(context, text_size); - - const rect_type total_rect{frame_rect.left_top(), text_rect.right_bottom()}; - if (not drawer.is_visible_area(context, total_rect)) - { - // invisible - return false; - } - drawer.test_last_item(context, total_rect); - - const auto state = test_mouse(context, id, slider_rect, false); - - // draw □ (frame + slider + text) - bool value_changed = false; - { - // frame - drawer.draw_widget_frame(context, frame_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); - - // slider - - // todo - constexpr float grab_size_in_pixels = 10.f; - - const auto slider_effective_width = slider_size.width - grab_size_in_pixels; - const auto slider_effective_x1 = slider_point.x + grab_size_in_pixels * .5f; - const auto slider_effective_x2 = slider_point.x + slider_size.width - grab_size_in_pixels * .5f; - - const auto linear_zero_pos = [=]() noexcept -> float - { - if (min * max < 0) - { - // different sign - const auto linear_dist_min_to_0 = std::powf(std::abs(.0f - min), 1.f / power); - const auto linear_dist_max_to_0 = std::powf(std::abs(max - .0f), 1.f / power); - return linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0); - } - - // same sign - return min < 0 ? 1.f : .0f; - }(); - - if (state & MouseState::KEEPING) - { - const auto mouse_position = context.mouse.position_current; - const auto normalized_x = std::ranges::clamp((mouse_position.x - slider_effective_x1) / slider_effective_width, .0f, 1.f); - - // account for logarithmic scale on both sides of the zero - auto value = [=]() noexcept -> float - { - if (normalized_x < linear_zero_pos) - { - // rescale to the negative range before powering - auto v = 1.f - (normalized_x / linear_zero_pos); - v = std::powf(v, power); - return std::lerp(std::ranges::min(max, .0f), min, v); - } - - // rescale to the positive range before powering - auto v = normalized_x; - if (std::abs(linear_zero_pos - 1.f) > 1e-6) - { - v = (v - linear_zero_pos) / (1.f - linear_zero_pos); - } - v = std::powf(v, power); - return std::lerp(std::ranges::max(min, .0f), max, v); - }(); - - const auto min_step = 1.f / std::powf(10.f, static_cast(decimal_precision)); - const auto remainder = std::fmodf(value, min_step); - - if (remainder <= min_step * .5f) - { - value -= remainder; - } - else - { - value += (min_step - remainder); - } - - if (reference != value) // NOLINT(clang-diagnostic-float-equal) - { - reference = value; - value_changed = true; - } - } - - // grab - { - const auto v = [=]() noexcept -> float - { - const auto clamped = std::ranges::clamp(reference, min, max); - if (clamped < .0f) - { - const auto f = 1.f - (clamped - min) / (std::ranges::min(.0f, max) - min); - return (1.f - std::powf(f, 1.f / power)) * linear_zero_pos; - } - - const auto f = (clamped - std::ranges::max(0.f, min)) / (max - std::ranges::max(0.f, min)); - return linear_zero_pos + std::powf(f, 1.f / power) * (1.f - linear_zero_pos); - }(); - - const auto x = std::lerp(slider_effective_x1, slider_effective_x2, v); - const point_type grab_point{x - grab_size_in_pixels * .5f, frame_point.y + 2.f}; - const extent_type grab_size{grab_size_in_pixels, frame_size.height - 4.f}; - const rect_type grab_rect{grab_point, grab_size}; - - if (state & MouseState::PRESSED) - { - draw_list_.rect_filled( - grab_rect, - color_of(theme, ThemeCategory::SLIDER_ACTIVATED), - theme.window_corner_rounding, - DrawFlag::ROUND_CORNER_ALL - ); - } - else - { - draw_list_.rect_filled( - grab_rect, - color_of(theme, ThemeCategory::SLIDER), - theme.window_corner_rounding, - DrawFlag::ROUND_CORNER_ALL - ); - } - } + Anonymous anonymous{.self = *this}; - // text - const auto value_text = std::format("{:.{}f}", reference, decimal_precision); - const auto value_text_size = internal::text_size(font, value_text, font_size, Font::no_auto_wrap); - const point_type value_text_point{slider_point.x + slider_size.width / 2 - value_text_size.width / 2, frame_point.y + theme.item_frame_padding.height}; - draw_list_.text( - font, - font_size, - value_text_point, - color_of(theme, ThemeCategory::TEXT), - value_text - ); - } + return anonymous.draw_slider(context, utf8_text, reference, min, max, decimal_precision, power); + } - // draw text - draw_list_.text( - font, - font_size, - text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), - utf8_text, - text_rect.width() - ); + auto Window::draw_slider_n( + Context& context, + const std::string_view utf8_text, + std::span references, + const float min, + const float max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + Anonymous anonymous{.self = *this}; - return value_changed; + return anonymous.draw_slider(context, utf8_text, references, min, max, decimal_precision, power); } // ReSharper disable once CppParameterMayBeConstPtrOrRef diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index f8489f5..79da321 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -111,6 +111,7 @@ namespace gal::prometheus private: class IdMaker; class Drawer; + class Anonymous; struct canvas_type { @@ -257,6 +258,16 @@ namespace gal::prometheus float power ) noexcept -> bool; + auto draw_slider_n( + Context& context, + std::string_view utf8_text, + std::span references, + float min, + float max, + std::uint32_t decimal_precision, + float power + ) noexcept -> bool; + // ----------------------------------- // WIDGET LAYOUT From 7bb910d65681362233dc00cc42de63d7f6a3ab72 Mon Sep 17 00:00:00 2001 From: life4gal Date: Thu, 17 Apr 2025 17:48:03 +0800 Subject: [PATCH 14/54] `feat`: Add gui::begin/end_child_window. `fix`: Collapsing a window (double-clicking on the titlebar) sets the size of the window correctly (this used to result in the contents of the child window in the canvas being rendered even after the window was collapsed). --- src/gui/gui.hpp | 17 ++ src/gui/internal/common.hpp | 225 +++++++++----- src/gui/internal/context.cpp | 224 +++++++------- src/gui/internal/context.hpp | 17 +- src/gui/internal/draw_list.cpp | 4 +- src/gui/internal/window.cpp | 463 ++++++++++++++++------------ src/gui/internal/window.hpp | 531 +++++++++++++++------------------ 7 files changed, 827 insertions(+), 654 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index bcc8b99..c5c0eca 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -434,6 +434,15 @@ namespace gal::prometheus ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; + auto begin_child_window( + Context& context, + std::string_view name, + const extent_type& size = {0, 0}, + bool border = false, + WindowFlag flag = WindowFlag::NONE + ) noexcept -> void; + auto end_child_window(Context& context) noexcept -> void; + /** * @brief Draw a piece of text */ @@ -701,6 +710,14 @@ namespace gal::prometheus ) noexcept -> bool; auto end_window() noexcept -> void; + auto begin_child_window( + std::string_view name, + const extent_type& size = {0, 0}, + bool border = false, + WindowFlag flag = WindowFlag::NONE + ) noexcept -> void; + auto end_child_window() noexcept -> void; + /** * @brief Draw a piece of text */ diff --git a/src/gui/internal/common.hpp b/src/gui/internal/common.hpp index e42a5b1..3962d60 100644 --- a/src/gui/internal/common.hpp +++ b/src/gui/internal/common.hpp @@ -13,93 +13,184 @@ #include #include -namespace gal::prometheus::gui::internal +namespace gal::prometheus { - using circle_type = primitive::basic_circle_2d; - using ellipse_type = primitive::basic_ellipse_2d; - - using frame_count_type = std::uint32_t; - // The number of frames is always counted from 1(call new_frame first), - // so we can set the number of uninitialized frames to the maximum value, - // and its +1 overflows to 0, then we can compare it with current frame count normally - constexpr static auto frame_count_uninitialized{std::numeric_limits::max()}; - - using widget_id_type = functional::hash_result_type; - constexpr auto invalid_widget_id = std::numeric_limits::max(); - - class [[nodiscard]] DrawListSharedData final + namespace gui::internal { - public: - using circle_segment_count_type = std::uint8_t; - constexpr static std::size_t circle_segment_counts_count = 64; - using circle_segment_counts_type = std::array; - - constexpr static std::uint32_t circle_segments_min = 4; - constexpr static std::uint32_t circle_segments_max = 512; + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; - constexpr static std::size_t vertex_sample_points_count = 48; - using vertex_sample_points_type = std::array; + using frame_count_type = std::uint32_t; + // The number of frames is always counted from 1(call new_frame first), + // so we can set the number of uninitialized frames to the maximum value, + // and its +1 overflows to 0, then we can compare it with current frame count normally + constexpr static auto frame_count_uninitialized{std::numeric_limits::max()}; - circle_segment_counts_type circle_segment_counts; - vertex_sample_points_type vertex_sample_points; + using widget_id_type = functional::hash_result_type; + constexpr auto invalid_widget_id = std::numeric_limits::max(); - // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. - // Decrease for higher quality but more geometry. - float circle_segment_max_error; - // Cutoff radius after which arc drawing will fall back to slower `path_arc` - float arc_fast_radius_cutoff; - // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. - // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float curve_tessellation_tolerance; + enum class WindowInternalFlag : std::uint16_t + { + NONE = 0, - DrawListSharedData() noexcept; + CHILD_WINDOW = 1 << 0, + CHILD_WINDOW_AUTO_FIT_X = 1 << 1, + CHILD_WINDOW_AUTO_FIT_Y = 1 << 2, - // -------------------------------------------------- + CATEGORY_TOOLTIP = 1 << 3, + }; + } - [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + namespace meta::user_defined + { + template<> + struct enum_is_flag : std::true_type {}; + } - [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + namespace gui::internal + { + class [[nodiscard]] WindowFlag final + { + gui::WindowFlag flag_; + WindowInternalFlag internal_flag_; + + public: + constexpr explicit (false) WindowFlag(const gui::WindowFlag flag) noexcept + : flag_{flag}, + internal_flag_{WindowInternalFlag::NONE} {} + + constexpr explicit WindowFlag(const gui::WindowFlag flag, const WindowInternalFlag internal_flag) noexcept + : flag_{flag}, + internal_flag_{internal_flag} {} + + constexpr auto operator|=(const gui::WindowFlag flag) noexcept -> WindowFlag& + { + flag_ |= flag; + return *this; + } + + constexpr auto operator|=(const WindowInternalFlag flag) noexcept -> WindowFlag& + { + internal_flag_ |= flag; + return *this; + } + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + + template + struct workaround + { + T flag; + + constexpr explicit (false) workaround(const T flag) noexcept + : flag{flag} {} + }; + + template Flag> + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(flag_) & Flag.flag; + } - // -------------------------------------------------- + template Flag> + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(internal_flag_) & Flag.flag; + } + + #else + + template + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(flag_) & Flag; + } + + template + [[nodiscard]] constexpr auto is() const noexcept -> bool + { + return std::to_underlying(internal_flag_) & Flag; + } + + #endif + }; + + static_assert(sizeof(WindowFlag) == sizeof(std::uint32_t)); + + class [[nodiscard]] DrawListSharedData final + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; + + circle_segment_counts_type circle_segment_counts; + vertex_sample_points_type vertex_sample_points; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + DrawListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- - auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; - auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; - }; + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; - class [[nodiscard]] PrimitiveAppender final - { - public: - using size_type = DrawData::size_type; + class [[nodiscard]] PrimitiveAppender final + { + public: + using size_type = DrawData::size_type; - using command_type = DrawData::command_type; + using command_type = DrawData::command_type; - using vertex_list_type = DrawData::vertex_list_type; - using index_list_type = DrawData::index_list_type; + using vertex_list_type = DrawData::vertex_list_type; + using index_list_type = DrawData::index_list_type; - private: - // command_type::element_count - memory::RefWrapper element_count_; - // DrawList::vertex_list - memory::RefWrapper vertex_list_; - // DrawList::index_list_; - memory::RefWrapper index_list_; + private: + // command_type::element_count + memory::RefWrapper element_count_; + // DrawList::vertex_list + memory::RefWrapper vertex_list_; + // DrawList::index_list_; + memory::RefWrapper index_list_; - public: - PrimitiveAppender( - command_type& command, - vertex_list_type& vertex_list, - index_list_type& index_list - ) noexcept; + public: + PrimitiveAppender( + command_type& command, + vertex_list_type& vertex_list, + index_list_type& index_list + ) noexcept; - [[nodiscard]] auto vertex_count() const noexcept -> size_type; + [[nodiscard]] auto vertex_count() const noexcept -> size_type; - auto reserve(size_type vertex_count, size_type index_count) noexcept -> void; + auto reserve(size_type vertex_count, size_type index_count) noexcept -> void; - auto reserve(std::size_t vertex_count, std::size_t index_count) noexcept -> void; + auto reserve(std::size_t vertex_count, std::size_t index_count) noexcept -> void; - auto add_vertex(const point_type& point, const uv_type& uv, color_type color) noexcept -> void; + auto add_vertex(const point_type& point, const uv_type& uv, color_type color) noexcept -> void; - auto add_index(index_type a, index_type b, index_type c) noexcept -> void; - }; + auto add_index(index_type a, index_type b, index_type c) noexcept -> void; + }; + } } diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 3acf213..14180d2 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -25,31 +25,29 @@ namespace */ [[nodiscard]] auto find_window(Context& context, const std::string_view name) noexcept -> internal::Window* { - auto view = context.window_list | std::views::all; - const auto it = std::ranges::find_if( - view, + context.window_hive, [name](const auto& window) noexcept -> bool { - return name == window->name(); + return window->name() == name; } ); - if (it == std::ranges::end(view)) + if (it != context.window_hive.end()) { - return nullptr; + return it->get(); } - return it.operator*(); + return nullptr; } /** - * @brief Find the last possible parent (not child) window from the available windows in this frame + * @brief Find the last possible root (not child) window from the available windows in this frame * @return If it is not found (if and only if there are no currently available windows, i.e. the window to be created is the first one), then the null pointer is returned */ [[nodiscard]] auto find_root_window(Context& context) noexcept -> internal::Window* { - auto view = context.window_current_stack | std::views::all; + const auto view = context.window_current_stack | std::views::reverse; const auto it = std::ranges::find_if( view, @@ -67,28 +65,42 @@ namespace return it.operator*(); } - [[nodiscard]] auto find_or_create_window(Context& context, const std::string_view name, const extent_type& size, const internal::Window::Flag flag) noexcept -> internal::Window& + [[nodiscard]] auto find_or_create_window(Context& context, const std::string_view name, const extent_type& size, const internal::WindowFlag flag) noexcept -> internal::Window& { [[maybe_unused]] const auto is_child_window = flag.is(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((is_child_window == true) == (context.window_current_stack.size() > 1)); + if (is_child_window) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + } if (auto* window = find_window(context, name); window == nullptr) { - // find root auto* root = is_child_window ? find_root_window(context) : nullptr; // fixme: load cached settings? - auto temp = memory::make_unique(name, flag, context.window_default_spawn_position, size, root); + auto temp = memory::make_unique( + name, + flag, + context.window_default_spawn_position, + size, + root + ); auto& ref = context.window_hive.emplace_back(std::move(temp)); + if (root == nullptr) + { + // The current window is the root window + context.window_root_list.emplace_back(ref.get()); + } - context.window_list.emplace_back(ref.get()); context.window_current_stack.emplace_back(ref.get()); } else { + // todo: Just need to reset the flag? window->reset(flag); + context.window_current_stack.emplace_back(window); } @@ -101,36 +113,17 @@ namespace template [[nodiscard]] auto find_hovered_window(Context& context, const point_type& position) noexcept -> internal::Window* { - auto view = context.window_list | std::views::reverse; - - const auto it = std::ranges::find_if( - view, - [position](const auto& window) noexcept -> bool + for (const auto view = context.window_root_list | std::views::reverse; + auto* root: view) + { + if (auto* window = root->find_hovered_window(position, ExcludesChildren); + window != nullptr) { - if (not window->visible()) - { - return false; - } - - if constexpr (ExcludesChildren) - { - if (window->flag().template is()) - { - return false; - } - } - - const auto rect = window->rect(); - return rect.includes(position); + return window; } - ); - - if (it == std::ranges::end(view)) - { - return nullptr; } - return it.operator*(); + return nullptr; } Context* g_context = nullptr; @@ -154,7 +147,7 @@ namespace gal::prometheus::gui .mouse = {}, .window_default_spawn_position = {50, 50}, .window_hive = {}, - .window_list = {}, + .window_root_list = {}, .window_current_stack = {}, .window_hovered = nullptr, .window_hovered_root = nullptr, @@ -252,7 +245,7 @@ namespace gal::prometheus::gui // Mark all windows as not visible std::ranges::for_each( - context.window_list, + context.window_root_list, [](auto* window) noexcept -> void { window->hide(); @@ -282,32 +275,6 @@ namespace gal::prometheus::gui if (is_first_render_this_frame) { - // Sort the window list so that all child windows are after their parent - // We cannot do that on `focus` because children may not exist yet - - std::vector sorted_windows{}; - sorted_windows.reserve(context.window_list.size()); - - std::ranges::for_each( - context.window_list, - [&](auto* window) noexcept -> void - { - // todo: child window - if ( - window->flag().template is() and - window->visible() - ) - { - return; - } - - sorted_windows.push_back(window); - } - ); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(sorted_windows.size() == context.window_list.size()); - - context.window_list.swap(sorted_windows); - // clear all data for new frame context.io.delta_time = -1; // context.io.mouse_position = {0, 0}; @@ -321,13 +288,10 @@ namespace gal::prometheus::gui { // gather windows to render std::ranges::for_each( - context.window_list, + context.window_root_list, [&context](auto* window) noexcept -> void { - if (window->visible()) - { - window->render(context); - } + window->render(context); } ); } @@ -369,26 +333,11 @@ namespace gal::prometheus::gui Context& context, const std::string_view name, const extent_type& size, - Theme::value_type fill_alpha, + const Theme::value_type fill_alpha, const WindowFlag flag ) noexcept -> bool { - const auto& theme = internal::current_theme(context); - - auto& window = find_or_create_window(context, name, size, flag); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.window_current_stack.back() == std::addressof(window)); - - // alpha - static_assert(Context::window_fill_alpha_not_set < 0); - if (fill_alpha < 0) - { - fill_alpha = theme.window_background_alpha; - } - - const auto is_child_window = window.flag().is(); - auto* parent = is_child_window ? context.window_current_stack[context.window_current_stack.size() - 2] : nullptr; - - return window.begin_draw(context, fill_alpha, parent); + return internal::begin_window(context, name, size, fill_alpha, flag); } auto end_window(Context& context) noexcept -> void @@ -396,8 +345,7 @@ namespace gal::prometheus::gui GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); auto& window = *context.window_current_stack.back(); - - window.end_draw(context); + window.end_window(context); // Select window for move/focus when we're done with all our widgets (we only consider non-children windows here) if (const auto rect = window.rect(); @@ -414,6 +362,31 @@ namespace gal::prometheus::gui context.window_current_stack.pop_back(); } + auto begin_child_window( + Context& context, + const std::string_view name, + const extent_type& size, + const bool border, + const WindowFlag flag + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + auto& parent = *context.window_current_stack.back(); + + parent.begin_child_window(context, name, size, border, flag); + } + + auto end_child_window(Context& context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.window_current_stack.size() >= 2); + + auto& window = **(context.window_current_stack.end() - 1); + auto& parent = **(context.window_current_stack.end() - 2); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag().is()); + + parent.end_child_window(context, window); + } + auto draw_text(Context& context, const std::string_view utf8_text) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -677,6 +650,25 @@ namespace gal::prometheus::gui return end_window(context); } + auto begin_child_window( + const std::string_view name, + const extent_type& size, + const bool border, + const WindowFlag flag + ) noexcept -> void + { + auto& context = get_current_context(); + + begin_child_window(context, name, size, border, flag); + } + + auto end_child_window() noexcept -> void + { + auto& context = get_current_context(); + + end_child_window(context); + } + auto draw_text(const std::string_view utf8_text) noexcept -> void { auto& context = get_current_context(); @@ -911,6 +903,32 @@ namespace gal::prometheus::gui namespace internal { + auto begin_window( + Context& context, + const std::string_view name, + const extent_type& size, + Theme::value_type fill_alpha, + const WindowFlag flag + ) noexcept -> bool + { + const auto& theme = current_theme(context); + + auto& window = find_or_create_window(context, name, size, flag); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.window_current_stack.back() == std::addressof(window)); + + // alpha + static_assert(Context::window_fill_alpha_not_set < 0); + if (fill_alpha < 0) + { + fill_alpha = theme.window_background_alpha; + } + + const auto is_child_window = window.flag().is(); + auto* parent = is_child_window ? context.window_current_stack[context.window_current_stack.size() - 2] : nullptr; + + return window.begin_window(context, parent, fill_alpha, size); + } + auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); @@ -1101,11 +1119,8 @@ namespace gal::prometheus::gui auto state = std::to_underlying(MouseState::NONE); const auto hovered = - // window - context.window_hovered_root == std::addressof(window) and - // new + context.window_hovered_root == std::addressof(window.root()) and context.widget_hovered == invalid_widget_id and - // mouse window.is_hovered(context, area); if (hovered) @@ -1172,13 +1187,20 @@ namespace gal::prometheus::gui context.window_focused = std::addressof(window); - const auto it = std::ranges::find(context.window_list, std::addressof(window)); + auto& root = window.root(); + + const auto it = std::ranges::find(context.window_root_list, std::addressof(root)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != context.window_root_list.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != context.window_list.end()); + if (it != context.window_root_list.end() - 1) + { + // The focused window is drawn last + const auto p = *it; + context.window_root_list.erase(it); + context.window_root_list.push_back(p); + } - const auto p = *it; - context.window_list.erase(it); - context.window_list.push_back(p); + // todo: reorder child window? } auto is_widget_hovered(const Context& context, const widget_id_type id) noexcept -> bool diff --git a/src/gui/internal/context.hpp b/src/gui/internal/context.hpp index 5297a3c..6e13d6a 100644 --- a/src/gui/internal/context.hpp +++ b/src/gui/internal/context.hpp @@ -72,11 +72,14 @@ namespace gal::prometheus point_type window_default_spawn_position; - // all created windows + // All created windows std::vector> window_hive; - // all created windows, but sorted (child windows are next to their parents, the most recently used window is always moved to the tail) - std::vector window_list; + // Root window only, child windows are managed by their parent window + // The order of the windows in this list determines the drawing order, the windows at the end are drawn last + std::vector window_root_list; + + // Current window stack, push the stack when begin_window is called, and pop the stack when end_window is called (Nested calls are also included in this list) std::vector window_current_stack; // catch mouse @@ -103,6 +106,14 @@ namespace gal::prometheus namespace internal { + auto begin_window( + Context& context, + std::string_view name, + const extent_type& size, + Theme::value_type fill_alpha, + WindowFlag flag + ) noexcept -> bool; + // ---------------------------------------------------------------------- // DrawListFlag diff --git a/src/gui/internal/draw_list.cpp b/src/gui/internal/draw_list.cpp index 71d9bd8..88b45bc 100644 --- a/src/gui/internal/draw_list.cpp +++ b/src/gui/internal/draw_list.cpp @@ -1156,7 +1156,7 @@ namespace gal::prometheus::gui::internal auto DrawList::push_command() noexcept -> void { // fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_command_texture_id_ != Font::invalid_texture_id); command_list_.emplace_back( @@ -1295,7 +1295,7 @@ namespace gal::prometheus::gui::internal auto DrawList::push_clip_rect(const rect_type& rect, const bool intersect_with_current_clip_rect) noexcept -> rect_type& { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 4e3ea57..9c9c6d7 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -131,7 +131,7 @@ namespace gal::prometheus::gui::internal const auto& theme = current_theme(context); - if (window.flag_.is() and not window.flag_.is()) + if (window.flag_.is() and not window.flag_.is()) { return {1, 1}; } @@ -146,7 +146,7 @@ namespace gal::prometheus::gui::internal { const auto& window = self.get(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); const auto& theme = current_theme(context); @@ -166,7 +166,7 @@ namespace gal::prometheus::gui::internal const auto& window = self.get(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag_.is()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag_.is()); return {window.point_, window.size_full_.width, height}; } @@ -201,7 +201,7 @@ namespace gal::prometheus::gui::internal { const auto& window = self.get(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); const auto height = titlebar_height(context); @@ -280,7 +280,7 @@ namespace gal::prometheus::gui::internal auto& window = self.get(); window.draw_list_.rect_filled(rect, color); - if (window.flag_.is()) + if (window.flag_.is()) { const point_type outer_point{rect.left_top() + extent_type{.5f, .5f}}; const extent_type outer_size{rect.size() - extent_type{.5f, .5f}}; @@ -300,7 +300,7 @@ namespace gal::prometheus::gui::internal auto& window = self.get(); window.draw_list_.circle_filled(circle, color); - if (window.flag_.is()) + if (window.flag_.is()) { const point_type outer_point{circle.center() + extent_type{.5f, .5f}}; const auto outer_radius = circle.radius - .5f; @@ -332,6 +332,14 @@ namespace gal::prometheus::gui::internal ) noexcept -> bool { constexpr auto is_list = std::is_same_v>; + if constexpr (is_list) + { + const auto list_size = reference.size(); + if (list_size == 1) + { + return this->draw_slider(context, utf8_text, reference[0], min, max, decimal_precision, power); + } + } auto& window = self.get(); @@ -352,6 +360,7 @@ namespace gal::prometheus::gui::internal { const auto slider_point = slider_rect.left_top(); const auto slider_size = slider_rect.size(); + const auto frame_point = frame_rect.left_top(); const auto frame_size = frame_rect.size(); @@ -406,7 +415,7 @@ namespace gal::prometheus::gui::internal // rescale to the positive range before powering auto v = normalized_x; - if (std::abs(linear_zero_pos - 1.f) > 1e-6) + if (std::fabs(linear_zero_pos - 1.f) > 1e-6) { v = (v - linear_zero_pos) / (1.f - linear_zero_pos); } @@ -493,43 +502,52 @@ namespace gal::prometheus::gui::internal const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); - // □ + text const auto last_item_width = window.canvas_.item_width.back(); - bool value_changed = false; - if constexpr (is_list) - { - const auto list_size = reference.size(); - if (list_size == 1) - { - return this->draw_slider(context, utf8_text, reference[0], min, max, decimal_precision, power); - } + const auto total_frame_point = window.canvas_.cursor_current_line; + const auto total_frame_size = extent_type{last_item_width, text_size.height} + theme.item_frame_padding * 2; + const rect_type total_frame_rect{total_frame_point, total_frame_size}; - const auto each_frame_width = (last_item_width - theme.item_inner_spacing.width * static_cast(list_size - 1)) / static_cast(list_size); - const auto each_slider_width = each_frame_width - theme.item_frame_padding.width * 2; + const auto total_slider_point = total_frame_point + theme.item_frame_padding; + const auto total_slider_size = total_frame_size - theme.item_frame_padding * 2; + const rect_type total_slider_rect{total_slider_point, total_slider_size}; - const auto total_frame_point = window.canvas_.cursor_current_line; - const extent_type total_frame_size{last_item_width + theme.item_frame_padding.width * 2, text_size.height + theme.item_frame_padding.height * 2}; - const rect_type total_frame_rect{total_frame_point, total_frame_size}; - drawer.adjust_item_size(context, total_frame_size); + drawer.adjust_item_size(context, total_frame_size); - // □ text - window.same_line(context, auto_size, theme.item_inner_spacing.width); + // □ text / □ □ .. □ □ text + window.same_line(context, auto_size, theme.item_inner_spacing.width); - // text - const auto text_point = window.canvas_.cursor_current_line + theme.item_frame_padding; - const rect_type text_rect{text_point, text_size}; - drawer.adjust_item_size(context, text_size); + // text + const auto text_point = window.canvas_.cursor_current_line + theme.item_frame_padding; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const rect_type total_rect{total_frame_point, text_rect.right_bottom()}; + if (not drawer.is_visible_area(context, total_rect)) + { + // invisible + return false; + } + drawer.test_last_item(context, total_rect); + + bool value_changed = false; - const rect_type total_rect{total_frame_rect.left_top(), text_rect.right_bottom()}; - if (not drawer.is_visible_area(context, total_rect)) + if constexpr (is_list) + { + const auto list_size = reference.size(); + + // width: (total - spacing) / size + // height: total + const auto each_frame_size = extent_type { - // invisible - return false; - } - drawer.test_last_item(context, total_rect); + (total_frame_size.width - theme.item_inner_spacing.width * static_cast(list_size - 1)) / static_cast(list_size), + total_frame_size.height + }; + const auto each_slider_size = each_frame_size - theme.item_frame_padding * 2; + + const auto offset_x = each_frame_size.width + theme.item_inner_spacing.width; - // draw all slider + // draw n slider // note: when drawing multiple sliders, the widget id is based on the slider index, not the label text // note: to avoid different sliders getting the same id (since ids are based on index), the label text is pushed in here as the seed window.push_id(context, utf8_text); @@ -538,94 +556,52 @@ namespace gal::prometheus::gui::internal { const auto id = id_maker.make_id(context, index); - const point_type slider_point - { - (total_frame_point.x + theme.item_frame_padding.width) + ((each_frame_width + theme.item_inner_spacing.width) * static_cast(index)), - (total_frame_point.y + theme.item_frame_padding.height) - }; - const extent_type slider_size - { - each_slider_width, - text_size.height - }; - const rect_type slider_rect{slider_point, slider_size}; - + // x: total + offset * n + // y: total const point_type frame_point { - (total_frame_point.x) + ((each_frame_width + theme.item_inner_spacing.width) * static_cast(index)), - (total_frame_point.y) + total_frame_point.x + offset_x * static_cast(index), + total_frame_point.y }; - const extent_type frame_size + const rect_type frame_rect{frame_point, each_frame_size}; + + // x: total + offset * n + // y: total + const point_type slider_point { - each_frame_width, - text_size.height + theme.item_frame_padding.height * 2 + total_slider_point.x + offset_x * static_cast(index), + total_slider_point.y }; - const rect_type frame_rect{frame_point, frame_size}; + const rect_type slider_rect{slider_point, each_slider_size}; value_changed |= do_draw_slider(id, ref_value, slider_rect, frame_rect); } window.pop_id(context); - - // draw text - window.draw_list_.text( - font, - font_size, - text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), - utf8_text, - text_rect.width() - ); } else { + // draw one slider const auto id = id_maker.make_id(context, utf8_text); - - // □, height equals last item width - const auto slider_point = window.canvas_.cursor_current_line + theme.item_frame_padding; - const auto slider_size = extent_type{last_item_width, text_size.height}; - const rect_type slider_rect{slider_point, slider_size}; - - const auto frame_point = window.canvas_.cursor_current_line; - const auto frame_size = slider_size + theme.item_frame_padding * 2; - const rect_type frame_rect{frame_point, frame_size}; - drawer.adjust_item_size(context, frame_size); - - // □ text - window.same_line(context, auto_size, theme.item_inner_spacing.width); - - // text - const auto text_point = window.canvas_.cursor_current_line + theme.item_frame_padding; - const rect_type text_rect{text_point, text_size}; - drawer.adjust_item_size(context, text_size); - - const rect_type total_rect{frame_rect.left_top(), text_rect.right_bottom()}; - if (not drawer.is_visible_area(context, total_rect)) - { - // invisible - return false; - } - drawer.test_last_item(context, total_rect); - - value_changed |= do_draw_slider(id, reference, slider_rect, frame_rect); - - // draw text - window.draw_list_.text( - font, - font_size, - text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), - utf8_text, - text_rect.width() - ); + value_changed |= do_draw_slider(id, reference, total_slider_rect, total_frame_rect); } + // draw text + window.draw_list_.text( + font, + font_size, + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + return value_changed; } }; Window::Window( const std::string_view name, - const Flag flag, + const WindowFlag flag, const point_type& point, const extent_type& size, Window* root @@ -647,8 +623,8 @@ namespace gal::prometheus::gui::internal flag_{flag}, root_{root}, point_{point}, - size_{size}, size_full_{size}, + size_{size}, size_of_content_{0, 0}, default_item_width_{0}, scroll_y_{0}, @@ -674,15 +650,15 @@ namespace gal::prometheus::gui::internal root_ = this; } - // auto fit - if (size.width < .001f or size.height < .001f) + // auto-fit + // if (size.width < 1e-3f or size.height < 1e-3f) { auto_fit_only_grows_ = true; auto_fit_frames_ = 2; } } - auto Window::reset(const Flag flag) noexcept -> void + auto Window::reset(const WindowFlag flag) noexcept -> void { flag_ = flag; } @@ -690,7 +666,7 @@ namespace gal::prometheus::gui::internal auto Window::handle_inputs(const Context& context) noexcept -> void { // scroll - if (not flag_.is()) + if (not flag_.is()) { // todo constexpr auto scroll_weight = static_cast(5); @@ -698,10 +674,11 @@ namespace gal::prometheus::gui::internal } } - auto Window::begin_draw( + auto Window::begin_window( Context& context, - value_type fill_alpha, - Window* parent + Window* parent, + value_type background_fill_alpha, + const extent_type& size ) noexcept -> bool { // This function can be called multiple times per frame, @@ -721,6 +698,8 @@ namespace gal::prometheus::gui::internal draw_list_.bind_context(context); // clear render data draw_list_.reset(); + // clear all child + children_this_frame_.clear(); // show visible_ = true; // clear clip rect @@ -733,36 +712,28 @@ namespace gal::prometheus::gui::internal if (flag_.is()) { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parent != nullptr); + parent->children_this_frame_.push_back(this); - parent->child_stack_.push_back(this); - - // moves the child window to the current cursor position of the parent window + // Moves the child window to the current cursor position of the parent window point_ = parent->canvas_.cursor_current_line; - - // the viewing area of the child window must not exceed that of the parent window - push_clip_rect(context, parent->clip_rect_stack_.back()); - } - else - { - // entire display area - push_clip_rect(context, {0, 0, display_size}); + // Follows the size of the parent window + size_full_ = size; } } - else + + // Outer clipping rectangle (window area) + if (flag_.is()) { - // the viewing area of the child window must not exceed that of the parent window - if (flag_.is()) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parent != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parent != nullptr); + const auto& last = parent->clip_rect_stack_.back(); - // the viewing area of the child window must not exceed that of the parent window - push_clip_rect(context, parent->clip_rect_stack_.back()); - } - else - { - // entire display area - push_clip_rect(context, {0, 0, display_size}); - } + // the viewing area of the child window must not exceed that of the parent window + push_clip_rect(context, last); + } + else + { + // entire display area + push_clip_rect(context, {0, 0, display_size}); } } @@ -779,13 +750,19 @@ namespace gal::prometheus::gui::internal const auto& theme = current_theme(context); const auto font_size = drawer.font_size(context); - const auto has_titlebar = not flag_.is(); + const auto has_titlebar = not flag_.is(); const auto is_child_window = flag_.is(); const auto is_tooltip_window = flag_.is(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(has_titlebar != is_child_window, "The child window is not allowed to contain a titlebar!"); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(has_titlebar != is_tooltip_window, "The tootip window is not allowed to contain a titlebar!"); + if (is_child_window) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not has_titlebar, "The child window is not allowed to contain a titlebar!"); + } + if (is_tooltip_window) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not has_titlebar, "The tootip window is not allowed to contain a titlebar!"); + } if (is_first_draw_this_frame) { @@ -822,7 +799,7 @@ namespace gal::prometheus::gui::internal // select current window focus_window(context, *this); - if (not flag_.is()) + if (not flag_.is()) { const auto delta = mouse.position_delta; @@ -856,15 +833,18 @@ namespace gal::prometheus::gui::internal if ( size_.width > 0 and not is_tooltip_window and - not flag_.is() + not flag_.is() ) { default_item_width_ = size_.width * theme.item_default_width_factor; } else { - // todo: default item width - default_item_width_ = theme.window_min_size.width * theme.item_default_width_factor; + // If this value is too small, the width of the widgets that use item_width will also be small, + // if the width of the newly created (showed) window depends on these widgets, + // the window may not be able to display the all widgets in its entirety + // default_item_width_ = theme.window_min_size.width * theme.item_default_width_factor; + default_item_width_ = 300; } // apply and clamp scrolling @@ -910,6 +890,7 @@ namespace gal::prometheus::gui::internal // titlebar only const auto rect = drawer.titlebar_rect(context); + size_ = rect.size(); draw_list_.rect_filled( rect, @@ -917,7 +898,7 @@ namespace gal::prometheus::gui::internal theme.window_corner_rounding ); - if (flag_.is()) + if (flag_.is()) { constexpr auto offset = extent_type{1, 1}; @@ -955,7 +936,7 @@ namespace gal::prometheus::gui::internal display_size - theme.window_auto_fit_padding ); - if (flag_.is()) + if (flag_.is()) { size_full_ = auto_fit_size; } @@ -971,7 +952,7 @@ namespace gal::prometheus::gui::internal size_full_ = auto_fit_size; } } - else if (not flag_.is()) + else if (not flag_.is()) { // resize grip const auto rect = drawer.resize_grip_rect(context); @@ -1024,16 +1005,16 @@ namespace gal::prometheus::gui::internal }(); // background rect - if (fill_alpha > 0) + if (background_fill_alpha > 0) { draw_list_.rect_filled( {point_, size_}, - color_of(theme, ThemeCategory::WINDOW_BACKGROUND, fill_alpha), + color_of(theme, ThemeCategory::WINDOW_BACKGROUND, background_fill_alpha), theme.window_corner_rounding ); // border - if (flag_.is()) + if (flag_.is()) { constexpr auto offset = extent_type{1, 1}; @@ -1061,7 +1042,7 @@ namespace gal::prometheus::gui::internal ); // border - if (flag_.is()) + if (flag_.is()) { draw_list_.line( current_titlebar_rect.left_bottom(), @@ -1074,7 +1055,7 @@ namespace gal::prometheus::gui::internal // scrollbar if ( // no scrollbar - flag_.is() or + flag_.is() or // window space is greater than the space required for content size_.height > size_of_content_.height) { @@ -1155,7 +1136,7 @@ namespace gal::prometheus::gui::internal } // resize-grip - if (not flag_.is()) + if (not flag_.is()) { const auto rect = drawer.resize_grip_rect(context); // if (const auto rounding = theme.window_corner_rounding; @@ -1191,7 +1172,7 @@ namespace gal::prometheus::gui::internal // titlebar context if (has_titlebar) { - if (not flag_.is()) + if (not flag_.is()) { const auto rect = drawer.close_button_rect(context); const auto id = id_of_close(context); @@ -1316,49 +1297,41 @@ namespace gal::prometheus::gui::internal canvas_.text_wrap_width.push_back(DrawList::text_wrap_width_not_set); } } - else - { - // - } - const auto window_rect = rect(); - const auto current_window_padding = drawer.window_padding(context); - const auto current_titlebar_rect = [&]() noexcept -> rect_type + // Inner clipping rectangle (canvas area) { - if (has_titlebar) + const auto window_rect = rect(); + const auto current_window_padding = drawer.window_padding(context); + const auto current_titlebar_rect = [&]() noexcept -> rect_type { - return drawer.titlebar_rect(context); - } - - return drawer.titlebar_rect(context, 0); - }(); + if (has_titlebar) + { + return drawer.titlebar_rect(context); + } - const point_type clip_rect_point - { - current_titlebar_rect.left_bottom().x + current_window_padding.width * .5f + .5f, - current_titlebar_rect.left_bottom().y + .5f - }; - const extent_type clip_rect_size - { - current_titlebar_rect.width() - current_window_padding.width - (scroll_y_visible_ ? theme.window_vertical_scrollbar_width : 0), - window_rect.height() - current_titlebar_rect.height() - 2 - }; - const rect_type clip_rect{clip_rect_point, clip_rect_size}; + return drawer.titlebar_rect(context, 0); + }(); - // Inner clipping rectangle (canvas area) - push_clip_rect(context, clip_rect); + const point_type clip_rect_point + { + current_titlebar_rect.left_bottom().x + current_window_padding.width * .5f + .5f, + current_titlebar_rect.left_bottom().y + .5f + }; + const extent_type clip_rect_size + { + current_titlebar_rect.width() - current_window_padding.width - (scroll_y_visible_ ? theme.window_vertical_scrollbar_width : 0), + window_rect.height() - current_titlebar_rect.height() - 2 + }; + const rect_type clip_rect{clip_rect_point, clip_rect_size}; - if (is_first_draw_this_frame) - { - accessed_ = false; + push_clip_rect(context, clip_rect); } - // > Limit the current window from moving outside the program's visual area (if it is not a child window) (see code above) - // If it is a child window, it may move out of the visual area because the parent window moves, and we collapse it (so that we can skip the widgets drawn on it earlier) + // Child windows may not be visible (located in areas not visible to the parent window), we collapse it manually (so that we can skip the widgets drawn on it earlier) if (is_child_window) { - // todo - collapsed_ = not clip_rect.valid(); + const auto last_clip_rect = clip_rect_stack_.back(); + collapsed_ = not last_clip_rect.valid(); // We also hide the window from rendering because we've already added its border to the command list // (we could perform the check earlier in the function, but it is simpler at this point) @@ -1374,6 +1347,11 @@ namespace gal::prometheus::gui::internal } skip_item_ = collapsed_ or (not visible_ and auto_fit_frames_ == 0); + + if (is_first_draw_this_frame) + { + accessed_ = false; + } } GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); @@ -1381,7 +1359,7 @@ namespace gal::prometheus::gui::internal return close_button_pressed; } - auto Window::end_draw(Context& context) noexcept -> void + auto Window::end_window(Context& context) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(clip_rect_stack_.size() == 2); @@ -1396,9 +1374,74 @@ namespace gal::prometheus::gui::internal // window_data.root = nullptr; } + auto Window::begin_child_window(Context& context, std::string_view name, extent_type size, const bool border, WindowFlag flag) noexcept -> void + { + const auto& theme = current_theme(context); + + flag |= gui::WindowFlag::NO_TITLEBAR | gui::WindowFlag::NO_CLOSE | gui::WindowFlag::NO_RESIZE | gui::WindowFlag::NO_MOVE; + flag |= WindowInternalFlag::CHILD_WINDOW; + + if (border) + { + flag |= gui::WindowFlag::BORDERED; + } + + const auto content_region = content_region_max(context); + const auto remaining_size = content_region - (canvas_.cursor_current_line - point_); + + if (size.width <= 0) + { + flag |= WindowInternalFlag::CHILD_WINDOW_AUTO_FIT_X; + + size.width = std::ranges::max(remaining_size.width, theme.window_min_size.width); + } + if (size.height <= 0) + { + flag |= WindowInternalFlag::CHILD_WINDOW_AUTO_FIT_Y; + + size.height = std::ranges::max(remaining_size.height, theme.window_min_size.height); + } + + const auto child_window_name = std::format("{}.{}", name_, name); + internal::begin_window(context, child_window_name, size, 0, flag); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Window::end_child_window(Context& context, Window& child) noexcept -> void + { + gui::end_window(context); + + // When using autofill child window, we don't provide the width/height to `adjust_item_size` so that it doesn't feed back into automatic size-fitting + auto size = child.size_; + if (child.flag_.is()) + { + size.width = 0; + } + if (child.flag_.is()) + { + size.height = 0; + } + + Drawer drawer{.self = *this}; + drawer.adjust_item_size(context, size); + } + auto Window::render(Context& context) const noexcept -> void { + if (not visible_) + { + return; + } + context.draw_lists.emplace_back(draw_list_); + + std::ranges::for_each( + children_this_frame_, + [&](auto& child) noexcept -> void + { + child->render(context); + } + ); } auto Window::draw_text(Context& context, const std::string_view utf8_text) noexcept -> void @@ -1895,7 +1938,7 @@ namespace gal::prometheus::gui::internal { std::ignore = context; - canvas_.item_width.push_back(new_item_width); + canvas_.item_width.push_back(new_item_width > 0 ? new_item_width : default_item_width_); } // ReSharper disable once CppParameterMayBeConstPtrOrRef @@ -2014,7 +2057,7 @@ namespace gal::prometheus::gui::internal return name_; } - auto Window::flag() const noexcept -> Flag + auto Window::flag() const noexcept -> WindowFlag { return flag_; } @@ -2134,14 +2177,52 @@ namespace gal::prometheus::gui::internal return canvas_.last_item_focused; } - auto Window::show() noexcept -> void - { - visible_ = true; - } + // auto Window::show() noexcept -> void + // { + // visible_ = true; + // + // std::ranges::for_each( + // children_this_frame_, + // &Window::show + // ); + // } auto Window::hide() noexcept -> void { visible_ = false; accessed_ = false; + + std::ranges::for_each( + children_this_frame_, + &Window::hide + ); + } + + auto Window::find_hovered_window(const point_type& position, const bool excludes_children) noexcept -> Window* + { + if (not visible_) + { + return nullptr; + } + + if (not excludes_children) + { + for (auto* child: children_this_frame_) + { + if (auto* window = child->find_hovered_window(position, excludes_children); + window != nullptr) + { + return window; + } + } + } + + if (const auto rect = this->rect(); + rect.includes(position)) + { + return this; + } + + return nullptr; } } diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index 79da321..a95f5b7 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -10,345 +10,296 @@ #include #include -namespace gal::prometheus +namespace gal::prometheus::gui::internal { - namespace gui::internal + class Window final { - enum class WindowInternalFlag : std::uint16_t - { - NONE = 0, - - CHILD_WINDOW = 1 << 0, - CHILD_WINDOW_AUTO_FIT_X = 1 << 1, - CHILD_WINDOW_AUTO_FIT_Y = 1 << 2, + public: + using value_type = extent_type::value_type; - CATEGORY_TOOLTIP = 1 << 3, - }; - } + // <= 0 + constexpr static auto auto_size = value_type{-.999999f}; - namespace meta::user_defined - { - template<> - struct enum_is_flag : std::true_type {}; - } + private: + class IdMaker; + class Drawer; + class Anonymous; - namespace gui::internal - { - class Window final + struct canvas_type { - public: - using value_type = extent_type::value_type; - - class [[nodiscard]] Flag final - { - WindowFlag flag_; - WindowInternalFlag internal_flag_; - - public: - constexpr explicit (false) Flag(const WindowFlag flag) noexcept - : flag_{flag}, - internal_flag_{WindowInternalFlag::NONE} {} - - constexpr explicit Flag(const WindowFlag flag, const WindowInternalFlag internal_flag) noexcept - : flag_{flag}, - internal_flag_{internal_flag} {} - - [[nodiscard]] constexpr auto flag() const noexcept -> WindowFlag - { - return flag_; - } - - [[nodiscard]] constexpr auto internal_flag() const noexcept -> WindowInternalFlag - { - return internal_flag_; - } - - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - - template - struct workaround - { - T flag; - - constexpr explicit (false) workaround(const T flag) noexcept - : flag{flag} {} - }; - - template Flag> - [[nodiscard]] constexpr auto is() const noexcept -> bool - { - return std::to_underlying(flag_) & Flag.flag; - } - - template Flag> - [[nodiscard]] constexpr auto is() const noexcept -> bool - { - return std::to_underlying(internal_flag_) & Flag.flag; - } - - #else - - template - [[nodiscard]] constexpr auto is() const noexcept -> bool - { - return std::to_underlying(flag_) & Flag; - } - - template - [[nodiscard]] constexpr auto is() const noexcept -> bool - { - return std::to_underlying(internal_flag_) & Flag; - } - - #endif - }; - - static_assert(sizeof(Flag) == sizeof(std::uint32_t)); - - // <= 0 - constexpr static auto auto_size = value_type{-.999999f}; - - private: - class IdMaker; - class Drawer; - class Anonymous; - - struct canvas_type - { - point_type cursor_start_line; - point_type cursor_current_line; - point_type cursor_previous_line; - - value_type height_current_line; - value_type height_previous_line; - - rect_type last_item_rect; - bool last_item_hovered; - bool last_item_focused; - - std::vector item_width; - // <0(DrawList::text_wrap_width_not_set): disable - // =0: window.content_region_max().width - // >0: width - std::vector text_wrap_width; - }; - - // ================== - // CANVAS - - canvas_type canvas_; - - // ================== - // DRAW LIST - - DrawList draw_list_; - - // ================== - // WINDOW INFO - - std::string name_; - Flag flag_; - Window* root_; - - point_type point_; - // size_full / collapsed titlebar - extent_type size_; - extent_type size_full_; - // size of contents, extents reach by the drawing cursor - extent_type size_of_content_; - - value_type default_item_width_; - - value_type scroll_y_; - value_type scroll_next_y_; - bool scroll_y_visible_; - - bool visible_; - // Set when collapsing window to become only titlebar - bool collapsed_; - // visible and not collapsed - bool skip_item_; - - // Set to true when any widget access the current window - bool accessed_; - - bool auto_fit_only_grows_; - // 2 boolean, avoid padding - std::int16_t auto_fit_frames_; - - frame_count_type last_drawn_frame_; - - std::vector child_stack_; - std::vector id_stack_; - std::vector clip_rect_stack_; - - public: - // ----------------------------------- - // ctor & reset - - /** - * @brief Create a new window - * @param name window name - * @param flag window flag - * @param point window position - * @param size window size - * @param root the root of the window (parent window), if the current window is not a child window, - * this parameter must be a null pointer (while the root of the window is itself) - */ - Window( - std::string_view name, - Flag flag, - const point_type& point, - const extent_type& size, - Window* root - ) noexcept; - - /** - * @brief If a window has already been created, each "re-creation" only resets its flag (if necessary) - * @param flag window flag - */ - auto reset(Flag flag) noexcept -> void; - - /** - * @brief Handles all input devices (mouse and keyboard) at the beginning of each frame (if necessary) - */ - auto handle_inputs(const Context& context) noexcept -> void; + // Cursor start position of the canvas + point_type cursor_start_line; + // Cursor current position of the canvas + point_type cursor_current_line; + // Cursor previous (line) position of the canvas + point_type cursor_previous_line; + + // Current line height + value_type height_current_line; + // Previous line height + value_type height_previous_line; + + // The drawing area of the previous widget + rect_type last_item_rect; + // Whether the drawing area of the previous widget is hovered by the mouse + bool last_item_hovered; + // Whether the drawing area of the previous widget is focused by the mouse + bool last_item_focused; + + // next item width + std::vector item_width; + // <0(DrawList::text_wrap_width_not_set): disable + // =0: window.content_region_max().width + // >0: width + std::vector text_wrap_width; + }; - // ----------------------------------- - // DRAW BEGIN + // ================== + // CANVAS + + canvas_type canvas_; + + // ================== + // DRAW LIST + + DrawList draw_list_; + + // ================== + // WINDOW INFO + + std::string name_; + WindowFlag flag_; + + // The last non-child window in the Context::window_current_stack (this is different from the parent window, which can be a child of another window), you can find it in Context::window_root_list + Window* root_; + // All child windows that have been presented in this frame + std::vector children_this_frame_; + + // Position of the window + point_type point_; + // Size of the full (expanded) window + extent_type size_full_; + // Current window size (depends on whether it is expanded or not, if it is not expanded then it is the size of the titlebar) + extent_type size_; + + // Current size of contents, extents reach by the drawing cursor + extent_type size_of_content_; + + // Default width when creating items + value_type default_item_width_; + + // Current position of the scrollbar + value_type scroll_y_; + // Next position of the scrollbar + value_type scroll_next_y_; + // Whether the scrollbar is visible (present) + bool scroll_y_visible_; + + // Whether the window is visible (not closed) + bool visible_; + // Whether the window is collapsed or not (titlebar only) + bool collapsed_; + // == visible and not collapsed (utility, avoid pad) + bool skip_item_; + + // Whether any (window's) widget accesses the current window + bool accessed_; + + // Does auto-fit apply only when the size gets bigger + bool auto_fit_only_grows_; + // Frames requiring automatic resizing + // 2 boolean, avoid padding + std::int16_t auto_fit_frames_; + + // The number of frames the current window was last drawn, if the window is not visible it will stop counting + frame_count_type last_drawn_frame_; + + // Used as a seed when generating widget ids, the first element is the id of the window itself + std::vector id_stack_; + // Clip area of the window, there should be only two elements before/after drawing the widget, the window area and the canvas area + std::vector clip_rect_stack_; + + public: + // ----------------------------------- + // ctor & reset + + /** + * @brief Create a new window + * @param name window name + * @param flag window flag + * @param point window position + * @param size window size + * @param root the root of the window (topmost parent window), if the current window is not a child window, + * this parameter must be a null pointer (while the root of the window is itself) + */ + Window( + std::string_view name, + WindowFlag flag, + const point_type& point, + const extent_type& size, + Window* root + ) noexcept; + + /** + * @brief If a window has already been created, each "re-creation" only resets its flag (if necessary) + * @param flag window flag + */ + auto reset(WindowFlag flag) noexcept -> void; + + /** + * @brief Handles all input devices (mouse and keyboard) at the beginning of each frame (if necessary) + */ + auto handle_inputs(const Context& context) noexcept -> void; + + // ----------------------------------- + // DRAW BEGIN + + /** + * @brief Creating a canvas for the window + * @param context + * @param parent If the current window is a child window, then @c parent is its parent, otherwise this parameter must be a null pointer + * @param background_fill_alpha Alpha for window background color fill + * @param size If the current window is a child window, set the window size to @c size + * @return Whether the window is visible (not closed) + * @note Widgets can be drawn in the window if and only if its canvas has been created + */ + [[nodiscard]] auto begin_window( + Context& context, + Window* parent, + value_type background_fill_alpha, + const extent_type& size + ) noexcept -> bool; + auto end_window(Context& context) noexcept -> void; - /** - * @brief Creating a canvas for the window - * @param context - * @param fill_alpha - * @param parent If the current window is a child window, then @c parent is its parent, otherwise this parameter must be a null pointer - * @return Whether the window is visible (not closed) - * @note Widgets can be drawn in the window if and only if its canvas has been created - */ - [[nodiscard]] auto begin_draw( - Context& context, - value_type fill_alpha, - Window* parent - ) noexcept -> bool; + auto begin_child_window( + Context& context, + std::string_view name, + extent_type size, + bool border, + WindowFlag flag + ) noexcept -> void; + auto end_child_window(Context& context, Window& child) noexcept -> void; - auto end_draw(Context& context) noexcept -> void; + auto render(Context& context) const noexcept -> void; - auto render(Context& context) const noexcept -> void; + // ----------------------------------- + // WIDGETS - // ----------------------------------- - // WIDGETS + auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; - auto draw_text(Context& context, std::string_view utf8_text) noexcept -> void; + auto draw_button(Context& context, std::string_view utf8_text, extent_type size, bool repeat_when_held) noexcept -> bool; - auto draw_button(Context& context, std::string_view utf8_text, extent_type size, bool repeat_when_held) noexcept -> bool; + auto draw_small_button(Context& context, std::string_view utf8_text, bool repeat_when_held) noexcept -> bool; - auto draw_small_button(Context& context, std::string_view utf8_text, bool repeat_when_held) noexcept -> bool; + auto draw_radio_button(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; - auto draw_radio_button(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + auto draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; - auto draw_checkbox(Context& context, std::string_view utf8_text, bool checked) noexcept -> bool; + auto draw_slider( + Context& context, + std::string_view utf8_text, + float& reference, + float min, + float max, + std::uint32_t decimal_precision, + float power + ) noexcept -> bool; - auto draw_slider( - Context& context, - std::string_view utf8_text, - float& reference, - float min, - float max, - std::uint32_t decimal_precision, - float power - ) noexcept -> bool; + auto draw_slider_n( + Context& context, + std::string_view utf8_text, + std::span references, + float min, + float max, + std::uint32_t decimal_precision, + float power + ) noexcept -> bool; - auto draw_slider_n( - Context& context, - std::string_view utf8_text, - std::span references, - float min, - float max, - std::uint32_t decimal_precision, - float power - ) noexcept -> bool; + // ----------------------------------- + // WIDGET LAYOUT - // ----------------------------------- - // WIDGET LAYOUT + auto same_line(Context& context, value_type column_width = layout_auto_size, value_type spacing_width = layout_auto_size) noexcept -> void; - auto same_line(Context& context, value_type column_width = layout_auto_size, value_type spacing_width = layout_auto_size) noexcept -> void; + // ----------------------------------- + // CANVAS LAYOUT - // ----------------------------------- - // CANVAS LAYOUT + auto push_item_width(Context& context, Theme::value_type new_item_width) noexcept -> void; + auto pop_item_width(Context& context) noexcept -> void; - auto push_item_width(Context& context, Theme::value_type new_item_width) noexcept -> void; - auto pop_item_width(Context& context) noexcept -> void; + auto push_text_wrap_width(Context& context, Theme::value_type new_wrap_width) noexcept -> void; + auto pop_text_wrap_width(Context& context) noexcept -> void; - auto push_text_wrap_width(Context& context, Theme::value_type new_wrap_width) noexcept -> void; - auto pop_text_wrap_width(Context& context) noexcept -> void; + // ----------------------------------- + // ID - // ----------------------------------- - // ID + [[nodiscard]] auto id_of_move(Context& context) const noexcept -> widget_id_type; - [[nodiscard]] auto id_of_move(Context& context) const noexcept -> widget_id_type; + [[nodiscard]] auto id_of_close(Context& context) const noexcept -> widget_id_type; - [[nodiscard]] auto id_of_close(Context& context) const noexcept -> widget_id_type; + [[nodiscard]] auto id_of_resize(Context& context) const noexcept -> widget_id_type; - [[nodiscard]] auto id_of_resize(Context& context) const noexcept -> widget_id_type; + [[nodiscard]] auto id_of_scrollbar(Context& context) const noexcept -> widget_id_type; - [[nodiscard]] auto id_of_scrollbar(Context& context) const noexcept -> widget_id_type; + auto push_id(Context& context, std::string_view string) noexcept -> void; - auto push_id(Context& context, std::string_view string) noexcept -> void; + auto push_id(Context& context, const void* pointer) noexcept -> void; - auto push_id(Context& context, const void* pointer) noexcept -> void; + auto push_id(Context& context, widget_id_type value) noexcept -> void; - auto push_id(Context& context, widget_id_type value) noexcept -> void; + auto pop_id(Context& context) noexcept -> void; - auto pop_id(Context& context) noexcept -> void; + // ----------------------------------- + // CLIP RECT - // ----------------------------------- - // CLIP RECT + auto push_clip_rect(Context& context, const rect_type& rect, bool clipped = true) noexcept -> void; - auto push_clip_rect(Context& context, const rect_type& rect, bool clipped = true) noexcept -> void; + auto pop_clip_rect(Context& context) noexcept -> void; - auto pop_clip_rect(Context& context) noexcept -> void; + // ----------------------------------- + // STATES - // ----------------------------------- - // STATES + [[nodiscard]] auto name() const noexcept -> std::string_view; - [[nodiscard]] auto name() const noexcept -> std::string_view; + [[nodiscard]] auto flag() const noexcept -> WindowFlag; - [[nodiscard]] auto flag() const noexcept -> Flag; + [[nodiscard]] auto position() const noexcept -> point_type; - [[nodiscard]] auto position() const noexcept -> point_type; + [[nodiscard]] auto width() const noexcept -> value_type; - [[nodiscard]] auto width() const noexcept -> value_type; + [[nodiscard]] auto height() const noexcept -> value_type; - [[nodiscard]] auto height() const noexcept -> value_type; + [[nodiscard]] auto size() const noexcept -> extent_type; - [[nodiscard]] auto size() const noexcept -> extent_type; + [[nodiscard]] auto rect() const noexcept -> rect_type; - [[nodiscard]] auto rect() const noexcept -> rect_type; + [[nodiscard]] auto visible() const noexcept -> bool; - [[nodiscard]] auto visible() const noexcept -> bool; + [[nodiscard]] auto collapsed() const noexcept -> bool; - [[nodiscard]] auto collapsed() const noexcept -> bool; + [[nodiscard]] auto root() const noexcept -> const Window&; - [[nodiscard]] auto root() const noexcept -> const Window&; + [[nodiscard]] auto content_region_max(const Context& context) const noexcept -> extent_type; + [[nodiscard]] auto window_content_region_min(const Context& context) const noexcept -> extent_type; + [[nodiscard]] auto window_content_region_max(const Context& context) const noexcept -> extent_type; - [[nodiscard]] auto content_region_max(const Context& context) const noexcept -> extent_type; - [[nodiscard]] auto window_content_region_min(const Context& context) const noexcept -> extent_type; - [[nodiscard]] auto window_content_region_max(const Context& context) const noexcept -> extent_type; + /** + * @brief Test if mouse cursor is hovering given rect + * @note @c rect is clipped by current clip rect setting + */ + [[nodiscard]] auto is_hovered(const Context& context, const rect_type& rect) const noexcept -> bool; - /** - * @brief Test if mouse cursor is hovering given rect - * @note @c rect is clipped by current clip rect setting - */ - [[nodiscard]] auto is_hovered(const Context& context, const rect_type& rect) const noexcept -> bool; + [[nodiscard]] auto is_item_hovered(const Context& context) const noexcept -> bool; + [[nodiscard]] auto is_item_focused(const Context& context) const noexcept -> bool; - [[nodiscard]] auto is_item_hovered(const Context& context) const noexcept -> bool; - [[nodiscard]] auto is_item_focused(const Context& context) const noexcept -> bool; + // ----------------------------------- + // CONTEXT - // ----------------------------------- - // + // auto show() noexcept -> void; + auto hide() noexcept -> void; - auto show() noexcept -> void; - auto hide() noexcept -> void; - }; - } + /** + * @brief Find the first (more top-level) window that contains the location of the given point + */ + [[nodiscard]] auto find_hovered_window(const point_type& position, bool excludes_children) noexcept -> Window*; + }; } From 27d27fdac43424da2198443fccfdd196150b0516 Mon Sep 17 00:00:00 2001 From: life4gal Date: Thu, 17 Apr 2025 17:50:07 +0800 Subject: [PATCH 15/54] `fix`: Remove unnecessary assertions. --- src/primitive/point.hpp | 10 +++++----- src/primitive/rect.hpp | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/primitive/point.hpp b/src/primitive/point.hpp index 2c8b1ae..98baad6 100644 --- a/src/primitive/point.hpp +++ b/src/primitive/point.hpp @@ -122,13 +122,13 @@ namespace gal::prometheus { if constexpr (Index == 0) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.x) < static_cast(p2.x)); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.x) < static_cast(p2.x)); return x >= static_cast(p1.x) and x < static_cast(p2.x); } else if constexpr (Index == 1) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.y) < static_cast(p2.y)); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.y) < static_cast(p2.y)); return y >= static_cast(p1.y) and y < static_cast(p2.y); } @@ -256,19 +256,19 @@ namespace gal::prometheus { if constexpr (Index == 0) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.x) < static_cast(p2.x)); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.x) < static_cast(p2.x)); return x >= static_cast(p1.x) and x < static_cast(p2.x); } else if constexpr (Index == 1) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.y) < static_cast(p2.y)); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.y) < static_cast(p2.y)); return y >= static_cast(p1.y) and y < static_cast(p2.y); } else if constexpr (Index == 2) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.z) < static_cast(p2.z)); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.z) < static_cast(p2.z)); return z >= static_cast(p1.z) and z < static_cast(p2.z); } diff --git a/src/primitive/rect.hpp b/src/primitive/rect.hpp index 05fca88..c71d27b 100644 --- a/src/primitive/rect.hpp +++ b/src/primitive/rect.hpp @@ -162,16 +162,16 @@ namespace gal::prometheus [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); return p.between(left_top(), right_bottom()); } [[nodiscard]] constexpr auto includes(const basic_rect& rect) const noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(width() > rect.width() and height() > rect.height()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(width() > rect.width() and height() > rect.height()); return rect.point.x >= point.x and @@ -182,8 +182,8 @@ namespace gal::prometheus [[nodiscard]] constexpr auto intersects(const basic_rect& rect) const noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); return not( rect.point.x >= point.x + width() or @@ -197,8 +197,8 @@ namespace gal::prometheus { return { - point.combine_min(rect.point), - extent.combine_max(rect.extent) + left_top().combine_min(rect.left_top()), + right_bottom().combine_max(rect.right_bottom()) }; } @@ -206,8 +206,8 @@ namespace gal::prometheus { return { - point.combine_max(rect.point), - extent.combine_min(rect.extent) + left_top().combine_max(rect.left_top()), + right_bottom().combine_min(rect.right_bottom()) }; } }; From 96dcfad6bd5f7c2748e32a5573f49604b7039ede Mon Sep 17 00:00:00 2001 From: life4gal Date: Thu, 17 Apr 2025 17:54:35 +0800 Subject: [PATCH 16/54] `test`: gui::begin/end_child_window & gui::draw_slider_n. --- unit_test/src/gui/dx11/backend.cpp | 64 +++++++++++++++++++--------- unit_test/src/gui/dx12/backend.cpp | 68 +++++++++++++++++++++--------- 2 files changed, 92 insertions(+), 40 deletions(-) diff --git a/unit_test/src/gui/dx11/backend.cpp b/unit_test/src/gui/dx11/backend.cpp index f1ea387..6b53295 100644 --- a/unit_test/src/gui/dx11/backend.cpp +++ b/unit_test/src/gui/dx11/backend.cpp @@ -418,6 +418,25 @@ auto prometheus_new_frame() -> void // auto prometheus_render() -> void { + static auto test_string = [] + { + std::string string{}; + string.resize(200); + for (int i = 0; i < 200; ++i) + { + if (i != 0 and i % 25 == 0) + { + string[i] = '\n'; + } + else + { + const auto c = 'a' + i % ('z' - 'a'); + string[i] = static_cast(c); + } + } + return string; + }(); + if (static bool window_closed = false; not window_closed) { @@ -441,27 +460,12 @@ auto prometheus_render() -> void gui::layout_same_line(); gui::draw_text("世界"); - std::string string{}; - string.resize(200); - for (int i = 0; i < 200; ++i) - { - if (i != 0 and i % 25 == 0) - { - string[i] = '\n'; - } - else - { - const auto c = 'a' + i % ('z' - 'a'); - string[i] = static_cast(c); - } - } - gui::push_text_wrap_width(150); - gui::draw_text(string); + gui::draw_text(test_string); gui::pop_text_wrap_width(); gui::push_text_wrap_width(300); - gui::draw_text(string); + gui::draw_text(test_string); gui::pop_text_wrap_width(); } @@ -519,8 +523,30 @@ auto prometheus_render() -> void gui::draw_text_colored("Slider", primitive::colors::red); { - static float v = 0; - gui::draw_slider("Slider", v, -1, 1); + static bool open_slider = false; + open_slider ^= gui::draw_button("OpenSlider"); + + if (open_slider) + { + gui::begin_window("SliderWindow"); + + gui::draw_text(test_string); + + gui::begin_child_window("SliderWindowChild"); + + static std::array v{0, 0, 0, 0, 0}; + + gui::draw_slider("Slider1", v[0], -1, 1); + gui::draw_slider_n("Slider1(n)", {v.begin(), v.begin() + 1}, -1, 1); + gui::draw_slider_n("Slider2", {v.begin(), v.begin() + 2}, -1, 1); + gui::draw_slider_n("Slider3", {v.begin(), v.begin() + 3}, -1, 1); + gui::draw_slider_n("Slider4", {v.begin(), v.begin() + 4}, -1, 1); + gui::draw_slider_n("Slider5", {v.begin(), v.begin() + 5}, -1, 1); + + gui::end_child_window(); + + gui::end_window(); + } } gui::end_window(); diff --git a/unit_test/src/gui/dx12/backend.cpp b/unit_test/src/gui/dx12/backend.cpp index f4848fa..40f1cc7 100644 --- a/unit_test/src/gui/dx12/backend.cpp +++ b/unit_test/src/gui/dx12/backend.cpp @@ -611,6 +611,25 @@ auto prometheus_new_frame() -> void // auto prometheus_render() -> void { + static auto test_string = [] + { + std::string string{}; + string.resize(200); + for (int i = 0; i < 200; ++i) + { + if (i != 0 and i % 25 == 0) + { + string[i] = '\n'; + } + else + { + const auto c = 'a' + i % ('z' - 'a'); + string[i] = static_cast(c); + } + } + return string; + }(); + if (static bool window_closed = false; not window_closed) { @@ -633,27 +652,12 @@ auto prometheus_render() -> void gui::layout_same_line(); gui::draw_text("世界"); - std::string string{}; - string.resize(200); - for (int i = 0; i < 200; ++i) - { - if (i != 0 and i % 25 == 0) - { - string[i] = '\n'; - } - else - { - const auto c = 'a' + i % ('z' - 'a'); - string[i] = static_cast(c); - } - } - gui::push_text_wrap_width(150); - gui::draw_text(string); + gui::draw_text(test_string); gui::pop_text_wrap_width(); gui::push_text_wrap_width(300); - gui::draw_text(string); + gui::draw_text(test_string); gui::pop_text_wrap_width(); } @@ -711,11 +715,33 @@ auto prometheus_render() -> void gui::draw_text_colored("Slider", primitive::colors::red); { - static float v = 0; - gui::draw_slider("Slider", v, -1, 1); - } + static bool open_slider = false; + open_slider ^= gui::draw_button("OpenSlider"); + + if (open_slider) + { + gui::begin_window("SliderWindow"); + + gui::draw_text(test_string); + + gui::begin_child_window("SliderWindowChild"); - gui::end_window(); + static std::array v{0, 0, 0, 0, 0}; + + gui::draw_slider("Slider1", v[0], -1, 1); + gui::draw_slider_n("Slider1(n)", {v.begin(), v.begin() + 1}, -1, 1); + gui::draw_slider_n("Slider2", {v.begin(), v.begin() + 2}, -1, 1); + gui::draw_slider_n("Slider3", {v.begin(), v.begin() + 3}, -1, 1); + gui::draw_slider_n("Slider4", {v.begin(), v.begin() + 4}, -1, 1); + gui::draw_slider_n("Slider5", {v.begin(), v.begin() + 5}, -1, 1); + + gui::end_child_window(); + + gui::end_window(); + } + + gui::end_window(); + } } gui::render(); From 91efa19ef7fb44ced85624747baab3d3fdb1b646 Mon Sep 17 00:00:00 2001 From: life4gal Date: Fri, 18 Apr 2025 10:44:29 +0800 Subject: [PATCH 17/54] =?UTF-8?q?`fix`:=20The=20window=20will=20draw=20the?= =?UTF-8?q?=20border=20correctly=20(instead=20of=20only=20drawing=20it=20w?= =?UTF-8?q?hen=20the=20background=20fill=20alpha=20is=20greater=20than=200?= =?UTF-8?q?,=20which=20used=20to=20cause=20child=20windows=20not=20to=20dr?= =?UTF-8?q?aw=20the=20border).=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/internal/common.hpp | 12 ++++++++++ src/gui/internal/window.cpp | 45 ++++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/gui/internal/common.hpp b/src/gui/internal/common.hpp index 3962d60..b29d35d 100644 --- a/src/gui/internal/common.hpp +++ b/src/gui/internal/common.hpp @@ -75,6 +75,18 @@ namespace gal::prometheus return *this; } + constexpr auto operator&=(const gui::WindowFlag flag) noexcept -> WindowFlag& + { + flag_ &= flag; + return *this; + } + + constexpr auto operator&=(const WindowInternalFlag flag) noexcept -> WindowFlag& + { + internal_flag_ &= flag; + return *this; + } + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) template diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 9c9c6d7..fd5871a 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -1012,23 +1012,6 @@ namespace gal::prometheus::gui::internal color_of(theme, ThemeCategory::WINDOW_BACKGROUND, background_fill_alpha), theme.window_corner_rounding ); - - // border - if (flag_.is()) - { - constexpr auto offset = extent_type{1, 1}; - - draw_list_.rect( - {point_ + offset, size_}, - color_of(theme, ThemeCategory::BORDER_SHADOW), - theme.window_corner_rounding - ); - draw_list_.rect( - {point_, size_}, - color_of(theme, ThemeCategory::BORDER), - theme.window_corner_rounding - ); - } } // titlebar rect @@ -1041,7 +1024,7 @@ namespace gal::prometheus::gui::internal DrawFlag::ROUND_CORNER_TOP ); - // border + // titlebar border if (flag_.is()) { draw_list_.line( @@ -1052,6 +1035,23 @@ namespace gal::prometheus::gui::internal } } + // background border + if (flag_.is()) + { + constexpr auto offset = extent_type{1, 1}; + + draw_list_.rect( + {point_ + offset, size_}, + color_of(theme, ThemeCategory::BORDER_SHADOW), + theme.window_corner_rounding + ); + draw_list_.rect( + {point_, size_}, + color_of(theme, ThemeCategory::BORDER), + theme.window_corner_rounding + ); + } + // scrollbar if ( // no scrollbar @@ -1404,6 +1404,15 @@ namespace gal::prometheus::gui::internal const auto child_window_name = std::format("{}.{}", name_, name); internal::begin_window(context, child_window_name, size, 0, flag); + + if (border and not flag_.is()) + { + auto& child = *children_this_frame_.back(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(child.name_ == child_window_name); + + // border only indicates whether the child window has a border, not the widgets of the child window + child.flag_ &= ~gui::WindowFlag::BORDERED; + } } // ReSharper disable once CppParameterMayBeConstPtrOrRef From 43afb983a32f6b681deeb3c98c53ef042301cbfe Mon Sep 17 00:00:00 2001 From: life4gal Date: Fri, 18 Apr 2025 11:18:28 +0800 Subject: [PATCH 18/54] =?UTF-8?q?`feat`:=20Add=20gui::begin/end=5Ftooltip?= =?UTF-8?q?=5Fwindow.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `fix`: Invisible items will correctly set the hover to false instead of doing nothing.🐛 --- src/gui/gui.hpp | 16 ++++++ src/gui/internal/context.cpp | 65 +++++++++++++++++++++++++ src/gui/internal/window.cpp | 94 +++++++++++++++++++++++++++--------- 3 files changed, 151 insertions(+), 24 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index c5c0eca..d8d01ab 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -443,6 +443,9 @@ namespace gal::prometheus ) noexcept -> void; auto end_child_window(Context& context) noexcept -> void; + auto begin_tooltip_window(Context& context) noexcept -> void; + auto end_tooltip_window(Context& context) noexcept -> void; + /** * @brief Draw a piece of text */ @@ -639,6 +642,11 @@ namespace gal::prometheus [[nodiscard]] auto get_window_content_region_min(const Context& context) noexcept -> extent_type; // The available area of the current window [[nodiscard]] auto get_window_content_region_max(const Context& context) noexcept -> extent_type; + + // Whether the last drawn item was hovered by the mouse + [[nodiscard]] auto is_item_hovered(const Context& context) noexcept -> bool; + // Whether the last drawn item was focused by the mouse + [[nodiscard]] auto is_item_focused(const Context& context) noexcept -> bool; } // ================================================== @@ -718,6 +726,9 @@ namespace gal::prometheus ) noexcept -> void; auto end_child_window() noexcept -> void; + auto begin_tooltip_window() noexcept -> void; + auto end_tooltip_window() noexcept -> void; + /** * @brief Draw a piece of text */ @@ -849,6 +860,11 @@ namespace gal::prometheus // The available area of the current window [[nodiscard]] auto get_window_content_region_max() noexcept -> extent_type; + // Whether the last drawn item was hovered by the mouse + [[nodiscard]] auto is_item_hovered() noexcept -> bool; + // Whether the last drawn item was focused by the mouse + [[nodiscard]] auto is_item_focused() noexcept -> bool; + //------------------------------------------------------------------ // FOR TEST //------------------------------------------------------------------ diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 14180d2..2399ccf 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -387,6 +387,27 @@ namespace gal::prometheus::gui parent.end_child_window(context, window); } + auto begin_tooltip_window(Context& context) noexcept -> void + { + // todo + constexpr std::string_view tooltip_window_name{"@WINDOW::TOOLTIP@"}; + constexpr internal::WindowFlag tooltip_window_flag + { + WindowFlag::NO_TITLEBAR | WindowFlag::NO_CLOSE | WindowFlag::NO_RESIZE | WindowFlag::NO_MOVE, + internal::WindowInternalFlag::CATEGORY_TOOLTIP + }; + + std::ignore = internal::begin_window(context, tooltip_window_name, {}, .9f, tooltip_window_flag); + } + + auto end_tooltip_window(Context& context) noexcept -> void + { + const auto& window = *context.window_current_stack.back(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag().is()); + + end_window(context); + } + auto draw_text(Context& context, const std::string_view utf8_text) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -530,6 +551,22 @@ namespace gal::prometheus::gui return window.window_content_region_max(context); } + auto is_item_hovered(const Context& context) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + return window.is_item_hovered(context); + } + + auto is_item_focused(const Context& context) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + return window.is_item_hovered(context); + } + auto set_current_context(Context& context) noexcept -> void { g_context = std::addressof(context); @@ -669,6 +706,20 @@ namespace gal::prometheus::gui end_child_window(context); } + auto begin_tooltip_window() noexcept -> void + { + auto& context = get_current_context(); + + begin_tooltip_window(context); + } + + auto end_tooltip_window() noexcept -> void + { + auto& context = get_current_context(); + + end_tooltip_window(context); + } + auto draw_text(const std::string_view utf8_text) noexcept -> void { auto& context = get_current_context(); @@ -795,6 +846,20 @@ namespace gal::prometheus::gui return get_window_content_region_max(context); } + auto is_item_hovered() noexcept -> bool + { + const auto& context = get_current_context(); + + return is_item_hovered(context); + } + + auto is_item_focused() noexcept -> bool + { + const auto& context = get_current_context(); + + return is_item_focused(context); + } + auto test_theme() noexcept -> Theme { constexpr auto default_colors = []() noexcept -> Theme::colors_type diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index fd5871a..db39172 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -250,24 +250,39 @@ namespace gal::prometheus::gui::internal window.size_of_content_ = window.size_of_content_.combine_max(extent_type{canvas.cursor_previous_line.x, canvas.cursor_current_line.y + window.scroll_y_} - window.point_); } - auto test_last_item(const Context& context, const rect_type& rect) noexcept -> void + // auto test_last_item(const Context& context, const rect_type& rect) noexcept -> void + // { + // auto& window = self.get(); + // auto& canvas = window.canvas_; + // + // canvas.last_item_rect = rect; + // canvas.last_item_focused = false; + // canvas.last_item_hovered = window.is_hovered(context, rect); + // } + // + // [[nodiscard]] auto is_visible_area(const Context& context, const rect_type& rect) const noexcept -> bool + // { + // std::ignore = context; + // + // auto& window = self.get(); + // + // const auto& last = window.clip_rect_stack_.back(); + // return last.intersects(rect); + // } + + [[nodiscard]] auto test_last_item_visible(const Context& context, const rect_type& rect) noexcept -> bool { auto& window = self.get(); auto& canvas = window.canvas_; + const auto& last = window.clip_rect_stack_.back(); + const auto visible = last.intersects(rect); + canvas.last_item_rect = rect; canvas.last_item_focused = false; - canvas.last_item_hovered = window.is_hovered(context, rect); - } - - [[nodiscard]] auto is_visible_area(const Context& context, const rect_type& rect) const noexcept -> bool - { - std::ignore = context; - - auto& window = self.get(); + canvas.last_item_hovered = visible ? window.is_hovered(context, rect) : false; - const auto& last = window.clip_rect_stack_.back(); - return last.intersects(rect); + return visible; } // ----------------------------------- @@ -523,12 +538,17 @@ namespace gal::prometheus::gui::internal drawer.adjust_item_size(context, text_size); const rect_type total_rect{total_frame_point, text_rect.right_bottom()}; - if (not drawer.is_visible_area(context, total_rect)) + // if (not drawer.is_visible_area(context, total_rect)) + // { + // // invisible + // return false; + // } + // drawer.test_last_item(context, total_rect); + if (not drawer.test_last_item_visible(context, total_rect)) { // invisible return false; } - drawer.test_last_item(context, total_rect); bool value_changed = false; @@ -1533,7 +1553,8 @@ namespace gal::prometheus::gui::internal drawer.adjust_item_size(context, text_size); // todo: test visible? - drawer.test_last_item(context, rect); + // drawer.test_last_item(context, rect); + std::ignore = drawer.test_last_item_visible(context, rect); } else { @@ -1549,11 +1570,16 @@ namespace gal::prometheus::gui::internal const rect_type rect{text_point, text_size}; drawer.adjust_item_size(context, text_size); - if (not drawer.is_visible_area(context, rect)) + // if (not drawer.is_visible_area(context, rect)) + // { + // return; + // } + // drawer.test_last_item(context, rect); + if (not drawer.test_last_item_visible(context, rect)) { + // invisible return; } - drawer.test_last_item(context, rect); draw_list_.text( font, @@ -1598,12 +1624,17 @@ namespace gal::prometheus::gui::internal const rect_type button_rect{button_point, button_size}; drawer.adjust_item_size(context, button_size); - if (not drawer.is_visible_area(context, button_rect)) + // if (not drawer.is_visible_area(context, button_rect)) + // { + // // invisible + // return false; + // } + // drawer.test_last_item(context, button_rect); + if (not drawer.test_last_item_visible(context, button_rect)) { // invisible return false; } - drawer.test_last_item(context, button_rect); const auto state = test_mouse(context, id, button_rect, repeat_when_held); @@ -1684,12 +1715,17 @@ namespace gal::prometheus::gui::internal const rect_type button_rect{button_point, button_size}; drawer.adjust_item_size(context, button_size); - if (not drawer.is_visible_area(context, button_rect)) + // if (not drawer.is_visible_area(context, button_rect)) + // { + // // invisible + // return false; + // } + // drawer.test_last_item(context, button_rect); + if (not drawer.test_last_item_visible(context, button_rect)) { // invisible return false; } - drawer.test_last_item(context, button_rect); const auto state = test_mouse(context, id, button_rect, repeat_when_held); @@ -1759,12 +1795,17 @@ namespace gal::prometheus::gui::internal drawer.adjust_item_size(context, text_size); const rect_type total_rect{check_rect.left_top(), text_rect.right_bottom()}; - if (not drawer.is_visible_area(context, total_rect)) + // if (not drawer.is_visible_area(context, total_rect)) + // { + // // invisible + // return false; + // } + // drawer.test_last_item(context, total_rect); + if (not drawer.test_last_item_visible(context, total_rect)) { // invisible return false; } - drawer.test_last_item(context, total_rect); // fixme: test check_rect or total_rect? const auto state = test_mouse(context, id, check_rect, false); @@ -1836,12 +1877,17 @@ namespace gal::prometheus::gui::internal drawer.adjust_item_size(context, text_size); const rect_type total_rect{check_rect.left_top(), text_rect.right_bottom()}; - if (not drawer.is_visible_area(context, total_rect)) + // if (not drawer.is_visible_area(context, total_rect)) + // { + // // invisible + // return false; + // } + // drawer.test_last_item(context, total_rect); + if (not drawer.test_last_item_visible(context, total_rect)) { // invisible return false; } - drawer.test_last_item(context, total_rect); // fixme: test check_rect or total_rect? const auto state = test_mouse(context, id, check_rect, false); From ae0b83e6ff8cb4af131942b1e00a1ce50fc6ccc3 Mon Sep 17 00:00:00 2001 From: life4gal Date: Fri, 18 Apr 2025 18:26:14 +0800 Subject: [PATCH 19/54] =?UTF-8?q?`feat`:=20Add=20gui::draw=5Fcombo.?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/gui.hpp | 51 +++- src/gui/internal/common.hpp | 1 + src/gui/internal/context.cpp | 157 +++++++++++- src/gui/internal/context.hpp | 46 +++- src/gui/internal/window.cpp | 448 ++++++++++++++++++++++++++++++++--- src/gui/internal/window.hpp | 28 ++- 6 files changed, 688 insertions(+), 43 deletions(-) diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index d8d01ab..bb78d5b 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -139,6 +139,10 @@ namespace gal::prometheus SLIDER, SLIDER_ACTIVATED, + COMBO_ITEM, + COMBO_ITEM_HOVERED, + COMBO_ITEM_ACTIVATED, + // ------------------------------- INTERNAL_COUNT }; @@ -429,7 +433,7 @@ namespace gal::prometheus Context& context, std::string_view name, const extent_type& size = {0, 0}, - Theme::value_type fill_alpha = Theme::window_fill_alpha_not_set, + Theme::alpha_type fill_alpha = Theme::window_fill_alpha_not_set, WindowFlag flag = WindowFlag::NONE ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; @@ -603,6 +607,30 @@ namespace gal::prometheus float power = 1 ) noexcept -> bool; + auto draw_combo( + Context& context, + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count = 7 + ) noexcept -> bool; + + auto draw_combo( + Context& context, + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count = 7 + ) noexcept -> bool; + + auto draw_combo( + Context& context, + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count = 7 + ) noexcept -> bool; + //------------------------------------------------------------------ // WIDGET LAYOUT //------------------------------------------------------------------ @@ -823,6 +851,27 @@ namespace gal::prometheus float power = 1 ) noexcept -> bool; + auto draw_combo( + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count = 7 + ) noexcept -> bool; + + auto draw_combo( + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count = 7 + ) noexcept -> bool; + + auto draw_combo( + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count = 7 + ) noexcept -> bool; + //------------------------------------------------------------------ // WIDGET LAYOUT //------------------------------------------------------------------ diff --git a/src/gui/internal/common.hpp b/src/gui/internal/common.hpp index b29d35d..52861c3 100644 --- a/src/gui/internal/common.hpp +++ b/src/gui/internal/common.hpp @@ -38,6 +38,7 @@ namespace gal::prometheus CHILD_WINDOW_AUTO_FIT_Y = 1 << 2, CATEGORY_TOOLTIP = 1 << 3, + CATEGORY_COMBO = 1 << 4, }; } diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 2399ccf..2aff7d7 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -333,7 +333,7 @@ namespace gal::prometheus::gui Context& context, const std::string_view name, const extent_type& size, - const Theme::value_type fill_alpha, + const Theme::alpha_type fill_alpha, const WindowFlag flag ) noexcept -> bool { @@ -370,10 +370,7 @@ namespace gal::prometheus::gui const WindowFlag flag ) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - auto& parent = *context.window_current_stack.back(); - - parent.begin_child_window(context, name, size, border, flag); + internal::begin_child_window(context, name, 0, size, border, flag); } auto end_child_window(Context& context) noexcept -> void @@ -487,6 +484,48 @@ namespace gal::prometheus::gui return window.draw_slider_n(context, utf8_text, references, min, max, decimal_precision, power); } + auto draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + + auto draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + + auto draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + auto& window = *context.window_current_stack.back(); + return window.draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + auto layout_same_line(Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); @@ -790,6 +829,42 @@ namespace gal::prometheus::gui return draw_slider_n(context, utf8_text, references, min, max, decimal_precision, power); } + auto draw_combo( + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + auto& context = get_current_context(); + + return draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + + auto draw_combo( + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + auto& context = get_current_context(); + + return draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + + auto draw_combo( + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + auto& context = get_current_context(); + + return draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + auto layout_same_line(const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { auto& context = get_current_context(); @@ -909,6 +984,10 @@ namespace gal::prometheus::gui colors[static_cast(SLIDER)] = primitive::colors::light_blue; colors[static_cast(SLIDER_ACTIVATED)] = primitive::colors::light_pink; + colors[static_cast(COMBO_ITEM)] = primitive::colors::red; + colors[static_cast(COMBO_ITEM_HOVERED)] = primitive::colors::violet_red; + colors[static_cast(COMBO_ITEM_ACTIVATED)] = primitive::colors::white; + return colors; }; @@ -994,6 +1073,21 @@ namespace gal::prometheus::gui return window.begin_window(context, parent, fill_alpha, size); } + auto begin_child_window( + Context& context, + const std::string_view name, + const Theme::alpha_type background_fill_alpha, + const extent_type& size, + const bool border, + const WindowFlag flag + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + auto& parent = *context.window_current_stack.back(); + + parent.begin_child_window(context, name, background_fill_alpha, size, border, flag); + } + auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); @@ -1238,6 +1332,34 @@ namespace gal::prometheus::gui return state; } + auto queue_mouse(Context& context, const widget_id_type id, const rect_type& area) noexcept -> std::underlying_type_t + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + + const auto& window = *context.window_current_stack.back(); + auto state = std::to_underlying(MouseState::NONE); + + const auto hovered = + context.window_hovered_root == std::addressof(window.root()) and + context.widget_hovered == invalid_widget_id and + window.is_hovered(context, area); + + if (hovered) + { + state |= MouseState::HOVERED; + + // hovering widget + context.widget_hovered = id; + + if (context.mouse.is_clicked(context, MouseKey::LEFT, false)) + { + state |= MouseState::PRESSED; + } + } + + return state; + } + auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool { return context.window_hovered == std::addressof(window); @@ -1273,11 +1395,21 @@ namespace gal::prometheus::gui return context.widget_hovered == id; } + auto is_any_widget_hovered(const Context& context) noexcept -> bool + { + return context.widget_hovered != invalid_widget_id; + } + auto is_widget_activated(const Context& context, const widget_id_type id) noexcept -> bool { return context.widget_activated == id; } + auto is_any_widget_activated(const Context& context) noexcept -> bool + { + return context.widget_activated != invalid_widget_id; + } + auto mark_widget_alive(Context& context, const widget_id_type id) noexcept -> bool { if (is_widget_activated(context, id)) @@ -1294,5 +1426,20 @@ namespace gal::prometheus::gui { context.widget_activated = id; } + + auto mark_combo_alive(Context& context, const widget_id_type id) noexcept -> void + { + context.widget_activated_combo_id = id; + } + + auto mark_combo_dead(Context& context, const widget_id_type id) noexcept -> void + { + context.widget_activated_combo_id = id; + } + + auto is_combo_activated(const Context& context, const widget_id_type id) noexcept -> bool + { + return context.widget_activated_combo_id == id; + } } } diff --git a/src/gui/internal/context.hpp b/src/gui/internal/context.hpp index 6e13d6a..769ad08 100644 --- a/src/gui/internal/context.hpp +++ b/src/gui/internal/context.hpp @@ -92,12 +92,18 @@ namespace gal::prometheus // ---------------------------------------------------------------------- // WIDGET + // widget for mouse hovering in this frame widget_id_type widget_hovered; + // widget for mouse selecting in this frame widget_id_type widget_activated; + // widget for mouse hovering in previous frame widget_id_type widget_activated_previous_frame; - + // widget for mouse selecting in this frame is still alive bool widget_activated_still_alive; + // Currently open combo window + widget_id_type widget_activated_combo_id; + // ---------------------------------------------------------------------- // DRAW LIST @@ -114,6 +120,15 @@ namespace gal::prometheus WindowFlag flag ) noexcept -> bool; + auto begin_child_window( + Context& context, + std::string_view name, + Theme::alpha_type background_fill_alpha, + const extent_type& size, + bool border, + WindowFlag flag + ) noexcept -> void; + // ---------------------------------------------------------------------- // DrawListFlag @@ -157,26 +172,43 @@ namespace gal::prometheus KEEPING = 1 << 2, }; + // Test the behavior of the mouse on the target widget [[nodiscard]] auto test_mouse(Context& context, widget_id_type id, const rect_type& area, bool repeat = false) noexcept -> std::underlying_type_t; + // Similar to test_mouse, but does not activate any widget + [[nodiscard]] auto queue_mouse(Context& context, widget_id_type id, const rect_type& area) noexcept -> std::underlying_type_t; // ---------------------------------------------------------------------- // WINDOW - auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool; + // Test that the mouse is hovering over the target window + [[nodiscard]] auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool; + // Focus on the target window (this determines the order in which the windows are drawn) auto focus_window(Context& context, Window& window) noexcept -> void; // ---------------------------------------------------------------------- // WIDGET - auto is_widget_hovered(const Context& context, widget_id_type id) noexcept -> bool; - auto is_widget_activated(const Context& context, widget_id_type id) noexcept -> bool; + // Whether the mouse is hovering over the target widget + [[nodiscard]] auto is_widget_hovered(const Context& context, widget_id_type id) noexcept -> bool; + // Whether the mouse is hovering over the widget + [[nodiscard]] auto is_any_widget_hovered(const Context& context) noexcept -> bool; + // Whether the mouse is selecting over the target widget + [[nodiscard]] auto is_widget_activated(const Context& context, widget_id_type id) noexcept -> bool; + // Whether the mouse is selecting over the widget + [[nodiscard]] auto is_any_widget_activated(const Context& context) noexcept -> bool; - /** - * @return @c is_widget_activated - */ + // Marks the current widget alive, returns whether it is currently selected (activated) or not auto mark_widget_alive(Context& context, widget_id_type id) noexcept -> bool; + // Marks that no widget is currently selected (activated) auto mark_widget_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + + // Activate the specified combo widget (window) + auto mark_combo_alive(Context& context, widget_id_type id) noexcept -> void; + // Deactivate the specified combo widget (window) + auto mark_combo_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + // Whether the target combo widget is active or not + [[nodiscard]] auto is_combo_activated(const Context& context, widget_id_type id) noexcept -> bool; } } diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index db39172..2fba2cf 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -250,6 +250,82 @@ namespace gal::prometheus::gui::internal window.size_of_content_ = window.size_of_content_.combine_max(extent_type{canvas.cursor_previous_line.x, canvas.cursor_current_line.y + window.scroll_y_} - window.point_); } + // cursor position is relative to window position + [[nodiscard]] auto cursor_position(const Context& context) const noexcept -> point_type + { + std::ignore = context; + + const auto& window = self.get(); + const auto& canvas = window.canvas_; + + return canvas.cursor_previous_line - window.point_; + } + + // cursor position in screen space + [[nodiscard]] auto cursor_screen_position(const Context& context) const noexcept -> point_type + { + std::ignore = context; + + const auto& window = self.get(); + const auto& canvas = window.canvas_; + + return canvas.cursor_current_line; + } + + auto set_cursor_position_x(const Context& context, const value_type x) noexcept -> void + { + std::ignore = context; + + auto& window = self.get(); + auto& canvas = window.canvas_; + + canvas.cursor_current_line.x = window.point_.x + x; + } + + auto set_cursor_position_y(const Context& context, const value_type y) noexcept -> void + { + std::ignore = context; + + auto& window = self.get(); + auto& canvas = window.canvas_; + + canvas.cursor_current_line.y = window.point_.y + y; + } + + auto set_cursor_position(const Context& context, const point_type& point) noexcept -> void + { + set_cursor_position_x(context, point.x); + set_cursor_position_y(context, point.y); + } + + auto set_scroll_position(const Context& context, const value_type y) noexcept -> void + { + std::ignore = context; + + auto& window = self.get(); + + window.scroll_next_y_ = y; + } + + // adjust scrolling position to center into the current cursor position + auto set_scroll_position_here(const Context& context) noexcept -> void + { + const auto& window = self.get(); + const auto& canvas = window.canvas_; + + const auto middle = window.point_.y + window.size_full_.height * .5f; + + auto y = canvas.cursor_current_line.y - window.scroll_y_ - window_padding(context).height; + + y -= middle; + if (not window.flag_.is()) + { + y -= titlebar_height(context); + } + + set_scroll_position(context, y); + } + // auto test_last_item(const Context& context, const rect_type& rect) noexcept -> void // { // auto& window = self.get(); @@ -288,7 +364,7 @@ namespace gal::prometheus::gui::internal // ----------------------------------- // FRAME - auto draw_widget_frame(const Context& context, const rect_type& rect, const color_type& color) noexcept -> void + auto draw_widget_frame(const Context& context, const rect_type& rect, const color_type color) noexcept -> void { const auto& theme = current_theme(context); @@ -308,7 +384,7 @@ namespace gal::prometheus::gui::internal } } - auto draw_widget_frame(const Context& context, const circle_type& circle, const color_type& color) noexcept -> void + auto draw_widget_frame(const Context& context, const circle_type& circle, const color_type color) noexcept -> void { const auto& theme = current_theme(context); @@ -327,6 +403,23 @@ namespace gal::prometheus::gui::internal window.draw_list_.circle({outer_point, outer_radius}, color_of(theme, ThemeCategory::BORDER)); } } + + auto draw_widget_frame(const Context& context, const point_type& a, const point_type& b, const point_type& c) noexcept -> void + { + const auto& theme = current_theme(context); + + auto& window = self.get(); + + if (window.flag_.is()) + { + const auto offset_a = a + extent_type{1.5f, 1.5f}; + const auto offset_b = b + extent_type{1.5f, 1.5f}; + const auto offset_c = c + extent_type{1.5f, 1.5f}; + + window.draw_list_.triangle_filled(offset_a, offset_b, offset_c, color_of(theme, ThemeCategory::BORDER_SHADOW)); + } + window.draw_list_.triangle_filled(a, b, c, color_of(theme, ThemeCategory::BORDER)); + } }; class Window::Anonymous final @@ -571,30 +664,32 @@ namespace gal::prometheus::gui::internal // note: when drawing multiple sliders, the widget id is based on the slider index, not the label text // note: to avoid different sliders getting the same id (since ids are based on index), the label text is pushed in here as the seed window.push_id(context, utf8_text); - for (const auto view = reference | std::views::enumerate; - auto [index, ref_value]: view) { - const auto id = id_maker.make_id(context, index); - - // x: total + offset * n - // y: total - const point_type frame_point + for (const auto view = reference | std::views::enumerate; + auto [index, ref_value]: view) { - total_frame_point.x + offset_x * static_cast(index), - total_frame_point.y - }; - const rect_type frame_rect{frame_point, each_frame_size}; + const auto id = id_maker.make_id(context, index); - // x: total + offset * n - // y: total - const point_type slider_point - { - total_slider_point.x + offset_x * static_cast(index), - total_slider_point.y - }; - const rect_type slider_rect{slider_point, each_slider_size}; + // x: total + offset * n + // y: total + const point_type frame_point + { + total_frame_point.x + offset_x * static_cast(index), + total_frame_point.y + }; + const rect_type frame_rect{frame_point, each_frame_size}; + + // x: total + offset * n + // y: total + const point_type slider_point + { + total_slider_point.x + offset_x * static_cast(index), + total_slider_point.y + }; + const rect_type slider_rect{slider_point, each_slider_size}; - value_changed |= do_draw_slider(id, ref_value, slider_rect, frame_rect); + value_changed |= do_draw_slider(id, ref_value, slider_rect, frame_rect); + } } window.pop_id(context); } @@ -617,6 +712,255 @@ namespace gal::prometheus::gui::internal return value_changed; } + + template + auto draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + auto& window = self.get(); + + if (window.skip_item_) + { + return false; + } + window.accessed_ = true; + + const auto& theme = current_theme(context); + const auto& font = current_font(context); + Drawer drawer{.self = window}; + const IdMaker id_maker{.self = window}; + + const auto font_size = drawer.font_size(context); + + const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); + + const auto last_item_width = window.canvas_.item_width.back(); + + const auto frame_point = window.canvas_.cursor_current_line; + const auto frame_size = extent_type{last_item_width, text_size.height} + theme.item_frame_padding * 2; + const rect_type frame_rect{frame_point, frame_size}; + + const auto drop_down_button_width = font_size + theme.item_frame_padding.width * 2; + const auto dropdown_button_point = frame_rect.right_top() - extent_type{drop_down_button_width, 0}; + const auto dropdown_button_size = extent_type{drop_down_button_width, drop_down_button_width}; + const rect_type dropdown_button_rect{dropdown_button_point, dropdown_button_size}; + + const auto selection_point = frame_point + theme.item_frame_padding; + const auto selection_size = frame_size - theme.item_frame_padding * 2; + // const rect_type selection_rect{selection_point, selection_size}; + + drawer.adjust_item_size(context, frame_size); + + // □ text + window.same_line(context, auto_size, theme.item_inner_spacing.width); + + // text + const auto text_point = window.canvas_.cursor_current_line + theme.item_frame_padding; + const rect_type text_rect{text_point, text_size}; + drawer.adjust_item_size(context, text_size); + + const rect_type total_rect{frame_point, text_rect.right_bottom()}; + // if (not drawer.is_visible_area(context, total_rect)) + // { + // // invisible + // return false; + // } + // drawer.test_last_item(context, total_rect); + if (not drawer.test_last_item_visible(context, total_rect)) + { + // invisible + return false; + } + + bool value_changed = false; + + const auto id = id_maker.make_id(context, utf8_text); + // note: Leave the frame inactive so that clicking on it again does not accidentally re-open the dropdown window again + const auto state = queue_mouse(context, id, frame_rect); + + // draw frame + drawer.draw_widget_frame(context, frame_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + + // draw dropdown button frame + { + if (state & MouseState::HOVERED) + { + drawer.draw_widget_frame(context, dropdown_button_rect, color_of(theme, ThemeCategory::BUTTON_HOVERED)); + } + else + { + drawer.draw_widget_frame(context, dropdown_button_rect, color_of(theme, ThemeCategory::BUTTON)); + } + + const auto r = font_size * .5f; + const auto center = dropdown_button_rect.center() - extent_type{0, r * .25f}; + + const auto a = center + extent_type{0, 1} * r; + const auto b = center + extent_type{-.667f, -.5f} * r; + const auto c = center + extent_type{.667f, -.5f} * r; + + drawer.draw_widget_frame(context, a, b, c); + } + + // fixme: leave empty or default? + if (selected >= selections.size()) + { + window.draw_list_.text( + font, + font_size, + selection_point, + color_of(theme, ThemeCategory::TEXT), + "", + selection_size.width + ); + } + else + { + const auto& string = selections[selected]; + + window.draw_list_.text( + font, + font_size, + selection_point, + color_of(theme, ThemeCategory::TEXT), + string, + selection_size.width + ); + } + + // draw text + window.draw_list_.text( + font, + font_size, + text_rect.left_top(), + color_of(theme, ThemeCategory::TEXT), + utf8_text, + text_rect.width() + ); + + // draw dropdown menu + bool menu_toggled = false; + if (state & MouseState::PRESSED) + { + menu_toggled = true; + + // If the combo is already open, click again to close the combo, otherwise open the combo + if (is_combo_activated(context, id)) + { + mark_combo_dead(context); + } + else + { + mark_combo_alive(context, id); + } + } + + if (is_combo_activated(context, id)) + { + // The contents of the dropdown window should not affect the layout of the parent window, so after drawing the dropdown window, we reset the parent window canvas cursor + const auto backup_position = drawer.cursor_position(context); + { + // const auto dropdown_offset_x = theme.item_inner_spacing.width; + constexpr auto dropdown_offset_x = value_type{0}; + const auto dropdown_height = + (text_size.height + theme.item_spacing.height) * static_cast(std::ranges::min(selections.size(), show_selection_count)) + + theme.window_padding.height; + + constexpr std::string_view combo_window_name{"@WINDOW::COMBO@"}; + const WindowFlag combo_window_flag + { + window.flag_.is() ? gui::WindowFlag::BORDERED : gui::WindowFlag::NONE, + WindowInternalFlag::CATEGORY_COMBO + }; + + const auto dropdown_point = point_type{frame_point.x + dropdown_offset_x, frame_point.y + frame_size.height}; + const auto dropdown_size = extent_type{frame_size.width - dropdown_offset_x, dropdown_height}; + + // begin dropdown window (with background) + internal::begin_child_window(context, combo_window_name, 1, dropdown_size, false, combo_window_flag); + { + auto& combo_window = *window.children_this_frame_.back(); + auto& combo_window_canvas = combo_window.canvas_; + IdMaker combo_window_id_maker{.self = combo_window}; + Drawer combo_window_drawer{.self = combo_window}; + + const auto item_height = font_size; + + // Close the dropdown if the user interacts with another widget (unless the widget is the dropdown window's scrollbar) + bool combo_item_active = false; + combo_item_active |= is_widget_activated(context, combo_window.id_of_scrollbar(context)); + + for (const auto view = selections | std::views::enumerate; + const auto [index, selection]: view) + { + const auto item_selected = std::cmp_equal(index, selected); + const auto item_id = combo_window_id_maker.make_id(context, selection); + + const auto item_point = point_type{dropdown_point.x, combo_window_canvas.cursor_current_line.y - theme.item_spacing.height / 2}; + const auto item_size = extent_type{dropdown_size.width, item_height + theme.item_spacing.height}; + const rect_type item_rect{item_point, item_size}; + + auto item_state = internal::test_mouse(context, item_id, item_rect); + combo_item_active |= is_widget_activated(context, item_id); + + if (item_state & MouseState::HOVERED or item_selected) + { + const auto color = [&]noexcept -> Theme::color_type + { + if (item_state & MouseState::HOVERED) + { + if (item_state & MouseState::KEEPING) + { + return color_of(theme, ThemeCategory::COMBO_ITEM_ACTIVATED); + } + return color_of(theme, ThemeCategory::COMBO_ITEM_HOVERED); + } + + return color_of(theme, ThemeCategory::COMBO_ITEM); + }(); + + combo_window_drawer.draw_widget_frame(context, item_rect, color); + } + + const auto& selection_text = selections[index]; + combo_window.draw_text(context, selection_text); + + // Scroll to the selected item when opening the window + if (item_selected and menu_toggled) + { + combo_window_drawer.set_scroll_position_here(context); + } + + // Close the dropdown window after selecting any item + if (item_state & MouseState::PRESSED) + { + mark_widget_dead(context); + mark_combo_dead(context); + + value_changed = true; + selected = index; + + break; + } + } + + if (not combo_item_active and is_any_widget_activated(context)) + { + mark_combo_dead(context); + } + } + gui::end_child_window(context); + } + window.canvas_.cursor_current_line = backup_position; + } + + return value_changed; + } }; Window::Window( @@ -697,7 +1041,7 @@ namespace gal::prometheus::gui::internal auto Window::begin_window( Context& context, Window* parent, - value_type background_fill_alpha, + alpha_type background_fill_alpha, const extent_type& size ) noexcept -> bool { @@ -1394,7 +1738,14 @@ namespace gal::prometheus::gui::internal // window_data.root = nullptr; } - auto Window::begin_child_window(Context& context, std::string_view name, extent_type size, const bool border, WindowFlag flag) noexcept -> void + auto Window::begin_child_window( + Context& context, + const std::string_view name, + const alpha_type background_fill_alpha, + extent_type size, + const bool border, + WindowFlag flag + ) noexcept -> void { const auto& theme = current_theme(context); @@ -1423,7 +1774,7 @@ namespace gal::prometheus::gui::internal } const auto child_window_name = std::format("{}.{}", name_, name); - internal::begin_window(context, child_window_name, size, 0, flag); + internal::begin_window(context, child_window_name, size, background_fill_alpha, flag); if (border and not flag_.is()) { @@ -1607,7 +1958,6 @@ namespace gal::prometheus::gui::internal const auto font_size = drawer.font_size(context); - const auto id = id_maker.make_id(context, utf8_text); const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); if (size.width <= 0) @@ -1636,6 +1986,7 @@ namespace gal::prometheus::gui::internal return false; } + const auto id = id_maker.make_id(context, utf8_text); const auto state = test_mouse(context, id, button_rect, repeat_when_held); color_type button_color = color_of(theme, ThemeCategory::BUTTON); @@ -1707,7 +2058,6 @@ namespace gal::prometheus::gui::internal const auto font_size = drawer.font_size(context); - const auto id = id_maker.make_id(context, utf8_text); const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); const auto button_point = canvas_.cursor_current_line; @@ -1727,6 +2077,7 @@ namespace gal::prometheus::gui::internal return false; } + const auto id = id_maker.make_id(context, utf8_text); const auto state = test_mouse(context, id, button_rect, repeat_when_held); color_type button_color = color_of(theme, ThemeCategory::BUTTON); @@ -1774,7 +2125,6 @@ namespace gal::prometheus::gui::internal const auto font_size = drawer.font_size(context); - const auto id = id_maker.make_id(context, utf8_text); const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); // ○ + text @@ -1807,6 +2157,7 @@ namespace gal::prometheus::gui::internal return false; } + const auto id = id_maker.make_id(context, utf8_text); // fixme: test check_rect or total_rect? const auto state = test_mouse(context, id, check_rect, false); @@ -1856,7 +2207,6 @@ namespace gal::prometheus::gui::internal const auto font_size = drawer.font_size(context); - const auto id = id_maker.make_id(context, utf8_text); const auto text_size = internal::text_size(font, utf8_text, font_size, Font::no_auto_wrap); // □ + text @@ -1889,6 +2239,7 @@ namespace gal::prometheus::gui::internal return false; } + const auto id = id_maker.make_id(context, utf8_text); // fixme: test check_rect or total_rect? const auto state = test_mouse(context, id, check_rect, false); @@ -1958,6 +2309,45 @@ namespace gal::prometheus::gui::internal return anonymous.draw_slider(context, utf8_text, references, min, max, decimal_precision, power); } + auto Window::draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + Anonymous anonymous{.self = *this}; + + return anonymous.draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + + auto Window::draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + Anonymous anonymous{.self = *this}; + + return anonymous.draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + + auto Window::draw_combo( + Context& context, + const std::string_view utf8_text, + const std::span selections, + std::size_t& selected, + const std::size_t show_selection_count + ) noexcept -> bool + { + Anonymous anonymous{.self = *this}; + + return anonymous.draw_combo(context, utf8_text, selections, selected, show_selection_count); + } + // ReSharper disable once CppParameterMayBeConstPtrOrRef auto Window::same_line(Context& context, const value_type column_width, value_type spacing_width) noexcept -> void { diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index a95f5b7..c3e3fef 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -16,6 +16,7 @@ namespace gal::prometheus::gui::internal { public: using value_type = extent_type::value_type; + using alpha_type = Theme::alpha_type; // <= 0 constexpr static auto auto_size = value_type{-.999999f}; @@ -166,7 +167,7 @@ namespace gal::prometheus::gui::internal [[nodiscard]] auto begin_window( Context& context, Window* parent, - value_type background_fill_alpha, + alpha_type background_fill_alpha, const extent_type& size ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; @@ -174,6 +175,7 @@ namespace gal::prometheus::gui::internal auto begin_child_window( Context& context, std::string_view name, + alpha_type background_fill_alpha, extent_type size, bool border, WindowFlag flag @@ -215,6 +217,30 @@ namespace gal::prometheus::gui::internal float power ) noexcept -> bool; + auto draw_combo( + Context& context, + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count + ) noexcept -> bool; + + auto draw_combo( + Context& context, + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count + ) noexcept -> bool; + + auto draw_combo( + Context& context, + std::string_view utf8_text, + std::span selections, + std::size_t& selected, + std::size_t show_selection_count + ) noexcept -> bool; + // ----------------------------------- // WIDGET LAYOUT From e9572e56ca61e32c08360d762ee7847626e718da Mon Sep 17 00:00:00 2001 From: life4gal Date: Mon, 21 Apr 2025 15:46:08 +0800 Subject: [PATCH 20/54] =?UTF-8?q?`refactor`:=20`func(context,=20...)`=20?= =?UTF-8?q?=3D>=20`context.func(...)`.=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 1 + src/gui/gui.hpp | 97 +-- src/gui/internal/common.hpp | 3 + src/gui/internal/context.cpp | 1367 ++++++++++++++++---------------- src/gui/internal/context.hpp | 257 ++++-- src/gui/internal/draw_list.cpp | 28 +- src/gui/internal/font.cpp | 44 +- src/gui/internal/font.hpp | 2 + src/gui/internal/gui.inl | 104 +++ src/gui/internal/mouse.cpp | 21 +- src/gui/internal/window.cpp | 293 +++---- src/gui/internal/window.hpp | 9 +- 12 files changed, 1200 insertions(+), 1026 deletions(-) create mode 100644 src/gui/internal/gui.inl diff --git a/scripts/library.cmake b/scripts/library.cmake index cd7c2a7..1a3aba8 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -362,6 +362,7 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/gui/gui.hpp + ${PROJECT_SOURCE_DIR}/src/gui/internal/gui.inl ${PROJECT_SOURCE_DIR}/src/gui/internal/common.hpp ${PROJECT_SOURCE_DIR}/src/gui/internal/font.hpp ${PROJECT_SOURCE_DIR}/src/gui/internal/draw_list.hpp diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp index bb78d5b..5b45980 100644 --- a/src/gui/gui.hpp +++ b/src/gui/gui.hpp @@ -160,7 +160,7 @@ namespace gal::prometheus using colors_type = std::array; // < 0 - constexpr static alpha_type window_fill_alpha_not_set{-.99999f}; + constexpr static alpha_type window_background_fill_alpha_not_set{-.99999f}; //----------------- // WINDOW @@ -433,7 +433,7 @@ namespace gal::prometheus Context& context, std::string_view name, const extent_type& size = {0, 0}, - Theme::alpha_type fill_alpha = Theme::window_fill_alpha_not_set, + Theme::alpha_type fill_alpha = Theme::window_background_fill_alpha_not_set, WindowFlag flag = WindowFlag::NONE ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; @@ -483,17 +483,8 @@ namespace gal::prometheus * @return Whether the radio button is pressed (being pressed does not mean that the state is switched, it can also be a repeat selection) */ template - auto draw_radio_button(Context& context, std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> bool - { - const auto prev_selected = reference == identifier; - const auto pressed = gui::draw_radio_button(context, utf8_text, prev_selected); + auto draw_radio_button(Context& context, std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> bool; - if (pressed) - { - reference = identifier; - } - return pressed; - } /** * @brief Draw a checkbox, its initial state is required @@ -506,30 +497,13 @@ namespace gal::prometheus * @return Current state of the checkbox (checked or unchecked) */ template - static auto draw_checkbox( + auto draw_checkbox( Context& context, - const std::string_view utf8_text, + std::string_view utf8_text, T& reference, const std::type_identity_t& checked_identifier, const std::type_identity_t& unchecked_identifier - ) noexcept -> bool - { - const auto prev_checked = reference == checked_identifier; - const auto checked = gui::draw_checkbox(context, utf8_text, prev_checked); - - if (checked != prev_checked) - { - if (prev_checked) - { - reference = unchecked_identifier; - } - else - { - reference = checked_identifier; - } - } - return checked; - } + ) noexcept -> bool; auto draw_slider( Context& context, @@ -545,57 +519,24 @@ namespace gal::prometheus requires std::is_arithmetic_v auto draw_slider( Context& context, - const std::string_view utf8_text, + std::string_view utf8_text, T& reference, - const ValueType min, - const std::type_identity_t max, - const std::uint32_t decimal_precision = 3, - const float power = 1 - ) noexcept -> bool - { - auto v = static_cast(reference); - - const auto result = gui::draw_slider( - context, - utf8_text, - v, - static_cast(min), - static_cast(max), - decimal_precision, - power - ); - reference = v; - - return result; - } + ValueType min, + std::type_identity_t max, + std::uint32_t decimal_precision = 3, + float power = 1 + ) noexcept -> bool; template requires std::is_arithmetic_v auto draw_slider( Context& context, T&& object, - const ValueType min, - const std::type_identity_t max, - const std::uint32_t decimal_precision = 3, - const float power = 1 - ) noexcept -> bool - { - auto& ref = meta::member_of_name(std::forward(object)); - auto v = static_cast(ref); - - const auto result = gui::draw_slider( - context, - MemberName, - v, - static_cast(min), - static_cast(max), - decimal_precision, - power - ); - ref = v; - - return result; - } + ValueType min, + std::type_identity_t max, + std::uint32_t decimal_precision = 3, + float power = 1 + ) noexcept -> bool; auto draw_slider_n( Context& context, @@ -741,7 +682,7 @@ namespace gal::prometheus auto begin_window( std::string_view name, const extent_type& size = {0, 0}, - Theme::value_type fill_alpha = Theme::window_fill_alpha_not_set, + Theme::value_type fill_alpha = Theme::window_background_fill_alpha_not_set, WindowFlag flag = WindowFlag::NONE ) noexcept -> bool; auto end_window() noexcept -> void; @@ -924,3 +865,5 @@ namespace gal::prometheus auto show_theme_editor() noexcept -> bool; } } + +#include diff --git a/src/gui/internal/common.hpp b/src/gui/internal/common.hpp index 52861c3..625c69b 100644 --- a/src/gui/internal/common.hpp +++ b/src/gui/internal/common.hpp @@ -29,6 +29,9 @@ namespace gal::prometheus using widget_id_type = functional::hash_result_type; constexpr auto invalid_widget_id = std::numeric_limits::max(); + class DrawList; + using draw_lists_type = std::vector>; + enum class WindowInternalFlag : std::uint16_t { NONE = 0, diff --git a/src/gui/internal/context.cpp b/src/gui/internal/context.cpp index 2aff7d7..86324f5 100644 --- a/src/gui/internal/context.cpp +++ b/src/gui/internal/context.cpp @@ -19,104 +19,513 @@ namespace using namespace gal::prometheus; using namespace gui; - /** - * @brief Finds the window with the given name - * @return Returns the corresponding window if it exists, otherwise it returns a null pointer - */ - [[nodiscard]] auto find_window(Context& context, const std::string_view name) noexcept -> internal::Window* + Context* g_context = nullptr; +} + +namespace gal::prometheus::gui +{ + Context::Context() noexcept + : + initialized_{false}, + draw_list_flag_{DrawListFlag::NONE}, + draw_list_shared_data_{}, + font_{}, + theme_{}, + theme_mod_stack_{}, + io_{}, + time_total_{0}, + frame_count_{0}, + frame_count_rendered_{0}, + mouse_{}, + window_default_spawn_position_{50, 50}, + window_hive_{}, + window_root_list_{}, + window_current_stack_{}, + window_hovered_{nullptr}, + window_hovered_root_{nullptr}, + window_focused_{nullptr}, + widget_hovered_{internal::invalid_widget_id}, + widget_activated_{internal::invalid_widget_id}, + widget_activated_previous_frame_{internal::invalid_widget_id}, + widget_activated_still_alive_{false}, + widget_activated_combo_id_{internal::invalid_widget_id}, + draw_lists_{} {} + + auto Context::create() noexcept -> Context* + { + auto* p = new Context{}; + + // todo + p->initialized_ = true; + + return p; + } + + auto Context::destroy(Context& context) noexcept -> void { - const auto it = std::ranges::find_if( - context.window_hive, - [name](const auto& window) noexcept -> bool + delete std::addressof(context); + } + + auto Context::set_default_draw_list_flag(const DrawListFlag flag) noexcept -> void + { + draw_list_flag_ = flag; + } + + auto Context::current_draw_list_flag() const noexcept -> DrawListFlag + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + // return draw_list_flag_stack[draw_list_flag_current]; + return draw_list_flag_; + } + + // auto Context::push_draw_list_flag(const DrawListFlag flag) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // draw_list_flag_current == Context::stack_pointer_default or + // draw_list_flag_current < Context::draw_list_flag_stack_size, + // "DrawListFlag stack overflow" + // ); + // + // static_assert( + // static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == + // static_cast<::Context::stack_pointer_type>(0) + // ); + // + // draw_list_flag_current += 1; + // draw_list_flag_stack[draw_list_flag_current] = flag; + // } + // + // auto Context::pop_draw_list_flag() noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // draw_list_flag_current != Context::stack_pointer_default, + // "Unable to popup the default DrawListFlag!" + // ); + // + // static_assert( + // static_cast(static_cast(0) - 1) == + // Context::stack_pointer_default + // ); + // + // draw_list_flag_stack[draw_list_flag_current] = DrawListFlag::NONE; + // draw_list_flag_current -= 1; + // } + + auto Context::current_draw_list_shared_data() const noexcept -> const internal::DrawListSharedData& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + // return draw_list_shared_data_stack[draw_list_shared_data_current]; + return draw_list_shared_data_; + } + + // auto Context::push_draw_list_shared_data(const DrawListSharedData& shared_data) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // draw_list_shared_data_current == Context::stack_pointer_default or + // draw_list_shared_data_current < Context::draw_list_shared_data_stack_size, + // "DrawListSharedData stack overflow" + // ); + // + // static_assert( + // static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == + // static_cast<::Context::stack_pointer_type>(0) + // ); + // + // draw_list_shared_data_current += 1; + // draw_list_shared_data_stack[draw_list_shared_data_current] = shared_data; + // } + // + // auto Context::pop_draw_list_shared_data() noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized); + // + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( + // draw_list_shared_data_current != Context::stack_pointer_default, + // "Unable to popup the default DrawListSharedData!" + // ); + // + // static_assert( + // static_cast(static_cast(0) - 1) == + // Context::stack_pointer_default + // ); + // + // draw_list_shared_data_stack[draw_list_shared_data_current] = {}; + // draw_list_shared_data_current -= 1; + // } + + auto Context::set_default_font(const FontOption& option) noexcept -> Texture + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + // if (context.font_current == Context::stack_pointer_default) + // { + // return push_font(context, option); + // } + // + // auto font = memory::make_unique(); + // auto texture = do_load_font(option, *font); + // + // context.font_stack[0] = std::move(font); + // + // return texture; + return font_.load(option); + } + + auto Context::current_font() const noexcept -> const internal::Font& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + // const auto& font = *font_stack[font_current]; + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font.loaded(), "Invalid font!"); + // + // return font; + return font_; + } + + auto Context::set_default_theme(const Theme& default_theme) noexcept -> void + { + theme_ = default_theme; + } + + auto Context::current_theme() const noexcept -> const Theme& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + return theme_; + } + + auto Context::push_theme(const ThemeCategory category, const Theme::color_type new_color) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + const auto old_color = color_of(category); + theme_mod_stack_.emplace_back(category, old_color); + + theme_.colors[static_cast(category)] = new_color; + } + + auto Context::pop_theme() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not theme_mod_stack_.empty()); + + const auto [category, old_color] = theme_mod_stack_.back(); + theme_mod_stack_.pop_back(); + theme_.colors[static_cast(category)] = old_color; + } + + auto Context::color_of(const ThemeCategory category, const Theme::value_type factor) const noexcept -> Theme::color_type + { + const auto& theme = current_theme(); + + return color_of(theme, category, factor); + } + + auto Context::color_of(const Theme& theme, const ThemeCategory category, const Theme::value_type factor) const noexcept -> Theme::color_type + { + std::ignore = this; + + const auto index = static_cast(category); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < theme_category_count); + auto color = theme.colors[static_cast(category)]; + + color.alpha = static_cast(theme.alpha * 255 * factor); + + return color; + } + + auto Context::io() noexcept -> IO& + { + return io_; + } + + auto Context::io() const noexcept -> const IO& + { + return io_; + } + + auto Context::mouse() const noexcept -> const internal::Mouse& + { + return mouse_; + } + + auto Context::test_mouse(const widget_id_type id, const rect_type& area, const bool repeat) noexcept -> std::underlying_type_t + { + const auto& window = current_window(); + const auto hovered = + window_hovered_root_ == std::addressof(window.root()) and + widget_hovered_ == internal::invalid_widget_id and + window.is_hovered(*this, area); + + auto state = std::to_underlying(MouseState::NONE); + if (hovered) + { + state |= MouseState::HOVERED; + + // hovering widget + widget_hovered_ = id; + + if (mouse_.is_clicked(*this, MouseKey::LEFT, false)) { - return window->name() == name; + // select widget + widget_activated_ = id; } - ); + else if ( + repeat and + widget_activated_ != internal::invalid_widget_id and + mouse_.is_clicked(*this, MouseKey::LEFT, true) + ) + { + state |= MouseState::PRESSED; + } + } - if (it != context.window_hive.end()) + if (widget_activated_ == id) { - return it->get(); + if (mouse_.is_down(*this, MouseKey::LEFT)) + { + // select current widget, keep the left mouse button pressed + state |= MouseState::KEEPING; + } + else + { + if (hovered) + { + // select current widget, release the left mouse button on the widget + state |= MouseState::PRESSED; + } + else + { + // select current widget, did not release the left mouse button on the widget + // do nothing + } + + // the widget is no longer selected + widget_activated_ = internal::invalid_widget_id; + } } - return nullptr; + return state; } - /** - * @brief Find the last possible root (not child) window from the available windows in this frame - * @return If it is not found (if and only if there are no currently available windows, i.e. the window to be created is the first one), then the null pointer is returned - */ - [[nodiscard]] auto find_root_window(Context& context) noexcept -> internal::Window* + auto Context::queue_mouse(const widget_id_type id, const rect_type& area) noexcept -> std::underlying_type_t { - const auto view = context.window_current_stack | std::views::reverse; + const auto& window = current_window(); + const auto hovered = + window_hovered_root_ == std::addressof(window.root()) and + widget_hovered_ == internal::invalid_widget_id and + window.is_hovered(*this, area); + + auto state = std::to_underlying(MouseState::NONE); + if (hovered) + { + state |= MouseState::HOVERED; + // hovering widget + widget_hovered_ = id; + + if (mouse_.is_clicked(*this, MouseKey::LEFT, false)) + { + state |= MouseState::PRESSED; + } + } + + return state; + } + + auto Context::find_window(std::string_view name) noexcept -> window_type* + { const auto it = std::ranges::find_if( - view, - [](const auto& window) noexcept -> bool + window_hive_, + [name](const auto& window) noexcept -> bool { - return not window->flag().template is(); + return window->name() == name; } ); - if (it == std::ranges::end(view)) + if (it != window_hive_.end()) { - return nullptr; + return it.operator*().get(); } - return it.operator*(); + return nullptr; } - [[nodiscard]] auto find_or_create_window(Context& context, const std::string_view name, const extent_type& size, const internal::WindowFlag flag) noexcept -> internal::Window& + auto Context::find_or_create_window(const std::string_view name, const extent_type& size, const internal::WindowFlag flag) noexcept -> window_type& { [[maybe_unused]] const auto is_child_window = flag.is(); if (is_child_window) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window_current_stack_.empty()); } - if (auto* window = find_window(context, name); + if (auto* window = find_window(name); window == nullptr) { - auto* root = is_child_window ? find_root_window(context) : nullptr; + auto* root = is_child_window ? current_root_window() : nullptr; + if (is_child_window and root == nullptr) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window_current_stack_.empty()); + } // fixme: load cached settings? - auto temp = memory::make_unique( + auto temp = std::make_unique( name, flag, - context.window_default_spawn_position, + window_default_spawn_position_, size, root ); - auto& ref = context.window_hive.emplace_back(std::move(temp)); + auto& ref = window_hive_.emplace_back(std::move(temp)); if (root == nullptr) { // The current window is the root window - context.window_root_list.emplace_back(ref.get()); + window_root_list_.emplace_back(ref.get()); } - context.window_current_stack.emplace_back(ref.get()); + window_current_stack_.emplace_back(ref.get()); } else { - // todo: Just need to reset the flag? - window->reset(flag); + window->reset(flag, size); + + window_current_stack_.emplace_back(window); + } + + return *window_current_stack_.back(); + } + + auto Context::current_window() const noexcept -> window_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window_current_stack_.empty()); + + const auto index = window_current_stack_.size() - 1; + return *window_current_stack_[index]; + } + + auto Context::current_parent_window() const noexcept -> window_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window_current_stack_.size() > 1); + + const auto index = window_current_stack_.size() - 2; + return *window_current_stack_[index]; + } + + auto Context::current_root_window() noexcept -> window_type* + { + const auto view = window_current_stack_ | std::views::reverse; + + const auto it = std::ranges::find_if( + view, + [](const auto& window) noexcept -> bool + { + const auto flag = window->flag(); + return not flag.template is(); + } + ); + + if (it == std::ranges::end(view)) + { + return nullptr; + } + + return it.operator*(); + } + + auto Context::set_next_window_point(const point_type& point) noexcept -> void + { + // todo + window_default_spawn_position_ = point; + } + + auto Context::begin_window( + const std::string_view name, + const extent_type& size, + Theme::value_type background_fill_alpha, + const internal::WindowFlag flag + ) noexcept -> bool + { + const auto is_child_window = flag.is(); + auto* parent = is_child_window ? std::addressof(current_window()) : nullptr; + + auto& window = find_or_create_window(name, size, flag); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window_current_stack_.back() == std::addressof(window)); + + // alpha + static_assert(window_fill_alpha_not_set < 0); + if (background_fill_alpha < 0) + { + background_fill_alpha = theme_.window_background_alpha; + } + + return window.begin_window(*this, parent, background_fill_alpha); + } + + auto Context::end_window() noexcept -> void + { + auto& window = current_window(); - context.window_current_stack.emplace_back(window); + window.end_window(*this); + + // Select window for move/focus when we're done with all our widgets (we only consider non-children windows here) + if (const auto rect = window.rect(); + widget_activated_ == internal::invalid_widget_id and + widget_hovered_ == internal::invalid_widget_id and + window_hovered_root_ == std::addressof(window) and + window.is_hovered(*this, rect) and + mouse_.is_clicked(*this, MouseKey::LEFT) + ) + { + widget_activated_ = window.id_of_move(*this); } - return *context.window_current_stack.back(); + window_current_stack_.pop_back(); + } + + auto Context::begin_child_window( + const std::string_view name, + const extent_type& size, + const Theme::alpha_type background_fill_alpha, + const internal::WindowFlag flag, + const bool border + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window_current_stack_.empty()); + auto& parent = current_window(); + + // todo + parent.begin_child_window(*this, name, background_fill_alpha, size, border, flag); + } + + auto Context::end_child_window() noexcept -> void + { + auto& parent = current_parent_window(); + auto& child = current_window(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(child.flag().is()); + + parent.end_child_window(*this, child); } - /** - * @brief Find the first (more top-level) window that contains the location of the given point - */ - template - [[nodiscard]] auto find_hovered_window(Context& context, const point_type& position) noexcept -> internal::Window* + auto Context::is_window_hovered(const window_type& window) const noexcept -> bool { - for (const auto view = context.window_root_list | std::views::reverse; + return window_hovered_ == std::addressof(window); + } + + auto Context::find_hovered_window(const point_type& position, const bool excludes_children) noexcept -> window_type* + { + for (const auto view = window_root_list_ | std::views::reverse; auto* root: view) { - if (auto* window = root->find_hovered_window(position, ExcludesChildren); + if (auto* window = root->find_hovered_window(position, excludes_children); window != nullptr) { return window; @@ -126,126 +535,138 @@ namespace return nullptr; } - Context* g_context = nullptr; -} + auto Context::focus_window(window_type& window) noexcept -> void + { + if (window_focused_ == std::addressof(window)) + { + return; + } -namespace gal::prometheus::gui -{ - auto create_context() noexcept -> Context* + window_focused_ = std::addressof(window); + + auto& root = window.root(); + + const auto it = std::ranges::find(window_root_list_, std::addressof(root)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != window_root_list_.end()); + + if (it != window_root_list_.end() - 1) + { + // The focused window is drawn last + const auto p = *it; + window_root_list_.erase(it); + window_root_list_.push_back(p); + } + + // todo: reorder child window? + } + + auto Context::is_widget_hovered(const widget_id_type id) const noexcept -> bool { - auto* p = new ::Context{ - .initialized = false, - .draw_list_flag = DrawListFlag::NONE, - .draw_list_shared_data = {}, - .font = {}, - .theme = {}, - .theme_mod_stack = {}, - .io = {}, - .time_total = 0, - .frame_count = 0, - .frame_count_rendered = 0, - .mouse = {}, - .window_default_spawn_position = {50, 50}, - .window_hive = {}, - .window_root_list = {}, - .window_current_stack = {}, - .window_hovered = nullptr, - .window_hovered_root = nullptr, - .window_focused = nullptr, - .widget_hovered = internal::invalid_widget_id, - .widget_activated = internal::invalid_widget_id, - .widget_activated_previous_frame = internal::invalid_widget_id, - .widget_activated_still_alive = false, - .draw_lists = {} - }; + return widget_hovered_ == id; + } - // todo - p->initialized = true; + auto Context::is_any_widget_hovered() const noexcept -> bool + { + return widget_hovered_ != internal::invalid_widget_id; + } - return p; + auto Context::is_widget_activated(const widget_id_type id) const noexcept -> bool + { + return widget_activated_ == id; } - auto destroy_context(Context& context) noexcept -> void + auto Context::is_any_widget_activated() const noexcept -> bool { - delete std::addressof(context); + return widget_activated_ != internal::invalid_widget_id; } - auto destroy_context(Context* context) noexcept -> void + auto Context::mark_widget_alive(const widget_id_type id) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context != nullptr); - destroy_context(*context); + if (is_widget_activated(id)) + { + widget_activated_still_alive_ = true; + + return true; + } + + return false; } - auto set_default_theme(Context& context, const Theme& theme) noexcept -> void + auto Context::mark_widget_dead(const widget_id_type id) noexcept -> void { - context.theme = theme; + widget_activated_ = id; } - auto push_theme(Context& context, const ThemeCategory category, const Theme::color_type new_color) noexcept -> void + auto Context::mark_combo_alive(const widget_id_type id) noexcept -> void { - internal::push_theme(context, category, new_color); + widget_activated_combo_id_ = id; } - auto pop_theme(Context& context) noexcept -> void + auto Context::mark_combo_dead(const widget_id_type id) noexcept -> void { - internal::pop_theme(context); + widget_activated_combo_id_ = id; } - auto get_io(Context& context) noexcept -> IO& + auto Context::is_combo_activated(const widget_id_type id) const noexcept -> bool { - return context.io; + return widget_activated_combo_id_ == id; } - auto set_default_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void + auto Context::current_time() const noexcept -> time_type { - context.draw_list_flag = flag; + return time_total_; } - auto new_frame(Context& context) noexcept -> void + auto Context::current_frame() const noexcept -> internal::frame_count_type { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.io.display_size.width > 0 and context.io.display_size.height > 0); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.io.delta_time > 0); + return frame_count_; + } - context.time_total += context.io.delta_time; - context.frame_count += 1; + auto Context::new_frame() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(io_.display_size.width > 0 and io_.display_size.height > 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(io_.delta_time > 0); + + time_total_ += io_.delta_time; + frame_count_ += 1; // Update mouse { - context.mouse.tick(context); + mouse_.tick(*this); } - const auto mouse_position = context.mouse.position_current; + const auto mouse_position = mouse_.position_current; // Update widget { // Clear reference to active widget if the widget isn't alive anymore - context.widget_hovered = internal::invalid_widget_id; + widget_hovered_ = internal::invalid_widget_id; if ( - not context.widget_activated_still_alive and - context.widget_activated_previous_frame == context.widget_activated and - context.widget_activated != internal::invalid_widget_id + not widget_activated_still_alive_ and + widget_activated_previous_frame_ == widget_activated_ and + widget_activated_ != internal::invalid_widget_id ) { - context.widget_activated = internal::invalid_widget_id; + widget_activated_ = internal::invalid_widget_id; } - context.widget_activated_previous_frame = context.widget_activated; - context.widget_activated_still_alive = false; + widget_activated_previous_frame_ = widget_activated_; + widget_activated_still_alive_ = false; } // Update window { - context.window_hovered = find_hovered_window(context, mouse_position); - context.window_hovered_root = find_hovered_window(context, mouse_position); + window_hovered_ = find_hovered_window(mouse_position, false); + window_hovered_root_ = find_hovered_window(mouse_position, true); - if (context.window_hovered != nullptr) + if (window_hovered_ != nullptr) { - context.window_hovered->handle_inputs(context); + window_hovered_->handle_inputs(*this); } // Mark all windows as not visible std::ranges::for_each( - context.window_root_list, + window_root_list_, [](auto* window) noexcept -> void { window->hide(); @@ -254,79 +675,177 @@ namespace gal::prometheus::gui // No window should be open at the beginning of the frame // But in order to allow the user to call `new_frame` multiple times without calling `render`, we are doing an explicit clear - context.window_current_stack.clear(); + window_current_stack_.clear(); } } - // ReSharper disable once CppParameterMayBeConstPtrOrRef - auto end_frame(Context& context) noexcept -> void + auto Context::end_frame() noexcept -> void { - std::ignore = context; + std::ignore = this; } - auto render(Context& context) noexcept -> void + auto Context::render() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); - const auto& theme = internal::current_theme(context); + const auto& theme = current_theme(); - const auto is_first_render_this_frame = context.frame_count_rendered != context.frame_count; - context.frame_count_rendered = context.frame_count; + const auto is_first_render_this_frame = frame_count_rendered_ != frame_count_; + frame_count_rendered_ = frame_count_; if (is_first_render_this_frame) { // clear all data for new frame - context.io.delta_time = -1; - // context.io.mouse_position = {0, 0}; - context.io.mouse_wheel = 0; - // context.io.mouse_button_state.state.fill(false); + io_.delta_time = -1; + // io.mouse_position = {0, 0}; + io_.mouse_wheel = 0; + // io.mouse_button_state.state.fill(false); } - context.draw_lists.clear(); + draw_lists_.clear(); if (theme.alpha > .0f) { // gather windows to render std::ranges::for_each( - context.window_root_list, - [&context](auto* window) noexcept -> void + window_root_list_, + [this](auto* window) noexcept -> void { - window->render(context); + window->render(*this, draw_lists_); } ); } } - auto get_draw_data(Context& context) noexcept -> std::vector + auto Context::get_draw_data() noexcept -> std::vector + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(initialized_); + + std::vector data; + data.reserve(draw_lists_.size()); + + std::ranges::transform( + draw_lists_, + std::back_inserter(data), + [](const auto& draw_list_ref) noexcept -> DrawData + { + auto& draw_list = draw_list_ref.get(); + + return + { + .vertex_list = draw_list.vertex_list(), + .index_list = draw_list.index_list(), + .command_list = draw_list.command_list() + }; + } + ); + + return data; + } + + auto Context::show_theme_editor() noexcept -> bool + { + auto& context = get_current_context(); + auto& theme = context.theme_; + + const auto window_closed = gui::begin_window(context, "ThemeEditor"); + + draw_slider<"window_background_alpha">(context, theme, 0, 1); + draw_slider<"window_corner_rounding">(context, theme, 0, 24); + draw_slider<"window_min_size.width">(context, theme, 64, 640); + draw_slider<"window_min_size.height">(context, theme, 48, 480); + draw_slider<"window_resize_grip_size.width">(context, theme, 10, 50); + draw_slider<"window_resize_grip_size.height">(context, theme, 10, 50); + draw_slider<"window_padding.width">(context, theme, 2, 20); + draw_slider<"window_padding.height">(context, theme, 2, 20); + draw_slider<"window_auto_fit_padding.width">(context, theme, 2, 20); + draw_slider<"window_auto_fit_padding.height">(context, theme, 2, 20); + draw_slider<"window_vertical_scrollbar_width">(context, theme, 6, 25); + draw_slider<"item_default_width_factor">(context, theme, .35f, .85f); + draw_slider<"item_frame_padding.width">(context, theme, 1, 10); + draw_slider<"item_frame_padding.height">(context, theme, 1, 10); + draw_slider<"item_spacing.width">(context, theme, 1, 10); + draw_slider<"item_spacing.height">(context, theme, 1, 10); + draw_slider<"alpha">(context, theme, 0, 1); + + // todo: update DrawListSharedData + draw_slider<"circle_segment_max_error">(context, theme, 0, 1); + draw_slider<"draw_curve_tessellation_tolerance">(context, theme, 0, 5); + + gui::end_window(context); + + return window_closed; + } + + auto create_context() noexcept -> Context* + { + return Context::create(); + } + + auto destroy_context(Context& context) noexcept -> void + { + Context::destroy(context); + } + + auto destroy_context(Context* context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context != nullptr); + destroy_context(*context); + } + + auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture + { + return context.set_default_font(option); + } + + auto set_default_theme(Context& context, const Theme& theme) noexcept -> void + { + context.set_default_theme(theme); + } + + auto push_theme(Context& context, const ThemeCategory category, const Theme::color_type new_color) noexcept -> void + { + context.push_theme(category, new_color); + } + + auto pop_theme(Context& context) noexcept -> void + { + context.pop_theme(); + } + + auto get_io(Context& context) noexcept -> IO& + { + return context.io(); + } + + auto set_default_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); + context.set_default_draw_list_flag(flag); + } - std::vector data; - data.reserve(context.draw_lists.size()); + auto new_frame(Context& context) noexcept -> void + { + context.new_frame(); + } - std::ranges::transform( - context.draw_lists, - std::back_inserter(data), - [](const auto& draw_list_ref) noexcept -> DrawData - { - auto& draw_list = draw_list_ref.get(); + auto end_frame(Context& context) noexcept -> void + { + context.end_frame(); + } - return - { - .vertex_list = draw_list.vertex_list(), - .index_list = draw_list.index_list(), - .command_list = draw_list.command_list() - }; - } - ); + auto render(Context& context) noexcept -> void + { + context.render(); + } - return data; + [[nodiscard]] auto get_draw_data(Context& context) noexcept -> std::vector + { + return context.get_draw_data(); } auto set_next_window_point(Context& context, const point_type& point) noexcept -> void { - // todo - context.window_default_spawn_position = point; + context.set_next_window_point(point); } auto begin_window( @@ -337,29 +856,12 @@ namespace gal::prometheus::gui const WindowFlag flag ) noexcept -> bool { - return internal::begin_window(context, name, size, fill_alpha, flag); + return context.begin_window(name, size, fill_alpha, flag); } auto end_window(Context& context) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); - window.end_window(context); - - // Select window for move/focus when we're done with all our widgets (we only consider non-children windows here) - if (const auto rect = window.rect(); - context.widget_activated == internal::invalid_widget_id and - context.widget_hovered == internal::invalid_widget_id and - context.window_hovered_root == std::addressof(window) and - window.is_hovered(context, rect) and - context.mouse.is_clicked(context, MouseKey::LEFT) - ) - { - context.widget_activated = window.id_of_move(context); - } - - context.window_current_stack.pop_back(); + context.end_window(); } auto begin_child_window( @@ -370,18 +872,12 @@ namespace gal::prometheus::gui const WindowFlag flag ) noexcept -> void { - internal::begin_child_window(context, name, 0, size, border, flag); + context.begin_child_window(name, size, 0, flag, border); } auto end_child_window(Context& context) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.window_current_stack.size() >= 2); - - auto& window = **(context.window_current_stack.end() - 1); - auto& parent = **(context.window_current_stack.end() - 2); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag().is()); - - parent.end_child_window(context, window); + context.end_child_window(); } auto begin_tooltip_window(Context& context) noexcept -> void @@ -394,22 +890,20 @@ namespace gal::prometheus::gui internal::WindowInternalFlag::CATEGORY_TOOLTIP }; - std::ignore = internal::begin_window(context, tooltip_window_name, {}, .9f, tooltip_window_flag); + std::ignore = context.begin_window(tooltip_window_name, {}, .9f, tooltip_window_flag); } auto end_tooltip_window(Context& context) noexcept -> void { - const auto& window = *context.window_current_stack.back(); + const auto& window = context.current_window(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window.flag().is()); - end_window(context); + context.end_window(); } auto draw_text(Context& context, const std::string_view utf8_text) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); window.draw_text(context, utf8_text); } @@ -422,33 +916,25 @@ namespace gal::prometheus::gui auto draw_button(Context& context, const std::string_view utf8_text, const extent_type& size, const bool repeat_when_held) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_button(context, utf8_text, size, repeat_when_held); } auto draw_small_button(Context& context, const std::string_view utf8_text, const bool repeat_when_held) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_small_button(context, utf8_text, repeat_when_held); } auto draw_radio_button(Context& context, const std::string_view utf8_text, const bool checked) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_radio_button(context, utf8_text, checked); } auto draw_checkbox(Context& context, const std::string_view utf8_text, const bool checked) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_checkbox(context, utf8_text, checked); } @@ -462,9 +948,7 @@ namespace gal::prometheus::gui const float power ) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_slider(context, utf8_text, reference, min, max, decimal_precision, power); } @@ -478,9 +962,7 @@ namespace gal::prometheus::gui const float power ) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_slider_n(context, utf8_text, references, min, max, decimal_precision, power); } @@ -492,9 +974,7 @@ namespace gal::prometheus::gui const std::size_t show_selection_count ) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_combo(context, utf8_text, selections, selected, show_selection_count); } @@ -506,9 +986,7 @@ namespace gal::prometheus::gui const std::size_t show_selection_count ) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_combo(context, utf8_text, selections, selected, show_selection_count); } @@ -520,89 +998,67 @@ namespace gal::prometheus::gui const std::size_t show_selection_count ) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); return window.draw_combo(context, utf8_text, selections, selected, show_selection_count); } auto layout_same_line(Context& context, const Theme::value_type column_width, const Theme::value_type spacing_width) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); window.same_line(context, column_width, spacing_width); } auto push_item_width(Context& context, const Theme::value_type new_item_width) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); window.push_item_width(context, new_item_width); } auto pop_item_width(Context& context) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); window.pop_item_width(context); } auto push_text_wrap_width(Context& context, const Theme::value_type new_wrap_width) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); window.push_text_wrap_width(context, new_wrap_width); } auto pop_text_wrap_width(Context& context) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - auto& window = *context.window_current_stack.back(); + auto& window = context.current_window(); window.pop_text_wrap_width(context); } auto get_content_region_max(const Context& context) noexcept -> extent_type { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); + const auto& window = context.current_window(); return window.content_region_max(context); } auto get_window_content_region_min(const Context& context) noexcept -> extent_type { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); + const auto& window = context.current_window(); return window.window_content_region_min(context); } auto get_window_content_region_max(const Context& context) noexcept -> extent_type { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); + const auto& window = context.current_window(); return window.window_content_region_max(context); } auto is_item_hovered(const Context& context) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); + const auto& window = context.current_window(); return window.is_item_hovered(context); } auto is_item_focused(const Context& context) noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); + const auto& window = context.current_window(); return window.is_item_hovered(context); } @@ -1013,433 +1469,6 @@ namespace gal::prometheus::gui auto show_theme_editor() noexcept -> bool { - auto& context = get_current_context(); - auto& theme = context.theme; - - const auto window_closed = begin_window(context, "ThemeEditor"); - - draw_slider<"window_background_alpha">(context, theme, 0, 1); - draw_slider<"window_corner_rounding">(context, theme, 0, 24); - draw_slider<"window_min_size.width">(context, theme, 64, 640); - draw_slider<"window_min_size.height">(context, theme, 48, 480); - draw_slider<"window_resize_grip_size.width">(context, theme, 10, 50); - draw_slider<"window_resize_grip_size.height">(context, theme, 10, 50); - draw_slider<"window_padding.width">(context, theme, 2, 20); - draw_slider<"window_padding.height">(context, theme, 2, 20); - draw_slider<"window_auto_fit_padding.width">(context, theme, 2, 20); - draw_slider<"window_auto_fit_padding.height">(context, theme, 2, 20); - draw_slider<"window_vertical_scrollbar_width">(context, theme, 6, 25); - draw_slider<"item_default_width_factor">(context, theme, .35f, .85f); - draw_slider<"item_frame_padding.width">(context, theme, 1, 10); - draw_slider<"item_frame_padding.height">(context, theme, 1, 10); - draw_slider<"item_spacing.width">(context, theme, 1, 10); - draw_slider<"item_spacing.height">(context, theme, 1, 10); - draw_slider<"alpha">(context, theme, 0, 1); - - // todo: update DrawListSharedData - draw_slider<"circle_segment_max_error">(context, theme, 0, 1); - draw_slider<"draw_curve_tessellation_tolerance">(context, theme, 0, 5); - - end_window(context); - - return window_closed; - } - - namespace internal - { - auto begin_window( - Context& context, - const std::string_view name, - const extent_type& size, - Theme::value_type fill_alpha, - const WindowFlag flag - ) noexcept -> bool - { - const auto& theme = current_theme(context); - - auto& window = find_or_create_window(context, name, size, flag); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.window_current_stack.back() == std::addressof(window)); - - // alpha - static_assert(Context::window_fill_alpha_not_set < 0); - if (fill_alpha < 0) - { - fill_alpha = theme.window_background_alpha; - } - - const auto is_child_window = window.flag().is(); - auto* parent = is_child_window ? context.window_current_stack[context.window_current_stack.size() - 2] : nullptr; - - return window.begin_window(context, parent, fill_alpha, size); - } - - auto begin_child_window( - Context& context, - const std::string_view name, - const Theme::alpha_type background_fill_alpha, - const extent_type& size, - const bool border, - const WindowFlag flag - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - auto& parent = *context.window_current_stack.back(); - - parent.begin_child_window(context, name, background_fill_alpha, size, border, flag); - } - - auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - // return context.draw_list_flag_stack[context.draw_list_flag_current]; - return context.draw_list_flag; - } - - // auto push_draw_list_flag(Context& context, const DrawListFlag flag) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - // - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - // context.draw_list_flag_current == Context::stack_pointer_default or - // context.draw_list_flag_current < Context::draw_list_flag_stack_size, - // "DrawListFlag stack overflow" - // ); - // - // static_assert( - // static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == - // static_cast<::Context::stack_pointer_type>(0) - // ); - // - // context.draw_list_flag_current += 1; - // context.draw_list_flag_stack[context.draw_list_flag_current] = flag; - // } - // - // auto pop_draw_list_flag(Context& context) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - // - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - // context.draw_list_flag_current != Context::stack_pointer_default, - // "Unable to popup the default DrawListFlag!" - // ); - // - // static_assert( - // static_cast(static_cast(0) - 1) == - // Context::stack_pointer_default - // ); - // - // context.draw_list_flag_stack[context.draw_list_flag_current] = DrawListFlag::NONE; - // context.draw_list_flag_current -= 1; - // } - - auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - // return context.draw_list_shared_data_stack[context.draw_list_shared_data_current]; - return context.draw_list_shared_data; - } - - // auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - // - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - // context.draw_list_shared_data_current == Context::stack_pointer_default or - // context.draw_list_shared_data_current < Context::draw_list_shared_data_stack_size, - // "DrawListSharedData stack overflow" - // ); - // - // static_assert( - // static_cast<::Context::stack_pointer_type>(::Context::stack_pointer_default + 1) == - // static_cast<::Context::stack_pointer_type>(0) - // ); - // - // context.draw_list_shared_data_current += 1; - // context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = shared_data; - // } - // - // auto pop_draw_list_shared_data(Context& context) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - // - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - // context.draw_list_shared_data_current != Context::stack_pointer_default, - // "Unable to popup the default DrawListSharedData!" - // ); - // - // static_assert( - // static_cast(static_cast(0) - 1) == - // Context::stack_pointer_default - // ); - // - // context.draw_list_shared_data_stack[context.draw_list_shared_data_current] = {}; - // context.draw_list_shared_data_current -= 1; - // } - - auto current_font(const Context& context) noexcept -> const Font& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - // const auto& font = *context.font_stack[context.font_current]; - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font.loaded(), "Invalid font!"); - // - // return font; - return context.font; - } - - // auto push_font(Context& context, memory::UniquePointer font) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - // - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - // context.font_current == Context::stack_pointer_default or - // context.font_current < Context::font_stack_size, - // "Font stack overflow" - // ); - // - // static_assert( - // static_cast(Context::stack_pointer_default + 1) == - // static_cast(0) - // ); - // - // context.font_current += 1; - // context.font_stack[context.font_current] = std::move(font); - // } - // - // auto pop_font(Context& context) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - // - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME( - // context.font_current != Context::stack_pointer_default, - // "Unable to popup the default Font!" - // ); - // - // static_assert( - // static_cast(static_cast(0) - 1) == - // Context::stack_pointer_default - // ); - // - // context.font_stack[context.font_current].reset(); - // context.font_current -= 1; - // } - - auto current_theme(const Context& context) noexcept -> const Theme& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - return context.theme; - } - - auto push_theme(Context& context, const ThemeCategory category, const Theme::color_type new_color) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - const auto old_color = color_of(context, category); - context.theme_mod_stack.emplace_back(category, old_color); - - context.theme.colors[static_cast(category)] = new_color; - } - - auto pop_theme(Context& context) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context.initialized); - - const auto [category, old_color] = context.theme_mod_stack.back(); - context.theme_mod_stack.pop_back(); - context.theme.colors[static_cast(category)] = old_color; - } - - auto color_of(const Theme& theme, const ThemeCategory category, const Theme::value_type factor) noexcept -> Theme::color_type - { - const auto index = static_cast(category); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < theme_category_count); - auto color = theme.colors[static_cast(category)]; - - color.alpha = static_cast(theme.alpha * 255 * factor); - - return color; - } - - auto color_of(const Context& context, const ThemeCategory category, const Theme::value_type factor) noexcept -> Theme::color_type - { - const auto& theme = current_theme(context); - - return color_of(theme, category, factor); - } - - auto test_mouse(Context& context, const widget_id_type id, const rect_type& area, const bool repeat) noexcept -> std::underlying_type_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); - auto state = std::to_underlying(MouseState::NONE); - - const auto hovered = - context.window_hovered_root == std::addressof(window.root()) and - context.widget_hovered == invalid_widget_id and - window.is_hovered(context, area); - - if (hovered) - { - state |= MouseState::HOVERED; - - // hovering widget - context.widget_hovered = id; - - if (context.mouse.is_clicked(context, MouseKey::LEFT, false)) - { - // select widget - context.widget_activated = id; - } - else if ( - repeat and - context.widget_activated != invalid_widget_id and - context.mouse.is_clicked(context, MouseKey::LEFT, true) - ) - { - state |= MouseState::PRESSED; - } - } - - if (context.widget_activated == id) - { - if (context.mouse.is_down(context, MouseKey::LEFT)) - { - // select current widget, keep the left mouse button pressed - state |= MouseState::KEEPING; - } - else - { - if (hovered) - { - // select current widget, release the left mouse button on the widget - state |= MouseState::PRESSED; - } - else - { - // select current widget, did not release the left mouse button on the widget - // do nothing - } - - // the widget is no longer selected - context.widget_activated = invalid_widget_id; - } - } - - return state; - } - - auto queue_mouse(Context& context, const widget_id_type id, const rect_type& area) noexcept -> std::underlying_type_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not context.window_current_stack.empty()); - - const auto& window = *context.window_current_stack.back(); - auto state = std::to_underlying(MouseState::NONE); - - const auto hovered = - context.window_hovered_root == std::addressof(window.root()) and - context.widget_hovered == invalid_widget_id and - window.is_hovered(context, area); - - if (hovered) - { - state |= MouseState::HOVERED; - - // hovering widget - context.widget_hovered = id; - - if (context.mouse.is_clicked(context, MouseKey::LEFT, false)) - { - state |= MouseState::PRESSED; - } - } - - return state; - } - - auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool - { - return context.window_hovered == std::addressof(window); - } - - auto focus_window(Context& context, Window& window) noexcept -> void - { - if (context.window_focused == std::addressof(window)) - { - return; - } - - context.window_focused = std::addressof(window); - - auto& root = window.root(); - - const auto it = std::ranges::find(context.window_root_list, std::addressof(root)); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != context.window_root_list.end()); - - if (it != context.window_root_list.end() - 1) - { - // The focused window is drawn last - const auto p = *it; - context.window_root_list.erase(it); - context.window_root_list.push_back(p); - } - - // todo: reorder child window? - } - - auto is_widget_hovered(const Context& context, const widget_id_type id) noexcept -> bool - { - return context.widget_hovered == id; - } - - auto is_any_widget_hovered(const Context& context) noexcept -> bool - { - return context.widget_hovered != invalid_widget_id; - } - - auto is_widget_activated(const Context& context, const widget_id_type id) noexcept -> bool - { - return context.widget_activated == id; - } - - auto is_any_widget_activated(const Context& context) noexcept -> bool - { - return context.widget_activated != invalid_widget_id; - } - - auto mark_widget_alive(Context& context, const widget_id_type id) noexcept -> bool - { - if (is_widget_activated(context, id)) - { - context.widget_activated_still_alive = true; - - return true; - } - - return false; - } - - auto mark_widget_dead(Context& context, const widget_id_type id) noexcept -> void - { - context.widget_activated = id; - } - - auto mark_combo_alive(Context& context, const widget_id_type id) noexcept -> void - { - context.widget_activated_combo_id = id; - } - - auto mark_combo_dead(Context& context, const widget_id_type id) noexcept -> void - { - context.widget_activated_combo_id = id; - } - - auto is_combo_activated(const Context& context, const widget_id_type id) noexcept -> bool - { - return context.widget_activated_combo_id == id; - } + return Context::show_theme_editor(); } } diff --git a/src/gui/internal/context.hpp b/src/gui/internal/context.hpp index 769ad08..eb66871 100644 --- a/src/gui/internal/context.hpp +++ b/src/gui/internal/context.hpp @@ -6,8 +6,7 @@ #pragma once #include - -#include +#include #include #include @@ -41,128 +40,128 @@ namespace gal::prometheus // < 0 constexpr static value_type window_fill_alpha_not_set{-.99999f}; - bool initialized; + private: + bool initialized_; // ---------------------------------------------------------------------- // DrawListFlag + DrawListSharedData + Font + Theme - DrawListFlag draw_list_flag; + DrawListFlag draw_list_flag_; - internal::DrawListSharedData draw_list_shared_data; + internal::DrawListSharedData draw_list_shared_data_; - internal::Font font; + internal::Font font_; - Theme theme; - std::vector theme_mod_stack; + Theme theme_; + std::vector theme_mod_stack_; // ---------------------------------------------------------------------- // IO - IO io; + IO io_; // time elapsed since program launch, in seconds - time_type time_total; - std::uint32_t frame_count; - std::uint32_t frame_count_rendered; + time_type time_total_; + std::uint32_t frame_count_; + std::uint32_t frame_count_rendered_; - internal::Mouse mouse; + internal::Mouse mouse_; // todo: keyboard // ---------------------------------------------------------------------- // WINDOW - point_type window_default_spawn_position; + point_type window_default_spawn_position_; // All created windows - std::vector> window_hive; + std::vector> window_hive_; // Root window only, child windows are managed by their parent window // The order of the windows in this list determines the drawing order, the windows at the end are drawn last - std::vector window_root_list; + std::vector window_root_list_; // Current window stack, push the stack when begin_window is called, and pop the stack when end_window is called (Nested calls are also included in this list) - std::vector window_current_stack; + std::vector window_current_stack_; // catch mouse - window_type* window_hovered; + window_type* window_hovered_; // catch mouse (for focus/move only) - window_type* window_hovered_root; + window_type* window_hovered_root_; // catch keyboard - window_type* window_focused; + window_type* window_focused_; // ---------------------------------------------------------------------- // WIDGET // widget for mouse hovering in this frame - widget_id_type widget_hovered; + widget_id_type widget_hovered_; // widget for mouse selecting in this frame - widget_id_type widget_activated; + widget_id_type widget_activated_; // widget for mouse hovering in previous frame - widget_id_type widget_activated_previous_frame; + widget_id_type widget_activated_previous_frame_; // widget for mouse selecting in this frame is still alive - bool widget_activated_still_alive; + bool widget_activated_still_alive_; // Currently open combo window - widget_id_type widget_activated_combo_id; + widget_id_type widget_activated_combo_id_; // ---------------------------------------------------------------------- // DRAW LIST - std::vector> draw_lists; - }; + internal::draw_lists_type draw_lists_; - namespace internal - { - auto begin_window( - Context& context, - std::string_view name, - const extent_type& size, - Theme::value_type fill_alpha, - WindowFlag flag - ) noexcept -> bool; + Context() noexcept; - auto begin_child_window( - Context& context, - std::string_view name, - Theme::alpha_type background_fill_alpha, - const extent_type& size, - bool border, - WindowFlag flag - ) noexcept -> void; + public: + // ---------------------------------------------------------------------- + // Context + + [[nodiscard]] static auto create() noexcept -> Context*; + static auto destroy(Context& context) noexcept -> void; // ---------------------------------------------------------------------- // DrawListFlag - auto current_draw_list_flag(const Context& context) noexcept -> DrawListFlag; - // auto push_draw_list_flag(Context& context, DrawListFlag flag) noexcept -> void; - // auto pop_draw_list_flag(Context& context) noexcept -> void; + auto set_default_draw_list_flag(DrawListFlag flag) noexcept -> void; + + [[nodiscard]] auto current_draw_list_flag() const noexcept -> DrawListFlag; + // auto push_draw_list_flag(DrawListFlag flag) noexcept -> void; + // auto pop_draw_list_flag() noexcept -> void; // ---------------------------------------------------------------------- // DrawListSharedData - auto current_draw_list_shared_data(const Context& context) noexcept -> const DrawListSharedData&; - // auto push_draw_list_shared_data(Context& context, const DrawListSharedData& shared_data) noexcept -> void; - // auto pop_draw_list_shared_data(Context& context) noexcept -> void; + [[nodiscard]] auto current_draw_list_shared_data() const noexcept -> const internal::DrawListSharedData&; + // auto push_draw_list_shared_data(const DrawListSharedData& shared_data) noexcept -> void; + // auto pop_draw_list_shared_data() noexcept -> void; // ---------------------------------------------------------------------- // Font - auto current_font(const Context& context) noexcept -> const Font&; - // auto push_font(Context& context, memory::UniquePointer font) noexcept -> void; - // auto pop_font(Context& context) noexcept -> void; + [[nodiscard]] auto set_default_font(const FontOption& option) noexcept -> Texture; + + [[nodiscard]] auto current_font() const noexcept -> const internal::Font&; // ---------------------------------------------------------------------- // Theme - auto current_theme(const Context& context) noexcept -> const Theme&; - auto push_theme(Context& context, ThemeCategory category, Theme::color_type new_color) noexcept -> void; - auto pop_theme(Context& context) noexcept -> void; + auto set_default_theme(const Theme& default_theme) noexcept -> void; + + [[nodiscard]] auto current_theme() const noexcept -> const Theme&; + auto push_theme(ThemeCategory category, Theme::color_type new_color) noexcept -> void; + auto pop_theme() noexcept -> void; - [[nodiscard]] auto color_of(const Theme& theme, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; - [[nodiscard]] auto color_of(const Context& context, ThemeCategory category, Theme::value_type factor = 1) noexcept -> Theme::color_type; + [[nodiscard]] auto color_of(ThemeCategory category, Theme::value_type factor = 1) const noexcept -> Theme::color_type; + [[nodiscard]] auto color_of(const Theme& theme, ThemeCategory category, Theme::value_type factor = 1) const noexcept -> Theme::color_type; // ---------------------------------------------------------------------- // IO + [[nodiscard]] auto io() noexcept -> IO&; + + [[nodiscard]] auto io() const noexcept -> const IO&; + + [[nodiscard]] auto mouse() const noexcept -> const internal::Mouse&; + enum class MouseState : std::uint8_t { NONE = 0, @@ -172,49 +171,143 @@ namespace gal::prometheus KEEPING = 1 << 2, }; - // Test the behavior of the mouse on the target widget - [[nodiscard]] auto test_mouse(Context& context, widget_id_type id, const rect_type& area, bool repeat = false) noexcept -> std::underlying_type_t; - // Similar to test_mouse, but does not activate any widget - [[nodiscard]] auto queue_mouse(Context& context, widget_id_type id, const rect_type& area) noexcept -> std::underlying_type_t; + /** + * @brief Test the behavior of the mouse on the target widget + * @param id widget id + * @param area widget rect + * @param repeat + * @note If the left mouse button is clicked, the target widget is selected + */ + [[nodiscard]] auto test_mouse(widget_id_type id, const rect_type& area, bool repeat = false) noexcept -> std::underlying_type_t; + + /** + * @brief Test the behavior of the mouse on the target widget + * @param id widget id + * @param area widget rect + * @note Even if the left mouse button is clicked, the target widget will not be selected, + * this applies to some widget that do not want to be selected + */ + [[nodiscard]] auto queue_mouse(widget_id_type id, const rect_type& area) noexcept -> std::underlying_type_t; // ---------------------------------------------------------------------- // WINDOW - // Test that the mouse is hovering over the target window - [[nodiscard]] auto is_window_hovered(const Context& context, const Window& window) noexcept -> bool; + private: + /** + * @brief Finds the window with the given name + * @return Returns the corresponding window if it exists, otherwise it returns a null pointer + * @note The naming convention for child windows is parent_name.child_name + */ + [[nodiscard]] auto find_window(std::string_view name) noexcept -> window_type*; + + /** + * @brief If the target window does not exist, then create it, otherwise return the corresponding window + * @param name window name + * @param size window size (used only when creating window) + * @param flag window flag + */ + [[nodiscard]] auto find_or_create_window(std::string_view name, const extent_type& size, internal::WindowFlag flag) noexcept -> window_type&; + + public: + /** + * @brief Find the last window from the available windows in this frame + */ + [[nodiscard]] auto current_window() const noexcept -> window_type&; + + /** + * @brief Find the last parent window from the available windows in this frame + */ + [[nodiscard]] auto current_parent_window() const noexcept -> window_type&; + + /** + * @brief Find the last possible root (not child) window from the available windows in this frame + * @return If it is not found (if and only if there are no currently available windows, i.e. the window to be created is the first one), then the null pointer is returned + */ + [[nodiscard]] auto current_root_window() noexcept -> window_type*; + + auto set_next_window_point(const point_type& point) noexcept -> void; + + auto begin_window( + std::string_view name, + const extent_type& size, + Theme::value_type background_fill_alpha, + internal::WindowFlag flag + ) noexcept -> bool; + auto end_window() noexcept -> void; + + auto begin_child_window( + std::string_view name, + const extent_type& size, + Theme::alpha_type background_fill_alpha, + internal::WindowFlag flag, + bool border + ) noexcept -> void; + auto end_child_window() noexcept -> void; + + // ---------------------------------------------------------------------- + // WINDOW STATE + + /** + * @brief Test that the mouse is hovering over the target window + */ + [[nodiscard]] auto is_window_hovered(const window_type& window) const noexcept -> bool; - // Focus on the target window (this determines the order in which the windows are drawn) - auto focus_window(Context& context, Window& window) noexcept -> void; + /** + * @brief Find the first (more top-level) window that contains the location of the given point + */ + [[nodiscard]] auto find_hovered_window(const point_type& position, bool excludes_children) noexcept -> window_type*; + + /** + * @brief Focus on the target window (this determines the order in which the windows are drawn) + */ + auto focus_window(window_type& window) noexcept -> void; // ---------------------------------------------------------------------- - // WIDGET + // WIDGET STATE // Whether the mouse is hovering over the target widget - [[nodiscard]] auto is_widget_hovered(const Context& context, widget_id_type id) noexcept -> bool; + [[nodiscard]] auto is_widget_hovered(widget_id_type id) const noexcept -> bool; // Whether the mouse is hovering over the widget - [[nodiscard]] auto is_any_widget_hovered(const Context& context) noexcept -> bool; + [[nodiscard]] auto is_any_widget_hovered() const noexcept -> bool; // Whether the mouse is selecting over the target widget - [[nodiscard]] auto is_widget_activated(const Context& context, widget_id_type id) noexcept -> bool; + [[nodiscard]] auto is_widget_activated(widget_id_type id) const noexcept -> bool; // Whether the mouse is selecting over the widget - [[nodiscard]] auto is_any_widget_activated(const Context& context) noexcept -> bool; + [[nodiscard]] auto is_any_widget_activated() const noexcept -> bool; // Marks the current widget alive, returns whether it is currently selected (activated) or not - auto mark_widget_alive(Context& context, widget_id_type id) noexcept -> bool; + auto mark_widget_alive(widget_id_type id) noexcept -> bool; // Marks that no widget is currently selected (activated) - auto mark_widget_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + auto mark_widget_dead(widget_id_type id = internal::invalid_widget_id) noexcept -> void; // Activate the specified combo widget (window) - auto mark_combo_alive(Context& context, widget_id_type id) noexcept -> void; + auto mark_combo_alive(widget_id_type id) noexcept -> void; // Deactivate the specified combo widget (window) - auto mark_combo_dead(Context& context, widget_id_type id = invalid_widget_id) noexcept -> void; + auto mark_combo_dead(widget_id_type id = internal::invalid_widget_id) noexcept -> void; // Whether the target combo widget is active or not - [[nodiscard]] auto is_combo_activated(const Context& context, widget_id_type id) noexcept -> bool; - } - } + [[nodiscard]] auto is_combo_activated(widget_id_type id) const noexcept -> bool; - namespace meta::user_defined - { - template<> - struct enum_is_flag : std::true_type {}; + // ---------------------------------------------------------------------- + // RENDER + + [[nodiscard]] auto current_time() const noexcept -> time_type; + + [[nodiscard]] auto current_frame() const noexcept -> internal::frame_count_type; + + auto new_frame() noexcept -> void; + + auto end_frame() noexcept -> void; + + auto render() noexcept -> void; + + [[nodiscard]] auto get_draw_data() noexcept -> std::vector; + + // ---------------------------------------------------------------------- + // TEST + + static auto show_theme_editor() noexcept -> bool; + }; } + + template<> + struct meta::user_defined::enum_is_flag : std::true_type {}; } diff --git a/src/gui/internal/draw_list.cpp b/src/gui/internal/draw_list.cpp index 88b45bc..424c532 100644 --- a/src/gui/internal/draw_list.cpp +++ b/src/gui/internal/draw_list.cpp @@ -156,7 +156,7 @@ namespace gal::prometheus::gui::internal const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& font = current_font(*draw_list.context_); + const auto& font = draw_list.context_->current_font(); auto appender = make_appender(); const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; @@ -202,7 +202,7 @@ namespace gal::prometheus::gui::internal const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& font = current_font(*draw_list.context_); + const auto& font = draw_list.context_->current_font(); const auto draw_list_flag = self.get().draw_list_flag_; auto appender = make_appender(); @@ -433,7 +433,7 @@ namespace gal::prometheus::gui::internal const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& font = current_font(*draw_list.context_); + const auto& font = draw_list.context_->current_font(); auto appender = make_appender(); const auto vertex_count = path_point_count; @@ -471,7 +471,7 @@ namespace gal::prometheus::gui::internal const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& font = current_font(*draw_list.context_); + const auto& font = draw_list.context_->current_font(); auto appender = make_appender(); const auto& opaque_uv = font.white_pixel_uv; @@ -553,7 +553,7 @@ namespace gal::prometheus::gui::internal { const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& font = current_font(*draw_list.context_); + const auto& font = draw_list.context_->current_font(); auto appender = make_appender(); // two triangle without path @@ -811,7 +811,7 @@ namespace gal::prometheus::gui::internal const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + const auto& draw_list_shared_data = draw_list.context_->current_draw_list_shared_data(); // Calculate arc auto segment step size auto step = DrawListSharedData::vertex_sample_points_count / draw_list_shared_data.circle_auto_segment_count(radius); @@ -936,7 +936,7 @@ namespace gal::prometheus::gui::internal const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + const auto& draw_list_shared_data = draw_list.context_->current_draw_list_shared_data(); // Automatic segment count if (radius <= draw_list_shared_data.arc_fast_radius_cutoff) @@ -1104,7 +1104,7 @@ namespace gal::prometheus::gui::internal { const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + const auto& draw_list_shared_data = draw_list.context_->current_draw_list_shared_data(); path_pin(p1); if (segments == 0) @@ -1130,7 +1130,7 @@ namespace gal::prometheus::gui::internal { const auto& draw_list = self.get(); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list.context_ != nullptr); - const auto& draw_list_shared_data = current_draw_list_shared_data(*draw_list.context_); + const auto& draw_list_shared_data = draw_list.context_->current_draw_list_shared_data(); path_pin(p1); if (segments == 0) @@ -1248,13 +1248,13 @@ namespace gal::prometheus::gui::internal { context_ = std::addressof(context); - draw_list_flag_ = current_draw_list_flag(context); + draw_list_flag_ = context.current_draw_list_flag(); } auto DrawList::reset() noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); - const auto& font = current_font(*context_); + const auto& font = context_->current_font(); command_list_.clear(); vertex_list_.clear(); @@ -1736,7 +1736,7 @@ namespace gal::prometheus::gui::internal ) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); - const auto& draw_list_shared_data = current_draw_list_shared_data(*context_); + const auto& draw_list_shared_data = context_->current_draw_list_shared_data(); if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) { @@ -1771,7 +1771,7 @@ namespace gal::prometheus::gui::internal ) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); - const auto& draw_list_shared_data = current_draw_list_shared_data(*context_); + const auto& draw_list_shared_data = context_->current_draw_list_shared_data(); if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) { @@ -1874,7 +1874,7 @@ namespace gal::prometheus::gui::internal ) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context_ != nullptr); - const auto& font = current_font(*context_); + const auto& font = context_->current_font(); text(font, font_size, point, color, utf8_text, wrap_width); } diff --git a/src/gui/internal/font.cpp b/src/gui/internal/font.cpp index cc0ef9e..8e48b1d 100644 --- a/src/gui/internal/font.cpp +++ b/src/gui/internal/font.cpp @@ -317,38 +317,6 @@ namespace namespace gal::prometheus::gui { - auto set_default_font(Context& context, const FontOption& option) noexcept -> Texture - { - // if (context.font_current == Context::stack_pointer_default) - // { - // return push_font(context, option); - // } - // - // auto font = memory::make_unique(); - // auto texture = do_load_font(option, *font); - // - // context.font_stack[0] = std::move(font); - // - // return texture; - auto texture = do_load_font(option, context.font); - return texture; - } - - // [[nodiscard]] auto push_font(Context& context, const FontOption& option) noexcept -> Texture - // { - // auto font = memory::make_unique(); - // auto texture = do_load_font(option, *font); - // - // internal::push_font(context, std::move(font)); - // - // return texture; - // } - // - // auto pop_font(Context& context) noexcept -> void - // { - // internal::pop_font(context); - // } - Texture::Texture(texture_id_type& texture_id) noexcept : width{0}, height{0}, @@ -390,6 +358,12 @@ namespace gal::prometheus::gui display_offset{0, 0}, texture_id{invalid_texture_id} {} + auto Font::load(const FontOption& option) noexcept -> Texture + { + auto texture = do_load_font(option, *this); + return texture; + } + auto Font::loaded() const noexcept -> bool { return not glyphs.empty() and texture_id != invalid_texture_id; @@ -432,7 +406,8 @@ namespace gal::prometheus::gui } else { - const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { + const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& + { if (const auto it = glyphs.find(c); it != glyphs.end()) { @@ -516,7 +491,8 @@ namespace gal::prometheus::gui continue; } - const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { + const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& + { if (const auto it = glyphs.find(c); it != glyphs.end()) { diff --git a/src/gui/internal/font.hpp b/src/gui/internal/font.hpp index 910eb5d..b8c708a 100644 --- a/src/gui/internal/font.hpp +++ b/src/gui/internal/font.hpp @@ -62,6 +62,8 @@ namespace gal::prometheus::gui::internal Font() noexcept; + auto load(const FontOption& option) noexcept -> Texture; + // --------------------------------------------------------- [[nodiscard]] auto loaded() const noexcept -> bool; diff --git a/src/gui/internal/gui.inl b/src/gui/internal/gui.inl new file mode 100644 index 0000000..c3943c0 --- /dev/null +++ b/src/gui/internal/gui.inl @@ -0,0 +1,104 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +namespace gal::prometheus::gui +{ + template + auto draw_radio_button(Context& context, std::string_view utf8_text, T& reference, const std::type_identity_t& identifier) noexcept -> bool + { + const auto prev_selected = reference == identifier; + const auto pressed = gui::draw_radio_button(context, utf8_text, prev_selected); + + if (pressed) + { + reference = identifier; + } + return pressed; + } + + template + auto draw_checkbox( + Context& context, + const std::string_view utf8_text, + T& reference, + const std::type_identity_t& checked_identifier, + const std::type_identity_t& unchecked_identifier + ) noexcept -> bool + { + const auto prev_checked = reference == checked_identifier; + const auto checked = gui::draw_checkbox(context, utf8_text, prev_checked); + + if (checked != prev_checked) + { + if (prev_checked) + { + reference = unchecked_identifier; + } + else + { + reference = checked_identifier; + } + } + return checked; + } + + template + requires std::is_arithmetic_v + auto draw_slider( + Context& context, + const std::string_view utf8_text, + T& reference, + const ValueType min, + const std::type_identity_t max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + auto v = static_cast(reference); + + const auto result = gui::draw_slider( + context, + utf8_text, + v, + static_cast(min), + static_cast(max), + decimal_precision, + power + ); + reference = v; + + return result; + } + + template + requires std::is_arithmetic_v + auto draw_slider( + Context& context, + T&& object, + const ValueType min, + const std::type_identity_t max, + const std::uint32_t decimal_precision, + const float power + ) noexcept -> bool + { + auto& ref = meta::member_of_name(std::forward(object)); + auto v = static_cast(ref); + + const auto result = gui::draw_slider( + context, + MemberName, + v, + static_cast(min), + static_cast(max), + decimal_precision, + power + ); + ref = v; + + return result; + } +} diff --git a/src/gui/internal/mouse.cpp b/src/gui/internal/mouse.cpp index 1e54ab8..874df9f 100644 --- a/src/gui/internal/mouse.cpp +++ b/src/gui/internal/mouse.cpp @@ -21,6 +21,8 @@ namespace gal::prometheus::gui::internal auto Mouse::is_clicked(const Context& context, MouseKey key, const bool repeat) const noexcept -> bool { + const auto& io = context.io(); + const auto index = static_cast(key); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < mouse_key_count); @@ -33,10 +35,10 @@ namespace gal::prometheus::gui::internal return true; } - if (repeat and down_time > context.io.mouse_repeat_click_delay) + if (repeat and down_time > io.mouse_repeat_click_delay) { - const auto v1 = std::fmodf(down_time - context.io.mouse_repeat_click_delay, context.io.mouse_repeat_click_rate) > context.io.mouse_repeat_click_rate * .5f; - const auto v2 = std::fmodf(down_time - context.io.delta_time, context.io.mouse_repeat_click_rate) > context.io.mouse_repeat_click_rate * .5f; + const auto v1 = std::fmodf(down_time - io.mouse_repeat_click_delay, io.mouse_repeat_click_rate) > io.mouse_repeat_click_rate * .5f; + const auto v2 = std::fmodf(down_time - io.delta_time, io.mouse_repeat_click_rate) > io.mouse_repeat_click_rate * .5f; return v1 != v2; } @@ -61,7 +63,8 @@ namespace gal::prometheus::gui::internal { // ---------------------- // read context - const auto& io = context.io; + const auto& io = context.io(); + const auto time = context.current_time(); position_current = io.mouse_position; wheel = io.mouse_wheel; @@ -95,7 +98,7 @@ namespace gal::prometheus::gui::internal static_assert(time_not_start < 0); std::ranges::for_each( key_statuses, - [this, &context](auto& key_status) noexcept -> void + [&](auto& key_status) noexcept -> void { if (key_status.down) { @@ -106,7 +109,7 @@ namespace gal::prometheus::gui::internal } else { - key_status.down_time += context.io.delta_time; + key_status.down_time += io.delta_time; } } else @@ -121,9 +124,9 @@ namespace gal::prometheus::gui::internal if (key_status.clicked) { // time_not_start < 0 - if (context.time_total - key_status.click_time < context.io.mouse_double_click_interval_threshold) + if (time - key_status.click_time < io.mouse_double_click_interval_threshold) { - if (position_current.distance(key_status.click_position) < context.io.mouse_double_click_distance_threshold) + if (position_current.distance(key_status.click_position) < io.mouse_double_click_distance_threshold) { key_status.double_clicked = true; } @@ -133,7 +136,7 @@ namespace gal::prometheus::gui::internal } else { - key_status.click_time = context.time_total; + key_status.click_time = time; key_status.click_position = position_current; } } diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 2fba2cf..2f0807e 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -10,6 +10,15 @@ #include #include +namespace +{ + using namespace gal::prometheus; + using namespace gui; + + // ReSharper disable once CppInconsistentNaming + using MouseState = Context::MouseState; +} + namespace gal::prometheus::gui::internal { class Window::IdMaker final @@ -68,7 +77,7 @@ namespace gal::prometheus::gui::internal const auto seed = window.id_stack_.back(); const auto id = make(seed, string); - mark_widget_alive(context, id); + context.mark_widget_alive(id); return id; } @@ -83,7 +92,7 @@ namespace gal::prometheus::gui::internal const auto seed = window.id_stack_.back(); const auto id = make(seed, pointer); - mark_widget_alive(context, id); + context.mark_widget_alive(id); return id; } @@ -98,7 +107,7 @@ namespace gal::prometheus::gui::internal const auto seed = window.id_stack_.back(); const auto id = make(seed, value); - mark_widget_alive(context, id); + context.mark_widget_alive(id); return id; } @@ -116,7 +125,7 @@ namespace gal::prometheus::gui::internal { std::ignore = this; - const auto& font = current_font(context); + const auto& font = context.current_font(); // todo: scale? return static_cast(font.pixel_height) * font.scale; @@ -129,7 +138,7 @@ namespace gal::prometheus::gui::internal { const auto& window = self.get(); - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); if (window.flag_.is() and not window.flag_.is()) { @@ -148,7 +157,7 @@ namespace gal::prometheus::gui::internal GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not window.flag_.is()); - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); return font_size(context) + theme.item_frame_padding.height * 2; } @@ -178,7 +187,7 @@ namespace gal::prometheus::gui::internal { std::ignore = this; - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); return theme.window_resize_grip_size; } @@ -224,7 +233,7 @@ namespace gal::prometheus::gui::internal auto adjust_item_size(const Context& context, const extent_type& size) noexcept -> void { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); auto& window = self.get(); auto& canvas = window.canvas_; @@ -366,7 +375,7 @@ namespace gal::prometheus::gui::internal auto draw_widget_frame(const Context& context, const rect_type& rect, const color_type color) noexcept -> void { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); auto& window = self.get(); @@ -379,14 +388,14 @@ namespace gal::prometheus::gui::internal const point_type inner_point{rect.left_top() + extent_type{1.5f, 1.5f}}; const extent_type inner_size{rect.size() - extent_type{.5f, .5f}}; - window.draw_list_.rect({inner_point, inner_size}, color_of(theme, ThemeCategory::BORDER_SHADOW)); - window.draw_list_.rect({outer_point, outer_size}, color_of(theme, ThemeCategory::BORDER)); + window.draw_list_.rect({inner_point, inner_size}, context.color_of(theme, ThemeCategory::BORDER_SHADOW)); + window.draw_list_.rect({outer_point, outer_size}, context.color_of(theme, ThemeCategory::BORDER)); } } auto draw_widget_frame(const Context& context, const circle_type& circle, const color_type color) noexcept -> void { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); auto& window = self.get(); @@ -399,14 +408,14 @@ namespace gal::prometheus::gui::internal const point_type inner_point{circle.center() + extent_type{1.5f, 1.5f}}; const auto inner_radius = circle.radius - .5f; - window.draw_list_.circle({inner_point, inner_radius}, color_of(theme, ThemeCategory::BORDER_SHADOW)); - window.draw_list_.circle({outer_point, outer_radius}, color_of(theme, ThemeCategory::BORDER)); + window.draw_list_.circle({inner_point, inner_radius}, context.color_of(theme, ThemeCategory::BORDER_SHADOW)); + window.draw_list_.circle({outer_point, outer_radius}, context.color_of(theme, ThemeCategory::BORDER)); } } auto draw_widget_frame(const Context& context, const point_type& a, const point_type& b, const point_type& c) noexcept -> void { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); auto& window = self.get(); @@ -416,9 +425,9 @@ namespace gal::prometheus::gui::internal const auto offset_b = b + extent_type{1.5f, 1.5f}; const auto offset_c = c + extent_type{1.5f, 1.5f}; - window.draw_list_.triangle_filled(offset_a, offset_b, offset_c, color_of(theme, ThemeCategory::BORDER_SHADOW)); + window.draw_list_.triangle_filled(offset_a, offset_b, offset_c, context.color_of(theme, ThemeCategory::BORDER_SHADOW)); } - window.draw_list_.triangle_filled(a, b, c, color_of(theme, ThemeCategory::BORDER)); + window.draw_list_.triangle_filled(a, b, c, context.color_of(theme, ThemeCategory::BORDER)); } }; @@ -457,8 +466,8 @@ namespace gal::prometheus::gui::internal } window.accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = window}; const IdMaker id_maker{.self = window}; @@ -472,13 +481,13 @@ namespace gal::prometheus::gui::internal const auto frame_point = frame_rect.left_top(); const auto frame_size = frame_rect.size(); - const auto state = test_mouse(context, id, slider_rect, false); + const auto state = context.test_mouse(id, slider_rect, false); // draw □ (frame + slider + text) bool value_changed = false; // frame - drawer.draw_widget_frame(context, frame_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + drawer.draw_widget_frame(context, frame_rect, context.color_of(theme, ThemeCategory::FRAME_BACKGROUND)); // slider @@ -505,7 +514,7 @@ namespace gal::prometheus::gui::internal if (state & MouseState::KEEPING) { - const auto mouse_position = context.mouse.position_current; + const auto mouse_position = context.mouse().position_current; const auto normalized_x = std::ranges::clamp((mouse_position.x - slider_effective_x1) / slider_effective_width, .0f, 1.f); // account for logarithmic scale on both sides of the zero @@ -577,7 +586,7 @@ namespace gal::prometheus::gui::internal { window.draw_list_.rect_filled( grab_rect, - color_of(theme, ThemeCategory::SLIDER_ACTIVATED), + context.color_of(theme, ThemeCategory::SLIDER_ACTIVATED), theme.window_corner_rounding, DrawFlag::ROUND_CORNER_ALL ); @@ -586,7 +595,7 @@ namespace gal::prometheus::gui::internal { window.draw_list_.rect_filled( grab_rect, - color_of(theme, ThemeCategory::SLIDER), + context.color_of(theme, ThemeCategory::SLIDER), theme.window_corner_rounding, DrawFlag::ROUND_CORNER_ALL ); @@ -601,7 +610,7 @@ namespace gal::prometheus::gui::internal font, font_size, value_text_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), value_text ); @@ -705,7 +714,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, text_rect.width() ); @@ -730,8 +739,8 @@ namespace gal::prometheus::gui::internal } window.accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = window}; const IdMaker id_maker{.self = window}; @@ -781,20 +790,20 @@ namespace gal::prometheus::gui::internal const auto id = id_maker.make_id(context, utf8_text); // note: Leave the frame inactive so that clicking on it again does not accidentally re-open the dropdown window again - const auto state = queue_mouse(context, id, frame_rect); + const auto state = context.queue_mouse(id, frame_rect); // draw frame - drawer.draw_widget_frame(context, frame_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + drawer.draw_widget_frame(context, frame_rect, context.color_of(theme, ThemeCategory::FRAME_BACKGROUND)); // draw dropdown button frame { if (state & MouseState::HOVERED) { - drawer.draw_widget_frame(context, dropdown_button_rect, color_of(theme, ThemeCategory::BUTTON_HOVERED)); + drawer.draw_widget_frame(context, dropdown_button_rect, context.color_of(theme, ThemeCategory::BUTTON_HOVERED)); } else { - drawer.draw_widget_frame(context, dropdown_button_rect, color_of(theme, ThemeCategory::BUTTON)); + drawer.draw_widget_frame(context, dropdown_button_rect, context.color_of(theme, ThemeCategory::BUTTON)); } const auto r = font_size * .5f; @@ -814,7 +823,7 @@ namespace gal::prometheus::gui::internal font, font_size, selection_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), "", selection_size.width ); @@ -827,7 +836,7 @@ namespace gal::prometheus::gui::internal font, font_size, selection_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), string, selection_size.width ); @@ -838,7 +847,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, text_rect.width() ); @@ -850,17 +859,17 @@ namespace gal::prometheus::gui::internal menu_toggled = true; // If the combo is already open, click again to close the combo, otherwise open the combo - if (is_combo_activated(context, id)) + if (context.is_combo_activated(id)) { - mark_combo_dead(context); + context.mark_combo_dead(); } else { - mark_combo_alive(context, id); + context.mark_combo_alive(id); } } - if (is_combo_activated(context, id)) + if (context.is_combo_activated(id)) { // The contents of the dropdown window should not affect the layout of the parent window, so after drawing the dropdown window, we reset the parent window canvas cursor const auto backup_position = drawer.cursor_position(context); @@ -882,7 +891,7 @@ namespace gal::prometheus::gui::internal const auto dropdown_size = extent_type{frame_size.width - dropdown_offset_x, dropdown_height}; // begin dropdown window (with background) - internal::begin_child_window(context, combo_window_name, 1, dropdown_size, false, combo_window_flag); + context.begin_child_window(combo_window_name, dropdown_size, 1, combo_window_flag, false); { auto& combo_window = *window.children_this_frame_.back(); auto& combo_window_canvas = combo_window.canvas_; @@ -893,7 +902,7 @@ namespace gal::prometheus::gui::internal // Close the dropdown if the user interacts with another widget (unless the widget is the dropdown window's scrollbar) bool combo_item_active = false; - combo_item_active |= is_widget_activated(context, combo_window.id_of_scrollbar(context)); + combo_item_active |= context.is_widget_activated(combo_window.id_of_scrollbar(context)); for (const auto view = selections | std::views::enumerate; const auto [index, selection]: view) @@ -905,8 +914,8 @@ namespace gal::prometheus::gui::internal const auto item_size = extent_type{dropdown_size.width, item_height + theme.item_spacing.height}; const rect_type item_rect{item_point, item_size}; - auto item_state = internal::test_mouse(context, item_id, item_rect); - combo_item_active |= is_widget_activated(context, item_id); + auto item_state = context.test_mouse(item_id, item_rect); + combo_item_active |= context.is_widget_activated(item_id); if (item_state & MouseState::HOVERED or item_selected) { @@ -916,12 +925,12 @@ namespace gal::prometheus::gui::internal { if (item_state & MouseState::KEEPING) { - return color_of(theme, ThemeCategory::COMBO_ITEM_ACTIVATED); + return context.color_of(theme, ThemeCategory::COMBO_ITEM_ACTIVATED); } - return color_of(theme, ThemeCategory::COMBO_ITEM_HOVERED); + return context.color_of(theme, ThemeCategory::COMBO_ITEM_HOVERED); } - return color_of(theme, ThemeCategory::COMBO_ITEM); + return context.color_of(theme, ThemeCategory::COMBO_ITEM); }(); combo_window_drawer.draw_widget_frame(context, item_rect, color); @@ -939,8 +948,8 @@ namespace gal::prometheus::gui::internal // Close the dropdown window after selecting any item if (item_state & MouseState::PRESSED) { - mark_widget_dead(context); - mark_combo_dead(context); + context.mark_widget_dead(); + context.mark_combo_dead(); value_changed = true; selected = index; @@ -949,9 +958,9 @@ namespace gal::prometheus::gui::internal } } - if (not combo_item_active and is_any_widget_activated(context)) + if (not combo_item_active and context.is_any_widget_activated()) { - mark_combo_dead(context); + context.mark_combo_dead(); } } gui::end_child_window(context); @@ -1022,36 +1031,42 @@ namespace gal::prometheus::gui::internal } } - auto Window::reset(const WindowFlag flag) noexcept -> void + auto Window::reset(const WindowFlag flag, const extent_type& size) noexcept -> void { flag_ = flag; + + if (flag_.is()) + { + size_full_ = size; + } } auto Window::handle_inputs(const Context& context) noexcept -> void { + const auto& mouse = context.mouse(); + // scroll if (not flag_.is()) { // todo constexpr auto scroll_weight = static_cast(5); - scroll_next_y_ -= context.mouse.wheel * Drawer{.self = *this}.font_size(context) * scroll_weight; + scroll_next_y_ -= mouse.wheel * Drawer{.self = *this}.font_size(context) * scroll_weight; } } auto Window::begin_window( Context& context, Window* parent, - alpha_type background_fill_alpha, - const extent_type& size + alpha_type background_fill_alpha ) noexcept -> bool { // This function can be called multiple times per frame, // but is only initialized the first time it is called (after that we can just append the contents) - const auto current_frame_count = context.frame_count; + const auto current_frame_count = context.current_frame(); const auto is_first_draw_this_frame = current_frame_count != last_drawn_frame_; - const auto display_size = context.io.display_size; + const auto display_size = context.io().display_size; // --------------------------------- // initialize the window, set the window's clip rect @@ -1080,8 +1095,6 @@ namespace gal::prometheus::gui::internal // Moves the child window to the current cursor position of the parent window point_ = parent->canvas_.cursor_current_line; - // Follows the size of the parent window - size_full_ = size; } } @@ -1109,9 +1122,9 @@ namespace gal::prometheus::gui::internal { auto drawer = Drawer{*this}; - const auto& mouse = context.mouse; - const auto& font = current_font(context); - const auto& theme = current_theme(context); + const auto& mouse = context.mouse(); + const auto& font = context.current_font(); + const auto& theme = context.current_theme(); const auto font_size = drawer.font_size(context); const auto has_titlebar = not flag_.is(); @@ -1134,7 +1147,7 @@ namespace gal::prometheus::gui::internal { // The current window was not drawn in the last frame (or even many frames before that), this is usually because the window was just created, or the window was not visible before // Focus on the current window - focus_window(context, *this); + context.focus_window(*this); if (is_tooltip_window) { @@ -1155,13 +1168,13 @@ namespace gal::prometheus::gui::internal else { // If the user drags the window, we determine the new position of the window before the frame is drawn to avoid lag - if (const auto activated = mark_widget_alive(context, id_of_move(context)); + if (const auto activated = context.mark_widget_alive(id_of_move(context)); activated) { if (mouse.is_down(context, MouseKey::LEFT)) { // select current window - focus_window(context, *this); + context.focus_window(*this); if (not flag_.is()) { @@ -1174,7 +1187,7 @@ namespace gal::prometheus::gui::internal else { // No widgets are active - mark_widget_dead(context); + context.mark_widget_dead(); } } } @@ -1227,7 +1240,7 @@ namespace gal::prometheus::gui::internal if (has_titlebar) { // `double left-click` on the `titlebar` of the `current window` to collapse the current window - if (is_window_hovered(context, *this)) + if (context.is_window_hovered(*this)) { if (const auto rect = drawer.titlebar_rect(context); is_hovered(context, rect) and @@ -1235,7 +1248,7 @@ namespace gal::prometheus::gui::internal ) { // select current window - focus_window(context, *this); + context.focus_window(*this); // collapse window by double-clicking on titlebar collapsed_ = not collapsed_; @@ -1258,7 +1271,7 @@ namespace gal::prometheus::gui::internal draw_list_.rect_filled( rect, - color_of(theme, ThemeCategory::TITLEBAR_COLLAPSED), + context.color_of(theme, ThemeCategory::TITLEBAR_COLLAPSED), theme.window_corner_rounding ); @@ -1268,12 +1281,12 @@ namespace gal::prometheus::gui::internal draw_list_.rect( {rect.point + offset, rect.extent}, - color_of(theme, ThemeCategory::BORDER), + context.color_of(theme, ThemeCategory::BORDER), theme.window_corner_rounding ); draw_list_.rect( rect, - color_of(theme, ThemeCategory::BORDER), + context.color_of(theme, ThemeCategory::BORDER), theme.window_corner_rounding ); } @@ -1282,7 +1295,7 @@ namespace gal::prometheus::gui::internal { size_ = size_full_; - auto resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP); + auto resize_grip_color = context.color_of(theme, ThemeCategory::RESIZE_GRIP); // The tooltip window always adapts to the size of the content if (is_tooltip_window) @@ -1322,14 +1335,15 @@ namespace gal::prometheus::gui::internal const auto rect = drawer.resize_grip_rect(context); const auto id = id_of_resize(context); - const auto state = test_mouse(context, id, rect); + const auto state = context.test_mouse(id, rect); + if (state & MouseState::KEEPING) { - resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP_ACTIVATED); + resize_grip_color = context.color_of(theme, ThemeCategory::RESIZE_GRIP_ACTIVATED); } else if (state & MouseState::HOVERED) { - resize_grip_color = color_of(theme, ThemeCategory::RESIZE_GRIP_HOVERED); + resize_grip_color = context.color_of(theme, ThemeCategory::RESIZE_GRIP_HOVERED); } if (state & MouseState::KEEPING) @@ -1373,7 +1387,7 @@ namespace gal::prometheus::gui::internal { draw_list_.rect_filled( {point_, size_}, - color_of(theme, ThemeCategory::WINDOW_BACKGROUND, background_fill_alpha), + context.color_of(theme, ThemeCategory::WINDOW_BACKGROUND, background_fill_alpha), theme.window_corner_rounding ); } @@ -1383,7 +1397,7 @@ namespace gal::prometheus::gui::internal { draw_list_.rect_filled( current_titlebar_rect, - color_of(theme, ThemeCategory::TITLEBAR), + context.color_of(theme, ThemeCategory::TITLEBAR), theme.window_corner_rounding, DrawFlag::ROUND_CORNER_TOP ); @@ -1394,7 +1408,7 @@ namespace gal::prometheus::gui::internal draw_list_.line( current_titlebar_rect.left_bottom(), current_titlebar_rect.right_bottom(), - color_of(theme, ThemeCategory::BORDER) + context.color_of(theme, ThemeCategory::BORDER) ); } } @@ -1406,12 +1420,12 @@ namespace gal::prometheus::gui::internal draw_list_.rect( {point_ + offset, size_}, - color_of(theme, ThemeCategory::BORDER_SHADOW), + context.color_of(theme, ThemeCategory::BORDER_SHADOW), theme.window_corner_rounding ); draw_list_.rect( {point_, size_}, - color_of(theme, ThemeCategory::BORDER), + context.color_of(theme, ThemeCategory::BORDER), theme.window_corner_rounding ); } @@ -1444,7 +1458,7 @@ namespace gal::prometheus::gui::internal const rect_type background_rect{background_point, background_size}; draw_list_.rect_filled( background_rect, - color_of(theme, ThemeCategory::SCROLLBAR_BACKGROUND) + context.color_of(theme, ThemeCategory::SCROLLBAR_BACKGROUND) ); // scrollbar area @@ -1460,15 +1474,16 @@ namespace gal::prometheus::gui::internal ); const auto grab_size_y = scrollbar_area_size.height * grab_size_y_normalized; - auto grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB); + auto grab_color = context.color_of(theme, ThemeCategory::SCROLLBAR_GRAB); if (grab_size_y_normalized < 1.f) { const auto id = id_of_scrollbar(context); - if (const auto state = test_mouse(context, id, scrollbar_area_rect); - state & MouseState::KEEPING) + const auto state = context.test_mouse(id, scrollbar_area_rect); + + if (state & MouseState::KEEPING) { - grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_ACTIVATED); + grab_color = context.color_of(theme, ThemeCategory::SCROLLBAR_GRAB_ACTIVATED); const auto y_normalized = std::ranges::clamp( (mouse.position_current.y - (scrollbar_area_point.y + grab_size_y * .5f)) / (scrollbar_area_size.height - grab_size_y), @@ -1481,7 +1496,7 @@ namespace gal::prometheus::gui::internal } else if (state & MouseState::HOVERED) { - grab_color = color_of(theme, ThemeCategory::SCROLLBAR_GRAB_HOVERED); + grab_color = context.color_of(theme, ThemeCategory::SCROLLBAR_GRAB_HOVERED); } } @@ -1541,18 +1556,19 @@ namespace gal::prometheus::gui::internal const auto rect = drawer.close_button_rect(context); const auto id = id_of_close(context); - const auto state = test_mouse(context, id, rect); + const auto state = context.test_mouse(id, rect); + + auto close_button_color = context.color_of(theme, ThemeCategory::CLOSE_BUTTON); - auto close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON); if (state & MouseState::HOVERED) { if (state & MouseState::KEEPING) { - close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON_ACTIVATED); + close_button_color = context.color_of(theme, ThemeCategory::CLOSE_BUTTON_ACTIVATED); } else { - close_button_color = color_of(theme, ThemeCategory::CLOSE_BUTTON_HOVERED); + close_button_color = context.color_of(theme, ThemeCategory::CLOSE_BUTTON_HOVERED); } } @@ -1575,12 +1591,12 @@ namespace gal::prometheus::gui::internal draw_list_.line( center + extent_type{x, y}, center + extent_type{-x, -y}, - color_of(theme, ThemeCategory::TEXT) + context.color_of(theme, ThemeCategory::TEXT) ); draw_list_.line( center + extent_type{x, -y}, center + extent_type{-x, y}, - color_of(theme, ThemeCategory::TEXT) + context.color_of(theme, ThemeCategory::TEXT) ); } @@ -1608,7 +1624,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), name_, width ); @@ -1620,7 +1636,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), name_, text_size.width ); @@ -1747,7 +1763,7 @@ namespace gal::prometheus::gui::internal WindowFlag flag ) noexcept -> void { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); flag |= gui::WindowFlag::NO_TITLEBAR | gui::WindowFlag::NO_CLOSE | gui::WindowFlag::NO_RESIZE | gui::WindowFlag::NO_MOVE; flag |= WindowInternalFlag::CHILD_WINDOW; @@ -1774,11 +1790,13 @@ namespace gal::prometheus::gui::internal } const auto child_window_name = std::format("{}.{}", name_, name); - internal::begin_window(context, child_window_name, size, background_fill_alpha, flag); + context.begin_window(child_window_name, size, background_fill_alpha, flag); + auto& window = context.current_window(); if (border and not flag_.is()) { auto& child = *children_this_frame_.back(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(std::addressof(window) == std::addressof(child)); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(child.name_ == child_window_name); // border only indicates whether the child window has a border, not the widgets of the child window @@ -1789,7 +1807,7 @@ namespace gal::prometheus::gui::internal // ReSharper disable once CppParameterMayBeConstPtrOrRef auto Window::end_child_window(Context& context, Window& child) noexcept -> void { - gui::end_window(context); + context.end_window(); // When using autofill child window, we don't provide the width/height to `adjust_item_size` so that it doesn't feed back into automatic size-fitting auto size = child.size_; @@ -1806,20 +1824,20 @@ namespace gal::prometheus::gui::internal drawer.adjust_item_size(context, size); } - auto Window::render(Context& context) const noexcept -> void + auto Window::render(Context& context, draw_lists_type& draw_lists) const noexcept -> void { if (not visible_) { return; } - context.draw_lists.emplace_back(draw_list_); + draw_lists.emplace_back(draw_list_); std::ranges::for_each( children_this_frame_, [&](auto& child) noexcept -> void { - child->render(context); + child->render(context, draw_lists); } ); } @@ -1832,8 +1850,8 @@ namespace gal::prometheus::gui::internal } accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = const_cast(*this)}; const auto font_size = drawer.font_size(context); @@ -1888,7 +1906,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), sub_string, Font::no_auto_wrap ); @@ -1936,7 +1954,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, this_wrap_width ); @@ -1951,8 +1969,8 @@ namespace gal::prometheus::gui::internal } accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; @@ -1987,17 +2005,17 @@ namespace gal::prometheus::gui::internal } const auto id = id_maker.make_id(context, utf8_text); - const auto state = test_mouse(context, id, button_rect, repeat_when_held); + const auto state = context.test_mouse(id, button_rect, repeat_when_held); - color_type button_color = color_of(theme, ThemeCategory::BUTTON); + color_type button_color = context.color_of(theme, ThemeCategory::BUTTON); { if (state & MouseState::KEEPING or state & MouseState::PRESSED) { - button_color = color_of(theme, ThemeCategory::BUTTON_ACTIVATED); + button_color = context.color_of(theme, ThemeCategory::BUTTON_ACTIVATED); } else if (state & MouseState::HOVERED) { - button_color = color_of(theme, ThemeCategory::BUTTON_HOVERED); + button_color = context.color_of(theme, ThemeCategory::BUTTON_HOVERED); } } @@ -2030,7 +2048,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, button_rect.width() ); @@ -2051,8 +2069,8 @@ namespace gal::prometheus::gui::internal } accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; @@ -2078,17 +2096,17 @@ namespace gal::prometheus::gui::internal } const auto id = id_maker.make_id(context, utf8_text); - const auto state = test_mouse(context, id, button_rect, repeat_when_held); + const auto state = context.test_mouse(id, button_rect, repeat_when_held); - color_type button_color = color_of(theme, ThemeCategory::BUTTON); + color_type button_color = context.color_of(theme, ThemeCategory::BUTTON); { if (state & MouseState::KEEPING or state & MouseState::PRESSED) { - button_color = color_of(theme, ThemeCategory::BUTTON_ACTIVATED); + button_color = context.color_of(theme, ThemeCategory::BUTTON_ACTIVATED); } else if (state & MouseState::HOVERED) { - button_color = color_of(theme, ThemeCategory::BUTTON_HOVERED); + button_color = context.color_of(theme, ThemeCategory::BUTTON_HOVERED); } } @@ -2102,7 +2120,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_area_point, - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, button_rect.width() ); @@ -2118,8 +2136,8 @@ namespace gal::prometheus::gui::internal } accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; @@ -2159,16 +2177,16 @@ namespace gal::prometheus::gui::internal const auto id = id_maker.make_id(context, utf8_text); // fixme: test check_rect or total_rect? - const auto state = test_mouse(context, id, check_rect, false); + const auto state = context.test_mouse(id, check_rect, false); // draw ○ if (state & MouseState::HOVERED) { - drawer.draw_widget_frame(context, check_circle, color_of(theme, ThemeCategory::RADIO_BUTTON_HOVERED)); + drawer.draw_widget_frame(context, check_circle, context.color_of(theme, ThemeCategory::RADIO_BUTTON_HOVERED)); } else { - drawer.draw_widget_frame(context, check_circle, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + drawer.draw_widget_frame(context, check_circle, context.color_of(theme, ThemeCategory::FRAME_BACKGROUND)); } if (checked) @@ -2176,7 +2194,7 @@ namespace gal::prometheus::gui::internal const auto check_fill_point = check_point + theme.item_inner_spacing; const auto check_fill_size = check_size - theme.item_inner_spacing * 2; const circle_type check_fill_circle{check_fill_point + check_fill_size / 2, check_fill_size.width / 2}; - draw_list_.circle_filled(check_fill_circle, color_of(theme, ThemeCategory::RADIO_BUTTON_ACTIVATED)); + draw_list_.circle_filled(check_fill_circle, context.color_of(theme, ThemeCategory::RADIO_BUTTON_ACTIVATED)); } // draw text @@ -2184,7 +2202,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, text_rect.width() ); @@ -2200,8 +2218,8 @@ namespace gal::prometheus::gui::internal } accessed_ = true; - const auto& theme = current_theme(context); - const auto& font = current_font(context); + const auto& theme = context.current_theme(); + const auto& font = context.current_font(); Drawer drawer{.self = const_cast(*this)}; const IdMaker id_maker{.self = *this}; @@ -2241,16 +2259,16 @@ namespace gal::prometheus::gui::internal const auto id = id_maker.make_id(context, utf8_text); // fixme: test check_rect or total_rect? - const auto state = test_mouse(context, id, check_rect, false); + const auto state = context.test_mouse(id, check_rect, false); // draw □ if (state & MouseState::HOVERED) { - drawer.draw_widget_frame(context, check_rect, color_of(theme, ThemeCategory::CHECKBOX_HOVERED)); + drawer.draw_widget_frame(context, check_rect, context.color_of(theme, ThemeCategory::CHECKBOX_HOVERED)); } else { - drawer.draw_widget_frame(context, check_rect, color_of(theme, ThemeCategory::FRAME_BACKGROUND)); + drawer.draw_widget_frame(context, check_rect, context.color_of(theme, ThemeCategory::FRAME_BACKGROUND)); } if (state & MouseState::PRESSED) @@ -2263,7 +2281,7 @@ namespace gal::prometheus::gui::internal const auto check_fill_point = check_point + theme.item_inner_spacing; const auto check_fill_size = check_size - theme.item_inner_spacing * 2; const rect_type check_fill_rect{check_fill_point, check_fill_size}; - draw_list_.rect_filled(check_fill_rect, color_of(theme, ThemeCategory::CHECKBOX_ACTIVATED)); + draw_list_.rect_filled(check_fill_rect, context.color_of(theme, ThemeCategory::CHECKBOX_ACTIVATED)); } // draw text @@ -2271,7 +2289,7 @@ namespace gal::prometheus::gui::internal font, font_size, text_rect.left_top(), - color_of(theme, ThemeCategory::TEXT), + context.color_of(theme, ThemeCategory::TEXT), utf8_text, text_rect.width() ); @@ -2356,7 +2374,7 @@ namespace gal::prometheus::gui::internal return; } - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); canvas_.height_current_line = canvas_.height_previous_line; canvas_.cursor_current_line = canvas_.cursor_previous_line; @@ -2488,7 +2506,7 @@ namespace gal::prometheus::gui::internal if (clip_rect_stack_.empty()) { - const auto size = context.io.display_size; + const auto size = context.io().display_size; draw_list_.push_clip_rect({0, 0, size}, false); } else @@ -2551,7 +2569,7 @@ namespace gal::prometheus::gui::internal auto Window::content_region_max(const Context& context) const noexcept -> extent_type { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); const Drawer drawer{.self = const_cast(*this)}; auto size = size_ - drawer.window_padding(context); @@ -2578,7 +2596,7 @@ namespace gal::prometheus::gui::internal auto Window::window_content_region_max(const Context& context) const noexcept -> extent_type { - const auto& theme = current_theme(context); + const auto& theme = context.current_theme(); const Drawer drawer{.self = const_cast(*this)}; auto size = size_ - drawer.window_padding(context); @@ -2593,6 +2611,9 @@ namespace gal::prometheus::gui::internal auto Window::is_hovered(const Context& context, const rect_type& rect) const noexcept -> bool { + const auto& mouse = context.mouse(); + const auto position = mouse.position_current; + const auto clipped = [&]() noexcept -> rect_type { if (not clip_rect_stack_.empty()) @@ -2605,7 +2626,7 @@ namespace gal::prometheus::gui::internal return rect; }(); - return clipped.includes(context.mouse.position_current); + return clipped.includes(position); } auto Window::is_item_hovered(const Context& context) const noexcept -> bool diff --git a/src/gui/internal/window.hpp b/src/gui/internal/window.hpp index c3e3fef..ad0d1f9 100644 --- a/src/gui/internal/window.hpp +++ b/src/gui/internal/window.hpp @@ -144,8 +144,9 @@ namespace gal::prometheus::gui::internal /** * @brief If a window has already been created, each "re-creation" only resets its flag (if necessary) * @param flag window flag + * @param size If the current window is a child window, set the window size to @c size */ - auto reset(WindowFlag flag) noexcept -> void; + auto reset(WindowFlag flag, const extent_type& size) noexcept -> void; /** * @brief Handles all input devices (mouse and keyboard) at the beginning of each frame (if necessary) @@ -160,15 +161,13 @@ namespace gal::prometheus::gui::internal * @param context * @param parent If the current window is a child window, then @c parent is its parent, otherwise this parameter must be a null pointer * @param background_fill_alpha Alpha for window background color fill - * @param size If the current window is a child window, set the window size to @c size * @return Whether the window is visible (not closed) * @note Widgets can be drawn in the window if and only if its canvas has been created */ [[nodiscard]] auto begin_window( Context& context, Window* parent, - alpha_type background_fill_alpha, - const extent_type& size + alpha_type background_fill_alpha ) noexcept -> bool; auto end_window(Context& context) noexcept -> void; @@ -182,7 +181,7 @@ namespace gal::prometheus::gui::internal ) noexcept -> void; auto end_child_window(Context& context, Window& child) noexcept -> void; - auto render(Context& context) const noexcept -> void; + auto render(Context& context, draw_lists_type& draw_lists) const noexcept -> void; // ----------------------------------- // WIDGETS From c7878afdc044812e60f5ebf3f2b8e6501fe03278 Mon Sep 17 00:00:00 2001 From: life4gal Date: Mon, 21 Apr 2025 15:47:44 +0800 Subject: [PATCH 21/54] =?UTF-8?q?`feat`:=20Add=20functional::EnumWrapper.?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functional/enumeration.hpp | 319 +++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) diff --git a/src/functional/enumeration.hpp b/src/functional/enumeration.hpp index 49ff4d2..be3f647 100644 --- a/src/functional/enumeration.hpp +++ b/src/functional/enumeration.hpp @@ -214,3 +214,322 @@ template { return !std::to_underlying(e); } + +namespace gal::prometheus::functional +{ + template + requires std::is_scoped_enum_v + class EnumWrapper + { + public: + using type = EnumType; + using underlying_type = std::underlying_type_t; + + private: + constexpr static auto is_flag = meta::user_defined::enum_is_flag::value; + + underlying_type value_; + + public: + constexpr explicit (false) EnumWrapper(const underlying_type value) noexcept + : value_{value} {} + + constexpr explicit (false) EnumWrapper(const type e) noexcept + : EnumWrapper{std::to_underlying(e)} {} + + [[nodiscard]] constexpr explicit (false) operator underlying_type() const noexcept + { + return value_; + } + + [[nodiscard]] constexpr explicit (false) operator type() const noexcept + { + return static_cast(value_); + } + + // ============================================================================= + // operator| + + template + [[nodiscard]] friend constexpr auto operator|(const EnumWrapper lhs, const ValueType rhs) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {lhs.operator type() | rhs}; + } + else + { + return {lhs.operator underlying_type() | rhs}; + } + } + + template + [[nodiscard]] friend constexpr auto operator|(const ValueType lhs, const EnumWrapper rhs) noexcept -> ValueType + { + if constexpr (is_flag) + { + return lhs | rhs.operator type(); + } + else + { + return lhs | rhs.operator underlying_type(); + } + } + + [[nodiscard]] friend constexpr auto operator|(const EnumWrapper lhs, const type rhs) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {lhs.operator type() | rhs}; + } + else + { + return {lhs.operator underlying_type() | rhs}; + } + } + + [[nodiscard]] friend constexpr auto operator|(const type lhs, const EnumWrapper rhs) noexcept -> type + { + if constexpr (is_flag) + { + return lhs | rhs.operator type(); + } + else + { + return lhs | rhs.operator underlying_type(); + } + } + + // ============================================================================= + // operator|= + + template + friend constexpr auto operator|=(EnumWrapper& lhs, const ValueType rhs) noexcept -> EnumWrapper& + { + lhs = lhs | rhs; + return lhs; + } + + template + friend constexpr auto operator|=(ValueType& lhs, const EnumWrapper rhs) noexcept -> ValueType& + { + lhs = lhs | rhs; + return lhs; + } + + friend constexpr auto operator|=(EnumWrapper& lhs, const type rhs) noexcept -> EnumWrapper& + { + lhs = lhs | rhs; + return lhs; + } + + friend constexpr auto operator|=(type& lhs, const EnumWrapper rhs) noexcept -> type& + { + lhs = lhs | rhs; + return lhs; + } + + // ============================================================================= + // operator& + + template + [[nodiscard]] friend constexpr auto operator&(const EnumWrapper lhs, const ValueType rhs) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {lhs.operator type() & rhs}; + } + else + { + return {lhs.operator underlying_type() & rhs}; + } + } + + template + [[nodiscard]] friend constexpr auto operator&(const ValueType lhs, const EnumWrapper rhs) noexcept -> ValueType + { + if constexpr (is_flag) + { + return lhs & rhs.operator type(); + } + else + { + return lhs & rhs.operator underlying_type(); + } + } + + [[nodiscard]] friend constexpr auto operator&(const EnumWrapper lhs, const type rhs) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {lhs.operator type() & rhs}; + } + else + { + return {lhs.operator underlying_type() & rhs}; + } + } + + [[nodiscard]] friend constexpr auto operator&(const type lhs, const EnumWrapper rhs) noexcept -> type + { + if constexpr (is_flag) + { + return lhs & rhs.operator type(); + } + else + { + return lhs & rhs.operator underlying_type(); + } + } + + // ============================================================================= + // operator&= + + template + friend constexpr auto operator&=(EnumWrapper& lhs, const ValueType rhs) noexcept -> EnumWrapper& + { + lhs = lhs & rhs; + return lhs; + } + + template + friend constexpr auto operator&=(ValueType& lhs, const EnumWrapper rhs) noexcept -> ValueType& + { + lhs = lhs & rhs; + return lhs; + } + + friend constexpr auto operator&=(EnumWrapper& lhs, const type rhs) noexcept -> EnumWrapper& + { + lhs = lhs & rhs; + return lhs; + } + + friend constexpr auto operator&=(type& lhs, const EnumWrapper rhs) noexcept -> type& + { + lhs = lhs & rhs; + return lhs; + } + + // ============================================================================= + // operator^ + + template + [[nodiscard]] friend constexpr auto operator^(const EnumWrapper lhs, const ValueType rhs) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {lhs.operator type() ^ rhs}; + } + else + { + return {lhs.operator underlying_type() ^ rhs}; + } + } + + template + [[nodiscard]] friend constexpr auto operator^(const ValueType lhs, const EnumWrapper rhs) noexcept -> ValueType + { + if constexpr (is_flag) + { + return lhs ^ rhs.operator type(); + } + else + { + return lhs ^ rhs.operator underlying_type(); + } + } + + [[nodiscard]] friend constexpr auto operator^(const EnumWrapper lhs, const type rhs) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {lhs.operator type() ^ rhs}; + } + else + { + return {lhs.operator underlying_type() ^ rhs}; + } + } + + [[nodiscard]] friend constexpr auto operator^(const type lhs, const EnumWrapper rhs) noexcept -> type + { + if constexpr (is_flag) + { + return lhs ^ rhs.operator type(); + } + else + { + return lhs ^ rhs.operator underlying_type(); + } + } + + // ============================================================================= + // operator^= + + template + friend constexpr auto operator^=(EnumWrapper& lhs, const ValueType rhs) noexcept -> EnumWrapper& + { + lhs = lhs ^ rhs; + return lhs; + } + + template + friend constexpr auto operator^=(ValueType& lhs, const EnumWrapper rhs) noexcept -> ValueType& + { + lhs = lhs ^ rhs; + return lhs; + } + + friend constexpr auto operator^=(EnumWrapper& lhs, const type rhs) noexcept -> EnumWrapper& + { + lhs = lhs ^ rhs; + return lhs; + } + + friend constexpr auto operator^=(type& lhs, const EnumWrapper rhs) noexcept -> type& + { + lhs = lhs ^ rhs; + return lhs; + } + + // ============================================================================= + // operator~ + + [[nodiscard]] friend constexpr auto operator~(const EnumWrapper self) noexcept -> EnumWrapper + { + if constexpr (is_flag) + { + return {~self.operator type()}; + } + else + { + return {~self.operator underlying_type()}; + } + } + + // ============================================================================= + // operator~ + + [[nodiscard]] friend constexpr auto operator!(const EnumWrapper self) noexcept -> bool + { + if constexpr (is_flag) + { + return !self.operator type(); + } + else + { + return !self.operator underlying_type(); + } + } + }; +} + +namespace std +{ + template + struct underlying_type> // NOLINT(cert-dcl58-cpp) + { + using type = typename gal::prometheus::functional::EnumWrapper::underlying_type; + }; +} From 94c28becbeca96d0acb9ecd26168d3718ab5fc6c Mon Sep 17 00:00:00 2001 From: life4gal Date: Mon, 21 Apr 2025 15:48:17 +0800 Subject: [PATCH 22/54] `chore`: Remove unneeded macro definitions. --- src/prometheus/macro.hpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/prometheus/macro.hpp b/src/prometheus/macro.hpp index d4b8f5c..c207b8b 100644 --- a/src/prometheus/macro.hpp +++ b/src/prometheus/macro.hpp @@ -482,17 +482,3 @@ #define GAL_PROMETHEUS_ERROR_DEBUG_ASSUME GAL_PROMETHEUS_ERROR_ASSUME #endif - -// ========================================================= -// MODULE: gal.prometheus.draw -// ========================================================= - - - -#if defined(DEBUG) or defined(_DEBUG) - -#if not defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) -#define GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG -#endif - -#endif From 77fcf350b176f1734599914d227b2c7570522627 Mon Sep 17 00:00:00 2001 From: life4gal Date: Mon, 21 Apr 2025 15:49:22 +0800 Subject: [PATCH 23/54] =?UTF-8?q?`test`:=20gui::tooltip=20&=20gui::combo.?= =?UTF-8?q?=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unit_test/src/gui/dx11/backend.cpp | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/unit_test/src/gui/dx11/backend.cpp b/unit_test/src/gui/dx11/backend.cpp index 6b53295..91dbdba 100644 --- a/unit_test/src/gui/dx11/backend.cpp +++ b/unit_test/src/gui/dx11/backend.cpp @@ -442,6 +442,8 @@ auto prometheus_render() -> void { window_closed = gui::begin_window("Window 1", {640, 480}); + gui::draw_text(std::format("FPS: {:.3}", g_fps)); + static bool theme_window_closed = true; theme_window_closed ^= gui::draw_button("OpenThemeEditor"); @@ -532,7 +534,10 @@ auto prometheus_render() -> void gui::draw_text(test_string); - gui::begin_child_window("SliderWindowChild"); + static bool border = false; + border ^= gui::draw_button("border"); + + gui::begin_child_window("SliderWindowChild", {}, border); static std::array v{0, 0, 0, 0, 0}; @@ -549,6 +554,27 @@ auto prometheus_render() -> void } } + gui::draw_text_colored("Tooltip", primitive::colors::red); + { + gui::draw_text("Hover me"); + if (gui::is_item_hovered()) + { + gui::begin_tooltip_window(); + + gui::draw_text("You found me!"); + + gui::end_tooltip_window(); + } + } + + gui::draw_text_colored("Combo", primitive::colors::red); + { + static std::array selections{"selection1", "selection2", "selection3", "selection4", "selection5", "selection6", "selection7", "selection8", "selection9"}; + static std::size_t selected = 8; + + gui::draw_combo("Combo", selections, selected); + } + gui::end_window(); } From 5ae7c5fb2148d72bc235394b1cd1f714a290e396 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 28 Apr 2025 23:22:13 +0800 Subject: [PATCH 24/54] =?UTF-8?q?`fix`:=20functional::overladed=20no=20lon?= =?UTF-8?q?ger=20requires=20inline=20lambda.=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functional/functor.hpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/functional/functor.hpp b/src/functional/functor.hpp index 7b12a56..cdc295d 100644 --- a/src/functional/functor.hpp +++ b/src/functional/functor.hpp @@ -150,8 +150,20 @@ namespace gal::prometheus::functional template struct overloaded : Ts... { - constexpr explicit overloaded(Ts&&... ts) noexcept((std::is_nothrow_constructible_v and ...)) - : Ts{std::forward(ts)}... {} + // const auto visitor = overloaded + // { + // [](const T1&) { ... }, + // [](const T2&) { ... }, + // [](const T3&) { ... }, + // }; + constexpr explicit overloaded(Ts&&... ts) noexcept((std::is_nothrow_constructible_v and ...)) : Ts{std::forward(ts)}... {} + + // const auto lambda1 = [](const T1&) { ... }; + // const auto lambda2 = [](const T2&) { ... }; + // const auto lambda3 = [](const T3&) { ... }; + // + // const auto visitor = overloaded{ lambda1, lambda2, lambda3 }; + constexpr explicit overloaded(const Ts&... ts) noexcept((std::is_nothrow_constructible_v and ...)) : Ts{ts}... {} // This makes all overloads viable to participate in the resolution using Ts::operator()...; From 616cf48e8fbeab8d9e88b38c17e9fb1f4cf440b5 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 28 Apr 2025 23:23:40 +0800 Subject: [PATCH 25/54] =?UTF-8?q?`feat`:=20Add=20io.inputs.=20(handles=20i?= =?UTF-8?q?nput=20from=20devices=20such=20as=20keyboard/mouse)=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 14 + src/io/device.ixx | 365 -------------------------- src/io/inputs.cpp | 557 +++++++++++++++++++++++++++++++++++++++ src/io/inputs.hpp | 589 ++++++++++++++++++++++++++++++++++++++++++ src/io/io.hpp | 8 + src/io/io.ixx | 22 -- 6 files changed, 1168 insertions(+), 387 deletions(-) delete mode 100644 src/io/device.ixx create mode 100644 src/io/inputs.cpp create mode 100644 src/io/inputs.hpp create mode 100644 src/io/io.hpp delete mode 100644 src/io/io.ixx diff --git a/scripts/library.cmake b/scripts/library.cmake index 1a3aba8..e3295d5 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -357,6 +357,14 @@ set( ${PROJECT_SOURCE_DIR}/src/unit_test/unit_test.hpp + # ========================= + # IO + # ========================= + + ${PROJECT_SOURCE_DIR}/src/io/inputs.hpp + + ${PROJECT_SOURCE_DIR}/src/io/io.hpp + # ========================= # GUI # ========================= @@ -402,6 +410,12 @@ set( ${PROJECT_SOURCE_DIR}/src/chars/scalar.cpp ${PROJECT_SOURCE_DIR}/src/chars/icelake.cpp + # ========================= + # IO + # ========================= + + ${PROJECT_SOURCE_DIR}/src/io/inputs.cpp + # ========================= # GUI # ========================= diff --git a/src/io/device.ixx b/src/io/device.ixx deleted file mode 100644 index 0bca879..0000000 --- a/src/io/device.ixx +++ /dev/null @@ -1,365 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED - -#include - -export module gal.prometheus:io.device; - -import std; - -#if GAL_PROMETHEUS_COMPILER_DEBUG -import :platform; -#endif - -#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED - -#if not GAL_PROMETHEUS_USE_MODULE - -#pragma once - -#include -#include -#include -#include - -#include - -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -#if GAL_PROMETHEUS_INTELLISENSE_WORKING -namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: io -#else -GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(io) -#endif -{ - enum class DeviceKey : std::uint16_t - { - NONE, - - // ======================================== - // keyboard <------------------------------------------------------- - // from left to right, top to bottom - // ======================================== - - KB_ESCAPE, - - // ================== - // function keys - - KB_F1, - KB_F2, - KB_F3, - KB_F4, - KB_F5, - KB_F6, - KB_F7, - KB_F8, - KB_F9, - KB_F10, - KB_F11, - FK_F12, - - // ================== - // main area - - // ` - KB_GRAVE_ACCENT, - KB_1, - KB_2, - KB_3, - KB_4, - KB_5, - KB_6, - KB_7, - KB_8, - KB_9, - KB_0, - // - OR _ - KB_MINUS, - // = OR + - KB_PLUS, - KB_BACKSPACE, - - KB_TAB, - KB_Q, - KB_W, - KB_E, - KB_R, - KB_T, - KB_Y, - KB_U, - KB_I, - KB_O, - KB_P, - // [ OR { - KB_LEFT_BRACKET, - // ] OR } - KB_RIGHT_BRACKET, - // \ OR | - KB_BACKSLASH, - - KB_CAPS_LOCK, - KB_A, - KB_S, - KB_D, - KB_F, - KB_G, - KB_H, - KB_J, - KB_K, - KB_L, - // ; OR : - KB_SEMICOLON, - // ' OR " - KB_QUOTATION, - KB_ENTER, - - KB_LEFT_SHIFT, - KB_Z, - KB_X, - KB_C, - KB_V, - KB_B, - KB_N, - KB_M, - // , OR < - KB_COMMA, - // . OR > - KB_PERIOD, - // / OR ? - KB_SLASH, - KB_RIGHT_SHIFT, - - KB_LEFT_CTRL, - KB_LEFT_SUPER, - KB_LEFT_ALT, - KB_SPACE, - KB_RIGHT_ALT, - KB_RIGHT_SUPER, - KB_MENU, - KB_RIGHT_CTRL, - - // ================== - // navigation keys - - KB_PAUSE, - KB_SCROLL_LOCK, - KB_PRINT_SCREEN, - - KB_INSERT, - KB_HOME, - KB_PAGE_UP, - KB_DELETE, - KB_END, - KB_PAGE_DOWN, - - KB_ARROW_UP, - KB_ARROW_LEFT, - KB_ARROW_DOWN, - KB_ARROW_RIGHT, - - // ================== - // Numeric Keypad - - KB_KEYPAD_NUM_LOCK, - // / - KB_KEYPAD_DIVIDE, - // * - KB_KEYPAD_MULTIPLY, - // - - KB_KEYPAD_MINUS, - - KB_KEYPAD_7, - KB_KEYPAD_8, - KB_KEYPAD_9, - KB_KEYPAD_PLUS, - - KB_KEYPAD_4, - KB_KEYPAD_5, - KB_KEYPAD_6, - - KB_KEYPAD_1, - KB_KEYPAD_2, - KB_KEYPAD_3, - KB_KEYPAD_ENTER, - - KB_KEYPAD_0, - KB_KEYPAD_DECIMAL, - - // ======================================== - // -------------------------------------------------------> keyboard - // ======================================== - - // incoming - }; - - enum class MouseButton : std::uint32_t - { - LEFT = 0, - MIDDLE = 1, - RIGHT = 2, - X1 = 3, - X2 = 4 - }; - - enum class MouseButtonStatus : std::uint32_t - { - PRESS, - RELEASE, - }; - - enum class DeviceActionType : std::uint8_t - { - MOUSE_MOV = 0, - MOUSE_BUTTON = 1, - MOUSE_WHEEL = 2, - - TEXT = 3, - KEY = 4, - }; - - class [[nodiscard]] DeviceEvent - { - public: - struct mouse_mov_type - { - float x; - float y; - }; - - struct mouse_button_type - { - MouseButton button; - MouseButtonStatus status; - }; - - struct mouse_wheel_type - { - float x; - float y; - }; - - struct text_type - { - std::uint32_t c; - std::uint32_t pad; - }; - - struct key_type - { - DeviceKey key; - bool down; - float analog_value; - - static_assert(sizeof(DeviceKey) == 2); - std::uint8_t pad; - }; - - using underlying_type = std::variant; - static_assert(std::is_same_v(DeviceActionType::MOUSE_MOV), underlying_type>, mouse_mov_type>); - static_assert(std::is_same_v(DeviceActionType::MOUSE_BUTTON), underlying_type>, mouse_button_type>); - static_assert(std::is_same_v(DeviceActionType::MOUSE_WHEEL), underlying_type>, mouse_wheel_type>); - static_assert(std::is_same_v(DeviceActionType::TEXT), underlying_type>, text_type>); - static_assert(std::is_same_v(DeviceActionType::KEY), underlying_type>, key_type>); - - private: - underlying_type underlying_; - - public: - template - requires std::is_constructible_v, Args...> - constexpr explicit DeviceEvent(std::in_place_type_t type, Args&&... args) noexcept - : underlying_{type, std::forward(args)...} {} - - template - requires std::is_constructible_v - constexpr explicit DeviceEvent(Args&&... args) noexcept - : underlying_{std::forward(args)...} {} - - [[nodiscard]] constexpr auto type() const noexcept -> DeviceActionType - { - return static_cast(underlying_.index()); - } - - // note: unchecked - template - [[nodiscard]] constexpr auto value() const noexcept -> const auto& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(underlying_.index() == static_cast(Type)); - return std::get(Type)>(underlying_); - } - - template - constexpr auto visit(Callable&& callable) const noexcept -> void - { - std::visit(std::forward(callable), underlying_); - } - }; - - class DeviceEventQueue - { - public: - template - using list_type = std::vector; - - using queue_type = list_type; - using size_type = queue_type::size_type; - - private: - queue_type queue_; - - public: - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - return queue_.size(); - } - - [[nodiscard]] constexpr auto consume() noexcept -> DeviceEvent - { - const auto e = queue_.back(); - queue_.pop_back(); - return e; - } - - constexpr auto mouse_move(const float x, const float y) noexcept -> void - { - // queue_.emplace_back(std::in_place_type, x, y); - queue_.emplace_back(DeviceEvent::mouse_mov_type{.x = x, .y = y}); - } - - constexpr auto mouse_button(const MouseButton button, const MouseButtonStatus status) noexcept -> void - { - // queue_.emplace_back(std::in_place_type, button, status); - queue_.emplace_back(DeviceEvent::mouse_button_type{.button = button, .status = status}); - } - - constexpr auto mouse_wheel(const float x, const float y) noexcept -> void - { - // queue_.emplace_back(std::in_place_type, x, y); - queue_.emplace_back(DeviceEvent::mouse_wheel_type{.x = x, .y = y}); - } - - constexpr auto text(const std::uint32_t c) noexcept -> void - { - // queue_.emplace_back(std::in_place_type, c); - queue_.emplace_back(DeviceEvent::text_type{.c = c, .pad = 0}); - } - - constexpr auto key(const DeviceKey key, const bool down, const float analog_value) noexcept -> void - { - // queue_.emplace_back(std::in_place_type, key, down, analog_value); - queue_.emplace_back(DeviceEvent::key_type{.key = key, .down = down, .analog_value = analog_value, .pad = 0}); - } - - constexpr auto key(const DeviceKey key, const bool down) noexcept -> void - { - return this->key(key, down, down ? 1.f : 0.f); - } - }; -} diff --git a/src/io/inputs.cpp b/src/io/inputs.cpp new file mode 100644 index 0000000..7dd4600 --- /dev/null +++ b/src/io/inputs.cpp @@ -0,0 +1,557 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include + +namespace +{ + using namespace gal::prometheus; + using namespace io; + + [[nodiscard]] auto current_time() noexcept -> time_point_type + { + return std::chrono::time_point_cast(time_point_type::clock::now()); + } +} + +namespace gal::prometheus::io +{ + auto InputHandler::Mouse::reset(const time_point_type& time_point, const InputHandler& handler) noexcept -> void + { + std::ranges::for_each( + states, + [&](key_state_type& state) noexcept -> void + { + state.action_this_frame = DeviceKeyAction::NONE; + + const auto view = state.press_records | std::views::reverse; + const auto it = std::ranges::find_if( + view, + [&](const press_record_type& record) noexcept -> bool + { + if (const auto interval = record.time_point - time_point; interval > handler.mouse_double_click_interval_threshold_) + { + return true; + } + + if (const auto distance = record.position.distance(position_current); distance > handler.mouse_double_click_distance_threshold_) + { + return true; + } + + return false; + } + ); + state.press_records.erase(state.press_records.begin(), it.base()); + } + ); + + position_delta = {0, 0}; + wheel = {0, 0}; + } + + auto InputHandler::Mouse::is_up(const InputHandler& self, const MouseButton button) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(button); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.action_this_frame == DeviceKeyAction::UP; + } + + auto InputHandler::Mouse::is_down(const InputHandler& self, const MouseButton button) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(button); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.action_this_frame == DeviceKeyAction::DOWN; + } + + auto InputHandler::Mouse::is_clicked(const InputHandler& self, const MouseButton button) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(button); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.action_this_frame == DeviceKeyAction::DOWN; + } + + auto InputHandler::Mouse::is_double_clicked(const InputHandler& self, const MouseButton button) const noexcept -> bool + { + const auto index = static_cast(button); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + if (state.action_this_frame != DeviceKeyAction::DOWN) + { + return false; + } + + const auto records_count = state.press_records.size(); + if (records_count < 2) + { + return false; + } + + const auto& [c1_time_point, c1_position] = state.press_records[records_count - 2]; + const auto& [c2_time_point, c2_position] = state.press_records[records_count - 1]; + + if (const auto distance = c2_position.distance(c1_position); distance > self.mouse_double_click_distance_threshold_) + { + return false; + } + + if (const auto time = c2_time_point - c1_time_point; time > self.mouse_double_click_interval_threshold_) + { + return false; + } + + return true; + } + + auto InputHandler::Mouse::is_pressing(const InputHandler& self, const MouseButton button) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(button); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.down_last_frame; + } + + auto InputHandler::Keyboard::reset(const time_point_type& time_point, const InputHandler& handler) noexcept -> void + { + std::ranges::for_each( + states, + [&](key_state_type& state) noexcept -> void + { + state.action_this_frame = DeviceKeyAction::NONE; + + const auto view = state.press_records | std::views::reverse; + const auto it = std::ranges::find_if( + view, + [&](const press_record_type& record) noexcept -> bool + { + if (const auto interval = record.time_point - time_point; interval > handler.mouse_double_click_interval_threshold_) + { + return true; + } + + return false; + } + ); + state.press_records.erase(state.press_records.begin(), it.base()); + } + ); + } + + auto InputHandler::Keyboard::is_up(const InputHandler& self, const KeyboardKeyCode code) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(code); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.action_this_frame == DeviceKeyAction::UP; + } + + auto InputHandler::Keyboard::is_down(const InputHandler& self, const KeyboardKeyCode code) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(code); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.action_this_frame == DeviceKeyAction::DOWN; + } + + auto InputHandler::Keyboard::is_pressing(const InputHandler& self, const KeyboardKeyCode code) const noexcept -> bool + { + std::ignore = self; + + const auto index = static_cast(code); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < states.size()); + + const auto& state = states[index]; + + return state.down_last_frame; + } + + auto InputHandler::Keyboard::is_combination_pressing(const InputHandler& self, const std::span codes) const noexcept -> bool + { + const auto first = codes | std::views::take(codes.size() - 1); + const auto last = codes.back(); + + if (not std::ranges::all_of( + first, + [&](const KeyboardKeyCode code) noexcept -> bool + { + return is_pressing(self, code); + } + ) + ) + { + return false; + } + + return is_down(self, last); + } + + auto InputHandler::Keyboard::is_combination_pressing(const InputHandler& self, std::initializer_list codes) const noexcept -> bool + { + const std::span list{codes}; + + return is_combination_pressing(self, list); + } + + auto InputHandler::process_event(const input_event_type& event) noexcept -> void + { + // ============ + // MOUSE + // ============ + const auto handle_mouse_move = [this](const MouseMoveEventData& data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.source != MouseInputSource::NONE); + + mouse_.input_source = data.source; + + mouse_.position_previous = mouse_.position_current; + mouse_.position_current = data.position; + mouse_.position_delta = (mouse_.position_current - mouse_.position_previous).to(); + }; + const auto handle_mouse_button = [this](const MouseButtonEventData& data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.source != MouseInputSource::NONE); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.action != DeviceKeyAction::NONE); + + mouse_.input_source = data.source; + + const auto button = static_cast(data.button); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(button < mouse_.states.size()); + + auto& state = mouse_.states[button]; + + state.action_this_frame = data.action; + if (data.action == DeviceKeyAction::DOWN) + { + state.down_last_frame = true; + state.press_records.emplace_back(Mouse::press_record_type{.time_point = current_time(), .position = mouse_.position_current}); + } + else + { + state.down_last_frame = false; + } + }; + const auto handle_mouse_wheel = [this](const MouseWheelEventData& data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.source != MouseInputSource::NONE); + mouse_.input_source = data.source; + + mouse_.wheel = data.value; + }; + + // ============ + // KEYBOARD + // ============ + const auto handle_keyboard = [this](const KeyboardEventData& data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.code != KeyboardKeyCode::NONE); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.action != DeviceKeyAction::NONE); + + const auto code = static_cast(data.code); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(code < keyboard_.states.size()); + + auto& state = keyboard_.states[code]; + + state.action_this_frame = data.action; + if (data.action == DeviceKeyAction::DOWN) + { + state.down_last_frame = true; + state.press_records.emplace_back(Keyboard::press_record_type{.time_point = current_time()}); + } + else + { + state.down_last_frame = false; + } + }; + + // ============ + // DISPLAY + // ============ + const auto handle_display_move = [this](const DisplayMoveEventData& data) noexcept -> void + { + display_.position = data.position; + }; + const auto handle_display_resize = [this](const DisplayResizeEventData& data) noexcept -> void + { + display_.size = data.size; + }; + + const auto visitor = functional::overloaded{ + handle_mouse_move, + handle_mouse_button, + handle_mouse_wheel, + handle_keyboard, + handle_display_move, + handle_display_resize, + [](const auto& data) noexcept -> void + { + std::ignore = data; + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + }; + + std::visit(visitor, event); + } + + InputHandler::InputHandler() noexcept + : + // ReSharper disable once CppRedundantMemberInitializer + mutex_{}, + // event_queue_pending_{}, + // event_queue_processing_{}, + mouse_ + { + .input_source = MouseInputSource::NONE, + .position_current = {0, 0}, + .position_previous = {0, 0}, + .position_delta = {0, 0}, + .wheel = {0, 0}, + .states = {} + }, + keyboard_ + { + .states = {} + }, + display_ + { + .position = {0, 0}, + .size = {0, 0} + }, + mouse_double_click_interval_threshold_{std::chrono::milliseconds{300}}, + mouse_double_click_distance_threshold_{6} {} + + auto InputHandler::begin_frame() noexcept -> void + { + { + std::scoped_lock lock{mutex_}; + + event_queue_processing_.append_range(std::move(event_queue_pending_)); + event_queue_pending_.clear(); + } + + std::ranges::for_each( + event_queue_processing_, + [this](const input_event_type& event) noexcept -> void + { + process_event(event); + } + ); + event_queue_processing_.clear(); + } + + auto InputHandler::end_frame() noexcept -> void + { + const auto now = current_time(); + + // ============ + // MOUSE + // ============ + mouse_.reset(now, *this); + + // ============ + // KEYBOARD + // ============ + keyboard_.reset(now, *this); + + // ============ + // DISPLAY + // ============ + // + } + + auto InputHandler::push_event(const input_event_type& event) noexcept -> void + { + std::scoped_lock lock{mutex_}; + event_queue_pending_.push_back(event); + } + + auto InputHandler::mouse_proxy::position() const noexcept -> position_type + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.position_current; + } + + auto InputHandler::mouse_proxy::position_delta() const noexcept -> extent_type + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.position_delta; + } + + auto InputHandler::mouse_proxy::wheel() const noexcept -> extent_type + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.wheel; + } + + auto InputHandler::mouse_proxy::is_up(const MouseButton button) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.is_up(handler, button); + } + + auto InputHandler::mouse_proxy::is_down(const MouseButton button) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.is_down(handler, button); + } + + auto InputHandler::mouse_proxy::is_click(const MouseButton button) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.is_clicked(handler, button); + } + + auto InputHandler::mouse_proxy::is_double_click(const MouseButton button) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.is_double_clicked(handler, button); + } + + auto InputHandler::mouse_proxy::is_pressing(const MouseButton button) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& mouse = handler.mouse_; + + return mouse.is_pressing(handler, button); + } + + auto InputHandler::mouse() const noexcept -> mouse_proxy + { + return {.self = *this}; + } + + auto InputHandler::keyboard_proxy::is_up(const KeyboardKeyCode code) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& keyboard = handler.keyboard_; + + return keyboard.is_up(handler, code); + } + + auto InputHandler::keyboard_proxy::is_down(const KeyboardKeyCode code) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& keyboard = handler.keyboard_; + + return keyboard.is_down(handler, code); + } + + auto InputHandler::keyboard_proxy::is_pressing(const KeyboardKeyCode code) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& keyboard = handler.keyboard_; + + return keyboard.is_pressing(handler, code); + } + + auto InputHandler::keyboard_proxy::is_combination_pressing(const std::span codes) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& keyboard = handler.keyboard_; + + return keyboard.is_combination_pressing(handler, codes); + } + + auto InputHandler::keyboard_proxy::is_combination_pressing(const std::initializer_list codes) const noexcept -> bool + { + const auto& handler = self.get(); + const auto& keyboard = handler.keyboard_; + + return keyboard.is_combination_pressing(handler, codes); + } + + auto InputHandler::keyboard() const noexcept -> keyboard_proxy + { + return {.self = *this}; + } + + auto InputHandler::display_proxy::position() const noexcept -> position_type + { + const auto& handler = self.get(); + // ReSharper disable once CppUseStructuredBinding + const auto& display = handler.display_; + + return display.position; + } + + auto InputHandler::display_proxy::size() const noexcept -> extent_type + { + const auto& handler = self.get(); + // ReSharper disable once CppUseStructuredBinding + const auto& display = handler.display_; + + return display.size; + } + + auto InputHandler::display() const noexcept -> display_proxy + { + return {.self = *this}; + } + + auto InputHandler::config_proxy::set_mouse_double_click_interval_threshold(const duration_type interval) noexcept -> void + { + auto& handler = self.get(); + + handler.mouse_double_click_interval_threshold_ = interval; + } + + auto InputHandler::config_proxy::set_mouse_double_click_distance_threshold(const value_type distance) noexcept -> void + { + auto& handler = self.get(); + + handler.mouse_double_click_distance_threshold_ = distance; + } + + auto InputHandler::config() noexcept -> config_proxy + { + return {.self = *this}; + } +} diff --git a/src/io/inputs.hpp b/src/io/inputs.hpp new file mode 100644 index 0000000..cd654e4 --- /dev/null +++ b/src/io/inputs.hpp @@ -0,0 +1,589 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace gal::prometheus::io +{ + using position_type = primitive::basic_point_2d; + using extent_type = primitive::basic_extent_2d; + + using duration_type = std::chrono::milliseconds; + using time_point_type = std::chrono::time_point; + + // ====================================================================== + // MOUSE + // ====================================================================== + + enum class MouseButton : std::uint8_t + { + LEFT = 0, + MIDDLE, + RIGHT, + X1, + X2, + + INTERNAL_COUNT, + NONE, + }; + + constexpr auto total_mouse_button = static_cast(MouseButton::INTERNAL_COUNT); + + enum class MouseWheel : std::uint8_t + { + HORIZONTAL = 0, + VERTICAL, + + NONE, + }; + + enum class MouseInputSource : std::uint8_t + { + MOUSE = 0, + TOUCH_SCREEN, + PEN, + + INTERNAL_COUNT, + NONE, + }; + + constexpr auto total_mouse_input_source = static_cast(MouseInputSource::INTERNAL_COUNT); + + // ====================================================================== + // KEYBOARD + // ====================================================================== + + // todo: keycode layout + enum class KeyboardKeyCode : std::uint8_t + { + // ======================================== + // from left to right, top to bottom + // ======================================== + + KB_ESCAPE = 0, + + // ================== + // function keys + + KB_F1, + KB_F2, + KB_F3, + KB_F4, + KB_F5, + KB_F6, + KB_F7, + KB_F8, + KB_F9, + KB_F10, + KB_F11, + FK_F12, + + // ================== + // main area + + // ` + KB_GRAVE_ACCENT, + KB_1, + KB_2, + KB_3, + KB_4, + KB_5, + KB_6, + KB_7, + KB_8, + KB_9, + KB_0, + // - OR _ + KB_MINUS, + // = OR + + KB_PLUS, + KB_BACKSPACE, + + KB_TAB, + KB_Q, + KB_W, + KB_E, + KB_R, + KB_T, + KB_Y, + KB_U, + KB_I, // NOLINT(misc-confusable-identifiers) + KB_O, // NOLINT(misc-confusable-identifiers) + KB_P, + // [ OR { + KB_LEFT_BRACKET, + // ] OR } + KB_RIGHT_BRACKET, + // \ OR | + KB_BACKSLASH, + + KB_CAPS_LOCK, + KB_A, + KB_S, + KB_D, + KB_F, + KB_G, + KB_H, + KB_J, + KB_K, + KB_L, + // ; OR : + KB_SEMICOLON, + // ' OR " + KB_QUOTATION, + KB_ENTER, + + KB_LEFT_SHIFT, + KB_Z, + KB_X, + KB_C, + KB_V, + KB_B, + KB_N, + KB_M, + // , OR < + KB_COMMA, + // . OR > + KB_PERIOD, + // / OR ? + KB_SLASH, + KB_RIGHT_SHIFT, + + KB_LEFT_CTRL, + KB_LEFT_SUPER, + KB_LEFT_ALT, + KB_SPACE, + KB_RIGHT_ALT, + KB_RIGHT_SUPER, + KB_MENU, + KB_RIGHT_CTRL, + + // ================== + // navigation keys + + KB_PAUSE, + KB_SCROLL_LOCK, + KB_PRINT_SCREEN, + + KB_INSERT, + KB_HOME, + KB_PAGE_UP, + KB_DELETE, + KB_END, + KB_PAGE_DOWN, + + KB_ARROW_UP, + KB_ARROW_LEFT, + KB_ARROW_DOWN, + KB_ARROW_RIGHT, + + // ================== + // numeric keypad + + KB_KEYPAD_NUM_LOCK, + // / + KB_KEYPAD_DIVIDE, + // * + KB_KEYPAD_MULTIPLY, + // - + KB_KEYPAD_MINUS, + + KB_KEYPAD_7, + KB_KEYPAD_8, + KB_KEYPAD_9, + KB_KEYPAD_PLUS, + + KB_KEYPAD_4, + KB_KEYPAD_5, + KB_KEYPAD_6, + + KB_KEYPAD_1, + KB_KEYPAD_2, + KB_KEYPAD_3, + KB_KEYPAD_ENTER, + + KB_KEYPAD_0, + KB_KEYPAD_DECIMAL, + + INTERNAL_COUNT, + NONE, + }; + + constexpr auto total_keyboard_code = static_cast(KeyboardKeyCode::INTERNAL_COUNT); + + // ====================================================================== + // ALL DEVICE + // ====================================================================== + + enum class DeviceKeyAction : std::uint8_t + { + NONE = 0, + + UP, + DOWN, + }; + + enum class DeviceType : std::uint8_t + { + NONE = 0, + + MOUSE, + KEYBOARD, + DISPLAY, + }; + + #if defined(GAL_PROMETHEUS_IO_INPUTS_FORCE_ALIGN) + + constexpr auto event_data_alignment = sizeof(position_type) + sizeof(extent_type); + + #define GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS alignas(event_data_alignment) + + #else + + #define GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS + + #endif + + #if defined(GAL_PROMETHEUS_IO_INPUTS_FORCE_ALIGN) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + GAL_PROMETHEUS_COMPILER_DISABLE_MSVC_WARNING(4324) + #endif + + // ====================================================================== + // MOUSE + // ====================================================================== + + // 1 byte + 8 bytes + class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS MouseMoveEventData final + { + public: + MouseInputSource source{MouseInputSource::NONE}; + + position_type position{0, 0}; + }; + + // 1 byte + 2 bytes + class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS MouseButtonEventData final + { + public: + MouseInputSource source{MouseInputSource::NONE}; + + MouseButton button{MouseButton::NONE}; + DeviceKeyAction action{DeviceKeyAction::NONE}; + }; + + // 1 byte + 8 bytes + class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS MouseWheelEventData final + { + public: + MouseInputSource source{MouseInputSource::NONE}; + + extent_type value; + }; + + // ====================================================================== + // KEYBOARD + // ====================================================================== + + // 2 bytes + class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS KeyboardEventData final + { + public: + KeyboardKeyCode code{KeyboardKeyCode::NONE}; + DeviceKeyAction action{DeviceKeyAction::NONE}; + }; + + // ====================================================================== + // DISPLAY + // ====================================================================== + + // 8 bytes + class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS DisplayMoveEventData final + { + public: + position_type position{0, 0}; + }; + + // 8 bytes + class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS DisplayResizeEventData final + { + public: + extent_type size{0, 0}; + }; + + #if defined(GAL_PROMETHEUS_IO_INPUTS_FORCE_ALIGN) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + #endif + + // ====================================================================== + // Handler + // ====================================================================== + + using input_event_type = std::variant< + MouseMoveEventData, + MouseButtonEventData, + MouseWheelEventData, + KeyboardEventData, + DisplayMoveEventData, + DisplayResizeEventData + >; + + class InputHandler + { + public: + // todo + #if not defined(GAL_PROMETHEUS_IO_INPUTS_MUTEX) + class TrivialMutex + { + public: + std::uintptr_t pad{0xdead'c0ffee}; + + constexpr auto lock() const noexcept -> void { std::ignore = this; } + + constexpr auto unlock() const noexcept -> void { std::ignore = this; } + }; + + using mutex_type = TrivialMutex; + #else + using mutext_type = GAL_PROMETHEUS_IO_INPUTS_MUTEX + #endif + + using event_queue_type = std::vector; + + using value_type = extent_type::value_type; + + private: + mutable mutex_type mutex_; + + // + event_queue_type event_queue_pending_; + event_queue_type event_queue_processing_; + + // ============ + // MOUSE + // ============ + + class Mouse final + { + public: + struct press_record_type + { + time_point_type time_point; + position_type position; + }; + + struct key_state_type + { + DeviceKeyAction action_this_frame; + bool down_last_frame; + + std::uint8_t pad1; + std::uint8_t pad2; + + std::vector press_records; + }; + + using mouse_states_type = std::array; + + MouseInputSource input_source; + + // Update every frame + // Current mouse position (this frame) + position_type position_current; + // Update every frame + // Previous mouse position (this frame) + position_type position_previous; + // Update every frame + // Mouse distance between two frames + extent_type position_delta; + // Update every frame + // Current mouse wheel value + extent_type wheel; + // Update every frame + // Current mouse button state (this frame) + mouse_states_type states; + + auto reset(const time_point_type& time_point, const InputHandler& handler) noexcept -> void; + + [[nodiscard]] auto is_up(const InputHandler& self, MouseButton button) const noexcept -> bool; + [[nodiscard]] auto is_down(const InputHandler& self, MouseButton button) const noexcept -> bool; + + [[nodiscard]] auto is_clicked(const InputHandler& self, MouseButton button) const noexcept -> bool; + [[nodiscard]] auto is_double_clicked(const InputHandler& self, MouseButton button) const noexcept -> bool; + + [[nodiscard]] auto is_pressing(const InputHandler& self, MouseButton button) const noexcept -> bool; + }; + + Mouse mouse_; + + // ============ + // KEYBOARD + // ============ + + class Keyboard final + { + public: + struct press_record_type + { + time_point_type time_point; + }; + + struct key_state_type + { + DeviceKeyAction action_this_frame; + bool down_last_frame; + + std::uint8_t pad1; + std::uint8_t pad2; + + std::vector press_records; + }; + + using keyboard_states_type = std::array; + + // Update every frame + // Current keyboard button state (this frame) + keyboard_states_type states; + + auto reset(const time_point_type& time_point, const InputHandler& handler) noexcept -> void; + + [[nodiscard]] auto is_up(const InputHandler& self, KeyboardKeyCode code) const noexcept -> bool; + [[nodiscard]] auto is_down(const InputHandler& self, KeyboardKeyCode code) const noexcept -> bool; + + [[nodiscard]] auto is_pressing(const InputHandler& self, KeyboardKeyCode code) const noexcept -> bool; + + [[nodiscard]] auto is_combination_pressing(const InputHandler& self, std::span codes) const noexcept -> bool; + [[nodiscard]] auto is_combination_pressing(const InputHandler& self, std::initializer_list codes) const noexcept -> bool; + }; + + Keyboard keyboard_; + + // ============ + // DISPLAY + // ============ + + struct display_data_type + { + // Update once (it can also be updated every frame basis if desired) + // Current window (viewport) position + position_type position; + // Update once (it can also be updated every frame basis if desired) + // Current window (viewport) size + extent_type size; + }; + + display_data_type display_; + + // ============ + // CONFIG + // ============ + + // Update once (it can also be updated every frame basis if desired) + // The interval between two clicks is less than this threshold to be considered as a double click, in seconds + duration_type mouse_double_click_interval_threshold_; + // Update once (it can also be updated every frame basis if desired) + // The mouse position delta between two clicks is less than this threshold to be considered as a double click + value_type mouse_double_click_distance_threshold_; + + auto process_event(const input_event_type& event) noexcept -> void; + + public: + InputHandler(const InputHandler&) noexcept = delete; + InputHandler(InputHandler&&) noexcept = default; + auto operator=(const InputHandler&) noexcept -> InputHandler& = delete; + auto operator=(InputHandler&&) noexcept -> InputHandler& = default; + ~InputHandler() noexcept = default; + + InputHandler() noexcept; + + auto begin_frame() noexcept -> void; + + auto end_frame() noexcept -> void; + + auto push_event(const input_event_type& event) noexcept -> void; + + // ============ + // MOUSE + // ============ + + struct [[nodiscard]] mouse_proxy final + { + memory::RefWrapper self; + + [[nodiscard]] auto position() const noexcept -> position_type; + [[nodiscard]] auto position_delta() const noexcept -> extent_type; + [[nodiscard]] auto wheel() const noexcept -> extent_type; + + [[nodiscard]] auto is_up(MouseButton button) const noexcept -> bool; + [[nodiscard]] auto is_down(MouseButton button) const noexcept -> bool; + + [[nodiscard]] auto is_click(MouseButton button) const noexcept -> bool; + [[nodiscard]] auto is_double_click(MouseButton button) const noexcept -> bool; + + [[nodiscard]] auto is_pressing(MouseButton button) const noexcept -> bool; + }; + + [[nodiscard]] auto mouse() const noexcept -> mouse_proxy; + + // ============ + // KEYBOARD + // ============ + + struct [[nodiscard]] keyboard_proxy final + { + memory::RefWrapper self; + + [[nodiscard]] auto is_up(KeyboardKeyCode code) const noexcept -> bool; + [[nodiscard]] auto is_down(KeyboardKeyCode code) const noexcept -> bool; + + [[nodiscard]] auto is_pressing(KeyboardKeyCode code) const noexcept -> bool; + + [[nodiscard]] auto is_combination_pressing(std::span codes) const noexcept -> bool; + [[nodiscard]] auto is_combination_pressing(std::initializer_list codes) const noexcept -> bool; + }; + + [[nodiscard]] auto keyboard() const noexcept -> keyboard_proxy; + + // ============ + // DISPLAY + // ============ + + struct [[nodiscard]] display_proxy final + { + memory::RefWrapper self; + + [[nodiscard]] auto position() const noexcept -> position_type; + [[nodiscard]] auto size() const noexcept -> extent_type; + }; + + [[nodiscard]] auto display() const noexcept -> display_proxy; + + // ============ + // CONFIG + // ============ + + struct [[nodiscard]] config_proxy final + { + memory::RefWrapper self; + + auto set_mouse_double_click_interval_threshold(duration_type interval) noexcept -> void; + auto set_mouse_double_click_distance_threshold(value_type distance) noexcept -> void; + }; + + [[nodiscard]] auto config() noexcept -> config_proxy; + }; +} diff --git a/src/io/io.hpp b/src/io/io.hpp new file mode 100644 index 0000000..1d4d9b6 --- /dev/null +++ b/src/io/io.hpp @@ -0,0 +1,8 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include diff --git a/src/io/io.ixx b/src/io/io.ixx deleted file mode 100644 index 1468b62..0000000 --- a/src/io/io.ixx +++ /dev/null @@ -1,22 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED - -#include - -export module gal.prometheus:io; - -export import :io.device; - -#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED - -#if not GAL_PROMETHEUS_USE_MODULE - -#pragma once - -#include - -#endif From e0f29b003e5cd2c70342f66e7aabd3359f269b1e Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 28 Apr 2025 23:26:30 +0800 Subject: [PATCH 26/54] =?UTF-8?q?`fix`:=20User-defined=20classes=20may=20f?= =?UTF-8?q?riend=20meta::extern=5Faccessor=20to=20enable=20access=20to=20t?= =?UTF-8?q?he=20class's=20private=20constructor.=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/meta/member.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/meta/member.hpp b/src/meta/member.hpp index 8425217..73ef40e 100644 --- a/src/meta/member.hpp +++ b/src/meta/member.hpp @@ -15,17 +15,17 @@ namespace gal::prometheus::meta { - namespace member_detail + template + struct extern_accessor { - template - struct extern_accessor + [[nodiscard]] constexpr static auto make() noexcept -> T { - [[nodiscard]] constexpr static auto make() noexcept -> T - { - return T{}; - } - }; + return T{}; + } + }; + namespace member_detail + { // note that this requires the target type to be default constructible template extern const auto extern_any = extern_accessor::make(); From 56f63a10341556e9fc87f62d7e8ac526cf6b1122 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Tue, 29 Apr 2025 23:06:10 +0800 Subject: [PATCH 27/54] =?UTF-8?q?`feat`:=20Allow=20`is=5Fclick`=20to=20be?= =?UTF-8?q?=20triggered=20repeatedly=20when=20a=20button=20is=20held=20dow?= =?UTF-8?q?n.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/io/inputs.cpp | 99 +++++++++++++++++++++++++++++++++-------------- src/io/inputs.hpp | 42 +++++++++++++------- 2 files changed, 98 insertions(+), 43 deletions(-) diff --git a/src/io/inputs.cpp b/src/io/inputs.cpp index 7dd4600..cae3a5b 100644 --- a/src/io/inputs.cpp +++ b/src/io/inputs.cpp @@ -28,7 +28,7 @@ namespace gal::prometheus::io states, [&](key_state_type& state) noexcept -> void { - state.action_this_frame = DeviceKeyAction::NONE; + state.down_this_frame = 0; const auto view = state.press_records | std::views::reverse; const auto it = std::ranges::find_if( @@ -65,7 +65,7 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.action_this_frame == DeviceKeyAction::UP; + return state.down_this_frame == 0; } auto InputHandler::Mouse::is_down(const InputHandler& self, const MouseButton button) const noexcept -> bool @@ -77,10 +77,10 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.action_this_frame == DeviceKeyAction::DOWN; + return state.down_this_frame != 0; } - auto InputHandler::Mouse::is_clicked(const InputHandler& self, const MouseButton button) const noexcept -> bool + auto InputHandler::Mouse::is_clicked(const InputHandler& self, const MouseButton button, const bool repeat) const noexcept -> bool { std::ignore = self; @@ -89,7 +89,30 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.action_this_frame == DeviceKeyAction::DOWN; + if (state.down_this_frame != 0) + { + return true; + } + + if (repeat and state.down_time != 0) + { + const auto now = current_time(); + + if (const auto interval = duration_type{now.time_since_epoch().count() - state.down_time}; + interval > self.mouse_repeat_click_delay_) + { + // todo: 1/60 second? + constexpr auto tick_time{std::chrono::milliseconds{16}}; + const auto half = self.mouse_repeat_click_rate_ / 2; + + const auto v1 = (interval - self.mouse_repeat_click_delay_) % self.mouse_repeat_click_rate_; + const auto v2 = (interval - tick_time) % self.mouse_repeat_click_rate_; + + return (v1 > half) != (v2 > half); + } + } + + return false; } auto InputHandler::Mouse::is_double_clicked(const InputHandler& self, const MouseButton button) const noexcept -> bool @@ -99,7 +122,7 @@ namespace gal::prometheus::io const auto& state = states[index]; - if (state.action_this_frame != DeviceKeyAction::DOWN) + if (state.down_this_frame == 0) { return false; } @@ -135,7 +158,7 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.down_last_frame; + return state.down_time != 0; } auto InputHandler::Keyboard::reset(const time_point_type& time_point, const InputHandler& handler) noexcept -> void @@ -144,7 +167,7 @@ namespace gal::prometheus::io states, [&](key_state_type& state) noexcept -> void { - state.action_this_frame = DeviceKeyAction::NONE; + state.down_this_frame = 0; const auto view = state.press_records | std::views::reverse; const auto it = std::ranges::find_if( @@ -173,7 +196,7 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.action_this_frame == DeviceKeyAction::UP; + return state.down_this_frame == 0; } auto InputHandler::Keyboard::is_down(const InputHandler& self, const KeyboardKeyCode code) const noexcept -> bool @@ -185,7 +208,7 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.action_this_frame == DeviceKeyAction::DOWN; + return state.down_this_frame != 0; } auto InputHandler::Keyboard::is_pressing(const InputHandler& self, const KeyboardKeyCode code) const noexcept -> bool @@ -197,7 +220,7 @@ namespace gal::prometheus::io const auto& state = states[index]; - return state.down_last_frame; + return state.down_time != 0; } auto InputHandler::Keyboard::is_combination_pressing(const InputHandler& self, const std::span codes) const noexcept -> bool @@ -252,17 +275,19 @@ namespace gal::prometheus::io const auto button = static_cast(data.button); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(button < mouse_.states.size()); - auto& state = mouse_.states[button]; - - state.action_this_frame = data.action; - if (data.action == DeviceKeyAction::DOWN) + if (auto& [down_this_frame, down_time, press_records] = mouse_.states[button]; + data.action == DeviceKeyAction::DOWN) { - state.down_last_frame = true; - state.press_records.emplace_back(Mouse::press_record_type{.time_point = current_time(), .position = mouse_.position_current}); + const auto now = current_time(); + + down_this_frame = 1; + down_time = now.time_since_epoch().count(); + press_records.emplace_back(Mouse::press_record_type{.time_point = now, .position = mouse_.position_current}); } else { - state.down_last_frame = false; + down_this_frame = 0; + down_time = 0; } }; const auto handle_mouse_wheel = [this](const MouseWheelEventData& data) noexcept -> void @@ -284,17 +309,19 @@ namespace gal::prometheus::io const auto code = static_cast(data.code); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(code < keyboard_.states.size()); - auto& state = keyboard_.states[code]; - - state.action_this_frame = data.action; - if (data.action == DeviceKeyAction::DOWN) + if (auto& [down_this_frame, down_time, press_records] = keyboard_.states[code]; + data.action == DeviceKeyAction::DOWN) { - state.down_last_frame = true; - state.press_records.emplace_back(Keyboard::press_record_type{.time_point = current_time()}); + const auto now = current_time(); + + down_this_frame = 1; + down_time = now.time_since_epoch().count(); + press_records.emplace_back(Keyboard::press_record_type{.time_point = now}); } else { - state.down_last_frame = false; + down_this_frame = 0; + down_time = 0; } }; @@ -352,7 +379,9 @@ namespace gal::prometheus::io .size = {0, 0} }, mouse_double_click_interval_threshold_{std::chrono::milliseconds{300}}, - mouse_double_click_distance_threshold_{6} {} + mouse_double_click_distance_threshold_{6}, + mouse_repeat_click_delay_{std::chrono::milliseconds{275}}, + mouse_repeat_click_rate_{std::chrono::milliseconds{50}} {} auto InputHandler::begin_frame() noexcept -> void { @@ -439,12 +468,12 @@ namespace gal::prometheus::io return mouse.is_down(handler, button); } - auto InputHandler::mouse_proxy::is_click(const MouseButton button) const noexcept -> bool + auto InputHandler::mouse_proxy::is_click(const MouseButton button, const bool repeat) const noexcept -> bool { const auto& handler = self.get(); const auto& mouse = handler.mouse_; - return mouse.is_clicked(handler, button); + return mouse.is_clicked(handler, button, repeat); } auto InputHandler::mouse_proxy::is_double_click(const MouseButton button) const noexcept -> bool @@ -550,6 +579,20 @@ namespace gal::prometheus::io handler.mouse_double_click_distance_threshold_ = distance; } + auto InputHandler::config_proxy::set_mouse_repeat_click_delay(const duration_type delay) noexcept -> void + { + auto& handler = self.get(); + + handler.mouse_repeat_click_delay_ = delay; + } + + auto InputHandler::config_proxy::set_mouse_repeat_click_rate(const duration_type rate) noexcept -> void + { + auto& handler = self.get(); + + handler.mouse_repeat_click_rate_ = rate; + } + auto InputHandler::config() noexcept -> config_proxy { return {.self = *this}; diff --git a/src/io/inputs.hpp b/src/io/inputs.hpp index cd654e4..932b43c 100644 --- a/src/io/inputs.hpp +++ b/src/io/inputs.hpp @@ -382,19 +382,21 @@ namespace gal::prometheus::io public: struct press_record_type { - time_point_type time_point; - position_type position; + time_point_type time_point{}; + position_type position{0, 0}; }; struct key_state_type { - DeviceKeyAction action_this_frame; - bool down_last_frame; + static_assert(sizeof(time_point_type) == sizeof(std::uint64_t)); - std::uint8_t pad1; - std::uint8_t pad2; + // Whether this key is pressed or not in this frame (depends on DeviceKeyAction), reset to 0 every frame + std::uint64_t down_this_frame : 1 {0}; + // If pressed, the time of the moment of the press, this value is only updated the next time it is released, not reset every frame + // fixme: If the runtime is too long, the 63-bit will not fully represent the 64-bit time values + std::uint64_t down_time : 63 {0}; - std::vector press_records; + std::vector press_records{}; }; using mouse_states_type = std::array; @@ -422,7 +424,7 @@ namespace gal::prometheus::io [[nodiscard]] auto is_up(const InputHandler& self, MouseButton button) const noexcept -> bool; [[nodiscard]] auto is_down(const InputHandler& self, MouseButton button) const noexcept -> bool; - [[nodiscard]] auto is_clicked(const InputHandler& self, MouseButton button) const noexcept -> bool; + [[nodiscard]] auto is_clicked(const InputHandler& self, MouseButton button, bool repeat) const noexcept -> bool; [[nodiscard]] auto is_double_clicked(const InputHandler& self, MouseButton button) const noexcept -> bool; [[nodiscard]] auto is_pressing(const InputHandler& self, MouseButton button) const noexcept -> bool; @@ -439,18 +441,20 @@ namespace gal::prometheus::io public: struct press_record_type { - time_point_type time_point; + time_point_type time_point{}; }; struct key_state_type { - DeviceKeyAction action_this_frame; - bool down_last_frame; + static_assert(sizeof(time_point_type) == sizeof(std::uint64_t)); - std::uint8_t pad1; - std::uint8_t pad2; + // Whether this key is pressed or not in this frame (depends on DeviceKeyAction), reset to 0 every frame + std::uint64_t down_this_frame : 1 {0}; + // If pressed, the time of the moment of the press, this value is only updated the next time it is released, not reset every frame + // fixme: If the runtime is too long, the 63-bit will not fully represent the 64-bit time values + std::uint64_t down_time : 63 {0}; - std::vector press_records; + std::vector press_records{}; }; using keyboard_states_type = std::array; @@ -498,6 +502,12 @@ namespace gal::prometheus::io // Update once (it can also be updated every frame basis if desired) // The mouse position delta between two clicks is less than this threshold to be considered as a double click value_type mouse_double_click_distance_threshold_; + // Update once (it can also be updated every frame basis if desired) + // When holding a button, time before it starts repeating, in seconds + duration_type mouse_repeat_click_delay_; + // Update once (it can also be updated every frame basis if desired) + // When holding a button, rate at which it repeats, in seconds + duration_type mouse_repeat_click_rate_; auto process_event(const input_event_type& event) noexcept -> void; @@ -531,7 +541,7 @@ namespace gal::prometheus::io [[nodiscard]] auto is_up(MouseButton button) const noexcept -> bool; [[nodiscard]] auto is_down(MouseButton button) const noexcept -> bool; - [[nodiscard]] auto is_click(MouseButton button) const noexcept -> bool; + [[nodiscard]] auto is_click(MouseButton button, bool repeat = false) const noexcept -> bool; [[nodiscard]] auto is_double_click(MouseButton button) const noexcept -> bool; [[nodiscard]] auto is_pressing(MouseButton button) const noexcept -> bool; @@ -582,6 +592,8 @@ namespace gal::prometheus::io auto set_mouse_double_click_interval_threshold(duration_type interval) noexcept -> void; auto set_mouse_double_click_distance_threshold(value_type distance) noexcept -> void; + auto set_mouse_repeat_click_delay(duration_type delay) noexcept -> void; + auto set_mouse_repeat_click_rate(duration_type rate) noexcept -> void; }; [[nodiscard]] auto config() noexcept -> config_proxy; From 8e6deb7b03ddacaff976e0884f4b3011c3bb64f2 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Wed, 30 Apr 2025 19:54:54 +0800 Subject: [PATCH 28/54] =?UTF-8?q?`build`:=20Force=20UTF8.=F0=9F=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/library.cmake b/scripts/library.cmake index e3295d5..4813b64 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -8,7 +8,7 @@ add_library(gal::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) # COMPILE FLAGS if (${PROJECT_NAME_PREFIX}COMPILER_MSVC) - set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS") + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS;/utf-8") # ==================== # PEDANTIC if (${PROJECT_NAME_PREFIX}PEDANTIC) From e67f3deb1d01ecca96cd1e843dce789fe162bb68 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Wed, 30 Apr 2025 19:58:21 +0800 Subject: [PATCH 29/54] =?UTF-8?q?`fix`:=20No=20longer=20tracking=20the=20m?= =?UTF-8?q?ouse=20input=20source=20devices=20(e.g.=20mouse,=20pen=20and=20?= =?UTF-8?q?touch=20screen).=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/io/inputs.cpp | 11 --------- src/io/inputs.hpp | 61 +++++++---------------------------------------- 2 files changed, 9 insertions(+), 63 deletions(-) diff --git a/src/io/inputs.cpp b/src/io/inputs.cpp index cae3a5b..3c1b74b 100644 --- a/src/io/inputs.cpp +++ b/src/io/inputs.cpp @@ -257,21 +257,14 @@ namespace gal::prometheus::io // ============ const auto handle_mouse_move = [this](const MouseMoveEventData& data) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.source != MouseInputSource::NONE); - - mouse_.input_source = data.source; - mouse_.position_previous = mouse_.position_current; mouse_.position_current = data.position; mouse_.position_delta = (mouse_.position_current - mouse_.position_previous).to(); }; const auto handle_mouse_button = [this](const MouseButtonEventData& data) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.source != MouseInputSource::NONE); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.action != DeviceKeyAction::NONE); - mouse_.input_source = data.source; - const auto button = static_cast(data.button); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(button < mouse_.states.size()); @@ -292,9 +285,6 @@ namespace gal::prometheus::io }; const auto handle_mouse_wheel = [this](const MouseWheelEventData& data) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.source != MouseInputSource::NONE); - mouse_.input_source = data.source; - mouse_.wheel = data.value; }; @@ -362,7 +352,6 @@ namespace gal::prometheus::io // event_queue_processing_{}, mouse_ { - .input_source = MouseInputSource::NONE, .position_current = {0, 0}, .position_previous = {0, 0}, .position_delta = {0, 0}, diff --git a/src/io/inputs.hpp b/src/io/inputs.hpp index 932b43c..fd1123d 100644 --- a/src/io/inputs.hpp +++ b/src/io/inputs.hpp @@ -15,8 +15,6 @@ #include #include -#include - namespace gal::prometheus::io { using position_type = primitive::basic_point_2d; @@ -51,18 +49,6 @@ namespace gal::prometheus::io NONE, }; - enum class MouseInputSource : std::uint8_t - { - MOUSE = 0, - TOUCH_SCREEN, - PEN, - - INTERNAL_COUNT, - NONE, - }; - - constexpr auto total_mouse_input_source = static_cast(MouseInputSource::INTERNAL_COUNT); - // ====================================================================== // KEYBOARD // ====================================================================== @@ -246,52 +232,29 @@ namespace gal::prometheus::io DISPLAY, }; - #if defined(GAL_PROMETHEUS_IO_INPUTS_FORCE_ALIGN) - - constexpr auto event_data_alignment = sizeof(position_type) + sizeof(extent_type); - - #define GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS alignas(event_data_alignment) - - #else - - #define GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS - - #endif - - #if defined(GAL_PROMETHEUS_IO_INPUTS_FORCE_ALIGN) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH - GAL_PROMETHEUS_COMPILER_DISABLE_MSVC_WARNING(4324) - #endif - // ====================================================================== // MOUSE // ====================================================================== - // 1 byte + 8 bytes - class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS MouseMoveEventData final + // 8 bytes + class MouseMoveEventData final { public: - MouseInputSource source{MouseInputSource::NONE}; - position_type position{0, 0}; }; - // 1 byte + 2 bytes - class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS MouseButtonEventData final + // 2 bytes + class MouseButtonEventData final { public: - MouseInputSource source{MouseInputSource::NONE}; - MouseButton button{MouseButton::NONE}; DeviceKeyAction action{DeviceKeyAction::NONE}; }; - // 1 byte + 8 bytes - class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS MouseWheelEventData final + // 8 bytes + class MouseWheelEventData final { public: - MouseInputSource source{MouseInputSource::NONE}; - extent_type value; }; @@ -300,7 +263,7 @@ namespace gal::prometheus::io // ====================================================================== // 2 bytes - class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS KeyboardEventData final + class KeyboardEventData final { public: KeyboardKeyCode code{KeyboardKeyCode::NONE}; @@ -312,23 +275,19 @@ namespace gal::prometheus::io // ====================================================================== // 8 bytes - class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS DisplayMoveEventData final + class DisplayMoveEventData final { public: position_type position{0, 0}; }; // 8 bytes - class GAL_PROMETHEUS_IO_INPUTS_EVENT_DATA_ALIGNAS DisplayResizeEventData final + class DisplayResizeEventData final { public: extent_type size{0, 0}; }; - #if defined(GAL_PROMETHEUS_IO_INPUTS_FORCE_ALIGN) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP - #endif - // ====================================================================== // Handler // ====================================================================== @@ -401,8 +360,6 @@ namespace gal::prometheus::io using mouse_states_type = std::array; - MouseInputSource input_source; - // Update every frame // Current mouse position (this frame) position_type position_current; From ba04567c4151d734a87d788bd075fade62063e44 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Tue, 6 May 2025 23:53:38 +0800 Subject: [PATCH 30/54] =?UTF-8?q?`fix`:=20Use=20`std::mutex`.=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/io/inputs.cpp | 15 ++++----------- src/io/inputs.hpp | 30 ++++++------------------------ 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/src/io/inputs.cpp b/src/io/inputs.cpp index 3c1b74b..53f30ac 100644 --- a/src/io/inputs.cpp +++ b/src/io/inputs.cpp @@ -5,8 +5,6 @@ #include -#include - #include namespace @@ -346,10 +344,6 @@ namespace gal::prometheus::io InputHandler::InputHandler() noexcept : - // ReSharper disable once CppRedundantMemberInitializer - mutex_{}, - // event_queue_pending_{}, - // event_queue_processing_{}, mouse_ { .position_current = {0, 0}, @@ -374,21 +368,20 @@ namespace gal::prometheus::io auto InputHandler::begin_frame() noexcept -> void { + event_queue_type queue{}; { std::scoped_lock lock{mutex_}; - event_queue_processing_.append_range(std::move(event_queue_pending_)); - event_queue_pending_.clear(); + event_queue_.swap(queue); } std::ranges::for_each( - event_queue_processing_, + queue, [this](const input_event_type& event) noexcept -> void { process_event(event); } ); - event_queue_processing_.clear(); } auto InputHandler::end_frame() noexcept -> void @@ -414,7 +407,7 @@ namespace gal::prometheus::io auto InputHandler::push_event(const input_event_type& event) noexcept -> void { std::scoped_lock lock{mutex_}; - event_queue_pending_.push_back(event); + event_queue_.push_back(event); } auto InputHandler::mouse_proxy::position() const noexcept -> position_type diff --git a/src/io/inputs.hpp b/src/io/inputs.hpp index fd1123d..e2ce6b3 100644 --- a/src/io/inputs.hpp +++ b/src/io/inputs.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -304,33 +305,14 @@ namespace gal::prometheus::io class InputHandler { public: - // todo - #if not defined(GAL_PROMETHEUS_IO_INPUTS_MUTEX) - class TrivialMutex - { - public: - std::uintptr_t pad{0xdead'c0ffee}; - - constexpr auto lock() const noexcept -> void { std::ignore = this; } - - constexpr auto unlock() const noexcept -> void { std::ignore = this; } - }; - - using mutex_type = TrivialMutex; - #else - using mutext_type = GAL_PROMETHEUS_IO_INPUTS_MUTEX - #endif - + using mutex_type = std::mutex; using event_queue_type = std::vector; using value_type = extent_type::value_type; private: - mutable mutex_type mutex_; - - // - event_queue_type event_queue_pending_; - event_queue_type event_queue_processing_; + mutex_type mutex_; + event_queue_type event_queue_; // ============ // MOUSE @@ -470,9 +452,9 @@ namespace gal::prometheus::io public: InputHandler(const InputHandler&) noexcept = delete; - InputHandler(InputHandler&&) noexcept = default; + InputHandler(InputHandler&&) noexcept = delete; auto operator=(const InputHandler&) noexcept -> InputHandler& = delete; - auto operator=(InputHandler&&) noexcept -> InputHandler& = default; + auto operator=(InputHandler&&) noexcept -> InputHandler& = delete; ~InputHandler() noexcept = default; InputHandler() noexcept; From 7085994b2e56299b1e4d9682bf4f839b0a0bea71 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Wed, 7 May 2025 01:03:51 +0800 Subject: [PATCH 31/54] =?UTF-8?q?`WIP`:=20Add=20gfx.=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 23 ++ src/gfx/draw_list_shared_data.cpp | 113 ++++++ src/gfx/draw_list_shared_data.hpp | 58 ++++ src/gfx/font.cpp | 536 +++++++++++++++++++++++++++++ src/gfx/font.hpp | 504 +++++++++++++++++++++++++++ src/gfx/gfx.hpp | 11 + src/gfx/glyph_parser_freetype.cpp | 249 ++++++++++++++ src/gfx/glyph_parser_freetype.hpp | 64 ++++ src/gfx/renderer.cpp | 11 + src/gfx/renderer.hpp | 38 ++ src/gfx/renderer_dx11.cpp | 555 ++++++++++++++++++++++++++++++ src/gfx/renderer_dx11.hpp | 85 +++++ src/gfx/type.hpp | 51 +++ 13 files changed, 2298 insertions(+) create mode 100644 src/gfx/draw_list_shared_data.cpp create mode 100644 src/gfx/draw_list_shared_data.hpp create mode 100644 src/gfx/font.cpp create mode 100644 src/gfx/font.hpp create mode 100644 src/gfx/gfx.hpp create mode 100644 src/gfx/glyph_parser_freetype.cpp create mode 100644 src/gfx/glyph_parser_freetype.hpp create mode 100644 src/gfx/renderer.cpp create mode 100644 src/gfx/renderer.hpp create mode 100644 src/gfx/renderer_dx11.cpp create mode 100644 src/gfx/renderer_dx11.hpp create mode 100644 src/gfx/type.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index 4813b64..be3e377 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -364,7 +364,20 @@ set( ${PROJECT_SOURCE_DIR}/src/io/inputs.hpp ${PROJECT_SOURCE_DIR}/src/io/io.hpp + + # ========================= + # GFX + # ========================= + + ${PROJECT_SOURCE_DIR}/src/gfx/type.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/draw_list_shared_data.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/font.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/gfx.hpp + # ========================= # GUI # ========================= @@ -415,6 +428,16 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/io/inputs.cpp + + # ========================= + # GFX + # ========================= + + ${PROJECT_SOURCE_DIR}/src/gfx/draw_list_shared_data.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/font.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.cpp # ========================= # GUI diff --git a/src/gfx/draw_list_shared_data.cpp b/src/gfx/draw_list_shared_data.cpp new file mode 100644 index 0000000..905ce09 --- /dev/null +++ b/src/gfx/draw_list_shared_data.cpp @@ -0,0 +1,113 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace gfx; + + // @see https://stackoverflow.com/a/2244088/15194693 + // Number of segments (N) is calculated using equation: + // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r + [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto + { + constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto + { + return (v + 1) / 2 * 2; + }; + + return std::ranges::clamp( + circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), + DrawListSharedData::circle_segments_min, + DrawListSharedData::circle_segments_max + ); + } + + [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto + { + return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); + } + + [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto + { + return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; + } + + template [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> DrawListSharedData::vertex_sample_points_type + { + return [](std::index_sequence) noexcept -> DrawListSharedData::vertex_sample_points_type + { + constexpr auto make_point = []() noexcept -> point_type + { + const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; + return {math::cos(a), -math::sin(a)}; + }; + + return {{make_point.template operator()()...}}; + }(std::make_index_sequence{}); + } +} + +namespace gal::prometheus::gfx +{ + DrawListSharedData::DrawListSharedData() noexcept + : circle_segment_counts{}, + vertex_sample_points{vertex_sample_points_calc()}, + circle_segment_max_error{0}, + arc_fast_radius_cutoff{0}, + curve_tessellation_tolerance{1.25f} + { + set_circle_tessellation_max_error(.3f); + } + + auto DrawListSharedData::circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type + { + // ceil to never reduce accuracy + if (const auto radius_index = static_cast(radius + .999999f); + radius_index < circle_segment_counts.size()) + { + return circle_segment_counts[radius_index]; + } + return static_cast(circle_segments_calc(radius, circle_segment_max_error)); + } + + auto DrawListSharedData::vertex_sample_point(const std::size_t index) const noexcept -> const point_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points.size()); + + return vertex_sample_points[index]; + } + + auto DrawListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); + + if (circle_segment_max_error == max_error) // NOLINT(clang-diagnostic-float-equal) + { + return; + } + + for (auto [index, count]: circle_segment_counts | std::views::enumerate) + { + const auto radius = static_cast(index); + count = static_cast(circle_segments_calc(radius, max_error)); + } + + circle_segment_max_error = max_error; + arc_fast_radius_cutoff = circle_segments_calc_radius(vertex_sample_points_count, max_error); + } + + auto DrawListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); + + curve_tessellation_tolerance = tolerance; + } +} diff --git a/src/gfx/draw_list_shared_data.hpp b/src/gfx/draw_list_shared_data.hpp new file mode 100644 index 0000000..8808fc1 --- /dev/null +++ b/src/gfx/draw_list_shared_data.hpp @@ -0,0 +1,58 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx +{ + class [[nodiscard]] DrawListSharedData final + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; + + constexpr static std::size_t baked_line_uv_count = 64; + using baked_line_uvs_type = std::array; + + circle_segment_counts_type circle_segment_counts; + + vertex_sample_points_type vertex_sample_points; + + baked_line_uvs_type baked_line_uvs; + point_type white_pixel_uv; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + DrawListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; +} diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp new file mode 100644 index 0000000..4503c88 --- /dev/null +++ b/src/gfx/font.cpp @@ -0,0 +1,536 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include +#include + +#define STB_RECT_PACK_IMPLEMENTATION +#include + +namespace gal::prometheus::gfx +{ + struct TextureAtlas::pack_context_type + { + stbrp_context context; + std::vector nodes; + }; + + TextureAtlas::TextureAtlas(const value_type width, const value_type height) noexcept + : pack_context_{memory::make_unique()}, + size_{width, height}, + uv_scale_{1.f / static_cast(width), 1.f / static_cast(height)}, + data_{std::make_unique_for_overwrite(static_cast(width) * height)}, + dirty_{false}, + texture_id_{invalid_texture_id} + { + pack_context_->nodes.resize(width); + + stbrp_init_target( + &pack_context_->context, + static_cast(width), + static_cast(height), + pack_context_->nodes.data(), + static_cast(pack_context_->nodes.size()) + ); + } + + auto TextureAtlas::build(Renderer& renderer) noexcept -> void + { + // todo + std::ignore = renderer; + } + + auto TextureAtlas::destroy(Renderer& renderer) noexcept -> void + { + data_.reset(); + dirty_ = false; + renderer.destroy_texture(texture_id_); + } + + auto TextureAtlas::valid() const noexcept -> bool + { + return texture_id_ != invalid_texture_id; + } + + auto TextureAtlas::dirty() const noexcept -> bool + { + return dirty_; + } + + auto TextureAtlas::id() const noexcept -> texture_id_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + return texture_id_; + } + + auto TextureAtlas::size() const noexcept -> size_type + { + return size_; + } + + auto TextureAtlas::uv_scale() const noexcept -> uv_scale_type + { + return uv_scale_; + } + + auto TextureAtlas::data() const noexcept -> data_view_type + { + const auto length = size_.width * size_.height; + + return {data_.get(), length}; + } + + auto TextureAtlas::seek(const size_type size) noexcept -> point_type + { + stbrp_rect rect + { + .id = -1, + .w = static_cast(size.width), + .h = static_cast(size.height), + .x = 0, + .y = 0, + .was_packed = 0 + }; + + if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) + { + return {static_cast(rect.x), static_cast(rect.y)}; + } + + return invalid_point; + } + + auto TextureAtlas::write(const point_type point, const size_type size, const data_view_type data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.x + size.width <= size_.width); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.y + size.height <= size_.height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= (static_cast(size.width) * size.height)); + + for (value_type y = 0; y < size.height; ++y) + { + const auto offset_y = (point.y + y) * size_.width; + + for (value_type x = 0; x < size.width; ++x) + { + const auto offset_x = point.x + x; + const auto index = offset_x + offset_y; + + data_[index] = data[y * size.width + x]; + } + } + + dirty_ = true; + } + + auto TextureAtlas::write(const point_type point, const size_type size, const data_type& data) noexcept -> void + { + return write(point, size, {data.get(), static_cast(size.width) * size.height}); + } + + GlyphParser::~GlyphParser() noexcept = default; + + auto GlyphParser::parse(const font_id_type id, const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> ParseResult + { + return this->parse(id, {.codepoint = codepoint, .size = size, .flag = flag}); + } + + auto FontFace::find_or_parse_glyph(const GlyphKey& key) noexcept -> GlyphInfo* + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_font_id); + + if (const auto it = glyphs_.find(key); + it != glyphs_.end()) + { + return std::addressof(it->second); + } + + auto& parser = context_.get().parser(); + if (not parser.has_glyph(id_, key.codepoint)) + { + return nullptr; + } + + auto result = parser.parse(id_, key); + if (not result.valid()) + { + return nullptr; + } + + const auto [it, inserted] = glyphs_.emplace(key, result.info); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(inserted); + upload_queue_.emplace_back(memory::ref(it->second), std::move(result.data)); + + return std::addressof(it->second); + } + + FontFace::FontFace(TextureContext& context, std::string name, const font_id_type id) noexcept + : context_{context}, + name_{std::move(name)}, + id_{id}, + fallback_glyph_{nullptr} {} + + FontFace::FontFace(TextureContext& context, const std::string_view name, const font_id_type id) noexcept + : FontFace{context, std::string{name}, id} {} + + auto FontFace::name() const noexcept -> std::string_view + { + return name_; + } + + auto FontFace::id() const noexcept -> font_id_type + { + return id_; + } + + auto FontFace::initialize() noexcept -> void + { + GlyphKey key{.codepoint = u'\xFFFD', .size = 16u, .flag = GlyphFlag::NONE}; + fallback_glyph_ = find_glyph_no_fallback(key); + + if (fallback_glyph_ == nullptr) + { + key.codepoint = u'?'; + fallback_glyph_ = find_glyph_no_fallback(key); + } + if (fallback_glyph_ == nullptr) + { + key.codepoint = u' '; + fallback_glyph_ = find_glyph_no_fallback(key); + } + + if (fallback_glyph_ == nullptr) + { + // todo + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + } + + auto FontFace::begin_frame() noexcept -> void + { + std::ignore = this; + } + + auto FontFace::end_frame() noexcept -> void + { + std::ranges::for_each( + upload_queue_, + [&context = context_.get()](GlyphUploadInfo& upload_info) noexcept -> void + { + const auto& info = upload_info.info.get(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id == invalid_texture_atlas_id); + context.upload_glyph(upload_info); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id != invalid_texture_atlas_id); + } + ); + upload_queue_.clear(); + } + + auto FontFace::find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo& + { + if (const auto* info = find_or_parse_glyph(key); + info) + { + return *info; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(fallback_glyph_ != nullptr); + return *fallback_glyph_; + } + + auto FontFace::find_glyph_no_fallback(const GlyphKey& key) noexcept -> const GlyphInfo* + { + if (const auto* info = find_or_parse_glyph(key); info) + { + return info; + } + + return nullptr; + } + + auto TextureContext::select_atlas(const texture_atlas_id_type id) noexcept -> TextureAtlas& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < texture_atlases_.size()); + + return texture_atlases_[id]; + } + + auto TextureContext::select_atlas(const TextureAtlas::size_type size) const noexcept -> texture_atlas_id_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlases_.empty()); + + // todo: @see TextureContext::TextureContext + std::ignore = size; + + return 0; + } + + auto TextureContext::select_atlas(const TextureAtlas::value_type width, const TextureAtlas::value_type height) const noexcept -> texture_atlas_id_type + { + return select_atlas({width, height}); + } + + auto TextureContext::make_territory(const TextureAtlas::size_type size) noexcept -> Territory& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); + + const auto atlas_id = select_atlas(size); + auto& atlas = select_atlas(atlas_id); + + const auto point = atlas.seek(size); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point != TextureAtlas::invalid_point); + + Territory territory{.id = atlas_id, .point = point, .size = size}; + + return territories_.emplace_back(territory); + } + + auto TextureContext::make_territory(const TextureAtlas::value_type width, const TextureAtlas::value_type height) noexcept -> Territory& + { + return make_territory({width, height}); + } + + TextureContext::TextureContext() noexcept + : parser_{nullptr} + { + // todo: @see select_atlas + texture_atlases_.emplace_back(2048, 2048); + } + + auto TextureContext::initialize(DrawListSharedData& shared_data) noexcept -> void + { + // ======================================== + // BAKE LINES (AA) + // ======================================== + { + constexpr std::uint32_t white_color = 0xff'ff'ff'ff; + + const auto atlas_id = select_atlas(DrawListSharedData::baked_line_uv_count, DrawListSharedData::baked_line_uv_count); + auto& atlas = select_atlas(atlas_id); + + const auto atlas_uv_scale = atlas.uv_scale(); + + const auto& aa = make_territory(DrawListSharedData::baked_line_uv_count, DrawListSharedData::baked_line_uv_count); + + // baked line rect area: + // white pixel + // ◿ + // auto data = std::make_unique_for_overwrite(DrawListSharedData::baked_line_uv_count * DrawListSharedData::baked_line_uv_count); + // auto data = std::make_unique(DrawListSharedData::baked_line_uv_count * DrawListSharedData::baked_line_uv_count); + TextureAtlas::data_type::element_type data[DrawListSharedData::baked_line_uv_count * DrawListSharedData::baked_line_uv_count]; + + // white pixel + { + data[0 + 0] = white_color; + data[0 + 1] = white_color; + data[aa.size.width + 0] = white_color; + data[aa.size.width + 1] = white_color; + + const auto uv_x = static_cast(static_cast(aa.point.x) + .5f) * atlas_uv_scale.width; + const auto uv_y = static_cast(static_cast(aa.point.y) + .5f) * atlas_uv_scale.height; + + shared_data.white_pixel_uv = {uv_x, uv_y}; + } + + // ◿ + for (std::uint32_t y = 0; y < aa.size.height; ++y) + { + const auto line_width = y; + + for (std::uint32_t x = line_width; x > 0; --x) + { + const auto index = aa.size.width - x; + + data[index] = white_color; + } + + const auto p_x = aa.point.x + (aa.size.width - line_width); + const auto p_y = aa.point.y + y; + const auto width = line_width; + constexpr auto height = .5f; + + const auto uv_x = static_cast(p_x) * atlas_uv_scale.width; + const auto uv_y = static_cast(p_y) * atlas_uv_scale.height; + const auto uv_width = static_cast(width) * atlas_uv_scale.width; + const auto uv_height = static_cast(height) * atlas_uv_scale.height; + + shared_data.baked_line_uvs[y] = {uv_x, uv_y, uv_width, uv_height}; + } + + // write texture + atlas.write(aa.point, aa.size, data); + } + + // ======================================== + // FontFace (load fallback glyph) + // ======================================== + std::ranges::for_each( + font_faces_, + [](auto& font_face) noexcept -> void + { + font_face.initialize(); + } + ); + } + + auto TextureContext::begin_frame() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + + std::ranges::for_each( + font_face_tasks_, + [this](const FontFaceTask& task) noexcept -> void + { + if (auto result = parser_->load({task.data.get(), task.size}); + result.valid()) + { + font_faces_.emplace_back(*this, std::move(result.name), result.id); + } + else + { + // todo: error handling + } + } + ); + font_face_tasks_.clear(); + + std::ranges::for_each(font_faces_, &FontFace::begin_frame); + } + + auto TextureContext::end_frame() noexcept -> void + { + std::ranges::for_each(font_faces_, &FontFace::end_frame); + } + + auto TextureContext::bind_parser(GlyphParser& parser) noexcept -> void + { + parser_ = std::addressof(parser); + } + + auto TextureContext::parser() const noexcept -> GlyphParser& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + return *parser_; + } + + auto TextureContext::add_font(const std::filesystem::path& path) noexcept -> void + { + std::ifstream file{path, std::ios::binary}; + if (not file.is_open()) + { + // todo: error handling + return; + } + + file.seekg(0, std::ios::end); + const auto size = file.tellg(); + + auto data = std::make_unique_for_overwrite(size); + file.seekg(0, std::ios::beg); + file.read(reinterpret_cast(data.get()), size); + file.close(); + + font_face_tasks_.emplace_back(std::move(data), size); + } + + auto TextureContext::add_font(const std::string_view path) noexcept -> void + { + add_font(std::filesystem::path{path}); + } + + auto TextureContext::upload_glyph(GlyphUploadInfo& upload_info) noexcept -> void + { + auto& info = upload_info.info.get(); + const auto& data = upload_info.data; + + const auto width = static_cast(info.rect.width()); + const auto height = static_cast(info.rect.height()); + + const auto atlas_id = select_atlas(width, height); + auto& atlas = select_atlas(atlas_id); + + const auto atlas_uv_scale = atlas.uv_scale(); + + const auto& territory = make_territory(width, height); + + info.texture_atlas_id = atlas_id; + info.uv.point = territory.point.to() * atlas_uv_scale; + info.uv.extent = territory.size.to() * atlas_uv_scale; + + atlas.write(territory.point, territory.size, data); + } + + auto TextureContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* + { + for (auto& face: font_faces_) + { + if (const auto* info = face.find_glyph_no_fallback({.codepoint = codepoint, .size = size, .flag = flag}); + info != nullptr) + { + return info; + } + } + + return nullptr; + } + + auto TextureContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector + { + const auto utf32_text = chars::convert(text); + + std::vector infos; + infos.reserve(utf32_text.size()); + + std::ranges::for_each( + utf32_text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto TextureContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + { + const auto* info = glyph_of(codepoint, size, flag); + if (info == nullptr) + { + return {0, 0}; + } + + return {info->advance_x, info->rect.height()}; + } + + auto TextureContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + { + const auto infos = glyph_of(text, size, flag); + + extent_type total_size{0, 0}; + std::ranges::for_each( + infos, + [&total_size](const auto* info) noexcept -> void + { + if (info == nullptr) + { + return; + } + + total_size.width += info->advance_x; + total_size.height = std::max(total_size.height, info->rect.height()); + } + ); + + return total_size; + } +} diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp new file mode 100644 index 0000000..ea2d6de --- /dev/null +++ b/src/gfx/font.hpp @@ -0,0 +1,504 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace gal::prometheus::gfx +{ + /** + * @brief Texture atlas uploaded to the GPU + */ + class TextureAtlas final + { + public: + using value_type = std::uint32_t; + using point_type = primitive::basic_point_2d; + using size_type = primitive::basic_extent_2d; + + using uv_scale_type = extent_type; + + // size.width * size.height (RGBA) + using data_type = std::unique_ptr; + using data_view_type = std::span; + + constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; + + private: + struct pack_context_type; + memory::UniquePointer pack_context_; + + // Size of texture atlas + size_type size_; + // UV scale of texture atlas (1.0f / size.width, 1.0f / size.height) + uv_scale_type uv_scale_; + // Texture atlas data (CPU side) + data_type data_; + + static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); + // Does this texture need to be updated (re-uploaded) + mutable std::uint64_t dirty_ : 1; + // GPU resource handle + mutable std::uint64_t texture_id_ : 63; + + public: + TextureAtlas(value_type width, value_type height) noexcept; + + auto build(Renderer& renderer) noexcept -> void; + auto destroy(Renderer& renderer) noexcept -> void; + + /** + * @brief Texture atlas size + */ + [[nodiscard]] auto size() const noexcept -> size_type; + + /** + * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) + */ + [[nodiscard]] auto uv_scale() const noexcept -> uv_scale_type; + + /** + * @brief Texture atlas data (CPU side) + */ + [[nodiscard]] auto data() const noexcept -> data_view_type; + + /** + * @brief Does this texture atlas is valid (uploaded to GPU) + */ + [[nodiscard]] auto valid() const noexcept -> bool; + + /** + * @brief Does this texture atlas need to be re-uploaded to the GPU + */ + [[nodiscard]] auto dirty() const noexcept -> bool; + + /** + * @brief Texture atlas id (usually a GPU resource handle) + */ + [[nodiscard]] auto id() const noexcept -> texture_id_type; + + /** + * @brief Find a region that can hold a (piece of) texture of @c size + * @param size texture size + * @return texture coordinate + * @note If such a region is not found, @c invalid_point is returned + */ + [[nodiscard]] auto seek(size_type size) noexcept -> point_type; + + /** + * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture + * @param point texture coordinate + * @param size texture size + * @param data texture data + * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height + */ + auto write(point_type point, size_type size, data_view_type data) noexcept -> void; + + /** + * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture + * @param point texture coordinate + * @param size texture size + * @param data texture data + * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height + */ + auto write(point_type point, size_type size, const data_type& data) noexcept -> void; + }; + + enum class GlyphFlag : std::uint8_t + { + NONE = 0, + BOLD = 1 << 0, + ITALIC = 1 << 1, + }; + + class GlyphKey final + { + public: + std::uint32_t codepoint; + std::uint32_t size : 26; + GlyphFlag flag : 6; + + [[nodiscard]] constexpr auto operator==(const GlyphKey& other) const noexcept -> bool + { + return codepoint == other.codepoint and size == other.size and flag == other.flag; + } + + struct hasher + { + [[nodiscard]] auto operator()(const GlyphKey& key) const noexcept -> std::size_t + { + return + std::hash{}(key.codepoint) ^ + std::hash{}(key.size) ^ + std::hash{}(static_cast(key.flag)); + } + }; + }; + + /** + * @brief Information about a glyph + */ + class GlyphInfo final + { + public: + using value_type = extent_type::value_type; + using uv_type = primitive::basic_rect_2d; + + // ============= + // Data filled when loading glyph + // ============= + + // Bitmap infos of this glyph + rect_type rect; + value_type advance_x; + bool visible; + bool colored; + + // ============= + // Data filled when writing texture + // ============= + + // The id of the texture atlas where the glyph is located + // This id is present if and only if the glyph is in a texture atlas, otherwise it is invalid_texture_atlas_id + texture_atlas_id_type texture_atlas_id; + uv_type uv; + }; + + /** + * @brief Write @c data to texture according to @c info, and set texture_atlas_id and uv of @c info + */ + class GlyphUploadInfo final + { + public: + memory::RefWrapper info; + TextureAtlas::data_type data; + }; + + /** + * @brief Getting glyph data from (binary) font data + */ + class GlyphParser + { + public: + using binary_data_type = std::span; + + class [[nodiscard]] LoadResult final + { + public: + std::string name; + font_id_type id; + + [[nodiscard]] explicit operator bool() const noexcept + { + return id != invalid_font_id; + } + + [[nodiscard]] auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + class [[nodiscard]] ParseResult final + { + public: + GlyphInfo info; + TextureAtlas::data_type data; + + [[nodiscard]] explicit operator bool() const noexcept + { + return data == nullptr; + } + + [[nodiscard]] auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + GlyphParser(const GlyphParser&) noexcept = delete; + GlyphParser(GlyphParser&&) noexcept = default; + auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; + auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; + + virtual ~GlyphParser() noexcept; + + GlyphParser() noexcept = default; + + [[nodiscard]] virtual auto initialize() noexcept -> bool = 0; + + /** + * @brief Load the font data, get all its glyph data, return the id of the font + */ + [[nodiscard]] virtual auto load(binary_data_type data) noexcept -> LoadResult = 0; + + /** + * @brief Determines whether the target font contains the glyphs of the specified codepoint + * @param id The id returned by loading the font from the previous load + * @param codepoint The codepoint of the glyph to be checked + * @return Exists or not + */ + [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; + + /** + * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer + * @param id The id returned by loading the font from the previous load + * @param key {codepoint, size, flag} + * @return the glyph information of the specified size (and style) of the target codepoint + */ + [[nodiscard]] virtual auto parse(font_id_type id, const GlyphKey& key) noexcept -> ParseResult = 0; + + /** + * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer + * @param id The id returned by loading the font from the previous load + * @param codepoint The codepoint of the glyph + * @param size The size of the glyph + * @param flag The style of the glyph (bold, italic, etc.) + * @return the glyph information of the specified size (and style) of the target codepoint + */ + [[nodiscard]] auto parse(font_id_type id, std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> ParseResult; + }; + + /** + * @brief All the glyph data used in a font + */ + class FontFace final + { + public: + using value_type = extent_type::value_type; + using uv_type = primitive::basic_rect_2d; + + private: + memory::RefWrapper context_; + + std::string name_; + font_id_type id_; + + std::vector upload_queue_; + + std::unordered_map glyphs_; + const GlyphInfo* fallback_glyph_; + + [[nodiscard]] auto find_or_parse_glyph(const GlyphKey& key) noexcept -> GlyphInfo*; + + public: + FontFace(const FontFace&) noexcept = delete; + FontFace(FontFace&&) noexcept = default; + auto operator=(const FontFace&) noexcept -> FontFace& = delete; + auto operator=(FontFace&&) noexcept -> FontFace& = default; + + ~FontFace() noexcept = default; + + FontFace(TextureContext& context, std::string name, font_id_type id) noexcept; + + FontFace(TextureContext& context, std::string_view name, font_id_type id) noexcept; + + /** + * @brief font name (obtained on GlyphParser::load) + */ + [[nodiscard]] auto name() const noexcept -> std::string_view; + + /** + * @brief font id (obtained on GlyphParser::load) + */ + [[nodiscard]] auto id() const noexcept -> font_id_type; + + /** + * @brief Initialization, usually used to load default glyph data (for fallbacks when the desired glyph is not found) + */ + auto initialize() noexcept -> void; + + /** + * @brief + */ + auto begin_frame() noexcept -> void; + + /** + * @brief Upload the glyph data used in this frame and not uploaded to the texture before to the texture + */ + auto end_frame() noexcept -> void; + + /** + * @brief Get the glyph information of the specified codepoint, if it can't be found, then return the fallback glyph information + * @param key {codepoint, size, flag} + * @return The glyph information of the specified codepoint, or the fallback glyph information if it can't be found + */ + [[nodiscard]] auto find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo&; + + /** + * @brief Get the glyph information of the specified codepoint, if it can't be found, then return a null pointer + * @param key {codepoint, size, flag} + * @return The glyph information of the specified codepoint, or a null pointer if it can't be found + */ + [[nodiscard]] auto find_glyph_no_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; + }; + + class TextureContext final + { + public: + using texture_atlases_type = std::vector; + using font_faces_type = std::vector; + + private: + class Territory final + { + public: + using value_type = TextureAtlas::value_type; + using point_type = TextureAtlas::point_type; + using size_type = TextureAtlas::size_type; + + texture_atlas_id_type id{invalid_texture_atlas_id}; + + point_type point{0, 0}; + size_type size{0, 0}; + }; + + using territories_type = std::vector; + + class FontFaceTask final + { + public: + using value_type = GlyphParser::binary_data_type::element_type; + + std::unique_ptr data; + std::size_t size; + }; + + using font_face_tasks_type = std::vector; + + GlyphParser* parser_; + + texture_atlases_type texture_atlases_; + font_faces_type font_faces_; + territories_type territories_; + font_face_tasks_type font_face_tasks_; + + /** + * @brief Get the texture atlas for the specified id + * @param id texture atlas id + * @return texture atlas + */ + [[nodiscard]] auto select_atlas(texture_atlas_id_type id) noexcept -> TextureAtlas&; + + /** + * @brief Gets a texture atlas large enough to hold the specified @c size sub texture + * @param size sub texture size + * @return texture atlas id + */ + [[nodiscard]] auto select_atlas(TextureAtlas::size_type size) const noexcept -> texture_atlas_id_type; + + /** + * @brief Gets a texture atlas large enough to hold the specified @c {width, height} sub texture + * @param width sub texture width + * @param height sub texture height + * @return texture atlas id + */ + [[nodiscard]] auto select_atlas(TextureAtlas::value_type width, TextureAtlas::value_type height) const noexcept -> texture_atlas_id_type; + + /** + * @brief Find a suitable texture atlas based on @c size, and then find a suitable region on it (for writing sub texture) + * @param size sub texture size + * @return region on the texture atlas + */ + auto make_territory(TextureAtlas::size_type size) noexcept -> Territory&; + + /** + * @brief Find a suitable texture atlas based on @c {width, height}, and then find a suitable region on it (for writing sub texture) + * @param width sub texture width + * @param height sub texture height + * @return region on the texture atlas + */ + auto make_territory(TextureAtlas::value_type width, TextureAtlas::value_type height) noexcept -> Territory&; + + public: + TextureContext(const TextureContext&) noexcept = delete; + TextureContext(TextureContext&&) noexcept = default; + auto operator=(const TextureContext&) noexcept -> TextureContext& = delete; + auto operator=(TextureContext&&) noexcept -> TextureContext& = default; + ~TextureContext() noexcept = default; + + TextureContext() noexcept; + + /** + * @brief Initialization, usually used to set up DrawListSharedData's anti-aliased lines (uv) and initialize all font faces + */ + auto initialize(DrawListSharedData& shared_data) noexcept -> void; + + /** + * @brief Load all fonts to be loaded (and not loaded) + */ + auto begin_frame() noexcept -> void; + + /** + * @brief font faces -> end_frame + */ + auto end_frame() noexcept -> void; + + /** + * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts + */ + auto bind_parser(GlyphParser& parser) noexcept -> void; + + /** + * @brief Get the current parser, usually used by FontFace to load glyph data + */ + [[nodiscard]] auto parser() const noexcept -> GlyphParser&; + + /** + * @brief Load fonts from the specified path, assuming the path is a valid font file + * @param path font path + */ + auto add_font(const std::filesystem::path& path) noexcept -> void; + + /** + * @brief Load fonts from the specified path, assuming the path is a valid font file + * @param path font path + */ + auto add_font(std::string_view path) noexcept -> void; + + /** + * @brief Write FontFace uploaded glyph data to texture, also set the texture atlas id and uv coordinates for this glyph data + */ + auto upload_glyph(GlyphUploadInfo& upload_info) noexcept -> void; + + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + + /** + * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety + * @param codepoint + * @param size + * @param flag + * @return + */ + [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + + /** + * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + * @param text + * @param size + * @param flag + * @return + */ + [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + }; +} // namespace gal::prometheus::gfx + +// ReSharper disable once CppRedundantNamespaceDefinition +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type {}; +} diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp new file mode 100644 index 0000000..e257e30 --- /dev/null +++ b/src/gfx/gfx.hpp @@ -0,0 +1,11 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include diff --git a/src/gfx/glyph_parser_freetype.cpp b/src/gfx/glyph_parser_freetype.cpp new file mode 100644 index 0000000..e6e5243 --- /dev/null +++ b/src/gfx/glyph_parser_freetype.cpp @@ -0,0 +1,249 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#if defined(GAL_PROMETHEUS_GFX_GLYPH_PARSER_FREETYPE) + +namespace +{ + [[nodiscard]] auto ft_size_to_float(const FT_Pos size) noexcept -> float + { + return static_cast((size + 63) >> 6); + } +} + +namespace gal::prometheus::gfx +{ + auto FreeTypeGlyphParser::FontInfo::set_pixel_height(const std::size_t height) noexcept -> bool + { + FT_Size_RequestRec request + { + .type = FT_SIZE_REQUEST_TYPE_NOMINAL, + .width = 0, + .height = static_cast(height) * 64, + .horiResolution = 0, + .vertResolution = 0 + }; + + if (const auto error = FT_Request_Size(face, &request); + error != FT_Err_Ok) + { + return false; + } + + const auto& metrics = face->size->metrics; + + ascender = ft_size_to_float(metrics.ascender); + descender = ft_size_to_float(metrics.descender); + line_spacing = ft_size_to_float(metrics.height); + line_gap = ft_size_to_float(metrics.height - metrics.ascender + metrics.descender); + max_advance_width = ft_size_to_float(metrics.max_advance); + + return true; + } + + FreeTypeGlyphParser::~FreeTypeGlyphParser() noexcept + { + std::ranges::for_each( + infos_, + [](auto& info) noexcept -> void + { + ::FT_Done_Face(info.face); + } + ); + + FT_Done_FreeType(library_); + } + + FreeTypeGlyphParser::FreeTypeGlyphParser() noexcept + : library_{nullptr} {} + + auto FreeTypeGlyphParser::initialize() noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(library_ == nullptr); + + if (const auto error = FT_Init_FreeType(&library_); + error != FT_Err_Ok) + { + return false; + } + + return true; + } + + auto FreeTypeGlyphParser::load(const binary_data_type data) noexcept -> LoadResult + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not data.empty()); + + LoadResult invalid_result{.name = {}, .id = invalid_font_id}; + + FT_Face face = nullptr; + if (const auto error = FT_New_Memory_Face(library_, data.data(), static_cast(data.size()), 0, &face); + error != FT_Err_Ok) + { + return invalid_result; + } + + if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); + error != FT_Err_Ok) + { + FT_Done_Face(face); + return invalid_result; + } + + const auto id = infos_.size(); + infos_.emplace_back(face); + return {.name = face->family_name, .id = static_cast(id)}; + } + + auto FreeTypeGlyphParser::has_glyph(const font_id_type id, const std::uint32_t codepoint) const noexcept -> bool + { + if (id > infos_.size()) + { + return false; + } + + const auto& info = infos_[id]; + if (const auto error = FT_Get_Char_Index(info.face, codepoint); error != FT_Err_Ok) + { + return false; + } + + return true; + } + + auto FreeTypeGlyphParser::parse(const font_id_type id, const GlyphKey& key) noexcept -> ParseResult + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < infos_.size()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(key.codepoint != 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(key.size != 0); + + ParseResult invalid_result{.info = {}, .data = nullptr}; + + auto& info = infos_[id]; + + const auto char_index = FT_Get_Char_Index(info.face, key.codepoint); + if (char_index == 0) + { + return invalid_result; + } + + info.set_pixel_height(key.size); + + if (const auto error = FT_Load_Glyph(info.face, char_index, FT_LOAD_DEFAULT); + error != FT_Err_Ok) + { + return invalid_result; + } + + const auto& slot = info.face->glyph; + + if (std::to_underlying(key.flag) & GlyphFlag::BOLD) + { + FT_GlyphSlot_Embolden(slot); + } + if (std::to_underlying(key.flag) & GlyphFlag::ITALIC) + { + FT_GlyphSlot_Oblique(slot); + } + + if (const auto error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); + error != FT_Err_Ok) + { + return invalid_result; + } + + const auto& bitmap = info.face->glyph->bitmap; + + const point_type point{static_cast(slot->bitmap_left), static_cast(slot->bitmap_top)}; + const extent_type size{static_cast(bitmap.width), static_cast(bitmap.rows)}; + const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; + + ParseResult result + { + .info = + { + .rect = {point, size}, + .advance_x = ft_size_to_float(slot->advance.x), + .visible = size.width > 0 and size.height > 0, + .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, + .texture_atlas_id = invalid_texture_atlas_id, + .uv = {} + }, + .data = std::make_unique_for_overwrite(data_length) + }; + + { + const auto* source = bitmap.buffer; + auto* dest = result.data.get(); + + if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) + { + for (std::uint32_t y = 0; y < bitmap.rows; ++y) + { + for (std::uint32_t x = 0; x < bitmap.width; ++x) + { + const auto a = source[x]; + const auto color = + // A + a << 24 | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + dest[x] = color; + } + + source += bitmap.pitch; + dest += bitmap.width; + } + } + else if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + for (std::uint32_t y = 0; y < bitmap.rows; ++y) + { + const std::uint8_t* p = source; + std::uint8_t bits = 0; + + for (std::uint32_t x = 0; x < bitmap.width; ++x) + { + if ((x & 7) == 0) + { + bits = *p; + p += 1; + } + + const auto a = (bits & 0x80) ? std::uint32_t{0xff} : std::uint32_t{0}; + const auto color = + // A + a << 24 | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + dest[x] = color; + + bits <<= 1; + } + + source += bitmap.pitch; + dest += bitmap.width; + } + } + } + + return result; + } +} + +#endif diff --git a/src/gfx/glyph_parser_freetype.hpp b/src/gfx/glyph_parser_freetype.hpp new file mode 100644 index 0000000..ac19921 --- /dev/null +++ b/src/gfx/glyph_parser_freetype.hpp @@ -0,0 +1,64 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +// todo +#define GAL_PROMETHEUS_GFX_GLYPH_PARSER_FREETYPE + +#if defined(GAL_PROMETHEUS_GFX_GLYPH_PARSER_FREETYPE) + +#include + +#include + +#include + +namespace gal::prometheus::gfx +{ + class FreeTypeGlyphParser final : public GlyphParser + { + class FontInfo final + { + public: + FT_Face face{nullptr}; + + float ascender{0}; + float descender{0}; + float line_spacing{0}; + float line_gap{0}; + float max_advance_width{0}; + + auto set_pixel_height(std::size_t height) noexcept -> bool; + }; + + public: + using infos_type = std::vector; + + private: + FT_Library library_; + + infos_type infos_; + + public: + FreeTypeGlyphParser(const FreeTypeGlyphParser&) noexcept = delete; + FreeTypeGlyphParser(FreeTypeGlyphParser&&) noexcept = default; + auto operator=(const FreeTypeGlyphParser&) noexcept -> FreeTypeGlyphParser& = delete; + auto operator=(FreeTypeGlyphParser&&) noexcept -> FreeTypeGlyphParser& = default; + ~FreeTypeGlyphParser() noexcept override; + + FreeTypeGlyphParser() noexcept; + + auto initialize() noexcept -> bool override; + + auto load(binary_data_type data) noexcept -> LoadResult override; + + [[nodiscard]] auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool override; + + auto parse(font_id_type id, const GlyphKey& key) noexcept -> ParseResult override; + }; +} + +#endif diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp new file mode 100644 index 0000000..af2acce --- /dev/null +++ b/src/gfx/renderer.cpp @@ -0,0 +1,11 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace gal::prometheus::gfx +{ + // +} diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp new file mode 100644 index 0000000..c373450 --- /dev/null +++ b/src/gfx/renderer.hpp @@ -0,0 +1,38 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx +{ + class Renderer + { + public: + Renderer(const Renderer&) noexcept = delete; + Renderer(Renderer&&) noexcept = default; + auto operator=(const Renderer&) noexcept -> Renderer& = delete; + auto operator=(Renderer&&) noexcept -> Renderer& = default; + + virtual ~Renderer() noexcept = default; + + protected: + Renderer() noexcept = default; + + public: + [[nodiscard]] virtual auto create() noexcept -> bool = 0; + virtual auto destroy() noexcept -> void = 0; + + [[nodiscard]] virtual auto ready() const noexcept -> bool = 0; + + virtual auto begin_frame(const RendererContext& context) noexcept -> void = 0; + virtual auto end_frame(const RendererContext& context) noexcept -> void = 0; + + // todo + virtual auto create_texture() noexcept -> void = 0; + virtual auto destroy_texture(texture_id_type texture_id) noexcept -> void = 0; + }; +} diff --git a/src/gfx/renderer_dx11.cpp b/src/gfx/renderer_dx11.cpp new file mode 100644 index 0000000..b388b84 --- /dev/null +++ b/src/gfx/renderer_dx11.cpp @@ -0,0 +1,555 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#if defined(GAL_PROMETHEUS_GFX_RENDER_DX11) + +#include + +#if GAL_PROMETHEUS_COMPILER_DEBUG +#define GAL_PROMETHEUS_GFX_DEBUG +#include +#endif + +#include + +#include +#include +#include + +#include +#include + +namespace +{ + [[nodiscard]] auto check_hr_error( + const HRESULT result + #if defined(GAL_PROMETHEUS_GFX_DEBUG) + , + const std::source_location& location = std::source_location::current() + #endif + ) noexcept -> bool + { + if (SUCCEEDED(result)) + { + return true; + } + + #if defined(GAL_PROMETHEUS_GFX_DEBUG) + + const _com_error err{result}; + std::println(stderr, "Error: {} --- at {}:{}", err.ErrorMessage(), location.file_name(), location.line()); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + + #else + + GAL_PROMETHEUS_COMPILER_UNREACHABLE(); + + #endif + + return false; + } + + using projection_matrix_type = float[4][4]; +} + +namespace gal::prometheus::gfx +{ + auto Dx11Renderer::create_blend_state() noexcept -> bool + { + constexpr D3D11_RENDER_TARGET_BLEND_DESC render_target + { + .BlendEnable = TRUE, + .SrcBlend = D3D11_BLEND_SRC_ALPHA, + .DestBlend = D3D11_BLEND_INV_SRC_ALPHA, + .BlendOp = D3D11_BLEND_OP_ADD, + .SrcBlendAlpha = D3D11_BLEND_ONE, + .DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA, + .BlendOpAlpha = D3D11_BLEND_OP_ADD, + .RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL + }; + constexpr D3D11_BLEND_DESC blend_desc + { + .AlphaToCoverageEnable = FALSE, + .IndependentBlendEnable = FALSE, + .RenderTarget = + { + render_target, + } + }; + + return check_hr_error(device_->CreateBlendState(&blend_desc, blend_state_.ReleaseAndGetAddressOf())); + } + + auto Dx11Renderer::create_rasterizer_state() noexcept -> bool + { + constexpr D3D11_RASTERIZER_DESC rasterizer_desc + { + .FillMode = D3D11_FILL_SOLID, + .CullMode = D3D11_CULL_NONE, + .FrontCounterClockwise = FALSE, + .DepthBias = 0, + .DepthBiasClamp = 0, + .SlopeScaledDepthBias = 0, + .DepthClipEnable = TRUE, + .ScissorEnable = TRUE, + .MultisampleEnable = TRUE, + .AntialiasedLineEnable = TRUE + }; + + return check_hr_error(device_->CreateRasterizerState(&rasterizer_desc, rasterizer_state_.ReleaseAndGetAddressOf())); + } + + auto Dx11Renderer::create_depth_stencil_state() noexcept -> bool + { + constexpr D3D11_DEPTH_STENCIL_DESC depth_stencil_desc + { + .DepthEnable = FALSE, + .DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL, + .DepthFunc = D3D11_COMPARISON_ALWAYS, + .StencilEnable = FALSE, + .StencilReadMask = 0, + .StencilWriteMask = 0, + .FrontFace = {.StencilFailOp = D3D11_STENCIL_OP_KEEP, .StencilDepthFailOp = D3D11_STENCIL_OP_KEEP, .StencilPassOp = D3D11_STENCIL_OP_KEEP, .StencilFunc = D3D11_COMPARISON_ALWAYS}, + .BackFace = {.StencilFailOp = D3D11_STENCIL_OP_KEEP, .StencilDepthFailOp = D3D11_STENCIL_OP_KEEP, .StencilPassOp = D3D11_STENCIL_OP_KEEP, .StencilFunc = D3D11_COMPARISON_ALWAYS} + }; + + return check_hr_error(device_->CreateDepthStencilState(&depth_stencil_desc, depth_stencil_state_.ReleaseAndGetAddressOf())); + } + + auto Dx11Renderer::create_vertex_shader() noexcept -> bool + { + constexpr char shader_code[] + { + "cbuffer vertexBuffer : register(b0)" + "{" + " float4x4 ProjectionMatrix;" + "};" + "struct VS_INPUT" + "{" + " float2 pos : POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "PS_INPUT main(VS_INPUT input)" + "{" + " PS_INPUT output;" + " output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));" + " output.col = input.col;" + " output.uv = input.uv;" + " return output;" + "}" + }; + + ComPtr shader_blob; + ComPtr error_message; + + if (const auto result = D3DCompile( + shader_code, + sizeof(shader_code), + nullptr, + nullptr, + nullptr, + "main", + "vs_5_0", + 0, + 0, + shader_blob.GetAddressOf(), + error_message.GetAddressOf() + ); + FAILED(result)) + { + std::println( + stderr, + "D3DCompile failed: {} -- at {}:{}", + static_cast(error_message->GetBufferPointer()), + std::source_location::current().file_name(), + std::source_location::current().line() + ); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return false; + } + + if (not check_hr_error( + device_->CreateVertexShader( + shader_blob->GetBufferPointer(), + shader_blob->GetBufferSize(), + nullptr, + vertex_shader_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + // vertex input layout + constexpr D3D11_INPUT_ELEMENT_DESC input_element_desc[]{ + { + .SemanticName = "POSITION", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, position)), + .InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "COLOR", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, color)), + .InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "TEXCOORD", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, uv)), + .InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + }; + if (not check_hr_error( + device_->CreateInputLayout( + input_element_desc, + static_cast(std::ranges::size(input_element_desc)), + shader_blob->GetBufferPointer(), + shader_blob->GetBufferSize(), + vertex_input_layout_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + // constant buffer + constexpr D3D11_BUFFER_DESC constant_buffer_desc{ + .ByteWidth = sizeof(projection_matrix_type), + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_CONSTANT_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, + .MiscFlags = 0, + .StructureByteStride = 0 + }; + if (not check_hr_error( + device_->CreateBuffer( + &constant_buffer_desc, + nullptr, + vertex_projection_matrix_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + return true; + } + + auto Dx11Renderer::create_pixel_shader() noexcept -> bool + { + constexpr char shader_code[] + { + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "sampler sampler0;" + "Texture2D texture0;" + "float4 main(PS_INPUT input) : SV_Target" + "{" + " float4 out_col = texture0.Sample(sampler0, input.uv);" + " return input.col * out_col;" + "}" + }; + + ComPtr shader_blob; + ComPtr error_message; + + if (const auto result = D3DCompile( + shader_code, + sizeof(shader_code), + nullptr, + nullptr, + nullptr, + "main", + "ps_5_0", + 0, + 0, + shader_blob.GetAddressOf(), + error_message.GetAddressOf() + ); + FAILED(result)) + { + std::println( + stderr, + "D3DCompile failed: {} -- at {}:{}", + static_cast(error_message->GetBufferPointer()), + std::source_location::current().file_name(), + std::source_location::current().line() + ); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return false; + } + + if (not check_hr_error( + device_->CreatePixelShader( + shader_blob->GetBufferPointer(), + shader_blob->GetBufferSize(), + nullptr, + pixel_shader_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + // Create pixel shader texture sampler + constexpr D3D11_SAMPLER_DESC sampler_desc + { + .Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR, + .AddressU = D3D11_TEXTURE_ADDRESS_WRAP, + .AddressV = D3D11_TEXTURE_ADDRESS_WRAP, + .AddressW = D3D11_TEXTURE_ADDRESS_WRAP, + .MipLODBias = 0, + .MaxAnisotropy = 0, + .ComparisonFunc = D3D11_COMPARISON_ALWAYS, + .BorderColor = {0, 0, 0, 0}, + .MinLOD = 0, + .MaxLOD = 0 + }; + if (not check_hr_error( + device_->CreateSamplerState( + &sampler_desc, + pixel_font_sampler_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + return false; + } + + auto Dx11Renderer::create_texture( + const TextureAtlas& texture, + const D3D11_USAGE usage, + const std::uint32_t bind_flags, + const std::uint32_t cpu_access_flags, + const std::uint32_t misc_flags, + const bool record_resource + ) noexcept -> texture_id_type + { + const auto texture_size = texture.size(); + const auto texture_data = texture.data(); + + const D3D11_TEXTURE2D_DESC texture_2d_desc + { + .Width = static_cast(texture_size.width), + .Height = static_cast(texture_size.height), + .MipLevels = 1, + .ArraySize = 1, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Usage = usage, + .BindFlags = bind_flags, + .CPUAccessFlags = cpu_access_flags, + .MiscFlags = misc_flags + }; + + const D3D11_SUBRESOURCE_DATA subresource_data + { + .pSysMem = texture_data.data(), + .SysMemPitch = static_cast(texture_size.width * 4), + .SysMemSlicePitch = 0 + }; + + ID3D11Texture2D* texture_2d = nullptr; + if (not check_hr_error( + device_->CreateTexture2D( + &texture_2d_desc, + &subresource_data, + &texture_2d + ) + )) + { + return invalid_texture_id; + } + + const D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_view_desc + { + .Format = texture_2d_desc.Format, + .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, + .Texture2D = + { + .MostDetailedMip = 0, + .MipLevels = texture_2d_desc.MipLevels + } + }; + + ID3D11ShaderResourceView* srv = nullptr; + if (not check_hr_error( + device_->CreateShaderResourceView( + texture_2d, + &shader_resource_view_desc, + &srv + ) + )) + { + return false; + } + + if (record_resource) + { + textures_.insert_or_assign(srv, texture_2d); + } + else + { + texture_2d->Release(); + } + + return reinterpret_cast(srv); + } + + Dx11Renderer::Dx11Renderer() noexcept + : device_{nullptr}, + device_immediate_context_{nullptr}, + blend_state_{nullptr}, + rasterizer_state_{nullptr}, + depth_stencil_state_{nullptr}, + vertex_shader_{nullptr}, + vertex_input_layout_{nullptr}, + vertex_projection_matrix_{nullptr}, + pixel_shader_{nullptr}, + pixel_font_sampler_{nullptr} {} + + Dx11Renderer::Dx11Renderer(ID3D11Device* device, ID3D11DeviceContext* device_immediate_context) noexcept + : Dx11Renderer{} + { + bind_device(device); + bind_device_context(device_immediate_context); + } + + Dx11Renderer::Dx11Renderer(ComPtr device, ComPtr device_immediate_context) noexcept + : Dx11Renderer{} + { + bind_device(std::move(device)); + bind_device_context(std::move(device_immediate_context)); + } + + auto Dx11Renderer::bind_device(ID3D11Device* device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = device; + } + + auto Dx11Renderer::bind_device(ComPtr device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = std::move(device); + } + + auto Dx11Renderer::bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device_immediate_context != nullptr); + device_immediate_context_ = device_immediate_context; + } + + auto Dx11Renderer::bind_device_context(ComPtr device_immediate_context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device_immediate_context != nullptr); + device_immediate_context_ = std::move(device_immediate_context); + } + + auto Dx11Renderer::create() noexcept -> bool + { + if (not create_blend_state()) + { + return false; + } + if (not create_rasterizer_state()) + { + return false; + } + if (not create_depth_stencil_state()) + { + return false; + } + if (not create_vertex_shader()) + { + return false; + } + if (not create_pixel_shader()) + { + return false; + } + + return true; + } + + auto Dx11Renderer::destroy() noexcept -> void + { + // ComPtr + + std::ranges::for_each( + textures_ | std::views::values, + [](auto* texture_2d) noexcept -> void + { + texture_2d->Release(); + } + ); + } + + auto Dx11Renderer::ready() const noexcept -> bool + { + if (device_ == nullptr or device_immediate_context_ == nullptr) + { + return false; + } + + if (blend_state_ == nullptr or rasterizer_state_ == nullptr or depth_stencil_state_ == nullptr) + { + return false; + } + + if (vertex_shader_ == nullptr or vertex_input_layout_ == nullptr or vertex_projection_matrix_ == nullptr) + { + return false; + } + + if (pixel_shader_ == nullptr or pixel_font_sampler_ == nullptr) + { + return false; + } + + return true; + } + + auto Dx11Renderer::begin_frame(const RendererContext& context) noexcept -> void + { + // todo: create texture + } + + auto Dx11Renderer::end_frame(const RendererContext& context) noexcept -> void + { + // + } +} + +#endif diff --git a/src/gfx/renderer_dx11.hpp b/src/gfx/renderer_dx11.hpp new file mode 100644 index 0000000..7cc5d3d --- /dev/null +++ b/src/gfx/renderer_dx11.hpp @@ -0,0 +1,85 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +// todo +#define GAL_PROMETHEUS_GFX_RENDER_DX11 + +#if defined(GAL_PROMETHEUS_GFX_RENDER_DX11) + +#include + +#include + +#include +#include + +namespace gal::prometheus::gfx +{ + using Microsoft::WRL::ComPtr; + + class Dx11Renderer final : public Renderer + { + public: + using textures_type = std::unordered_map; + + private: + ComPtr device_; + ComPtr device_immediate_context_; + + ComPtr blend_state_; + ComPtr rasterizer_state_; + ComPtr depth_stencil_state_; + + ComPtr vertex_shader_; + ComPtr vertex_input_layout_; + ComPtr vertex_projection_matrix_; + + ComPtr pixel_shader_; + ComPtr pixel_font_sampler_; + + textures_type textures_; + + [[nodiscard]] auto create_blend_state() noexcept -> bool; + [[nodiscard]] auto create_rasterizer_state() noexcept -> bool; + [[nodiscard]] auto create_depth_stencil_state() noexcept -> bool; + + [[nodiscard]] auto create_vertex_shader() noexcept -> bool; + [[nodiscard]] auto create_pixel_shader() noexcept -> bool; + + [[nodiscard]] auto create_texture( + const TextureAtlas& texture, + D3D11_USAGE usage = D3D11_USAGE_DEFAULT, + std::uint32_t bind_flags = D3D11_BIND_SHADER_RESOURCE, + std::uint32_t cpu_access_flags = 0, + std::uint32_t misc_flags = 0, + bool record_resource = true + ) noexcept -> texture_id_type; + + public: + Dx11Renderer() noexcept; + Dx11Renderer(ID3D11Device* device, ID3D11DeviceContext* device_immediate_context) noexcept; + Dx11Renderer(ComPtr device, ComPtr device_immediate_context) noexcept; + + auto bind_device(ID3D11Device* device) noexcept -> void; + auto bind_device(ComPtr device) noexcept -> void; + auto bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void; + auto bind_device_context(ComPtr device_immediate_context) noexcept -> void; + + auto create() noexcept -> bool override; + auto destroy() noexcept -> void override; + + [[nodiscard]] auto ready() const noexcept -> bool override; + + auto begin_frame(const RendererContext& context) noexcept -> void override; + auto end_frame(const RendererContext& context) noexcept -> void override; + + auto create_texture() noexcept -> void override; + auto destroy_texture(texture_id_type texture_id) noexcept -> void override; + }; +} + +#endif diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp new file mode 100644 index 0000000..b59d74b --- /dev/null +++ b/src/gfx/type.hpp @@ -0,0 +1,51 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include + +namespace gal::prometheus::gfx +{ + using point_type = primitive::basic_point_2d; + using uv_type = primitive::basic_point_2d; + using color_type = primitive::basic_color; + using vertex_type = primitive::basic_vertex; + + using extent_type = primitive::basic_extent_2d; + using rect_type = primitive::basic_rect_2d; + + // ========================================================= + // FONT + // ========================================================= + + using texture_id_type = std::uintptr_t; + constexpr texture_id_type invalid_texture_id{0}; + + using texture_atlas_id_type = std::uint32_t; + constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; + + using font_id_type = std::uint32_t; + constexpr texture_id_type invalid_font_id{std::numeric_limits::max()}; + + class TextureAtlas; + class GlyphKey; + class GlyphInfo; + class GlyphUploadInfo; + class GlyphParser; + class FontFace; + class TextureContext; + + // ========================================================= + // RENDERER + // ========================================================= + + class Renderer; + class RendererContext; +} From 88cb7c2fdf111672ff382294530963ebaa2f2522 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Fri, 9 May 2025 01:36:30 +0800 Subject: [PATCH 32/54] =?UTF-8?q?`WIP`:=20gfx.=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 23 +- src/gfx/context.cpp | 347 +++++ src/gfx/context.hpp | 207 +++ src/gfx/draw_list_shared_data.cpp | 113 -- src/gfx/draw_list_shared_data.hpp | 58 - src/gfx/font.cpp | 439 +----- src/gfx/font.hpp | 310 +--- src/gfx/render_list.cpp | 2225 +++++++++++++++++++++++++++++ src/gfx/render_list.hpp | 555 +++++++ src/gfx/renderer.cpp | 2 +- src/gfx/renderer.hpp | 12 +- src/gfx/renderer_dx11.cpp | 41 +- src/gfx/renderer_dx11.hpp | 10 +- src/gfx/texture.cpp | 306 ++++ src/gfx/texture.hpp | 242 ++++ src/gfx/type.hpp | 33 +- 16 files changed, 4006 insertions(+), 917 deletions(-) create mode 100644 src/gfx/context.cpp create mode 100644 src/gfx/context.hpp delete mode 100644 src/gfx/draw_list_shared_data.cpp delete mode 100644 src/gfx/draw_list_shared_data.hpp create mode 100644 src/gfx/render_list.cpp create mode 100644 src/gfx/render_list.hpp create mode 100644 src/gfx/texture.cpp create mode 100644 src/gfx/texture.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index be3e377..e27e9e2 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -364,20 +364,26 @@ set( ${PROJECT_SOURCE_DIR}/src/io/inputs.hpp ${PROJECT_SOURCE_DIR}/src/io/io.hpp - + # ========================= # GFX # ========================= ${PROJECT_SOURCE_DIR}/src/gfx/type.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/draw_list_shared_data.hpp + + ${PROJECT_SOURCE_DIR}/src/gfx/texture.hpp ${PROJECT_SOURCE_DIR}/src/gfx/font.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/render_list.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer.hpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/context.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/gfx.hpp - + # ========================= # GUI # ========================= @@ -428,17 +434,22 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/io/inputs.cpp - + # ========================= # GFX # ========================= - - ${PROJECT_SOURCE_DIR}/src/gfx/draw_list_shared_data.cpp + + ${PROJECT_SOURCE_DIR}/src/gfx/texture.cpp ${PROJECT_SOURCE_DIR}/src/gfx/font.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/render_list.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer.cpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/context.cpp + # ========================= # GUI # ========================= diff --git a/src/gfx/context.cpp b/src/gfx/context.cpp new file mode 100644 index 0000000..df60d6c --- /dev/null +++ b/src/gfx/context.cpp @@ -0,0 +1,347 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include + +#include + +namespace gal::prometheus::gfx +{ + auto TextureContext::root_atlas() noexcept -> Texture& + { + return texture_atlases_.front(); + } + + auto TextureContext::root_atlas() const noexcept -> const Texture& + { + return texture_atlases_.front(); + } + + auto TextureContext::select_atlas(const texture_atlas_id_type id) noexcept -> Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < texture_atlases_.size()); + + return texture_atlases_[id]; + } + + auto TextureContext::select_atlas(const Texture::size_type size) const noexcept -> texture_atlas_id_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlases_.empty()); + + std::ignore = size; + + // todo: root only? + return 0; + } + + auto TextureContext::make_territory(const Texture::size_type size) noexcept -> SubTexture + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); + + const auto atlas_id = select_atlas(size); + auto& atlas = select_atlas(atlas_id); + + const auto texture = atlas.select(size); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.valid()); + + Territory territory{.id = atlas_id, .point = texture.point(), .size = size}; + territories_.emplace_back(territory); + + return texture; + } + + TextureContext::TextureContext() noexcept + : parser_{nullptr} + { + // root atlas + constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; + texture_atlases_.emplace_back(root_texture_atlas_size); + } + + auto TextureContext::initialize(RenderListSharedData& shared_data) noexcept -> void + { + // ======================================== + // BAKE LINES (AA) + // ======================================== + { + constexpr std::uint32_t white_color = 0xff'ff'ff'ff; + constexpr auto aa_width = RenderListSharedData::baked_line_uv_count; + constexpr auto aa_height = RenderListSharedData::baked_line_uv_count; + constexpr auto aa_size = Texture::size_type{aa_width, aa_height}; + + const auto atlas_id = select_atlas(aa_size); + auto& atlas = select_atlas(atlas_id); + + const auto atlas_uv_scale = atlas.uv(); + + const auto& aa_texture = make_territory(aa_size); + const auto aa_point = aa_texture.point(); + + // baked line rect area: + // white pixel + // ◿ + aa_texture.fill(0); + + // white pixel + { + aa_texture[0, 0] = white_color; + aa_texture[1, 0] = white_color; + aa_texture[0, 1] = white_color; + aa_texture[1, 1] = white_color; + + const auto uv_x = static_cast(static_cast(aa_point.x) + .5f) * atlas_uv_scale.width; + const auto uv_y = static_cast(static_cast(aa_point.y) + .5f) * atlas_uv_scale.height; + + shared_data.white_pixel_uv = {uv_x, uv_y}; + } + + // ◿ + for (Texture::size_type::value_type y = 1; y < aa_height; ++y) + { + const auto line_width = y; + const auto offset = aa_width - line_width; + + aa_texture.fill(y, offset, line_width, white_color); + + const auto p_x = aa_point.x + offset; + const auto p_y = aa_point.y + y; + const auto width = line_width; + constexpr auto height = .5f; + + const auto uv_x = static_cast(p_x) * atlas_uv_scale.width; + const auto uv_y = static_cast(p_y) * atlas_uv_scale.height; + const auto uv_width = static_cast(width) * atlas_uv_scale.width; + const auto uv_height = static_cast(height) * atlas_uv_scale.height; + + shared_data.baked_line_uvs[y] = {uv_x, uv_y, uv_width, uv_height}; + } + } + + // ======================================== + // FontFace (load fallback glyph) + // ======================================== + std::ranges::for_each( + font_faces_, + [](auto& font_face) noexcept -> void + { + font_face.initialize(); + } + ); + } + + auto TextureContext::bind_parser(GlyphParser& parser) noexcept -> void + { + parser_ = std::addressof(parser); + } + + auto TextureContext::parser() const noexcept -> GlyphParser& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + return *parser_; + } + + auto TextureContext::add_font(const std::filesystem::path& path) noexcept -> void + { + std::ifstream file{path, std::ios::binary}; + if (not file.is_open()) + { + // todo: error handling + return; + } + + file.seekg(0, std::ios::end); + const auto size = file.tellg(); + + auto data = std::make_unique_for_overwrite(size); + file.seekg(0, std::ios::beg); + file.read(reinterpret_cast(data.get()), size); + file.close(); + + font_face_tasks_.emplace_back(std::move(data), size); + } + + auto TextureContext::add_font(const std::string_view path) noexcept -> void + { + add_font(std::filesystem::path{path}); + } + + auto TextureContext::load_all_font() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + + std::ranges::for_each( + font_face_tasks_, + [this](const FontFaceTask& task) noexcept -> void + { + if (auto result = parser_->load({task.data.get(), task.size}); result.valid()) + { + font_faces_.emplace_back(*this, std::move(result.name), result.id); + } + else + { + // todo: error handling + } + } + ); + font_face_tasks_.clear(); + } + + auto TextureContext::upload_all_font_face() noexcept -> void + { + std::ranges::for_each(font_faces_, &FontFace::upload); + } + + auto TextureContext::upload_glyph_to_texture(GlyphUploadInfo& upload_info) noexcept -> void + { + auto& info = upload_info.info.get(); + const auto& data = upload_info.data; + + const auto width = static_cast(info.rect.width()); + const auto height = static_cast(info.rect.height()); + const auto size = Texture::size_type{width, height}; + + const auto atlas_id = select_atlas(size); + const auto& atlas = select_atlas(atlas_id); + + const auto atlas_uv_scale = atlas.uv(); + + const auto& texture = make_territory(size); + texture.fill({data.get(), width * height}); + + const auto texture_point = texture.point(); + info.texture_atlas_id = atlas_id; + info.uv.point = texture_point.to() * atlas_uv_scale; + info.uv.extent = size.to() * atlas_uv_scale; + } + + auto TextureContext::upload_all_texture(Renderer& renderer) noexcept -> void + { + std::ranges::for_each( + texture_atlases_, + [&renderer](auto& atlas) noexcept -> void + { + if (not atlas.valid()) + { + atlas.build(renderer); + } + } + ); + } + + auto TextureContext::root_texture() const noexcept -> texture_id_type + { + return root_atlas().id(); + } + + auto TextureContext::atlas_of(const GlyphInfo& info) noexcept -> const Texture& + { + return select_atlas(info.texture_atlas_id); + } + + auto TextureContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* + { + for (auto& face: font_faces_) + { + if (const auto* info = face.find_glyph_no_fallback({.codepoint = codepoint, .size = size, .flag = flag}); info != nullptr) + { + return info; + } + } + + return nullptr; + } + + auto TextureContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector + { + const auto utf32_text = chars::convert(text); + + std::vector infos; + infos.reserve(utf32_text.size()); + + std::ranges::for_each( + utf32_text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto TextureContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + { + const auto* info = glyph_of(codepoint, size, flag); + if (info == nullptr) + { + return {0, 0}; + } + + return {info->advance_x, info->rect.height()}; + } + + auto TextureContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + { + const auto infos = glyph_of(text, size, flag); + + extent_type total_size{0, 0}; + std::ranges::for_each( + infos, + [&total_size](const auto* info) noexcept -> void + { + if (info == nullptr) + { + return; + } + + total_size.width += info->advance_x; + total_size.height = std::max(total_size.height, info->rect.height()); + } + ); + + return total_size; + } + + RendererContext::~RendererContext() noexcept = default; + + RendererContext::RendererContext() noexcept = default; + + auto RendererContext::initialize() noexcept -> void + { + texture_context_.initialize(render_list_shared_data_); + } + + auto RendererContext::begin_frame(Renderer& renderer) noexcept -> void + { + texture_context_.load_all_font(); + texture_context_.upload_all_texture(renderer); + } + + auto RendererContext::end_frame() noexcept -> void + { + texture_context_.upload_all_font_face(); + } + + auto RendererContext::texture_context() noexcept -> TextureContext& + { + return texture_context_; + } + + auto RendererContext::texture_context() const noexcept -> const TextureContext& + { + return texture_context_; + } + + auto RendererContext::render_list_shared_data() const noexcept -> const RenderListSharedData& + { + return render_list_shared_data_; + } +} // namespace gal::prometheus::gfx diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp new file mode 100644 index 0000000..2f97324 --- /dev/null +++ b/src/gfx/context.hpp @@ -0,0 +1,207 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include +#include +#include + +namespace gal::prometheus::gfx +{ + class TextureContext final + { + public: + using texture_atlases_type = std::vector; + using font_faces_type = std::vector; + + private: + class Territory final + { + public: + using point_type = TextureDescriptor::point_type; + using size_type = TextureDescriptor::size_type; + + texture_atlas_id_type id{invalid_texture_atlas_id}; + + point_type point{0, 0}; + size_type size{0, 0}; + }; + + using territories_type = std::vector; + + class FontFaceTask final + { + public: + using value_type = GlyphParser::binary_data_type::element_type; + + std::unique_ptr data; + std::size_t size; + }; + + using font_face_tasks_type = std::vector; + + GlyphParser* parser_; + + texture_atlases_type texture_atlases_; + font_faces_type font_faces_; + territories_type territories_; + font_face_tasks_type font_face_tasks_; + + /** + * @brief Retain at least one texture atlas (root) + */ + [[nodiscard]] auto root_atlas() noexcept -> Texture&; + + /** + * @brief Retain at least one texture atlas (root) + */ + [[nodiscard]] auto root_atlas() const noexcept -> const Texture&; + + /** + * @brief Get the texture atlas for the specified id + * @param id Texture atlas id + * @return Texture atlas + */ + [[nodiscard]] auto select_atlas(texture_atlas_id_type id) noexcept -> Texture&; + + /** + * @brief Gets a texture atlas large enough to hold the specified @c size sub texture + * @param size sub texture size + * @return Texture atlas id + */ + [[nodiscard]] auto select_atlas(Texture::size_type size) const noexcept -> texture_atlas_id_type; + + /** + * @brief Find a suitable texture atlas based on @c size, and then find a suitable region on it (for writing sub texture) + * @param size Sub texture size + * @return Region on the texture atlas + */ + auto make_territory(Texture::size_type size) noexcept -> SubTexture; + + public: + TextureContext(const TextureContext&) noexcept = delete; + TextureContext(TextureContext&&) noexcept = default; + auto operator=(const TextureContext&) noexcept -> TextureContext& = delete; + auto operator=(TextureContext&&) noexcept -> TextureContext& = default; + ~TextureContext() noexcept = default; + + TextureContext() noexcept; + + /** + * @brief Initialization, usually used to set up RenderListSharedData's anti-aliased lines (uv) and initialize all font faces + */ + auto initialize(RenderListSharedData& shared_data) noexcept -> void; + + /** + * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts + */ + auto bind_parser(GlyphParser& parser) noexcept -> void; + + /** + * @brief Get the current parser, usually used by FontFace to load glyph data + */ + [[nodiscard]] auto parser() const noexcept -> GlyphParser&; + + /** + * @brief Load fonts from the specified path, assuming the path is a valid font file + * @param path Font path + */ + auto add_font(const std::filesystem::path& path) noexcept -> void; + + /** + * @brief Load fonts from the specified path, assuming the path is a valid font file + * @param path Font path + */ + auto add_font(std::string_view path) noexcept -> void; + + /** + * @brief Load the fonts previously added by @c add_font + */ + auto load_all_font() noexcept -> void; + + /** + * @brief UUpload all used glyphs to the texture (if it is not already uploaded) + */ + auto upload_all_font_face() noexcept -> void; + + /** + * @brief Write FontFace uploaded glyph data to texture, also set the texture atlas id and uv coordinates for this glyph data + */ + auto upload_glyph_to_texture(GlyphUploadInfo& upload_info) noexcept -> void; + + /** + * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) + */ + auto upload_all_texture(Renderer& renderer) noexcept -> void; + + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root_texture() const noexcept -> texture_id_type; + + /** + * @brief Get texture atlas for glyph information + */ + [[nodiscard]] auto atlas_of(const GlyphInfo& info) noexcept -> const Texture&; + + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + + /** + * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety + * @param codepoint + * @param size + * @param flag + * @return + */ + [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + + /** + * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + * @param text + * @param size + * @param flag + * @return + */ + [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + }; + + class RendererContext final + { + public: + using render_lists_type = std::vector; + + private: + TextureContext texture_context_; + RenderListSharedData render_list_shared_data_; + + render_lists_type render_lists_; + + public: + RendererContext(const RendererContext&) noexcept = delete; + RendererContext(RendererContext&&) noexcept = default; + auto operator=(const RendererContext&) noexcept -> RendererContext& = delete; + auto operator=(RendererContext&&) noexcept -> RendererContext& = default; + + ~RendererContext() noexcept; + + RendererContext() noexcept; + + auto initialize() noexcept -> void; + + auto begin_frame(Renderer& renderer) noexcept -> void; + + auto end_frame() noexcept -> void; + + [[nodiscard]] auto texture_context() noexcept -> TextureContext&; + [[nodiscard]] auto texture_context() const noexcept -> const TextureContext&; + + [[nodiscard]] auto render_list_shared_data() const noexcept -> const RenderListSharedData&; + }; +} // namespace gal::prometheus::gfx diff --git a/src/gfx/draw_list_shared_data.cpp b/src/gfx/draw_list_shared_data.cpp deleted file mode 100644 index 905ce09..0000000 --- a/src/gfx/draw_list_shared_data.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -namespace -{ - using namespace gal::prometheus; - using namespace gfx; - - // @see https://stackoverflow.com/a/2244088/15194693 - // Number of segments (N) is calculated using equation: - // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r - [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto - { - constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto - { - return (v + 1) / 2 * 2; - }; - - return std::ranges::clamp( - circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), - DrawListSharedData::circle_segments_min, - DrawListSharedData::circle_segments_max - ); - } - - [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto - { - return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); - } - - [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto - { - return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; - } - - template [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> DrawListSharedData::vertex_sample_points_type - { - return [](std::index_sequence) noexcept -> DrawListSharedData::vertex_sample_points_type - { - constexpr auto make_point = []() noexcept -> point_type - { - const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; - return {math::cos(a), -math::sin(a)}; - }; - - return {{make_point.template operator()()...}}; - }(std::make_index_sequence{}); - } -} - -namespace gal::prometheus::gfx -{ - DrawListSharedData::DrawListSharedData() noexcept - : circle_segment_counts{}, - vertex_sample_points{vertex_sample_points_calc()}, - circle_segment_max_error{0}, - arc_fast_radius_cutoff{0}, - curve_tessellation_tolerance{1.25f} - { - set_circle_tessellation_max_error(.3f); - } - - auto DrawListSharedData::circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type - { - // ceil to never reduce accuracy - if (const auto radius_index = static_cast(radius + .999999f); - radius_index < circle_segment_counts.size()) - { - return circle_segment_counts[radius_index]; - } - return static_cast(circle_segments_calc(radius, circle_segment_max_error)); - } - - auto DrawListSharedData::vertex_sample_point(const std::size_t index) const noexcept -> const point_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points.size()); - - return vertex_sample_points[index]; - } - - auto DrawListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); - - if (circle_segment_max_error == max_error) // NOLINT(clang-diagnostic-float-equal) - { - return; - } - - for (auto [index, count]: circle_segment_counts | std::views::enumerate) - { - const auto radius = static_cast(index); - count = static_cast(circle_segments_calc(radius, max_error)); - } - - circle_segment_max_error = max_error; - arc_fast_radius_cutoff = circle_segments_calc_radius(vertex_sample_points_count, max_error); - } - - auto DrawListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); - - curve_tessellation_tolerance = tolerance; - } -} diff --git a/src/gfx/draw_list_shared_data.hpp b/src/gfx/draw_list_shared_data.hpp deleted file mode 100644 index 8808fc1..0000000 --- a/src/gfx/draw_list_shared_data.hpp +++ /dev/null @@ -1,58 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -namespace gal::prometheus::gfx -{ - class [[nodiscard]] DrawListSharedData final - { - public: - using circle_segment_count_type = std::uint8_t; - constexpr static std::size_t circle_segment_counts_count = 64; - using circle_segment_counts_type = std::array; - - constexpr static std::uint32_t circle_segments_min = 4; - constexpr static std::uint32_t circle_segments_max = 512; - - constexpr static std::size_t vertex_sample_points_count = 48; - using vertex_sample_points_type = std::array; - - constexpr static std::size_t baked_line_uv_count = 64; - using baked_line_uvs_type = std::array; - - circle_segment_counts_type circle_segment_counts; - - vertex_sample_points_type vertex_sample_points; - - baked_line_uvs_type baked_line_uvs; - point_type white_pixel_uv; - - // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. - // Decrease for higher quality but more geometry. - float circle_segment_max_error; - // Cutoff radius after which arc drawing will fall back to slower `path_arc` - float arc_fast_radius_cutoff; - // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. - // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float curve_tessellation_tolerance; - - DrawListSharedData() noexcept; - - // -------------------------------------------------- - - [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; - - [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; - - // -------------------------------------------------- - - auto set_circle_tessellation_max_error(float max_error) noexcept -> void; - - auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; - }; -} diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp index 4503c88..b59dc4a 100644 --- a/src/gfx/font.cpp +++ b/src/gfx/font.cpp @@ -7,133 +7,14 @@ #include -#include #include +#include #define STB_RECT_PACK_IMPLEMENTATION #include namespace gal::prometheus::gfx { - struct TextureAtlas::pack_context_type - { - stbrp_context context; - std::vector nodes; - }; - - TextureAtlas::TextureAtlas(const value_type width, const value_type height) noexcept - : pack_context_{memory::make_unique()}, - size_{width, height}, - uv_scale_{1.f / static_cast(width), 1.f / static_cast(height)}, - data_{std::make_unique_for_overwrite(static_cast(width) * height)}, - dirty_{false}, - texture_id_{invalid_texture_id} - { - pack_context_->nodes.resize(width); - - stbrp_init_target( - &pack_context_->context, - static_cast(width), - static_cast(height), - pack_context_->nodes.data(), - static_cast(pack_context_->nodes.size()) - ); - } - - auto TextureAtlas::build(Renderer& renderer) noexcept -> void - { - // todo - std::ignore = renderer; - } - - auto TextureAtlas::destroy(Renderer& renderer) noexcept -> void - { - data_.reset(); - dirty_ = false; - renderer.destroy_texture(texture_id_); - } - - auto TextureAtlas::valid() const noexcept -> bool - { - return texture_id_ != invalid_texture_id; - } - - auto TextureAtlas::dirty() const noexcept -> bool - { - return dirty_; - } - - auto TextureAtlas::id() const noexcept -> texture_id_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - return texture_id_; - } - - auto TextureAtlas::size() const noexcept -> size_type - { - return size_; - } - - auto TextureAtlas::uv_scale() const noexcept -> uv_scale_type - { - return uv_scale_; - } - - auto TextureAtlas::data() const noexcept -> data_view_type - { - const auto length = size_.width * size_.height; - - return {data_.get(), length}; - } - - auto TextureAtlas::seek(const size_type size) noexcept -> point_type - { - stbrp_rect rect - { - .id = -1, - .w = static_cast(size.width), - .h = static_cast(size.height), - .x = 0, - .y = 0, - .was_packed = 0 - }; - - if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) - { - return {static_cast(rect.x), static_cast(rect.y)}; - } - - return invalid_point; - } - - auto TextureAtlas::write(const point_type point, const size_type size, const data_view_type data) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.x + size.width <= size_.width); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.y + size.height <= size_.height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= (static_cast(size.width) * size.height)); - - for (value_type y = 0; y < size.height; ++y) - { - const auto offset_y = (point.y + y) * size_.width; - - for (value_type x = 0; x < size.width; ++x) - { - const auto offset_x = point.x + x; - const auto index = offset_x + offset_y; - - data_[index] = data[y * size.width + x]; - } - } - - dirty_ = true; - } - - auto TextureAtlas::write(const point_type point, const size_type size, const data_type& data) noexcept -> void - { - return write(point, size, {data.get(), static_cast(size.width) * size.height}); - } - GlyphParser::~GlyphParser() noexcept = default; auto GlyphParser::parse(const font_id_type id, const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> ParseResult @@ -145,8 +26,7 @@ namespace gal::prometheus::gfx { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_font_id); - if (const auto it = glyphs_.find(key); - it != glyphs_.end()) + if (const auto it = glyphs_.find(key); it != glyphs_.end()) { return std::addressof(it->second); } @@ -174,10 +54,14 @@ namespace gal::prometheus::gfx : context_{context}, name_{std::move(name)}, id_{id}, - fallback_glyph_{nullptr} {} + fallback_glyph_{nullptr} + { + } FontFace::FontFace(TextureContext& context, const std::string_view name, const font_id_type id) noexcept - : FontFace{context, std::string{name}, id} {} + : FontFace{context, std::string{name}, id} + { + } auto FontFace::name() const noexcept -> std::string_view { @@ -212,31 +96,25 @@ namespace gal::prometheus::gfx } } - auto FontFace::begin_frame() noexcept -> void - { - std::ignore = this; - } - - auto FontFace::end_frame() noexcept -> void + auto FontFace::upload() noexcept -> void { std::ranges::for_each( - upload_queue_, - [&context = context_.get()](GlyphUploadInfo& upload_info) noexcept -> void - { - const auto& info = upload_info.info.get(); + upload_queue_, + [&context = context_.get()](GlyphUploadInfo& upload_info) noexcept -> void + { + const auto& info = upload_info.info.get(); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id == invalid_texture_atlas_id); - context.upload_glyph(upload_info); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id != invalid_texture_atlas_id); - } + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id == invalid_texture_atlas_id); + context.upload_glyph_to_texture(upload_info); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id != invalid_texture_atlas_id); + } ); upload_queue_.clear(); } auto FontFace::find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo& { - if (const auto* info = find_or_parse_glyph(key); - info) + if (const auto* info = find_or_parse_glyph(key); info != nullptr) { return *info; } @@ -254,283 +132,4 @@ namespace gal::prometheus::gfx return nullptr; } - - auto TextureContext::select_atlas(const texture_atlas_id_type id) noexcept -> TextureAtlas& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < texture_atlases_.size()); - - return texture_atlases_[id]; - } - - auto TextureContext::select_atlas(const TextureAtlas::size_type size) const noexcept -> texture_atlas_id_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlases_.empty()); - - // todo: @see TextureContext::TextureContext - std::ignore = size; - - return 0; - } - - auto TextureContext::select_atlas(const TextureAtlas::value_type width, const TextureAtlas::value_type height) const noexcept -> texture_atlas_id_type - { - return select_atlas({width, height}); - } - - auto TextureContext::make_territory(const TextureAtlas::size_type size) noexcept -> Territory& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); - - const auto atlas_id = select_atlas(size); - auto& atlas = select_atlas(atlas_id); - - const auto point = atlas.seek(size); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point != TextureAtlas::invalid_point); - - Territory territory{.id = atlas_id, .point = point, .size = size}; - - return territories_.emplace_back(territory); - } - - auto TextureContext::make_territory(const TextureAtlas::value_type width, const TextureAtlas::value_type height) noexcept -> Territory& - { - return make_territory({width, height}); - } - - TextureContext::TextureContext() noexcept - : parser_{nullptr} - { - // todo: @see select_atlas - texture_atlases_.emplace_back(2048, 2048); - } - - auto TextureContext::initialize(DrawListSharedData& shared_data) noexcept -> void - { - // ======================================== - // BAKE LINES (AA) - // ======================================== - { - constexpr std::uint32_t white_color = 0xff'ff'ff'ff; - - const auto atlas_id = select_atlas(DrawListSharedData::baked_line_uv_count, DrawListSharedData::baked_line_uv_count); - auto& atlas = select_atlas(atlas_id); - - const auto atlas_uv_scale = atlas.uv_scale(); - - const auto& aa = make_territory(DrawListSharedData::baked_line_uv_count, DrawListSharedData::baked_line_uv_count); - - // baked line rect area: - // white pixel - // ◿ - // auto data = std::make_unique_for_overwrite(DrawListSharedData::baked_line_uv_count * DrawListSharedData::baked_line_uv_count); - // auto data = std::make_unique(DrawListSharedData::baked_line_uv_count * DrawListSharedData::baked_line_uv_count); - TextureAtlas::data_type::element_type data[DrawListSharedData::baked_line_uv_count * DrawListSharedData::baked_line_uv_count]; - - // white pixel - { - data[0 + 0] = white_color; - data[0 + 1] = white_color; - data[aa.size.width + 0] = white_color; - data[aa.size.width + 1] = white_color; - - const auto uv_x = static_cast(static_cast(aa.point.x) + .5f) * atlas_uv_scale.width; - const auto uv_y = static_cast(static_cast(aa.point.y) + .5f) * atlas_uv_scale.height; - - shared_data.white_pixel_uv = {uv_x, uv_y}; - } - - // ◿ - for (std::uint32_t y = 0; y < aa.size.height; ++y) - { - const auto line_width = y; - - for (std::uint32_t x = line_width; x > 0; --x) - { - const auto index = aa.size.width - x; - - data[index] = white_color; - } - - const auto p_x = aa.point.x + (aa.size.width - line_width); - const auto p_y = aa.point.y + y; - const auto width = line_width; - constexpr auto height = .5f; - - const auto uv_x = static_cast(p_x) * atlas_uv_scale.width; - const auto uv_y = static_cast(p_y) * atlas_uv_scale.height; - const auto uv_width = static_cast(width) * atlas_uv_scale.width; - const auto uv_height = static_cast(height) * atlas_uv_scale.height; - - shared_data.baked_line_uvs[y] = {uv_x, uv_y, uv_width, uv_height}; - } - - // write texture - atlas.write(aa.point, aa.size, data); - } - - // ======================================== - // FontFace (load fallback glyph) - // ======================================== - std::ranges::for_each( - font_faces_, - [](auto& font_face) noexcept -> void - { - font_face.initialize(); - } - ); - } - - auto TextureContext::begin_frame() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); - - std::ranges::for_each( - font_face_tasks_, - [this](const FontFaceTask& task) noexcept -> void - { - if (auto result = parser_->load({task.data.get(), task.size}); - result.valid()) - { - font_faces_.emplace_back(*this, std::move(result.name), result.id); - } - else - { - // todo: error handling - } - } - ); - font_face_tasks_.clear(); - - std::ranges::for_each(font_faces_, &FontFace::begin_frame); - } - - auto TextureContext::end_frame() noexcept -> void - { - std::ranges::for_each(font_faces_, &FontFace::end_frame); - } - - auto TextureContext::bind_parser(GlyphParser& parser) noexcept -> void - { - parser_ = std::addressof(parser); - } - - auto TextureContext::parser() const noexcept -> GlyphParser& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); - return *parser_; - } - - auto TextureContext::add_font(const std::filesystem::path& path) noexcept -> void - { - std::ifstream file{path, std::ios::binary}; - if (not file.is_open()) - { - // todo: error handling - return; - } - - file.seekg(0, std::ios::end); - const auto size = file.tellg(); - - auto data = std::make_unique_for_overwrite(size); - file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(data.get()), size); - file.close(); - - font_face_tasks_.emplace_back(std::move(data), size); - } - - auto TextureContext::add_font(const std::string_view path) noexcept -> void - { - add_font(std::filesystem::path{path}); - } - - auto TextureContext::upload_glyph(GlyphUploadInfo& upload_info) noexcept -> void - { - auto& info = upload_info.info.get(); - const auto& data = upload_info.data; - - const auto width = static_cast(info.rect.width()); - const auto height = static_cast(info.rect.height()); - - const auto atlas_id = select_atlas(width, height); - auto& atlas = select_atlas(atlas_id); - - const auto atlas_uv_scale = atlas.uv_scale(); - - const auto& territory = make_territory(width, height); - - info.texture_atlas_id = atlas_id; - info.uv.point = territory.point.to() * atlas_uv_scale; - info.uv.extent = territory.size.to() * atlas_uv_scale; - - atlas.write(territory.point, territory.size, data); - } - - auto TextureContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* - { - for (auto& face: font_faces_) - { - if (const auto* info = face.find_glyph_no_fallback({.codepoint = codepoint, .size = size, .flag = flag}); - info != nullptr) - { - return info; - } - } - - return nullptr; - } - - auto TextureContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector - { - const auto utf32_text = chars::convert(text); - - std::vector infos; - infos.reserve(utf32_text.size()); - - std::ranges::for_each( - utf32_text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto TextureContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type - { - const auto* info = glyph_of(codepoint, size, flag); - if (info == nullptr) - { - return {0, 0}; - } - - return {info->advance_x, info->rect.height()}; - } - - auto TextureContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type - { - const auto infos = glyph_of(text, size, flag); - - extent_type total_size{0, 0}; - std::ranges::for_each( - infos, - [&total_size](const auto* info) noexcept -> void - { - if (info == nullptr) - { - return; - } - - total_size.width += info->advance_x; - total_size.height = std::max(total_size.height, info->rect.height()); - } - ); - - return total_size; - } -} +} // namespace gal::prometheus::gfx diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp index ea2d6de..8f29466 100644 --- a/src/gfx/font.hpp +++ b/src/gfx/font.hpp @@ -5,117 +5,14 @@ #pragma once -#include -#include -#include - +#include #include -#include #include -#include #include namespace gal::prometheus::gfx { - /** - * @brief Texture atlas uploaded to the GPU - */ - class TextureAtlas final - { - public: - using value_type = std::uint32_t; - using point_type = primitive::basic_point_2d; - using size_type = primitive::basic_extent_2d; - - using uv_scale_type = extent_type; - - // size.width * size.height (RGBA) - using data_type = std::unique_ptr; - using data_view_type = std::span; - - constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; - - private: - struct pack_context_type; - memory::UniquePointer pack_context_; - - // Size of texture atlas - size_type size_; - // UV scale of texture atlas (1.0f / size.width, 1.0f / size.height) - uv_scale_type uv_scale_; - // Texture atlas data (CPU side) - data_type data_; - - static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); - // Does this texture need to be updated (re-uploaded) - mutable std::uint64_t dirty_ : 1; - // GPU resource handle - mutable std::uint64_t texture_id_ : 63; - - public: - TextureAtlas(value_type width, value_type height) noexcept; - - auto build(Renderer& renderer) noexcept -> void; - auto destroy(Renderer& renderer) noexcept -> void; - - /** - * @brief Texture atlas size - */ - [[nodiscard]] auto size() const noexcept -> size_type; - - /** - * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) - */ - [[nodiscard]] auto uv_scale() const noexcept -> uv_scale_type; - - /** - * @brief Texture atlas data (CPU side) - */ - [[nodiscard]] auto data() const noexcept -> data_view_type; - - /** - * @brief Does this texture atlas is valid (uploaded to GPU) - */ - [[nodiscard]] auto valid() const noexcept -> bool; - - /** - * @brief Does this texture atlas need to be re-uploaded to the GPU - */ - [[nodiscard]] auto dirty() const noexcept -> bool; - - /** - * @brief Texture atlas id (usually a GPU resource handle) - */ - [[nodiscard]] auto id() const noexcept -> texture_id_type; - - /** - * @brief Find a region that can hold a (piece of) texture of @c size - * @param size texture size - * @return texture coordinate - * @note If such a region is not found, @c invalid_point is returned - */ - [[nodiscard]] auto seek(size_type size) noexcept -> point_type; - - /** - * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture - * @param point texture coordinate - * @param size texture size - * @param data texture data - * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height - */ - auto write(point_type point, size_type size, data_view_type data) noexcept -> void; - - /** - * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture - * @param point texture coordinate - * @param size texture size - * @param data texture data - * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height - */ - auto write(point_type point, size_type size, const data_type& data) noexcept -> void; - }; - enum class GlyphFlag : std::uint8_t { NONE = 0, @@ -123,6 +20,9 @@ namespace gal::prometheus::gfx ITALIC = 1 << 1, }; + /** + * @brief Glyph + */ class GlyphKey final { public: @@ -139,10 +39,7 @@ namespace gal::prometheus::gfx { [[nodiscard]] auto operator()(const GlyphKey& key) const noexcept -> std::size_t { - return - std::hash{}(key.codepoint) ^ - std::hash{}(key.size) ^ - std::hash{}(static_cast(key.flag)); + return std::hash{}(key.codepoint) ^ std::hash{}(key.size) ^ std::hash{}(static_cast(key.flag)); } }; }; @@ -177,17 +74,7 @@ namespace gal::prometheus::gfx }; /** - * @brief Write @c data to texture according to @c info, and set texture_atlas_id and uv of @c info - */ - class GlyphUploadInfo final - { - public: - memory::RefWrapper info; - TextureAtlas::data_type data; - }; - - /** - * @brief Getting glyph data from (binary) font data + * @brief Parse glyph data from (binary) font data */ class GlyphParser { @@ -215,7 +102,7 @@ namespace gal::prometheus::gfx { public: GlyphInfo info; - TextureAtlas::data_type data; + TextureDescriptor::data_type data; [[nodiscard]] explicit operator bool() const noexcept { @@ -256,7 +143,7 @@ namespace gal::prometheus::gfx * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer * @param id The id returned by loading the font from the previous load * @param key {codepoint, size, flag} - * @return the glyph information of the specified size (and style) of the target codepoint + * @return The glyph information of the specified size (and style) of the target codepoint */ [[nodiscard]] virtual auto parse(font_id_type id, const GlyphKey& key) noexcept -> ParseResult = 0; @@ -266,11 +153,21 @@ namespace gal::prometheus::gfx * @param codepoint The codepoint of the glyph * @param size The size of the glyph * @param flag The style of the glyph (bold, italic, etc.) - * @return the glyph information of the specified size (and style) of the target codepoint + * @return The glyph information of the specified size (and style) of the target codepoint */ [[nodiscard]] auto parse(font_id_type id, std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> ParseResult; }; + /** + * @brief Write @c data to texture according to @c info, and set texture_atlas_id and uv of @c info + */ + class GlyphUploadInfo final + { + public: + memory::RefWrapper info; + TextureDescriptor::data_type data; + }; + /** * @brief All the glyph data used in a font */ @@ -306,12 +203,12 @@ namespace gal::prometheus::gfx FontFace(TextureContext& context, std::string_view name, font_id_type id) noexcept; /** - * @brief font name (obtained on GlyphParser::load) + * @brief Font name (obtained on GlyphParser::load) */ [[nodiscard]] auto name() const noexcept -> std::string_view; /** - * @brief font id (obtained on GlyphParser::load) + * @brief Font id (obtained on GlyphParser::load) */ [[nodiscard]] auto id() const noexcept -> font_id_type; @@ -321,14 +218,9 @@ namespace gal::prometheus::gfx auto initialize() noexcept -> void; /** - * @brief - */ - auto begin_frame() noexcept -> void; - - /** - * @brief Upload the glyph data used in this frame and not uploaded to the texture before to the texture + * @brief Upload the glyph data used and not uploaded to the texture before to the texture */ - auto end_frame() noexcept -> void; + auto upload() noexcept -> void; /** * @brief Get the glyph information of the specified codepoint, if it can't be found, then return the fallback glyph information @@ -344,161 +236,13 @@ namespace gal::prometheus::gfx */ [[nodiscard]] auto find_glyph_no_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; }; - - class TextureContext final - { - public: - using texture_atlases_type = std::vector; - using font_faces_type = std::vector; - - private: - class Territory final - { - public: - using value_type = TextureAtlas::value_type; - using point_type = TextureAtlas::point_type; - using size_type = TextureAtlas::size_type; - - texture_atlas_id_type id{invalid_texture_atlas_id}; - - point_type point{0, 0}; - size_type size{0, 0}; - }; - - using territories_type = std::vector; - - class FontFaceTask final - { - public: - using value_type = GlyphParser::binary_data_type::element_type; - - std::unique_ptr data; - std::size_t size; - }; - - using font_face_tasks_type = std::vector; - - GlyphParser* parser_; - - texture_atlases_type texture_atlases_; - font_faces_type font_faces_; - territories_type territories_; - font_face_tasks_type font_face_tasks_; - - /** - * @brief Get the texture atlas for the specified id - * @param id texture atlas id - * @return texture atlas - */ - [[nodiscard]] auto select_atlas(texture_atlas_id_type id) noexcept -> TextureAtlas&; - - /** - * @brief Gets a texture atlas large enough to hold the specified @c size sub texture - * @param size sub texture size - * @return texture atlas id - */ - [[nodiscard]] auto select_atlas(TextureAtlas::size_type size) const noexcept -> texture_atlas_id_type; - - /** - * @brief Gets a texture atlas large enough to hold the specified @c {width, height} sub texture - * @param width sub texture width - * @param height sub texture height - * @return texture atlas id - */ - [[nodiscard]] auto select_atlas(TextureAtlas::value_type width, TextureAtlas::value_type height) const noexcept -> texture_atlas_id_type; - - /** - * @brief Find a suitable texture atlas based on @c size, and then find a suitable region on it (for writing sub texture) - * @param size sub texture size - * @return region on the texture atlas - */ - auto make_territory(TextureAtlas::size_type size) noexcept -> Territory&; - - /** - * @brief Find a suitable texture atlas based on @c {width, height}, and then find a suitable region on it (for writing sub texture) - * @param width sub texture width - * @param height sub texture height - * @return region on the texture atlas - */ - auto make_territory(TextureAtlas::value_type width, TextureAtlas::value_type height) noexcept -> Territory&; - - public: - TextureContext(const TextureContext&) noexcept = delete; - TextureContext(TextureContext&&) noexcept = default; - auto operator=(const TextureContext&) noexcept -> TextureContext& = delete; - auto operator=(TextureContext&&) noexcept -> TextureContext& = default; - ~TextureContext() noexcept = default; - - TextureContext() noexcept; - - /** - * @brief Initialization, usually used to set up DrawListSharedData's anti-aliased lines (uv) and initialize all font faces - */ - auto initialize(DrawListSharedData& shared_data) noexcept -> void; - - /** - * @brief Load all fonts to be loaded (and not loaded) - */ - auto begin_frame() noexcept -> void; - - /** - * @brief font faces -> end_frame - */ - auto end_frame() noexcept -> void; - - /** - * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts - */ - auto bind_parser(GlyphParser& parser) noexcept -> void; - - /** - * @brief Get the current parser, usually used by FontFace to load glyph data - */ - [[nodiscard]] auto parser() const noexcept -> GlyphParser&; - - /** - * @brief Load fonts from the specified path, assuming the path is a valid font file - * @param path font path - */ - auto add_font(const std::filesystem::path& path) noexcept -> void; - - /** - * @brief Load fonts from the specified path, assuming the path is a valid font file - * @param path font path - */ - auto add_font(std::string_view path) noexcept -> void; - - /** - * @brief Write FontFace uploaded glyph data to texture, also set the texture atlas id and uv coordinates for this glyph data - */ - auto upload_glyph(GlyphUploadInfo& upload_info) noexcept -> void; - - [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; - - /** - * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety - * @param codepoint - * @param size - * @param flag - * @return - */ - [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; - - /** - * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety - * @param text - * @param size - * @param flag - * @return - */ - [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; - }; } // namespace gal::prometheus::gfx // ReSharper disable once CppRedundantNamespaceDefinition namespace gal::prometheus::meta::user_defined { template<> - struct enum_is_flag : std::true_type {}; -} + struct enum_is_flag : std::true_type + { + }; +} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/render_list.cpp b/src/gfx/render_list.cpp new file mode 100644 index 0000000..d3c53a0 --- /dev/null +++ b/src/gfx/render_list.cpp @@ -0,0 +1,2225 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace gfx; + + // @see https://stackoverflow.com/a/2244088/15194693 + // Number of segments (N) is calculated using equation: + // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r + [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto + { + constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto + { + return (v + 1) / 2 * 2; + }; + + return std::ranges::clamp( + circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), + RenderListSharedData::circle_segments_min, + RenderListSharedData::circle_segments_max + ); + } + + [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto + { + return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); + } + + [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto + { + return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; + } + + template + [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> RenderListSharedData::vertex_sample_points_type + { + return [](std::index_sequence) noexcept -> RenderListSharedData::vertex_sample_points_type + { + constexpr auto make_point = []() noexcept -> point_type + { + const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; + return {math::cos(a), -math::sin(a)}; + }; + + return { {make_point.template operator()()...} }; + }(std::make_index_sequence{}); + } + + [[nodiscard]] constexpr auto range_of_arc(const RenderArcFlag flag) noexcept -> std::pair + { + static_assert(RenderListSharedData::vertex_sample_points_count % 12 == 0); + constexpr auto factor = static_cast(RenderListSharedData::vertex_sample_points_count / 12); + + switch (flag) + { + case RenderArcFlag::Q1: + { + return std::make_pair(0 * factor, 3 * factor); + } + case RenderArcFlag::Q2: + { + return std::make_pair(3 * factor, 6 * factor); + } + case RenderArcFlag::Q3: + { + return std::make_pair(6 * factor, 9 * factor); + } + case RenderArcFlag::Q4: + { + return std::make_pair(9 * factor, 12 * factor); + } + case RenderArcFlag::TOP: + { + return std::make_pair(0 * factor, 6 * factor); + } + case RenderArcFlag::BOTTOM: + { + return std::make_pair(6 * factor, 12 * factor); + } + case RenderArcFlag::LEFT: + { + return std::make_pair(3 * factor, 9 * factor); + } + case RenderArcFlag::RIGHT: + { + return std::make_pair(9 * factor, 15 * factor); + } + case RenderArcFlag::ALL: + { + return std::make_pair(0 * factor, 12 * factor); + } + case RenderArcFlag::Q1_CLOCK_WISH: + { + return std::make_pair(3 * factor, 0 * factor); + } + case RenderArcFlag::Q2_CLOCK_WISH: + { + return std::make_pair(6 * factor, 3 * factor); + } + case RenderArcFlag::Q3_CLOCK_WISH: + { + return std::make_pair(9 * factor, 6 * factor); + } + case RenderArcFlag::Q4_CLOCK_WISH: + { + return std::make_pair(12 * factor, 9 * factor); + } + case RenderArcFlag::TOP_CLOCK_WISH: + { + return std::make_pair(6 * factor, 0 * factor); + } + case RenderArcFlag::BOTTOM_CLOCK_WISH: + { + return std::make_pair(12 * factor, 6 * factor); + } + case RenderArcFlag::LEFT_CLOCK_WISH: + { + return std::make_pair(9 * factor, 3 * factor); + } + case RenderArcFlag::RIGHT_CLOCK_WISH: + { + return std::make_pair(15 * factor, 9 * factor); + } + case RenderArcFlag::ALL_CLOCK_WISH: + { + return std::make_pair(12 * factor, 0 * factor); + } + default: // NOLINT(clang-diagnostic-covered-switch-default) + { + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + } + } + } + + [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const RenderFlag flag) noexcept -> RenderFlag + { + using enum RenderFlag; + + if ((flag & ROUND_CORNER_MASK) == NONE) + { + return ROUND_CORNER_ALL | flag; + } + + return flag; + } + + [[nodiscard]] constexpr auto to_fixed_normal(const float x, const float y) noexcept -> std::pair + { + if (const auto d = math::pow(x, 2) + math::pow(y, 2); d > 1e-6f) + { + // fixme + const auto inv_len = [d] + { + // #if defined(__AVX512F__) + // __m512 d_v = _mm512_set1_ps(d); + // __m512 inv_len_v = _mm512_rcp14_ps(d_v); + // return _mm512_cvtss_f32(inv_len_v); + // #elif defined(__AVX__) + // __m256 d_v = _mm256_set1_ps(d); + // __m256 inv_len_v = _mm256_rcp_ps(d_v); + // return _mm256_cvtss_f32(inv_len_v); + // #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) + // __m128 d_v = _mm_set_ss(d); + // __m128 inv_len_v = _mm_rcp_ss(d_v); + // return _mm_cvtss_f32(inv_len_v); + // #else + return 1.0f / d; + // #endif + }(); + + return {x * inv_len, y * inv_len}; + } + + return {x, y}; + } + + // fixme + constexpr std::size_t bezier_curve_casteljau_max_level = 10; + + constexpr auto bezier_cubic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 3); + const auto w2 = 3 * math::pow(u, 2) * tolerance; + const auto w3 = 3 * u * math::pow(tolerance, 2); + const auto w4 = math::pow(tolerance, 3); + + return {p1.x * w1 + p2.x * w2 + p3.x * w3 + p4.x * w4, p1.y * w1 + p2.y * w2 + p3.y * w3 + p4.y * w4}; + }; + + constexpr auto bezier_quadratic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 2); + const auto w2 = 2 * u * tolerance; + const auto w3 = math::pow(tolerance, 2); + + return {p1.x * w1 + p2.x * w2 + p3.x * w3, p1.y * w1 + p2.y * w2 + p3.y * w3}; + }; + + class RenderDataAppender final + { + public: + using size_type = RenderData::size_type; + + using command_type = RenderData::command_type; + + using vertex_list_type = RenderData::vertex_list_type; + using index_list_type = RenderData::index_list_type; + + private: + // command_type::element_count + memory::RefWrapper element_count_; + // RenderList::vertex_list + memory::RefWrapper vertex_list_; + // RenderList::index_list_; + memory::RefWrapper index_list_; + + public: + RenderDataAppender(command_type& command, vertex_list_type& vertex_list, index_list_type& index_list) noexcept + : element_count_{command.element_count}, + vertex_list_{vertex_list}, + index_list_{index_list} + { + } + + [[nodiscard]] auto vertex_count() const noexcept -> size_type + { + const auto& list = vertex_list_.get(); + const auto size = list.size(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size < std::numeric_limits::max(), "Too many vertex!"); + + return static_cast(size); + } + + auto reserve(const size_type vertex_count, const size_type index_count) noexcept -> void + { + auto& element_count = element_count_.get(); + auto& vertex = vertex_list_.get(); + auto& index = index_list_.get(); + + element_count += index_count; + vertex.reserve(vertex.size() + vertex_count); + index.reserve(index.size() + index_count); + } + + auto reserve(const std::size_t vertex_count, const std::size_t index_count) noexcept -> void + { + reserve(static_cast(vertex_count), static_cast(index_count)); + } + + auto add_vertex(const point_type& point, const uv_type& uv, color_type color) noexcept -> void + { + auto& list = vertex_list_.get(); + + list.emplace_back(point, uv, color); + } + + auto add_index(const index_type a, const index_type b, const index_type c) noexcept -> void + { + auto& list = index_list_.get(); + + list.push_back(a); + list.push_back(b); + list.push_back(c); + } + }; +} // namespace + +namespace gal::prometheus::gfx +{ + RenderListSharedData::RenderListSharedData() noexcept + : circle_segment_counts{}, + vertex_sample_points{vertex_sample_points_calc()}, + circle_segment_max_error{0}, + arc_fast_radius_cutoff{0}, + curve_tessellation_tolerance{1.25f} + { + set_circle_tessellation_max_error(.3f); + } + + auto RenderListSharedData::circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type + { + // ceil to never reduce accuracy + if (const auto radius_index = static_cast(radius + .999999f); radius_index < circle_segment_counts.size()) + { + return circle_segment_counts[radius_index]; + } + return static_cast(circle_segments_calc(radius, circle_segment_max_error)); + } + + auto RenderListSharedData::vertex_sample_point(const std::size_t index) const noexcept -> const point_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points.size()); + + return vertex_sample_points[index]; + } + + auto RenderListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); + + if (circle_segment_max_error == max_error) // NOLINT(clang-diagnostic-float-equal) + { + return; + } + + for (auto [index, count]: circle_segment_counts | std::views::enumerate) + { + const auto radius = static_cast(index); + count = static_cast(circle_segments_calc(radius, max_error)); + } + + circle_segment_max_error = max_error; + arc_fast_radius_cutoff = circle_segments_calc_radius(vertex_sample_points_count, max_error); + } + + auto RenderListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); + + curve_tessellation_tolerance = tolerance; + } + + class RenderList::Drawer + { + public: + memory::RefWrapper self; + + using path_list_type = std::vector; + + path_list_type path_list; + + [[nodiscard]] auto make_appender() noexcept -> RenderDataAppender + { + auto& draw_list = self.get(); + + return {draw_list.command_list_.back(), draw_list.vertex_list_, draw_list.index_list_}; + } + + auto draw_polygon_line(const color_type color, const RenderFlag draw_flag, const float thickness) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + auto appender = make_appender(); + + const auto is_closed = (draw_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + + const auto vertex_count = segments_count * 4; + const auto index_count = segments_count * 6; + appender.reserve(vertex_count, index_count); + + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + + const auto& p1 = path_point[i]; + const auto& p2 = path_point[n]; + + auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); + normalized_x *= (thickness * .5f); + normalized_y *= (thickness * .5f); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = shared_data.white_pixel_uv; + + appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + } + + auto draw_polygon_line_aa(const color_type color, const RenderFlag draw_flag, float thickness) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + const auto draw_list_flag = draw_list.draw_list_flag_; + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + auto appender = make_appender(); + + const auto& opaque_uv = shared_data.white_pixel_uv; + const auto transparent_color = color.transparent(); + + const auto is_closed = (draw_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + const auto is_thick_line = thickness > 1.f; + + thickness = std::ranges::max(thickness, 1.f); + const auto thickness_integer = static_cast(thickness); + const auto thickness_fractional = thickness - static_cast(thickness_integer); + + const auto is_use_texture = + ((draw_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); + + const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); + const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); + appender.reserve(vertex_cont, index_count); + + // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; + + // Calculate normals (tangents) for each line segment + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + + if (not is_closed) + { + temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + } + + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + if (is_use_texture or not is_thick_line) + { + // [PATH 1] Texture-based lines (thick or non-thick) + + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA + const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; + } + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + // closed + (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + ); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // dm_x, dm_y are offset to the outer edge of the AA area + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= half_draw_size; + dm_y *= half_draw_size; + + // Add temporary vertexes for the outer edges + temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; + temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; + + if (is_use_texture) + { + // Add indices for two triangles + + // right + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); + // left + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); + } + else + { + // Add indexes for four triangles + + // right 1 + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); + // right 2 + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); + // left 1 + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // left 2 + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + } + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertexes for each point on the line + if (is_use_texture) + { + const auto& uv = shared_data.baked_line_uvs[thickness_integer]; + + const auto uv0 = uv.left_top(); + const auto uv1 = uv.right_bottom(); + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); + } + } + else + { + // If we're not using a texture, we need the center vertex as well + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // center of line + appender.add_vertex(path_point[i], opaque_uv, color); + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); + } + } + } + else + { + // [PATH 2] Non-texture-based lines (non-thick) + + // we need to draw the solid line core and thus require four vertices per point + const auto half_inner_thickness = (thickness - 1.f) * .5f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + const auto point_last = path_point_count - 1; + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + } + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast((first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + 4)); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); + const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); + const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); + const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); + + // Add temporary vertices + temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; + temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + + // Add indexes + appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); + appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertices + for (std::decay_t i = 0; i < path_point_count; ++i) + { + appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); + appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); + } + } + } + + auto draw_convex_polygon_line_filled(const color_type color) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + auto appender = make_appender(); + + const auto vertex_count = path_point_count; + const auto index_count = (path_point_count - 2) * 3; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = shared_data.white_pixel_uv; + + std::ranges::for_each( + path_point, + [&](const point_type& point) noexcept -> void + { + appender.add_vertex(point, opaque_uv, color); + } + ); + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); + + appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); + } + } + + auto draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + auto appender = make_appender(); + + const auto& opaque_uv = shared_data.white_pixel_uv; + const auto transparent_color = color.transparent(); + + const auto vertex_count = path_point_count * 2; + const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_inner_index = static_cast(appender.vertex_count()); + const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); + + // Add indexes for fill + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + + appender.add_index(current_vertex_inner_index + 0, current_vertex_inner_index + static_cast((i - 1) << 1), current_vertex_inner_index + static_cast(i << 1)); + } + + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + // Average normals + const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= .5f; + dm_y *= .5f; + + // inner + appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); + // outer + appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + + // Add indexes for fringes + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + + appender.add_index( + current_vertex_inner_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(i << 1) + ); + appender.add_index( + current_vertex_outer_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(n << 1) + ); + } + } + + // clang-format off + auto draw_rect_filled( + const rect_type& rect, + const uv_type& uv, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + // clang-format on + { + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(rect.left_top(), uv, color_left_top); + appender.add_vertex(rect.right_top(), uv, color_right_top); + appender.add_vertex(rect.right_bottom(), uv, color_right_bottom); + appender.add_vertex(rect.left_bottom(), uv, color_left_bottom); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + + // clang-format off + auto draw_rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + // clang-format on + { + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& opaque_uv = shared_data.white_pixel_uv; + + draw_rect_filled(rect, opaque_uv, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + // clang-format off + auto draw_text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& p, + const color_type color, + const float wrap_width + ) noexcept -> void + // clang-format on + { + auto& draw_list = self.get(); + auto& texture_context = draw_list.context_.get().texture_context(); + + const auto& glyphs = texture_context.glyph_of(utf8_text, font_size); + for (auto x = p.x; const auto& glyph: glyphs) + { + if (glyph == nullptr) + { + return; + } + + if (glyph->visible) + { + const auto& atlas = texture_context.atlas_of(*glyph); + + const auto new_texture = draw_list.this_command_texture_ != atlas.id(); + + if (new_texture) + { + draw_list.push_texture(atlas.id()); + } + + // todo + { + const auto color_may_colored = glyph->colored ? color.transparent() : color; + const auto glyph_point = point_type{x, glyph->rect.point.y}; + const auto glyph_width = glyph->rect.width(); + const auto glyph_height = glyph->rect.height(); + + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(glyph_point, glyph->uv.left_top(), color_may_colored); + appender.add_vertex(glyph_point + extent_type{glyph_width, 0}, glyph->uv.right_top(), color_may_colored); + appender.add_vertex(glyph_point + extent_type{0, glyph_height}, glyph->uv.left_bottom(), color_may_colored); + appender.add_vertex(glyph_point + extent_type{glyph_width, glyph_height}, glyph->uv.right_bottom(), color_may_colored); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + + if (new_texture) + { + draw_list.pop_texture(); + } + } + + x += glyph->advance_x; + } + } + + // clang-format off + auto draw_image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void + // clang-format on + { + auto& draw_list = self.get(); + + const auto new_texture = draw_list.this_command_texture_ != texture_id; + + if (new_texture) + { + draw_list.push_texture(texture_id); + } + + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(display_p1, uv_p1, color); + appender.add_vertex(display_p2, uv_p2, color); + appender.add_vertex(display_p3, uv_p3, color); + appender.add_vertex(display_p4, uv_p4, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + + if (new_texture) + { + draw_list.pop_texture(); + } + } + + // clang-format off + auto draw_image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color, + float rounding, + RenderFlag flag + ) noexcept -> void + // clang-format on + { + // @see `path_rect` + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; + const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + { + draw_image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + else + { + auto& draw_list = self.get(); + + const auto new_texture = draw_list.this_command_texture_ != texture_id; + + if (new_texture) + { + draw_list.push_texture(texture_id); + } + + const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + + path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + + const auto before_vertex_count = draw_list.vertex_list_.size(); + // draw + path_stroke(color); + const auto after_vertex_count = draw_list.vertex_list_.size(); + + // set uv manually + + const auto display_size = display_rect.size(); + const auto uv_size = uv_rect.size(); + const auto scale = uv_size / display_size; + + auto it = draw_list.vertex_list_.begin() + static_cast(before_vertex_count); + const auto end = draw_list.vertex_list_.begin() + static_cast(after_vertex_count); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == draw_list.vertex_list_.end()); + + // note: linear uv + const auto uv_min = uv_rect.left_top(); + // const auto uv_max = uv_rect.right_bottom(); + while (it != end) + { + const auto v = uv_min + (it->position - display_rect.left_top()) * scale; + + it->uv = { + // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + v.x, + // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + v.y + }; + it += 1; + } + + if (new_texture) + { + draw_list.pop_texture(); + } + } + } + + auto path_clear() noexcept -> void + { + path_list.clear(); + } + + auto path_reserve(const std::size_t size) noexcept -> void + { + path_list.reserve(size); + } + + auto path_reserve_extra(const std::size_t size) noexcept -> void + { + path_reserve(path_list.size() + size); + } + + auto path_pin(const point_type& point) noexcept -> void + { + path_list.push_back(point); + } + + auto path_stroke(const color_type color, const RenderFlag flag, const float thickness) noexcept -> void + { + if (const auto draw_list_flag = self.get().draw_list_flag_; (draw_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) + { + draw_polygon_line_aa(color, flag, thickness); + } + else + { + draw_polygon_line(color, flag, thickness); + } + + path_clear(); + } + + auto path_stroke(const color_type color) noexcept -> void + { + if (const auto draw_list_flag = self.get().draw_list_flag_; (draw_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) + { + draw_convex_polygon_line_filled_aa(color); + } + else + { + draw_convex_polygon_line_filled(color); + } + + path_clear(); + } + + auto path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void + { + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + + // Calculate arc auto segment step size + auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); + // Make sure we never do steps larger than one quarter of the circle + step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); + + const auto sample_range = math::abs(to - from); + const auto next_step = step; + + auto extra_max_sample = false; + if (step > 1) + { + const auto overstep = sample_range % step; + if (overstep > 0) + { + extra_max_sample = true; + + // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + step -= (step - overstep) / 2; + } + + path_reserve_extra(sample_range / step + 1 + (overstep > 0)); + } + else + { + path_reserve_extra(sample_range + 1); + } + + auto sample_index = from; + if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + { + sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); + if (sample_index < 0) + { + sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + } + } + + if (to >= from) + { + for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + { + sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = shared_data.vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + else + { + for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + { + sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = shared_data.vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + + if (extra_max_sample) + { + auto normalized_max_sample_index = to % static_cast(RenderListSharedData::vertex_sample_points_count); + if (normalized_max_sample_index < 0) + { + normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; + } + + const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); + + path_pin({center + sample_point * radius}); + } + } + + auto path_arc_fast(const circle_type& circle, const RenderArcFlag flag) noexcept -> void + { + const auto [from, to] = range_of_arc(flag); + + return path_arc_fast(circle, from, to); + } + + auto path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); + + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); + } + } + + auto path_arc(const circle_type& circle, const float from, const float to) noexcept -> void + { + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + + // Automatic segment count + if (radius <= shared_data.arc_fast_radius_cutoff) + { + const auto is_reversed = to < from; + + // We are going to use precomputed values for mid-samples. + // Determine first and last sample in lookup table that belong to the arc + const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); + const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); + + const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); + const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); + const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); + + const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + + const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; + const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; + + if (emit_start) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); + } + if (sample_mid > 0) + { + path_arc_fast(circle, sample_from, sample_to); + } + if (emit_end) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); + } + } + else + { + const auto arc_length = to - from; + const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); + const auto arc_segment_count = std::ranges::max( + static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + static_cast(std::numbers::pi_v * 2 / arc_length) + ); + path_arc_n(circle, from, to, arc_segment_count); + } + } + + auto path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + const auto& [center, radius, rotation] = ellipse; + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + const auto offset = point_type{math::cos(a), math::sin(a)} * radius; + const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; + const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; + path_pin({center + point_type{prime_x, prime_y}}); + } + } + + auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void + { + path_pin(p1); + path_pin(p2); + path_pin(p3); + path_pin(p4); + } + + auto path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; + const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + { + path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + } + else + { + const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + + path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + } + } + + // clang-format off + auto path_bezier_cubic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + // clang-format on + { + const auto dx = p4.x - p1.x; + const auto dy = p4.y - p1.y; + const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); + const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); + + if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p4); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_34 = (p3 + p4) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + const auto p_234 = (p_23 + p_34) * .5f; + const auto p_1234 = (p_123 + p_234) * .5f; + + path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); + path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); + } + } + + // clang-format off + auto path_bezier_quadratic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + // clang-format on + { + const auto dx = p3.x - p1.x; + const auto dy = p3.y - p1.y; + const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; + + if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p3); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + + path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); + path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); + } + } + + auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void + { + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data.curve_tessellation_tolerance, 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); + } + } + } + + auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void + { + const auto& draw_list = self.get(); + const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data.curve_tessellation_tolerance, 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + } + } + } + }; + + auto RenderList::push_command() noexcept -> void + { + // Fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_command_texture_ != invalid_texture_id); + + command_list_.emplace_back( + command_type{ + .scissor = this_command_scissor_, + .texture = this_command_texture_, + .index_offset = static_cast(index_list_.size()), + // set by draw_xxx + .element_count = 0 + } + ); + } + + auto RenderList::on_scissor_changed() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); + + const auto command_count = command_list_.size(); + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list_.back(); + + if (current_element_count != 0) + { + if (current_scissor != this_command_scissor_) + { + push_command(); + + return; + } + } + + // try to merge with previous command if it matches, else use current command + if (command_count > 1) + { + if (const auto& [previous_scissor, previous_texture, previous_index_offset, previous_element_count] = command_list_[command_count - 2]; + current_element_count == 0 and (this_command_scissor_ == previous_scissor and this_command_texture_ == previous_texture) and + // sequential + current_index_offset == previous_index_offset + previous_element_count) + { + command_list_.pop_back(); + return; + } + } + + command_list_.back().scissor = this_command_scissor_; + } + + auto RenderList::on_texture_changed() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); + + const auto command_count = command_list_.size(); + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list_.back(); + + if (current_element_count != 0) + { + if (current_texture != this_command_texture_) + { + push_command(); + + return; + } + } + + // try to merge with previous command if it matches, else use current command + if (command_count > 1) + { + if (const auto& [previous_scissor, previous_texture, previous_index_offset, previous_element_count] = command_list_[command_count - 2]; + current_element_count == 0 and (this_command_scissor_ == previous_scissor and this_command_texture_ == previous_texture) and + // sequential + current_index_offset == previous_index_offset + previous_element_count) + { + command_list_.pop_back(); + return; + } + } + + command_list_.back().texture = this_command_texture_; + } + + RenderList::RenderList(const RenderListFlag flag, RendererContext& context) noexcept + : draw_list_flag_{flag}, + context_{context}, + this_command_scissor_{0, 0, 0, 0}, + this_command_texture_{context.texture_context().root_texture()} + { + // we always have a command ready in the buffer + command_list_.emplace_back( + command_type{ + .scissor = this_command_scissor_, + .texture = this_command_texture_, + .index_offset = static_cast(index_list_.size()), + // set by subsequent draw_xxx + .element_count = 0 + } + ); + } + + auto RenderList::render_data() const noexcept -> RenderData + { + return {.vertex_list = vertex_list_, .index_list = index_list_, .command_list = command_list_}; + } + + auto RenderList::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& + { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); + + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list_.back(); + + this_command_scissor_ = intersect_with_current_scissor ? rect.combine_min(current_scissor) : rect; + + on_scissor_changed(); + return command_list_.back().scissor; + } + + auto RenderList::pop_scissor() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list_.size() > 1); + this_command_scissor_ = command_list_[command_list_.size() - 2].scissor; + + on_scissor_changed(); + } + + auto RenderList::push_texture(const texture_id_type texture) noexcept -> void + { + this_command_texture_ = texture; + + on_texture_changed(); + } + + auto RenderList::pop_texture() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list_.size() > 1); + this_command_texture_ = command_list_[command_list_.size() - 2].texture; + + on_texture_changed(); + } + + // clang-format off + auto RenderList::line( + const point_type& from, + const point_type& to, + const color_type color, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_pin(from); + drawer.path_pin(to); + + drawer.path_stroke(color, RenderFlag::NONE, thickness); + } + + // clang-format off + auto RenderList::triangle( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_pin(a); + drawer.path_pin(b); + drawer.path_pin(c); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + // clang-format off + auto RenderList::triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_pin(a); + drawer.path_pin(b); + drawer.path_pin(c); + + drawer.path_stroke(color); + } + + // clang-format off + auto RenderList::rect( + const rect_type& rect, + const color_type color, + const float rounding, + const RenderFlag flag, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_rect(rect, rounding, flag); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + // clang-format off + auto RenderList::rect( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const RenderFlag flag, + const float thickness + ) noexcept -> void + // clang-format on + { + return rect({left_top, right_bottom}, color, rounding, flag, thickness); + } + + // clang-format off + auto RenderList::rect_filled( + const rect_type& rect, + const color_type color, + const float rounding, + const RenderFlag flag + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + { + drawer.draw_rect_filled(rect, color, color, color, color); + } + else + { + drawer.path_rect(rect, rounding, flag); + drawer.path_stroke(color); + } + } + + // clang-format off + auto RenderList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const RenderFlag flag + ) noexcept -> void + // clang-format on + { + return rect_filled({left_top, right_bottom}, color, rounding, flag); + } + + // clang-format off + auto RenderList::rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + // clang-format on + { + if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + // clang-format off + auto RenderList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + // clang-format on + { + return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + // clang-format off + auto RenderList::quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_quadrilateral(p1, p2, p3, p4); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + // clang-format off + auto RenderList::quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_quadrilateral(p1, p2, p3, p4); + + drawer.path_stroke(color); + } + + // clang-format off + auto RenderList::circle_n( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + // clang-format off + auto RenderList::circle_n( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + return circle_n({center, radius}, color, segments, thickness); + } + + // clang-format off + auto RenderList::ellipse_n( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + // clang-format off + void RenderList::ellipse_n( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + // clang-format on + { + return ellipse_n({center, radius, rotation}, color, segments, thickness); + } + + // clang-format off + auto RenderList::circle_n_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color); + } + + // clang-format off + auto RenderList::circle_n_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + return circle_n_filled({center, radius}, color, segments); + } + + // clang-format off + auto RenderList::ellipse_n_filled( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color); + } + + // clang-format off + auto RenderList::ellipse_n_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + return ellipse_n_filled({center, radius, rotation}, color, segments); + } + + // clang-format off + void RenderList::circle( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + // clang-format on + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, RenderListSharedData::circle_segments_min, RenderListSharedData::circle_segments_max); + + circle_n(circle, color, clamped_segments, thickness); + } + } + + // clang-format off + auto RenderList::circle( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + return circle({center, radius}, color, segments, thickness); + } + + // clang-format off + auto RenderList::circle_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + + drawer.path_stroke(color); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, RenderListSharedData::circle_segments_min, RenderListSharedData::circle_segments_max); + + circle_n_filled(circle, color, clamped_segments); + } + } + + // clang-format off + auto RenderList::circle_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + circle_filled({center, radius}, color, segments); + } + + // clang-format off + auto RenderList::ellipse( + const ellipse_type& ellipse, + const color_type color, + std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + const auto& shared_data = context_.get().render_list_shared_data(); + + if (segments == 0) + { + // fixme + segments = shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n(ellipse, color, segments, thickness); + } + + // clang-format off + auto RenderList::ellipse( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + return ellipse({center, radius, rotation}, color, segments, thickness); + } + + // clang-format off + auto RenderList::ellipse_filled( + const ellipse_type& ellipse, + const color_type color, + std::uint32_t segments + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + const auto& shared_data = context_.get().render_list_shared_data(); + + if (segments == 0) + { + // fixme + segments = shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n_filled(ellipse, color, segments); + } + + // clang-format off + auto RenderList::ellipse_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + // clang-format on + { + return ellipse_filled({center, radius, rotation}, color, segments); + } + + // clang-format off + auto RenderList::bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_bezier_curve(p1, p2, p3, p4, segments); + + drawer.path_stroke(color, RenderFlag::NONE, thickness); + } + + // clang-format off + auto RenderList::bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.path_bezier_quadratic_curve(p1, p2, p3, segments); + + drawer.path_stroke(color, RenderFlag::NONE, thickness); + } + + // clang-format off + auto RenderList::text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& point, + const color_type color, + const float wrap_width + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(wrap_width > 0); + + Drawer drawer{.self = *this, .path_list = {}}; + drawer.draw_text(utf8_text, font_size, point, color, wrap_width); + } + + // clang-format off + auto RenderList::image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); + } + + // clang-format off + auto RenderList::image( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void + // clang-format on + { + image(texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color); + } + + // clang-format off + auto RenderList::image( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + // clang-format on + { + image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); + } + + // clang-format off + auto RenderList::image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const float rounding, + const RenderFlag flag, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + Drawer drawer{.self = *this, .path_list = {}}; + + drawer.draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); + } + + // clang-format off + auto RenderList::image_rounded( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const float rounding, + const RenderFlag flag, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + // clang-format on + { + image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + } +} // namespace gal::prometheus::gfx diff --git a/src/gfx/render_list.hpp b/src/gfx/render_list.hpp new file mode 100644 index 0000000..031a313 --- /dev/null +++ b/src/gfx/render_list.hpp @@ -0,0 +1,555 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include + +namespace gal::prometheus::gfx +{ + class RenderListSharedData + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; + + constexpr static std::size_t baked_line_uv_count = 64; + using baked_line_uvs_type = std::array; + + circle_segment_counts_type circle_segment_counts; + + vertex_sample_points_type vertex_sample_points; + + baked_line_uvs_type baked_line_uvs; + point_type white_pixel_uv; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + // -------------------------------------------------- + + RenderListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; + + enum class RenderListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + }; + + enum class RenderFlag : std::uint8_t + { + NONE = 0, + // specify that shape should be closed + // @see RenderList::draw_polygon_line + // @see RenderList::draw_polygon_line_aa + // @see RenderList::path_stroke + CLOSED = 1 << 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + }; + + enum class RenderArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + }; + + class RenderData final + { + public: + using size_type = std::uint32_t; + + struct [[nodiscard]] command_type + { + rect_type scissor; + texture_id_type texture; + + // ======================= + + // set by RenderList::index_list.size() + // start offset in @c RenderList::index_list + size_type index_offset; + // set by RenderList::draw_xxx + // number of indices (multiple of 3) to be rendered as triangles + size_type element_count; + }; + + using vertex_list_type = std::vector; + using index_list_type = std::vector; + using command_list_type = std::vector; + + std::reference_wrapper vertex_list; + std::reference_wrapper index_list; + std::reference_wrapper command_list; + }; + + class RenderList final + { + public: + using size_type = RenderData::size_type; + + using command_type = RenderData::command_type; + + using vertex_list_type = RenderData::vertex_list_type; + using index_list_type = RenderData::index_list_type; + using command_list_type = RenderData::command_list_type; + + constexpr static float text_no_auto_wrap = 9999999.f; + + private: + class Drawer; + + RenderListFlag draw_list_flag_; + memory::RefWrapper context_; + + // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) + // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 + // command_list: + // 0: .scissor = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) + // 1: .scissor = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) + // 2: .scissor = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) + command_list_type command_list_; + vertex_list_type vertex_list_; + index_list_type index_list_; + + rect_type this_command_scissor_; + texture_id_type this_command_texture_; + + auto push_command() noexcept -> void; + + auto on_scissor_changed() noexcept -> void; + auto on_texture_changed() noexcept -> void; + + public: + RenderList(RenderListFlag flag, RendererContext& context) noexcept; + + // ---------------------------------------------------------------------------- + // RENDER DATA + + [[nodiscard]] auto render_data() const noexcept -> RenderData; + + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE + + auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; + + auto pop_scissor() noexcept -> void; + + auto push_texture(texture_id_type texture) noexcept -> void; + + auto pop_texture() noexcept -> void; + + // clang-format off + + // ---------------------------------------------------------------------------- + // PRIMITIVE + + auto line( + const point_type& from, + const point_type& to, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color + ) noexcept -> void; + + auto rect( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const point_type& left_top, + const point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color + ) noexcept -> void; + + auto circle_n( + const circle_type& circle, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle_n_filled( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle( + const circle_type& circle, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto circle_filled( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse_filled( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // TEXT + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + float wrap_width = text_no_auto_wrap + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // IMAGE + + // p1________ p2 + // | | + // | | + // p4|_______| p3 + auto image( + texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1 = {0, 0}, + const uv_type& uv_p2 = {1, 0}, + const uv_type& uv_p3 = {1, 1}, + const uv_type& uv_p4 = {0, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + float rounding = .0f, + RenderFlag flag = RenderFlag::NONE, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + float rounding = .0f, + RenderFlag flag = RenderFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + // clang-format on + }; +} // namespace gal::prometheus::gfx + +// ReSharper disable once CppRedundantNamespaceDefinition +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type + { + }; + + template<> + struct enum_is_flag : std::true_type + { + }; + + template<> + struct enum_is_flag : std::true_type + { + }; +} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index af2acce..e1d00b2 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -7,5 +7,5 @@ namespace gal::prometheus::gfx { - // + Renderer::~Renderer() noexcept = default; } diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index c373450..3a9b8da 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -5,7 +5,7 @@ #pragma once -#include +#include namespace gal::prometheus::gfx { @@ -17,7 +17,7 @@ namespace gal::prometheus::gfx auto operator=(const Renderer&) noexcept -> Renderer& = delete; auto operator=(Renderer&&) noexcept -> Renderer& = default; - virtual ~Renderer() noexcept = default; + virtual ~Renderer() noexcept; protected: Renderer() noexcept = default; @@ -28,11 +28,7 @@ namespace gal::prometheus::gfx [[nodiscard]] virtual auto ready() const noexcept -> bool = 0; - virtual auto begin_frame(const RendererContext& context) noexcept -> void = 0; - virtual auto end_frame(const RendererContext& context) noexcept -> void = 0; - - // todo - virtual auto create_texture() noexcept -> void = 0; + virtual auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type = 0; virtual auto destroy_texture(texture_id_type texture_id) noexcept -> void = 0; }; -} +} // namespace gal::prometheus::gfx diff --git a/src/gfx/renderer_dx11.cpp b/src/gfx/renderer_dx11.cpp index b388b84..8021fd9 100644 --- a/src/gfx/renderer_dx11.cpp +++ b/src/gfx/renderer_dx11.cpp @@ -25,6 +25,9 @@ namespace { + using namespace gal::prometheus; + using namespace gfx; + [[nodiscard]] auto check_hr_error( const HRESULT result #if defined(GAL_PROMETHEUS_GFX_DEBUG) @@ -55,6 +58,16 @@ namespace } using projection_matrix_type = float[4][4]; + + [[nodiscard]] auto id_to_gpu_handle(const texture_id_type id) noexcept -> ID3D11ShaderResourceView* + { + return reinterpret_cast(id); // NOLINT(performance-no-int-to-ptr) + } + + [[nodiscard]] auto gpu_handle_to_id(const ID3D11ShaderResourceView* srv) noexcept -> texture_id_type + { + return reinterpret_cast(srv); + } } namespace gal::prometheus::gfx @@ -347,8 +360,9 @@ namespace gal::prometheus::gfx return false; } - auto Dx11Renderer::create_texture( - const TextureAtlas& texture, + auto Dx11Renderer::upload_texture( + const Texture::data_view_type data, + const Texture::size_type size, const D3D11_USAGE usage, const std::uint32_t bind_flags, const std::uint32_t cpu_access_flags, @@ -356,13 +370,10 @@ namespace gal::prometheus::gfx const bool record_resource ) noexcept -> texture_id_type { - const auto texture_size = texture.size(); - const auto texture_data = texture.data(); - const D3D11_TEXTURE2D_DESC texture_2d_desc { - .Width = static_cast(texture_size.width), - .Height = static_cast(texture_size.height), + .Width = static_cast(size.width), + .Height = static_cast(size.height), .MipLevels = 1, .ArraySize = 1, .Format = DXGI_FORMAT_R8G8B8A8_UNORM, @@ -375,8 +386,8 @@ namespace gal::prometheus::gfx const D3D11_SUBRESOURCE_DATA subresource_data { - .pSysMem = texture_data.data(), - .SysMemPitch = static_cast(texture_size.width * 4), + .pSysMem = data.data(), + .SysMemPitch = static_cast(size.width * 4), .SysMemSlicePitch = 0 }; @@ -424,7 +435,7 @@ namespace gal::prometheus::gfx texture_2d->Release(); } - return reinterpret_cast(srv); + return gpu_handle_to_id(srv); } Dx11Renderer::Dx11Renderer() noexcept @@ -541,14 +552,16 @@ namespace gal::prometheus::gfx return true; } - auto Dx11Renderer::begin_frame(const RendererContext& context) noexcept -> void + auto Dx11Renderer::create_texture(const Texture::data_view_type data, const Texture::size_type size) noexcept -> texture_id_type { - // todo: create texture + return upload_texture(data, size); } - auto Dx11Renderer::end_frame(const RendererContext& context) noexcept -> void + auto Dx11Renderer::destroy_texture(const texture_id_type texture_id) noexcept -> void { - // + auto* srv = id_to_gpu_handle(texture_id); + + textures_.erase(srv); } } diff --git a/src/gfx/renderer_dx11.hpp b/src/gfx/renderer_dx11.hpp index 7cc5d3d..4f1c966 100644 --- a/src/gfx/renderer_dx11.hpp +++ b/src/gfx/renderer_dx11.hpp @@ -50,8 +50,9 @@ namespace gal::prometheus::gfx [[nodiscard]] auto create_vertex_shader() noexcept -> bool; [[nodiscard]] auto create_pixel_shader() noexcept -> bool; - [[nodiscard]] auto create_texture( - const TextureAtlas& texture, + [[nodiscard]] auto upload_texture( + Texture::data_view_type data, + Texture::size_type size, D3D11_USAGE usage = D3D11_USAGE_DEFAULT, std::uint32_t bind_flags = D3D11_BIND_SHADER_RESOURCE, std::uint32_t cpu_access_flags = 0, @@ -74,10 +75,7 @@ namespace gal::prometheus::gfx [[nodiscard]] auto ready() const noexcept -> bool override; - auto begin_frame(const RendererContext& context) noexcept -> void override; - auto end_frame(const RendererContext& context) noexcept -> void override; - - auto create_texture() noexcept -> void override; + auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type override; auto destroy_texture(texture_id_type texture_id) noexcept -> void override; }; } diff --git a/src/gfx/texture.cpp b/src/gfx/texture.cpp new file mode 100644 index 0000000..eba59aa --- /dev/null +++ b/src/gfx/texture.cpp @@ -0,0 +1,306 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#define STB_RECT_PACK_IMPLEMENTATION +#include + +namespace gal::prometheus::gfx +{ + SubTexture::SubTexture(const point_type point, const data_type data) noexcept + : point_{point}, + data_{data} + { + } + + auto SubTexture::valid() const noexcept -> bool + { + return point_ != invalid_point; + } + + auto SubTexture::point() const noexcept -> point_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + return point_; + } + + auto SubTexture::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n < data_.extent(1)); + + for (size_type::value_type x = offset; x < offset + n; ++x) + { + data_[y, x] = element; + } + } + + auto SubTexture::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + data.size() < data_.extent(1)); + + for (size_type::value_type x = 0; x < data.size(); ++x) + { + data_[y, x + offset] = data[x]; + } + } + + auto SubTexture::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(n < data_.extent(1)); + + fill(y, 0, n, element); + } + + auto SubTexture::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() < data_.extent(1)); + + fill(y, 0, data); + } + + auto SubTexture::fill(const size_type::value_type y, const element_type element) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + + fill(y, data_.extent(1), element); + } + + auto SubTexture::fill(const element_type element) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + for (size_type::value_type y = 0; y < data_.extent(0); ++y) + { + fill(y, element); + } + } + + auto SubTexture::fill(const data_view_type data) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + for (size_type::value_type y = 0; y < data_.extent(0); ++y) + { + const data_view_type sub{data.begin() + y * data_.extent(1), data_.extent(1)}; + + fill(y, sub); + } + } + + auto SubTexture::operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < data_.extent(1)); + + return data_[y, x]; + } + + struct Texture::pack_context_type + { + stbrp_context context{}; + std::vector nodes{}; + }; + + Texture::Texture(const size_type size) noexcept + : pack_context_{memory::make_unique()}, + descriptor_{ + .data = std::make_unique_for_overwrite(static_cast(size.width) * size.height), + .size = size, + .dirty = false, + .id = invalid_texture_id, + } + { + pack_context_->nodes.resize(size.width); + + stbrp_init_target( + &pack_context_->context, static_cast(size.width), static_cast(size.height), pack_context_->nodes.data(), static_cast(pack_context_->nodes.size()) + ); + } + + auto Texture::data() const noexcept -> data_view_type + { + const auto length = descriptor_.size.width * descriptor_.size.height; + + return {descriptor_.data.get(), length}; + } + + auto Texture::bind_id(const texture_id_type id) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id != invalid_texture_id); + + descriptor_.id = id; + } + + auto Texture::size() const noexcept -> size_type + { + return descriptor_.size; + } + + auto Texture::uv() const noexcept -> uv_type + { + const auto s = size(); + + return {1.f / static_cast(s.width), 1.f / static_cast(s.height)}; + } + + auto Texture::dirty() const noexcept -> bool + { + return descriptor_.dirty; + } + + auto Texture::uploaded() const noexcept -> bool + { + return descriptor_.id != invalid_texture_id; + } + + auto Texture::id() const noexcept -> texture_id_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uploaded()); + + return descriptor_.id; + } + + auto Texture::select(const size_type size) noexcept -> SubTexture + { + stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; + + if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) + { + const point_type point{static_cast(rect.x), static_cast(rect.y)}; + + auto* address = descriptor_.data.get() + (point.y * descriptor_.size.width + point.x); + const auto mapping = data_type::mapping_type{ + std::dextents{size.height, size.width}, + std::array{descriptor_.size.width, 1}, + }; + const auto data = data_type{address, mapping}; + + descriptor_.dirty = true; + return {point, data}; + } + + return {SubTexture::invalid_point, {}}; + } + + // struct TextureAtlas::pack_context_type + // { + // stbrp_context context; + // std::vector nodes; + // }; + + // TextureAtlas::TextureAtlas(const value_type width, const value_type height) noexcept + // : pack_context_{memory::make_unique()}, + // size_{width, height}, + // uv_scale_{1.f / static_cast(width), 1.f / static_cast(height)}, + // data_{std::make_unique_for_overwrite(static_cast(width) * height)}, + // dirty_{false}, + // texture_id_{invalid_texture_id} + // { + // pack_context_->nodes.resize(width); + // + // stbrp_init_target(&pack_context_->context, static_cast(width), static_cast(height), pack_context_->nodes.data(), static_cast(pack_context_->nodes.size())); + // } + // + // auto TextureAtlas::build(Renderer& renderer) noexcept -> void + // { + // texture_id_ = renderer.create_texture(); + // } + // + // auto TextureAtlas::destroy(Renderer& renderer) noexcept -> void + // { + // data_.reset(); + // dirty_ = false; + // renderer.destroy_texture(texture_id_); + // texture_id_ = invalid_texture_id; + // } + // + // auto TextureAtlas::valid() const noexcept -> bool + // { + // return texture_id_ != invalid_texture_id; + // } + // + // auto TextureAtlas::dirty() const noexcept -> bool + // { + // return dirty_; + // } + // + // auto TextureAtlas::id() const noexcept -> texture_id_type + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + // return texture_id_; + // } + // + // auto TextureAtlas::size() const noexcept -> size_type + // { + // return size_; + // } + // + // auto TextureAtlas::uv_scale() const noexcept -> uv_scale_type + // { + // return uv_scale_; + // } + // + // auto TextureAtlas::data() const noexcept -> data_view_type + // { + // const auto length = size_.width * size_.height; + // + // return {data_.get(), length}; + // } + // + // auto TextureAtlas::seek(const size_type size) noexcept -> point_type + // { + // stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; + // + // if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) + // { + // return {static_cast(rect.x), static_cast(rect.y)}; + // } + // + // return invalid_point; + // } + // + // auto TextureAtlas::write(const point_type point, const size_type size, const data_view_type data) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.x + size.width <= size_.width); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.y + size.height <= size_.height); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= (static_cast(size.width) * size.height)); + // + // for (value_type y = 0; y < size.height; ++y) + // { + // const auto offset_y = (point.y + y) * size_.width; + // + // for (value_type x = 0; x < size.width; ++x) + // { + // const auto offset_x = point.x + x; + // const auto index = offset_x + offset_y; + // + // data_[index] = data[y * size.width + x]; + // } + // } + // + // dirty_ = true; + // } + // + // auto TextureAtlas::write(const point_type point, const size_type size, const data_type& data) noexcept -> void + // { + // return write(point, size, {data.get(), static_cast(size.width) * size.height}); + // } + +} // namespace gal::prometheus::gfx diff --git a/src/gfx/texture.hpp b/src/gfx/texture.hpp new file mode 100644 index 0000000..ba4b4a0 --- /dev/null +++ b/src/gfx/texture.hpp @@ -0,0 +1,242 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include + +#include + +namespace gal::prometheus::gfx +{ + class TextureDescriptor final + { + public: + using point_type = primitive::basic_point_2d; + using size_type = primitive::basic_extent_2d; + + using element_type = std::uint32_t; + // size.width * size.height (RGBA) + using data_type = std::unique_ptr; + using data_view_type = std::span; + + // ============================== + // CPU side + // ============================== + + data_type data; + size_type size; + + static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); + std::uint64_t dirty : 1; + + // ============================== + // GPU side + // ============================== + + std::uint64_t id : 63; + }; + + class SubTexture + { + public: + using point_type = TextureDescriptor::point_type; + using size_type = TextureDescriptor::size_type; + + using element_type = TextureDescriptor::element_type; + using data_type = std::mdspan, std::layout_stride>; + using data_view_type = TextureDescriptor::data_view_type; + + constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; + + private: + point_type point_; + data_type data_; + + public: + SubTexture(point_type point, data_type data) noexcept; + + [[nodiscard]] auto valid() const noexcept -> bool; + + [[nodiscard]] auto point() const noexcept -> point_type; + + auto fill(size_type::value_type y, size_type::value_type offset, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, size_type::value_type offset, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, element_type element) const noexcept -> void; + + auto fill(element_type element) const noexcept -> void; + auto fill(data_view_type data) const noexcept -> void; + + auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference; + }; + + class Texture final + { + public: + using point_type = SubTexture::point_type; + using size_type = SubTexture::size_type; + + using element_type = SubTexture::element_type; + using data_type = SubTexture::data_type; + using data_view_type = SubTexture::data_view_type; + + using uv_type = primitive::basic_extent_2d; + + private: + struct pack_context_type; + memory::UniquePointer pack_context_; + + TextureDescriptor descriptor_; + + public: + explicit Texture(size_type size) noexcept; + + /** + * @brief Texture atlas data (for upload) + */ + [[nodiscard]] auto data() const noexcept -> data_view_type; + + /** + * @brief Set texture atlas id (GPU handle) + */ + auto bind_id(texture_id_type id) noexcept -> void; + + /** + * @brief Texture atlas size + */ + [[nodiscard]] auto size() const noexcept -> size_type; + + /** + * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) + */ + [[nodiscard]] auto uv() const noexcept -> uv_type; + + /** + * @brief Does this texture atlas need to be re-uploaded to the GPU + */ + [[nodiscard]] auto dirty() const noexcept -> bool; + + /** + * @brief Texture atlas uploaded (to GPU) + */ + [[nodiscard]] auto uploaded() const noexcept -> bool; + + /** + * @brief Texture atlas id (usually a GPU resource handle) + */ + [[nodiscard]] auto id() const noexcept -> texture_id_type; + + /** + * @brief Find a region that can hold a (piece of) texture of @c size + * @param size Texture size + */ + [[nodiscard]] auto select(size_type size) noexcept -> SubTexture; + }; + + // /** + // * @brief Texture atlas uploaded to the GPU + // */ + // class TextureAtlas final + // { + // public: + // using value_type = std::uint32_t; + // using point_type = primitive::basic_point_2d; + // using size_type = primitive::basic_extent_2d; + // + // using uv_scale_type = extent_type; + // + // // size.width * size.height (RGBA) + // using data_type = std::unique_ptr; + // using data_view_type = std::span; + // + // constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; + // + // private: + // struct pack_context_type; + // memory::UniquePointer pack_context_; + // + // // Size of texture atlas + // size_type size_; + // // UV scale of texture atlas (1.0f / size.width, 1.0f / size.height) + // uv_scale_type uv_scale_; + // // Texture atlas data (CPU side) + // data_type data_; + // + // static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); + // // Does this texture need to be updated (re-uploaded) + // std::uint64_t dirty_ : 1; + // // GPU resource handle + // std::uint64_t texture_id_ : 63; + // + // public: + // TextureAtlas(value_type width, value_type height) noexcept; + // + // auto build(Renderer& renderer) noexcept -> void; + // auto destroy(Renderer& renderer) noexcept -> void; + // + // /** + // * @brief Texture atlas size + // */ + // [[nodiscard]] auto size() const noexcept -> size_type; + // + // /** + // * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) + // */ + // [[nodiscard]] auto uv_scale() const noexcept -> uv_scale_type; + // + // /** + // * @brief Texture atlas data (CPU side) + // */ + // [[nodiscard]] auto data() const noexcept -> data_view_type; + // + // /** + // * @brief Does this texture atlas is valid (uploaded to GPU) + // */ + // [[nodiscard]] auto valid() const noexcept -> bool; + // + // /** + // * @brief Does this texture atlas need to be re-uploaded to the GPU + // */ + // [[nodiscard]] auto dirty() const noexcept -> bool; + // + // /** + // * @brief Texture atlas id (usually a GPU resource handle) + // */ + // [[nodiscard]] auto id() const noexcept -> texture_id_type; + // + // /** + // * @brief Find a region that can hold a (piece of) texture of @c size + // * @param size texture size + // * @return texture coordinate + // * @note If such a region is not found, @c invalid_point is returned + // */ + // [[nodiscard]] auto seek(size_type size) noexcept -> point_type; + // + // /** + // * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture + // * @param point texture coordinate + // * @param size texture size + // * @param data texture data + // * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height + // */ + // auto write(point_type point, size_type size, data_view_type data) noexcept -> void; + // + // /** + // * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture + // * @param point texture coordinate + // * @param size texture size + // * @param data texture data + // * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height + // */ + // auto write(point_type point, size_type size, const data_type& data) noexcept -> void; + // }; +} // namespace gal::prometheus::gfx diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index b59d74b..71736bd 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -5,10 +5,12 @@ #pragma once -#include +#include +#include +#include #include +#include #include -#include #include namespace gal::prometheus::gfx @@ -17,35 +19,50 @@ namespace gal::prometheus::gfx using uv_type = primitive::basic_point_2d; using color_type = primitive::basic_color; using vertex_type = primitive::basic_vertex; + using index_type = std::uint16_t; using extent_type = primitive::basic_extent_2d; using rect_type = primitive::basic_rect_2d; + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; // ========================================================= - // FONT + // TEXTURE // ========================================================= + // DX11: ID3D11ShaderResourceView + // DX12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr using texture_id_type = std::uintptr_t; constexpr texture_id_type invalid_texture_id{0}; + class TextureDescriptor; + class SubTexture; + class Texture; + + // ========================================================= + // FONT + // ========================================================= + using texture_atlas_id_type = std::uint32_t; constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; using font_id_type = std::uint32_t; - constexpr texture_id_type invalid_font_id{std::numeric_limits::max()}; + constexpr font_id_type invalid_font_id{std::numeric_limits::max()}; - class TextureAtlas; class GlyphKey; class GlyphInfo; - class GlyphUploadInfo; class GlyphParser; + class GlyphUploadInfo; class FontFace; - class TextureContext; // ========================================================= // RENDERER // ========================================================= + class TextureContext; + + class RenderListSharedData; + class RenderList; class Renderer; class RendererContext; -} +} // namespace gal::prometheus::gfx From 0b17cb003327c5ffbba711e4e8538cbddd712e21 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Fri, 9 May 2025 01:36:50 +0800 Subject: [PATCH 33/54] `format`: Update .clang-format. --- .clang-format | 59 +++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/.clang-format b/.clang-format index bbbefa9..50d665b 100644 --- a/.clang-format +++ b/.clang-format @@ -7,43 +7,45 @@ AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: Align AlignTrailingComments: - Kind: Always - OverEmptyLines: 2 + Kind: Always + OverEmptyLines: 2 AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All +AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: WithoutElse -AllowShortLambdasOnASingleLine: Inline +AllowShortLambdasOnASingleLine: Empty AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true BinPackArguments: false BinPackParameters: false BreakBeforeBraces: Allman -BraceWrapping: - AfterCaseLabel: true - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterStruct: true - AfterUnion: true - AfterExternBlock: true - BeforeCatch: true - BeforeElse: true - BeforeLambdaBody: true - BeforeWhile: true - IndentBraces: false - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: true + BeforeWhile: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BracedInitializerIndentWidth: 4 BreakBeforeBinaryOperators: None BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon BreakStringLiterals: true +BreakTemplateDeclarations: Yes ColumnLimit: 200 CompactNamespaces: false ConstructorInitializerIndentWidth: 4 @@ -51,12 +53,12 @@ ContinuationIndentWidth: 8 Cpp11BracedListStyle: true FixNamespaceComments: true IncludeCategories: - - Regex: '^<.*' - Priority: 1 - - Regex: '^".*' - Priority: 2 - - Regex: '.*' - Priority: 3 + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: true IndentPPDirectives: BeforeHash @@ -69,6 +71,7 @@ MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 2 NamespaceIndentation: All +PackConstructorInitializers: Never PointerAlignment: Left ReflowComments: false SpaceAfterCStyleCast: false From 8cba731df8647ce77de65c2b57586d6e422c2dad Mon Sep 17 00:00:00 2001 From: Life4gal Date: Fri, 9 May 2025 16:55:42 +0800 Subject: [PATCH 34/54] =?UTF-8?q?`WIP`:=20gfx.=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gfx/context.cpp | 142 +++++++------- src/gfx/context.hpp | 68 +++---- src/gfx/font.cpp | 53 +++-- src/gfx/font.hpp | 78 ++++++-- src/gfx/glyph_parser_freetype.cpp | 64 +++--- src/gfx/glyph_parser_freetype.hpp | 4 +- src/gfx/render_list.cpp | 118 ++++++----- src/gfx/render_list.hpp | 6 +- src/gfx/texture.cpp | 316 +++++++++++------------------- src/gfx/texture.hpp | 216 ++++++-------------- src/gfx/type.hpp | 24 ++- 11 files changed, 489 insertions(+), 600 deletions(-) diff --git a/src/gfx/context.cpp b/src/gfx/context.cpp index df60d6c..cb53489 100644 --- a/src/gfx/context.cpp +++ b/src/gfx/context.cpp @@ -41,7 +41,7 @@ namespace gal::prometheus::gfx return 0; } - auto TextureContext::make_territory(const Texture::size_type size) noexcept -> SubTexture + auto TextureContext::make_territory(const Texture::size_type size) noexcept -> BorrowTexture { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); @@ -72,8 +72,8 @@ namespace gal::prometheus::gfx // ======================================== { constexpr std::uint32_t white_color = 0xff'ff'ff'ff; - constexpr auto aa_width = RenderListSharedData::baked_line_uv_count; - constexpr auto aa_height = RenderListSharedData::baked_line_uv_count; + constexpr auto aa_width = static_cast(RenderListSharedData::baked_line_uv_count); + constexpr auto aa_height = static_cast(RenderListSharedData::baked_line_uv_count); constexpr auto aa_size = Texture::size_type{aa_width, aa_height}; const auto atlas_id = select_atlas(aa_size); @@ -147,92 +147,30 @@ namespace gal::prometheus::gfx return *parser_; } - auto TextureContext::add_font(const std::filesystem::path& path) noexcept -> void + auto TextureContext::add_font(const std::filesystem::path& path) noexcept -> bool { std::ifstream file{path, std::ios::binary}; if (not file.is_open()) { // todo: error handling - return; + return false; } file.seekg(0, std::ios::end); const auto size = file.tellg(); - auto data = std::make_unique_for_overwrite(size); + auto data = std::make_unique_for_overwrite(size); file.seekg(0, std::ios::beg); file.read(reinterpret_cast(data.get()), size); file.close(); - font_face_tasks_.emplace_back(std::move(data), size); + font_pending_load_datas_.emplace_back(std::move(data), static_cast(size)); + return true; } - auto TextureContext::add_font(const std::string_view path) noexcept -> void + auto TextureContext::add_font(const std::string_view path) noexcept -> bool { - add_font(std::filesystem::path{path}); - } - - auto TextureContext::load_all_font() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); - - std::ranges::for_each( - font_face_tasks_, - [this](const FontFaceTask& task) noexcept -> void - { - if (auto result = parser_->load({task.data.get(), task.size}); result.valid()) - { - font_faces_.emplace_back(*this, std::move(result.name), result.id); - } - else - { - // todo: error handling - } - } - ); - font_face_tasks_.clear(); - } - - auto TextureContext::upload_all_font_face() noexcept -> void - { - std::ranges::for_each(font_faces_, &FontFace::upload); - } - - auto TextureContext::upload_glyph_to_texture(GlyphUploadInfo& upload_info) noexcept -> void - { - auto& info = upload_info.info.get(); - const auto& data = upload_info.data; - - const auto width = static_cast(info.rect.width()); - const auto height = static_cast(info.rect.height()); - const auto size = Texture::size_type{width, height}; - - const auto atlas_id = select_atlas(size); - const auto& atlas = select_atlas(atlas_id); - - const auto atlas_uv_scale = atlas.uv(); - - const auto& texture = make_territory(size); - texture.fill({data.get(), width * height}); - - const auto texture_point = texture.point(); - info.texture_atlas_id = atlas_id; - info.uv.point = texture_point.to() * atlas_uv_scale; - info.uv.extent = size.to() * atlas_uv_scale; - } - - auto TextureContext::upload_all_texture(Renderer& renderer) noexcept -> void - { - std::ranges::for_each( - texture_atlases_, - [&renderer](auto& atlas) noexcept -> void - { - if (not atlas.valid()) - { - atlas.build(renderer); - } - } - ); + return add_font(std::filesystem::path{path}); } auto TextureContext::root_texture() const noexcept -> texture_id_type @@ -310,6 +248,66 @@ namespace gal::prometheus::gfx return total_size; } + auto TextureContext::load_all_font() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + + std::ranges::for_each( + font_pending_load_datas_, + [this](const FontPendingLoadData& data) noexcept -> void + { + if (auto result = parser_->load(data); result.valid()) + { + font_faces_.emplace_back(*this, std::move(result.name), result.id); + } + else + { + // todo: error handling + } + } + ); + font_pending_load_datas_.clear(); + } + + auto TextureContext::upload_all_font_face() noexcept -> void + { + std::ranges::for_each(font_faces_, &FontFace::upload); + } + + auto TextureContext::upload_glyph_to_texture(GlyphInfo& info, const GlyphParsedInfo::data_type& data) noexcept -> void + { + const auto width = static_cast(info.rect.width()); + const auto height = static_cast(info.rect.height()); + const auto size = Texture::size_type{width, height}; + + const auto atlas_id = select_atlas(size); + const auto& atlas = select_atlas(atlas_id); + + const auto atlas_uv_scale = atlas.uv(); + + const auto& texture = make_territory(size); + texture.fill({data.get(), width * height}); + + const auto texture_point = texture.point(); + info.texture_atlas_id = atlas_id; + info.uv.point = texture_point.to() * atlas_uv_scale; + info.uv.extent = size.to() * atlas_uv_scale; + } + + auto TextureContext::upload_all_texture(Renderer& renderer) noexcept -> void + { + std::ranges::for_each( + texture_atlases_, + [&renderer](auto& atlas) noexcept -> void + { + if (not atlas.uploaded()) + { + atlas.upload(renderer); + } + } + ); + } + RendererContext::~RendererContext() noexcept = default; RendererContext::RendererContext() noexcept = default; diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp index 2f97324..84b54eb 100644 --- a/src/gfx/context.hpp +++ b/src/gfx/context.hpp @@ -24,8 +24,8 @@ namespace gal::prometheus::gfx class Territory final { public: - using point_type = TextureDescriptor::point_type; - using size_type = TextureDescriptor::size_type; + using point_type = Texture::point_type; + using size_type = Texture::size_type; texture_atlas_id_type id{invalid_texture_atlas_id}; @@ -35,23 +35,15 @@ namespace gal::prometheus::gfx using territories_type = std::vector; - class FontFaceTask final - { - public: - using value_type = GlyphParser::binary_data_type::element_type; - - std::unique_ptr data; - std::size_t size; - }; - - using font_face_tasks_type = std::vector; + using font_pending_load_datas = std::vector; GlyphParser* parser_; texture_atlases_type texture_atlases_; - font_faces_type font_faces_; territories_type territories_; - font_face_tasks_type font_face_tasks_; + + font_faces_type font_faces_; + font_pending_load_datas font_pending_load_datas_; /** * @brief Retain at least one texture atlas (root) @@ -82,7 +74,7 @@ namespace gal::prometheus::gfx * @param size Sub texture size * @return Region on the texture atlas */ - auto make_territory(Texture::size_type size) noexcept -> SubTexture; + auto make_territory(Texture::size_type size) noexcept -> BorrowTexture; public: TextureContext(const TextureContext&) noexcept = delete; @@ -112,33 +104,13 @@ namespace gal::prometheus::gfx * @brief Load fonts from the specified path, assuming the path is a valid font file * @param path Font path */ - auto add_font(const std::filesystem::path& path) noexcept -> void; + auto add_font(const std::filesystem::path& path) noexcept -> bool; /** * @brief Load fonts from the specified path, assuming the path is a valid font file * @param path Font path */ - auto add_font(std::string_view path) noexcept -> void; - - /** - * @brief Load the fonts previously added by @c add_font - */ - auto load_all_font() noexcept -> void; - - /** - * @brief UUpload all used glyphs to the texture (if it is not already uploaded) - */ - auto upload_all_font_face() noexcept -> void; - - /** - * @brief Write FontFace uploaded glyph data to texture, also set the texture atlas id and uv coordinates for this glyph data - */ - auto upload_glyph_to_texture(GlyphUploadInfo& upload_info) noexcept -> void; - - /** - * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) - */ - auto upload_all_texture(Renderer& renderer) noexcept -> void; + auto add_font(std::string_view path) noexcept -> bool; /** * @brief Get root (default) texture @@ -170,6 +142,28 @@ namespace gal::prometheus::gfx * @return */ [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + + // ============================================================================================================================= + + /** + * @brief Load the fonts previously added by @c add_font + */ + auto load_all_font() noexcept -> void; + + /** + * @brief Upload all used glyphs to the texture (if it is not already uploaded) + */ + auto upload_all_font_face() noexcept -> void; + + /** + * @brief Write FontFace uploaded glyph data to texture, also set the @c texture_atlas_id and @c uv coordinates for this glyph data + */ + auto upload_glyph_to_texture(GlyphInfo& info, const GlyphParsedInfo::data_type& data) noexcept -> void; + + /** + * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) + */ + auto upload_all_texture(Renderer& renderer) noexcept -> void; }; class RendererContext final diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp index b59dc4a..6fea44a 100644 --- a/src/gfx/font.cpp +++ b/src/gfx/font.cpp @@ -10,13 +10,46 @@ #include #include -#define STB_RECT_PACK_IMPLEMENTATION -#include - namespace gal::prometheus::gfx { + FontPendingLoadData::FontPendingLoadData(data_type data, const size_type size) noexcept + : data_{std::move(data)}, + size_{size} + { + } + + auto FontPendingLoadData::data() const noexcept -> data_view_type + { + return {data_.get(), size()}; + } + + auto FontPendingLoadData::size() const noexcept -> size_type + { + return size_; + } + + GlyphParsedInfo::GlyphParsedInfo(GlyphInfo& info, data_type data) noexcept + : info_{info}, + data_{std::move(data)} + { + } + + auto GlyphParsedInfo::upload(TextureContext& texture_context) noexcept -> void + { + auto& info = info_.get(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id == invalid_texture_atlas_id); + texture_context.upload_glyph_to_texture(info, data_); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id != invalid_texture_atlas_id); + } + GlyphParser::~GlyphParser() noexcept = default; + auto GlyphParser::load(const FontPendingLoadData& data) noexcept -> LoadResult + { + return this->load(data.data()); + } + auto GlyphParser::parse(const font_id_type id, const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> ParseResult { return this->parse(id, {.codepoint = codepoint, .size = size, .flag = flag}); @@ -45,7 +78,7 @@ namespace gal::prometheus::gfx const auto [it, inserted] = glyphs_.emplace(key, result.info); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(inserted); - upload_queue_.emplace_back(memory::ref(it->second), std::move(result.data)); + parsed_infos_upload_queue_.emplace_back(memory::ref(it->second), std::move(result.data)); return std::addressof(it->second); } @@ -99,17 +132,13 @@ namespace gal::prometheus::gfx auto FontFace::upload() noexcept -> void { std::ranges::for_each( - upload_queue_, - [&context = context_.get()](GlyphUploadInfo& upload_info) noexcept -> void + parsed_infos_upload_queue_, + [&context = context_.get()](GlyphParsedInfo& parsed_info) noexcept -> void { - const auto& info = upload_info.info.get(); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id == invalid_texture_atlas_id); - context.upload_glyph_to_texture(upload_info); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id != invalid_texture_atlas_id); + parsed_info.upload(context); } ); - upload_queue_.clear(); + parsed_infos_upload_queue_.clear(); } auto FontFace::find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo& diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp index 8f29466..f68093b 100644 --- a/src/gfx/font.hpp +++ b/src/gfx/font.hpp @@ -73,14 +73,65 @@ namespace gal::prometheus::gfx uv_type uv; }; + /** + * @brief Font data to be loaded, then GlyphParser::load will load its bitmap data + */ + class FontPendingLoadData final + { + public: + using element_type = std::uint8_t; + using data_type = std::unique_ptr; + using size_type = std::uint32_t; + + using data_view_type = std::span; + + private: + data_type data_; + size_type size_; + + public: + FontPendingLoadData(data_type data, size_type size) noexcept; + + [[nodiscard]] auto data() const noexcept -> data_view_type; + + [[nodiscard]] auto size() const noexcept -> size_type; + }; + + /** + * @brief Glyph data parsed by @c GlyphParser::parse, + * which references @c GlyphInfo (to set its @c texture_atlas_id and @c uv) and holds the bitmap data for the glyph (which is automatically released after writing it to the texture atlas) + */ + class GlyphParsedInfo final + { + public: + using info_type = memory::RefWrapper; + using data_type = Texture::data_type; + + private: + info_type info_; + data_type data_; + + public: + /** + * @param info Glyph info + * @param data Glypy bitmap data + * + * @link FontFace::find_or_parse_glyph + */ + GlyphParsedInfo(GlyphInfo& info, data_type data) noexcept; + + /** + * @brief Upload the glyph data to the texture atlas, set its @c texture_atlas_id and @c uv + */ + auto upload(TextureContext& texture_context) noexcept -> void; + }; + /** * @brief Parse glyph data from (binary) font data */ class GlyphParser { public: - using binary_data_type = std::span; - class [[nodiscard]] LoadResult final { public: @@ -102,7 +153,9 @@ namespace gal::prometheus::gfx { public: GlyphInfo info; - TextureDescriptor::data_type data; + + // todo: Borrows a memory region from the texture to write to, rather than having it allocated by the parser + GlyphParsedInfo::data_type data; [[nodiscard]] explicit operator bool() const noexcept { @@ -129,7 +182,12 @@ namespace gal::prometheus::gfx /** * @brief Load the font data, get all its glyph data, return the id of the font */ - [[nodiscard]] virtual auto load(binary_data_type data) noexcept -> LoadResult = 0; + [[nodiscard]] virtual auto load(FontPendingLoadData::data_view_type data) noexcept -> LoadResult = 0; + + /** + * @brief Load the font data, get all its glyph data, return the id of the font + */ + [[nodiscard]] auto load(const FontPendingLoadData& data) noexcept -> LoadResult; /** * @brief Determines whether the target font contains the glyphs of the specified codepoint @@ -158,16 +216,6 @@ namespace gal::prometheus::gfx [[nodiscard]] auto parse(font_id_type id, std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> ParseResult; }; - /** - * @brief Write @c data to texture according to @c info, and set texture_atlas_id and uv of @c info - */ - class GlyphUploadInfo final - { - public: - memory::RefWrapper info; - TextureDescriptor::data_type data; - }; - /** * @brief All the glyph data used in a font */ @@ -183,7 +231,7 @@ namespace gal::prometheus::gfx std::string name_; font_id_type id_; - std::vector upload_queue_; + std::vector parsed_infos_upload_queue_; std::unordered_map glyphs_; const GlyphInfo* fallback_glyph_; diff --git a/src/gfx/glyph_parser_freetype.cpp b/src/gfx/glyph_parser_freetype.cpp index e6e5243..d9fc0dc 100644 --- a/src/gfx/glyph_parser_freetype.cpp +++ b/src/gfx/glyph_parser_freetype.cpp @@ -5,33 +5,25 @@ #include -#include - #if defined(GAL_PROMETHEUS_GFX_GLYPH_PARSER_FREETYPE) +#include + namespace { [[nodiscard]] auto ft_size_to_float(const FT_Pos size) noexcept -> float { return static_cast((size + 63) >> 6); } -} +} // namespace namespace gal::prometheus::gfx { auto FreeTypeGlyphParser::FontInfo::set_pixel_height(const std::size_t height) noexcept -> bool { - FT_Size_RequestRec request - { - .type = FT_SIZE_REQUEST_TYPE_NOMINAL, - .width = 0, - .height = static_cast(height) * 64, - .horiResolution = 0, - .vertResolution = 0 - }; + FT_Size_RequestRec request{.type = FT_SIZE_REQUEST_TYPE_NOMINAL, .width = 0, .height = static_cast(height) * 64, .horiResolution = 0, .vertResolution = 0}; - if (const auto error = FT_Request_Size(face, &request); - error != FT_Err_Ok) + if (const auto error = FT_Request_Size(face, &request); error != FT_Err_Ok) { return false; } @@ -50,25 +42,26 @@ namespace gal::prometheus::gfx FreeTypeGlyphParser::~FreeTypeGlyphParser() noexcept { std::ranges::for_each( - infos_, - [](auto& info) noexcept -> void - { - ::FT_Done_Face(info.face); - } + infos_, + [](auto& info) noexcept -> void + { + ::FT_Done_Face(info.face); + } ); FT_Done_FreeType(library_); } FreeTypeGlyphParser::FreeTypeGlyphParser() noexcept - : library_{nullptr} {} + : library_{nullptr} + { + } auto FreeTypeGlyphParser::initialize() noexcept -> bool { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(library_ == nullptr); - if (const auto error = FT_Init_FreeType(&library_); - error != FT_Err_Ok) + if (const auto error = FT_Init_FreeType(&library_); error != FT_Err_Ok) { return false; } @@ -76,7 +69,7 @@ namespace gal::prometheus::gfx return true; } - auto FreeTypeGlyphParser::load(const binary_data_type data) noexcept -> LoadResult + auto FreeTypeGlyphParser::load(const FontPendingLoadData::data_view_type data) noexcept -> LoadResult { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not data.empty()); @@ -84,14 +77,12 @@ namespace gal::prometheus::gfx LoadResult invalid_result{.name = {}, .id = invalid_font_id}; FT_Face face = nullptr; - if (const auto error = FT_New_Memory_Face(library_, data.data(), static_cast(data.size()), 0, &face); - error != FT_Err_Ok) + if (const auto error = FT_New_Memory_Face(library_, data.data(), static_cast(data.size()), 0, &face); error != FT_Err_Ok) { return invalid_result; } - if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); - error != FT_Err_Ok) + if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) { FT_Done_Face(face); return invalid_result; @@ -136,8 +127,7 @@ namespace gal::prometheus::gfx info.set_pixel_height(key.size); - if (const auto error = FT_Load_Glyph(info.face, char_index, FT_LOAD_DEFAULT); - error != FT_Err_Ok) + if (const auto error = FT_Load_Glyph(info.face, char_index, FT_LOAD_DEFAULT); error != FT_Err_Ok) { return invalid_result; } @@ -153,8 +143,7 @@ namespace gal::prometheus::gfx FT_GlyphSlot_Oblique(slot); } - if (const auto error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); - error != FT_Err_Ok) + if (const auto error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); error != FT_Err_Ok) { return invalid_result; } @@ -165,18 +154,17 @@ namespace gal::prometheus::gfx const extent_type size{static_cast(bitmap.width), static_cast(bitmap.rows)}; const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; - ParseResult result - { - .info = - { + ParseResult result{ + .info = + { .rect = {point, size}, .advance_x = ft_size_to_float(slot->advance.x), .visible = size.width > 0 and size.height > 0, .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, .texture_atlas_id = invalid_texture_atlas_id, - .uv = {} - }, - .data = std::make_unique_for_overwrite(data_length) + .uv = {}, + }, + .data = std::make_unique_for_overwrite(data_length) }; { @@ -244,6 +232,6 @@ namespace gal::prometheus::gfx return result; } -} +} // namespace gal::prometheus::gfx #endif diff --git a/src/gfx/glyph_parser_freetype.hpp b/src/gfx/glyph_parser_freetype.hpp index ac19921..22463bc 100644 --- a/src/gfx/glyph_parser_freetype.hpp +++ b/src/gfx/glyph_parser_freetype.hpp @@ -53,12 +53,12 @@ namespace gal::prometheus::gfx auto initialize() noexcept -> bool override; - auto load(binary_data_type data) noexcept -> LoadResult override; + auto load(FontPendingLoadData::data_view_type data) noexcept -> LoadResult override; [[nodiscard]] auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool override; auto parse(font_id_type id, const GlyphKey& key) noexcept -> ParseResult override; }; -} +} // namespace gal::prometheus::gfx #endif diff --git a/src/gfx/render_list.cpp b/src/gfx/render_list.cpp index d3c53a0..61d8bf7 100644 --- a/src/gfx/render_list.cpp +++ b/src/gfx/render_list.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE @@ -345,12 +347,12 @@ namespace gal::prometheus::gfx [[nodiscard]] auto make_appender() noexcept -> RenderDataAppender { - auto& draw_list = self.get(); + auto& render_list = self.get(); - return {draw_list.command_list_.back(), draw_list.vertex_list_, draw_list.index_list_}; + return {render_list.command_list_.back(), render_list.vertex_list_, render_list.index_list_}; } - auto draw_polygon_line(const color_type color, const RenderFlag draw_flag, const float thickness) noexcept -> void + auto draw_polygon_line(const color_type color, const RenderFlag render_flag, const float thickness) noexcept -> void { const auto path_point_count = path_list.size(); const auto& path_point = path_list; @@ -360,11 +362,11 @@ namespace gal::prometheus::gfx return; } - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); auto appender = make_appender(); - const auto is_closed = (draw_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; const auto segments_count = is_closed ? path_point_count : path_point_count - 1; const auto vertex_count = segments_count * 4; @@ -395,7 +397,7 @@ namespace gal::prometheus::gfx } } - auto draw_polygon_line_aa(const color_type color, const RenderFlag draw_flag, float thickness) noexcept -> void + auto draw_polygon_line_aa(const color_type color, const RenderFlag render_flag, float thickness) noexcept -> void { const auto path_point_count = path_list.size(); const auto& path_point = path_list; @@ -405,15 +407,15 @@ namespace gal::prometheus::gfx return; } - const auto& draw_list = self.get(); - const auto draw_list_flag = draw_list.draw_list_flag_; - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto render_list_flag = render_list.render_list_flag_; + const auto& shared_data = render_list.shared_data(); auto appender = make_appender(); const auto& opaque_uv = shared_data.white_pixel_uv; const auto transparent_color = color.transparent(); - const auto is_closed = (draw_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; const auto segments_count = is_closed ? path_point_count : path_point_count - 1; const auto is_thick_line = thickness > 1.f; @@ -422,7 +424,7 @@ namespace gal::prometheus::gfx const auto thickness_fractional = thickness - static_cast(thickness_integer); const auto is_use_texture = - ((draw_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); @@ -624,8 +626,8 @@ namespace gal::prometheus::gfx return; } - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); auto appender = make_appender(); const auto vertex_count = path_point_count; @@ -661,8 +663,8 @@ namespace gal::prometheus::gfx return; } - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); auto appender = make_appender(); const auto& opaque_uv = shared_data.white_pixel_uv; @@ -769,8 +771,8 @@ namespace gal::prometheus::gfx ) noexcept -> void // clang-format on { - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); const auto& opaque_uv = shared_data.white_pixel_uv; draw_rect_filled(rect, opaque_uv, color_left_top, color_right_top, color_left_bottom, color_right_bottom); @@ -786,8 +788,10 @@ namespace gal::prometheus::gfx ) noexcept -> void // clang-format on { - auto& draw_list = self.get(); - auto& texture_context = draw_list.context_.get().texture_context(); + std::ignore = wrap_width; + + auto& render_list = self.get(); + auto& texture_context = render_list.context_.get().texture_context(); const auto& glyphs = texture_context.glyph_of(utf8_text, font_size); for (auto x = p.x; const auto& glyph: glyphs) @@ -801,11 +805,11 @@ namespace gal::prometheus::gfx { const auto& atlas = texture_context.atlas_of(*glyph); - const auto new_texture = draw_list.this_command_texture_ != atlas.id(); + const auto new_texture = render_list.this_command_texture_ != atlas.id(); if (new_texture) { - draw_list.push_texture(atlas.id()); + render_list.push_texture(atlas.id()); } // todo @@ -835,7 +839,7 @@ namespace gal::prometheus::gfx if (new_texture) { - draw_list.pop_texture(); + render_list.pop_texture(); } } @@ -858,13 +862,13 @@ namespace gal::prometheus::gfx ) noexcept -> void // clang-format on { - auto& draw_list = self.get(); + auto& render_list = self.get(); - const auto new_texture = draw_list.this_command_texture_ != texture_id; + const auto new_texture = render_list.this_command_texture_ != texture_id; if (new_texture) { - draw_list.push_texture(texture_id); + render_list.push_texture(texture_id); } auto appender = make_appender(); @@ -886,7 +890,7 @@ namespace gal::prometheus::gfx if (new_texture) { - draw_list.pop_texture(); + render_list.pop_texture(); } } @@ -933,13 +937,13 @@ namespace gal::prometheus::gfx } else { - auto& draw_list = self.get(); + auto& render_list = self.get(); - const auto new_texture = draw_list.this_command_texture_ != texture_id; + const auto new_texture = render_list.this_command_texture_ != texture_id; if (new_texture) { - draw_list.push_texture(texture_id); + render_list.push_texture(texture_id); } const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; @@ -952,10 +956,10 @@ namespace gal::prometheus::gfx path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); - const auto before_vertex_count = draw_list.vertex_list_.size(); + const auto before_vertex_count = render_list.vertex_list_.size(); // draw path_stroke(color); - const auto after_vertex_count = draw_list.vertex_list_.size(); + const auto after_vertex_count = render_list.vertex_list_.size(); // set uv manually @@ -963,10 +967,10 @@ namespace gal::prometheus::gfx const auto uv_size = uv_rect.size(); const auto scale = uv_size / display_size; - auto it = draw_list.vertex_list_.begin() + static_cast(before_vertex_count); - const auto end = draw_list.vertex_list_.begin() + static_cast(after_vertex_count); + auto it = render_list.vertex_list_.begin() + static_cast(before_vertex_count); + const auto end = render_list.vertex_list_.begin() + static_cast(after_vertex_count); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == draw_list.vertex_list_.end()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == render_list.vertex_list_.end()); // note: linear uv const auto uv_min = uv_rect.left_top(); @@ -986,7 +990,7 @@ namespace gal::prometheus::gfx if (new_texture) { - draw_list.pop_texture(); + render_list.pop_texture(); } } } @@ -1013,7 +1017,7 @@ namespace gal::prometheus::gfx auto path_stroke(const color_type color, const RenderFlag flag, const float thickness) noexcept -> void { - if (const auto draw_list_flag = self.get().draw_list_flag_; (draw_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) + if (const auto render_list_flag = self.get().render_list_flag_; (render_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) { draw_polygon_line_aa(color, flag, thickness); } @@ -1027,7 +1031,7 @@ namespace gal::prometheus::gfx auto path_stroke(const color_type color) noexcept -> void { - if (const auto draw_list_flag = self.get().draw_list_flag_; (draw_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) + if (const auto render_list_flag = self.get().render_list_flag_; (render_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) { draw_convex_polygon_line_filled_aa(color); } @@ -1049,8 +1053,8 @@ namespace gal::prometheus::gfx return; } - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); // Calculate arc auto segment step size auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); @@ -1173,8 +1177,8 @@ namespace gal::prometheus::gfx return; } - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); // Automatic segment count if (radius <= shared_data.arc_fast_radius_cutoff) @@ -1346,8 +1350,8 @@ namespace gal::prometheus::gfx auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void { - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); path_pin(p1); if (segments == 0) @@ -1371,8 +1375,8 @@ namespace gal::prometheus::gfx auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void { - const auto& draw_list = self.get(); - const auto& shared_data = draw_list.context_.get().render_list_shared_data(); + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); path_pin(p1); if (segments == 0) @@ -1478,11 +1482,21 @@ namespace gal::prometheus::gfx command_list_.back().texture = this_command_texture_; } + auto RenderList::shared_data() const noexcept -> const RenderListSharedData& + { + return context_.get().render_list_shared_data(); + } + + auto RenderList::default_texture() const noexcept -> texture_id_type + { + return context_.get().texture_context().root_texture(); + } + RenderList::RenderList(const RenderListFlag flag, RendererContext& context) noexcept - : draw_list_flag_{flag}, + : render_list_flag_{flag}, context_{context}, this_command_scissor_{0, 0, 0, 0}, - this_command_texture_{context.texture_context().root_texture()} + this_command_texture_{default_texture()} { // we always have a command ready in the buffer command_list_.emplace_back( @@ -1996,12 +2010,10 @@ namespace gal::prometheus::gfx return; } - const auto& shared_data = context_.get().render_list_shared_data(); - if (segments == 0) { // fixme - segments = shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + segments = shared_data().circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); } ellipse_n(ellipse, color, segments, thickness); @@ -2034,12 +2046,10 @@ namespace gal::prometheus::gfx return; } - const auto& shared_data = context_.get().render_list_shared_data(); - if (segments == 0) { // fixme - segments = shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + segments = shared_data().circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); } ellipse_n_filled(ellipse, color, segments); diff --git a/src/gfx/render_list.hpp b/src/gfx/render_list.hpp index 031a313..9a2ea54 100644 --- a/src/gfx/render_list.hpp +++ b/src/gfx/render_list.hpp @@ -196,7 +196,7 @@ namespace gal::prometheus::gfx private: class Drawer; - RenderListFlag draw_list_flag_; + RenderListFlag render_list_flag_; memory::RefWrapper context_; // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) @@ -217,6 +217,10 @@ namespace gal::prometheus::gfx auto on_scissor_changed() noexcept -> void; auto on_texture_changed() noexcept -> void; + [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData&; + + [[nodiscard]] auto default_texture() const noexcept -> texture_id_type; + public: RenderList(RenderListFlag flag, RendererContext& context) noexcept; diff --git a/src/gfx/texture.cpp b/src/gfx/texture.cpp index eba59aa..b86bb13 100644 --- a/src/gfx/texture.cpp +++ b/src/gfx/texture.cpp @@ -5,32 +5,133 @@ #include +#include + #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +namespace +{ #define STB_RECT_PACK_IMPLEMENTATION #include +} // namespace namespace gal::prometheus::gfx { - SubTexture::SubTexture(const point_type point, const data_type data) noexcept + struct Texture::pack_context_type + { + stbrp_context context{}; + std::vector nodes{}; + }; + + Texture::Texture(Texture&&) noexcept = default; + Texture& Texture::operator=(Texture&&) noexcept = default; + + Texture::~Texture() noexcept = default; + + Texture::Texture(const size_type size) noexcept + : pack_context_{memory::make_unique()}, + data_{std::make_unique_for_overwrite(static_cast(size.width) * size.height)}, + size_{size}, + dirty_{false}, + id_{invalid_texture_id} + { + pack_context_->nodes.resize(size.width); + + stbrp_init_target( + &pack_context_->context, static_cast(size.width), static_cast(size.height), pack_context_->nodes.data(), static_cast(pack_context_->nodes.size()) + ); + } + + auto Texture::upload(Renderer& renderer) noexcept -> void + { + // todo: destroy texture? + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not uploaded()); + const auto id = renderer.create_texture(data(), size_); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id != invalid_texture_id); + + id_ = id; + } + + auto Texture::data() const noexcept -> data_view_type + { + return {data_.get(), area_size()}; + } + + auto Texture::area_size() const noexcept -> std::size_t + { + return size_.width * size_.height; + } + + auto Texture::size() const noexcept -> size_type + { + return size_; + } + + auto Texture::uv() const noexcept -> uv_type + { + const auto s = size(); + + return {1.f / static_cast(s.width), 1.f / static_cast(s.height)}; + } + + auto Texture::dirty() const noexcept -> bool + { + return dirty_; + } + + auto Texture::uploaded() const noexcept -> bool + { + return id_ != invalid_texture_id; + } + + auto Texture::id() const noexcept -> texture_id_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uploaded()); + + return id_; + } + + auto Texture::select(const size_type size) noexcept -> BorrowTexture + { + stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; + + if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) + { + const point_type point{static_cast(rect.x), static_cast(rect.y)}; + + auto* address = data_.get() + (point.y * size.width + point.x); + const auto mapping = BorrowTexture::data_type::mapping_type{ + std::dextents{size.height, size.width}, + std::array{size_.width, 1}, + }; + const auto data = BorrowTexture::data_type{address, mapping}; + + dirty_ = true; + return {point, data}; + } + + return {BorrowTexture::invalid_point, {}}; + } + + BorrowTexture::BorrowTexture(const point_type point, const data_type data) noexcept : point_{point}, data_{data} { } - auto SubTexture::valid() const noexcept -> bool + auto BorrowTexture::valid() const noexcept -> bool { return point_ != invalid_point; } - auto SubTexture::point() const noexcept -> point_type + auto BorrowTexture::point() const noexcept -> point_type { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); return point_; } - auto SubTexture::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void + auto BorrowTexture::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); @@ -42,7 +143,7 @@ namespace gal::prometheus::gfx } } - auto SubTexture::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void + auto BorrowTexture::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); @@ -54,7 +155,7 @@ namespace gal::prometheus::gfx } } - auto SubTexture::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void + auto BorrowTexture::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); @@ -63,7 +164,7 @@ namespace gal::prometheus::gfx fill(y, 0, n, element); } - auto SubTexture::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void + auto BorrowTexture::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); @@ -72,7 +173,7 @@ namespace gal::prometheus::gfx fill(y, 0, data); } - auto SubTexture::fill(const size_type::value_type y, const element_type element) const noexcept -> void + auto BorrowTexture::fill(const size_type::value_type y, const element_type element) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); @@ -80,7 +181,7 @@ namespace gal::prometheus::gfx fill(y, data_.extent(1), element); } - auto SubTexture::fill(const element_type element) const noexcept -> void + auto BorrowTexture::fill(const element_type element) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); @@ -90,7 +191,7 @@ namespace gal::prometheus::gfx } } - auto SubTexture::fill(const data_view_type data) const noexcept -> void + auto BorrowTexture::fill(const data_view_type data) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); @@ -102,7 +203,7 @@ namespace gal::prometheus::gfx } } - auto SubTexture::operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference + auto BorrowTexture::operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); @@ -110,197 +211,4 @@ namespace gal::prometheus::gfx return data_[y, x]; } - - struct Texture::pack_context_type - { - stbrp_context context{}; - std::vector nodes{}; - }; - - Texture::Texture(const size_type size) noexcept - : pack_context_{memory::make_unique()}, - descriptor_{ - .data = std::make_unique_for_overwrite(static_cast(size.width) * size.height), - .size = size, - .dirty = false, - .id = invalid_texture_id, - } - { - pack_context_->nodes.resize(size.width); - - stbrp_init_target( - &pack_context_->context, static_cast(size.width), static_cast(size.height), pack_context_->nodes.data(), static_cast(pack_context_->nodes.size()) - ); - } - - auto Texture::data() const noexcept -> data_view_type - { - const auto length = descriptor_.size.width * descriptor_.size.height; - - return {descriptor_.data.get(), length}; - } - - auto Texture::bind_id(const texture_id_type id) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id != invalid_texture_id); - - descriptor_.id = id; - } - - auto Texture::size() const noexcept -> size_type - { - return descriptor_.size; - } - - auto Texture::uv() const noexcept -> uv_type - { - const auto s = size(); - - return {1.f / static_cast(s.width), 1.f / static_cast(s.height)}; - } - - auto Texture::dirty() const noexcept -> bool - { - return descriptor_.dirty; - } - - auto Texture::uploaded() const noexcept -> bool - { - return descriptor_.id != invalid_texture_id; - } - - auto Texture::id() const noexcept -> texture_id_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uploaded()); - - return descriptor_.id; - } - - auto Texture::select(const size_type size) noexcept -> SubTexture - { - stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; - - if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) - { - const point_type point{static_cast(rect.x), static_cast(rect.y)}; - - auto* address = descriptor_.data.get() + (point.y * descriptor_.size.width + point.x); - const auto mapping = data_type::mapping_type{ - std::dextents{size.height, size.width}, - std::array{descriptor_.size.width, 1}, - }; - const auto data = data_type{address, mapping}; - - descriptor_.dirty = true; - return {point, data}; - } - - return {SubTexture::invalid_point, {}}; - } - - // struct TextureAtlas::pack_context_type - // { - // stbrp_context context; - // std::vector nodes; - // }; - - // TextureAtlas::TextureAtlas(const value_type width, const value_type height) noexcept - // : pack_context_{memory::make_unique()}, - // size_{width, height}, - // uv_scale_{1.f / static_cast(width), 1.f / static_cast(height)}, - // data_{std::make_unique_for_overwrite(static_cast(width) * height)}, - // dirty_{false}, - // texture_id_{invalid_texture_id} - // { - // pack_context_->nodes.resize(width); - // - // stbrp_init_target(&pack_context_->context, static_cast(width), static_cast(height), pack_context_->nodes.data(), static_cast(pack_context_->nodes.size())); - // } - // - // auto TextureAtlas::build(Renderer& renderer) noexcept -> void - // { - // texture_id_ = renderer.create_texture(); - // } - // - // auto TextureAtlas::destroy(Renderer& renderer) noexcept -> void - // { - // data_.reset(); - // dirty_ = false; - // renderer.destroy_texture(texture_id_); - // texture_id_ = invalid_texture_id; - // } - // - // auto TextureAtlas::valid() const noexcept -> bool - // { - // return texture_id_ != invalid_texture_id; - // } - // - // auto TextureAtlas::dirty() const noexcept -> bool - // { - // return dirty_; - // } - // - // auto TextureAtlas::id() const noexcept -> texture_id_type - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - // return texture_id_; - // } - // - // auto TextureAtlas::size() const noexcept -> size_type - // { - // return size_; - // } - // - // auto TextureAtlas::uv_scale() const noexcept -> uv_scale_type - // { - // return uv_scale_; - // } - // - // auto TextureAtlas::data() const noexcept -> data_view_type - // { - // const auto length = size_.width * size_.height; - // - // return {data_.get(), length}; - // } - // - // auto TextureAtlas::seek(const size_type size) noexcept -> point_type - // { - // stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; - // - // if (stbrp_pack_rects(&pack_context_->context, &rect, 1)) - // { - // return {static_cast(rect.x), static_cast(rect.y)}; - // } - // - // return invalid_point; - // } - // - // auto TextureAtlas::write(const point_type point, const size_type size, const data_view_type data) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.x + size.width <= size_.width); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point.y + size.height <= size_.height); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= (static_cast(size.width) * size.height)); - // - // for (value_type y = 0; y < size.height; ++y) - // { - // const auto offset_y = (point.y + y) * size_.width; - // - // for (value_type x = 0; x < size.width; ++x) - // { - // const auto offset_x = point.x + x; - // const auto index = offset_x + offset_y; - // - // data_[index] = data[y * size.width + x]; - // } - // } - // - // dirty_ = true; - // } - // - // auto TextureAtlas::write(const point_type point, const size_type size, const data_type& data) noexcept -> void - // { - // return write(point, size, {data.get(), static_cast(size.width) * size.height}); - // } - } // namespace gal::prometheus::gfx diff --git a/src/gfx/texture.hpp b/src/gfx/texture.hpp index ba4b4a0..3c64342 100644 --- a/src/gfx/texture.hpp +++ b/src/gfx/texture.hpp @@ -14,7 +14,7 @@ namespace gal::prometheus::gfx { - class TextureDescriptor final + class Texture final { public: using point_type = primitive::basic_point_2d; @@ -25,90 +25,49 @@ namespace gal::prometheus::gfx using data_type = std::unique_ptr; using data_view_type = std::span; + using uv_type = primitive::basic_extent_2d; + + private: + struct pack_context_type; + memory::UniquePointer pack_context_; + // ============================== // CPU side // ============================== - data_type data; - size_type size; + data_type data_; + size_type size_; static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); - std::uint64_t dirty : 1; + std::uint64_t dirty_ : 1; // ============================== // GPU side // ============================== - std::uint64_t id : 63; - }; + std::uint64_t id_ : 63; - class SubTexture - { public: - using point_type = TextureDescriptor::point_type; - using size_type = TextureDescriptor::size_type; + Texture(const Texture&) noexcept = delete; + Texture(Texture&&) noexcept; // = default; + auto operator=(const Texture&) noexcept -> Texture& = delete; + auto operator=(Texture&&) noexcept -> Texture&; // = default; - using element_type = TextureDescriptor::element_type; - using data_type = std::mdspan, std::layout_stride>; - using data_view_type = TextureDescriptor::data_view_type; - - constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; - - private: - point_type point_; - data_type data_; - - public: - SubTexture(point_type point, data_type data) noexcept; - - [[nodiscard]] auto valid() const noexcept -> bool; - - [[nodiscard]] auto point() const noexcept -> point_type; - - auto fill(size_type::value_type y, size_type::value_type offset, size_type::value_type n, element_type element) const noexcept -> void; - auto fill(size_type::value_type y, size_type::value_type offset, data_view_type data) const noexcept -> void; - - auto fill(size_type::value_type y, size_type::value_type n, element_type element) const noexcept -> void; - auto fill(size_type::value_type y, data_view_type data) const noexcept -> void; - - auto fill(size_type::value_type y, element_type element) const noexcept -> void; - - auto fill(element_type element) const noexcept -> void; - auto fill(data_view_type data) const noexcept -> void; - - auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference; - }; - - class Texture final - { - public: - using point_type = SubTexture::point_type; - using size_type = SubTexture::size_type; + ~Texture() noexcept; - using element_type = SubTexture::element_type; - using data_type = SubTexture::data_type; - using data_view_type = SubTexture::data_view_type; - - using uv_type = primitive::basic_extent_2d; - - private: - struct pack_context_type; - memory::UniquePointer pack_context_; - - TextureDescriptor descriptor_; - - public: explicit Texture(size_type size) noexcept; + auto upload(Renderer& renderer) noexcept -> void; + /** * @brief Texture atlas data (for upload) */ [[nodiscard]] auto data() const noexcept -> data_view_type; /** - * @brief Set texture atlas id (GPU handle) + * @brief Texture atlas area size */ - auto bind_id(texture_id_type id) noexcept -> void; + [[nodiscard]] auto area_size() const noexcept -> std::size_t; /** * @brief Texture atlas size @@ -139,104 +98,43 @@ namespace gal::prometheus::gfx * @brief Find a region that can hold a (piece of) texture of @c size * @param size Texture size */ - [[nodiscard]] auto select(size_type size) noexcept -> SubTexture; + [[nodiscard]] auto select(size_type size) noexcept -> BorrowTexture; }; - // /** - // * @brief Texture atlas uploaded to the GPU - // */ - // class TextureAtlas final - // { - // public: - // using value_type = std::uint32_t; - // using point_type = primitive::basic_point_2d; - // using size_type = primitive::basic_extent_2d; - // - // using uv_scale_type = extent_type; - // - // // size.width * size.height (RGBA) - // using data_type = std::unique_ptr; - // using data_view_type = std::span; - // - // constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; - // - // private: - // struct pack_context_type; - // memory::UniquePointer pack_context_; - // - // // Size of texture atlas - // size_type size_; - // // UV scale of texture atlas (1.0f / size.width, 1.0f / size.height) - // uv_scale_type uv_scale_; - // // Texture atlas data (CPU side) - // data_type data_; - // - // static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); - // // Does this texture need to be updated (re-uploaded) - // std::uint64_t dirty_ : 1; - // // GPU resource handle - // std::uint64_t texture_id_ : 63; - // - // public: - // TextureAtlas(value_type width, value_type height) noexcept; - // - // auto build(Renderer& renderer) noexcept -> void; - // auto destroy(Renderer& renderer) noexcept -> void; - // - // /** - // * @brief Texture atlas size - // */ - // [[nodiscard]] auto size() const noexcept -> size_type; - // - // /** - // * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) - // */ - // [[nodiscard]] auto uv_scale() const noexcept -> uv_scale_type; - // - // /** - // * @brief Texture atlas data (CPU side) - // */ - // [[nodiscard]] auto data() const noexcept -> data_view_type; - // - // /** - // * @brief Does this texture atlas is valid (uploaded to GPU) - // */ - // [[nodiscard]] auto valid() const noexcept -> bool; - // - // /** - // * @brief Does this texture atlas need to be re-uploaded to the GPU - // */ - // [[nodiscard]] auto dirty() const noexcept -> bool; - // - // /** - // * @brief Texture atlas id (usually a GPU resource handle) - // */ - // [[nodiscard]] auto id() const noexcept -> texture_id_type; - // - // /** - // * @brief Find a region that can hold a (piece of) texture of @c size - // * @param size texture size - // * @return texture coordinate - // * @note If such a region is not found, @c invalid_point is returned - // */ - // [[nodiscard]] auto seek(size_type size) noexcept -> point_type; - // - // /** - // * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture - // * @param point texture coordinate - // * @param size texture size - // * @param data texture data - // * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height - // */ - // auto write(point_type point, size_type size, data_view_type data) noexcept -> void; - // - // /** - // * @brief Write a (piece of) texture @c data of @c size at the specified @c point of the current texture - // * @param point texture coordinate - // * @param size texture size - // * @param data texture data - // * @note Do not check the length of the @c data, assume it is at least @c size.width * @c size.height - // */ - // auto write(point_type point, size_type size, const data_type& data) noexcept -> void; - // }; + class BorrowTexture + { + public: + using point_type = Texture::point_type; + using size_type = Texture::size_type; + + using element_type = Texture::element_type; + using data_type = std::mdspan, std::layout_stride>; + using data_view_type = Texture::data_view_type; + + constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; + + private: + point_type point_; + data_type data_; + + public: + BorrowTexture(point_type point, data_type data) noexcept; + + [[nodiscard]] auto valid() const noexcept -> bool; + + [[nodiscard]] auto point() const noexcept -> point_type; + + auto fill(size_type::value_type y, size_type::value_type offset, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, size_type::value_type offset, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, element_type element) const noexcept -> void; + + auto fill(element_type element) const noexcept -> void; + auto fill(data_view_type data) const noexcept -> void; + + auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference; + }; } // namespace gal::prometheus::gfx diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index 71736bd..1626a94 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -35,9 +35,8 @@ namespace gal::prometheus::gfx using texture_id_type = std::uintptr_t; constexpr texture_id_type invalid_texture_id{0}; - class TextureDescriptor; - class SubTexture; class Texture; + class BorrowTexture; // ========================================================= // FONT @@ -51,18 +50,31 @@ namespace gal::prometheus::gfx class GlyphKey; class GlyphInfo; + + class FontPendingLoadData; + + class GlyphParsedInfo; class GlyphParser; - class GlyphUploadInfo; + class FontFace; // ========================================================= - // RENDERER + // RENDERER LIST // ========================================================= - class TextureContext; - class RenderListSharedData; class RenderList; + + // ========================================================= + // RENDERER + // ========================================================= + class Renderer; + + // ========================================================= + // CONTEXT + // ========================================================= + + class TextureContext; class RendererContext; } // namespace gal::prometheus::gfx From 00462a4e63b7f206b5eac96e7d333a034a04d11a Mon Sep 17 00:00:00 2001 From: Life4gal Date: Fri, 9 May 2025 16:56:12 +0800 Subject: [PATCH 35/54] `format`: IndentPPDirectives -> None. --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 50d665b..e5daeb4 100644 --- a/.clang-format +++ b/.clang-format @@ -61,7 +61,7 @@ IncludeCategories: Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: true -IndentPPDirectives: BeforeHash +IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false InsertNewlineAtEOF: true From 859520e71f6602214666a931d1629973b92ce20e Mon Sep 17 00:00:00 2001 From: Life4gal Date: Sun, 11 May 2025 01:36:39 +0800 Subject: [PATCH 36/54] =?UTF-8?q?`feat`:=20GFX=20basic=20Framework.?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 6 +- src/gfx/context.cpp | 177 +++++++++++++------- src/gfx/context.hpp | 108 ++++++++++--- src/gfx/font.cpp | 31 +--- src/gfx/font.hpp | 72 ++++----- src/gfx/gfx.hpp | 10 +- src/gfx/glyph_parser_freetype.cpp | 135 +++++++++++----- src/gfx/glyph_parser_freetype.hpp | 28 ++-- src/gfx/render_list.cpp | 209 ++++++++++++------------ src/gfx/render_list.hpp | 6 +- src/gfx/renderer.hpp | 3 + src/gfx/renderer_dx11.cpp | 261 ++++++++++++++++++++++++++++-- src/gfx/renderer_dx11.hpp | 21 ++- src/gfx/texture.cpp | 94 +++++++---- src/gfx/texture.hpp | 17 +- src/gfx/type.hpp | 4 +- 16 files changed, 828 insertions(+), 354 deletions(-) diff --git a/scripts/library.cmake b/scripts/library.cmake index e27e9e2..f25e0cb 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -373,10 +373,9 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx/texture.hpp ${PROJECT_SOURCE_DIR}/src/gfx/font.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/render_list.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/render_list.hpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer.hpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.hpp @@ -441,10 +440,9 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx/texture.cpp ${PROJECT_SOURCE_DIR}/src/gfx/font.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/render_list.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/render_list.cpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer.cpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.cpp diff --git a/src/gfx/context.cpp b/src/gfx/context.cpp index cb53489..2bd45b7 100644 --- a/src/gfx/context.cpp +++ b/src/gfx/context.cpp @@ -10,6 +10,7 @@ #include #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus::gfx { @@ -32,7 +33,8 @@ namespace gal::prometheus::gfx auto TextureContext::select_atlas(const Texture::size_type size) const noexcept -> texture_atlas_id_type { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); + // width/height == 0 ==> whitespace + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width >= 0 and size.height >= 0); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlases_.empty()); std::ignore = size; @@ -43,7 +45,8 @@ namespace gal::prometheus::gfx auto TextureContext::make_territory(const Texture::size_type size) noexcept -> BorrowTexture { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width > 0 and size.height > 0); + // width/height == 0 ==> whitespace + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size.width >= 0 and size.height >= 0); const auto atlas_id = select_atlas(size); auto& atlas = select_atlas(atlas_id); @@ -128,11 +131,11 @@ namespace gal::prometheus::gfx // FontFace (load fallback glyph) // ======================================== std::ranges::for_each( - font_faces_, - [](auto& font_face) noexcept -> void - { - font_face.initialize(); - } + font_faces_, + [](auto& font_face) noexcept -> void + { + font_face.initialize(); + } ); } @@ -159,20 +162,15 @@ namespace gal::prometheus::gfx file.seekg(0, std::ios::end); const auto size = file.tellg(); - auto data = std::make_unique_for_overwrite(size); + auto* data = new FontData::element_type[size]; file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(data.get()), size); + file.read(reinterpret_cast(data), size); file.close(); - font_pending_load_datas_.emplace_back(std::move(data), static_cast(size)); + font_data_list_.emplace_back(std::unique_ptr{data}, static_cast(size)); return true; } - auto TextureContext::add_font(const std::string_view path) noexcept -> bool - { - return add_font(std::filesystem::path{path}); - } - auto TextureContext::root_texture() const noexcept -> texture_id_type { return root_atlas().id(); @@ -204,12 +202,12 @@ namespace gal::prometheus::gfx infos.reserve(utf32_text.size()); std::ranges::for_each( - utf32_text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } + utf32_text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } ); return infos; @@ -232,17 +230,17 @@ namespace gal::prometheus::gfx extent_type total_size{0, 0}; std::ranges::for_each( - infos, - [&total_size](const auto* info) noexcept -> void + infos, + [&total_size](const auto* info) noexcept -> void + { + if (info == nullptr) { - if (info == nullptr) - { - return; - } - - total_size.width += info->advance_x; - total_size.height = std::max(total_size.height, info->rect.height()); + return; } + + total_size.width += info->advance_x; + total_size.height = std::max(total_size.height, info->rect.height()); + } ); return total_size; @@ -253,20 +251,22 @@ namespace gal::prometheus::gfx GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); std::ranges::for_each( - font_pending_load_datas_, - [this](const FontPendingLoadData& data) noexcept -> void + font_data_list_, + [this](FontData& data) noexcept -> void + { + // note: Transferring ownership of font file data + if (auto result = parser_->load(std::move(data.data), data.size); result.valid()) + { + font_faces_.emplace_back(*this, std::move(result.name), result.id); + } + else { - if (auto result = parser_->load(data); result.valid()) - { - font_faces_.emplace_back(*this, std::move(result.name), result.id); - } - else - { - // todo: error handling - } + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); } + } ); - font_pending_load_datas_.clear(); + font_data_list_.clear(); } auto TextureContext::upload_all_font_face() noexcept -> void @@ -286,7 +286,7 @@ namespace gal::prometheus::gfx const auto atlas_uv_scale = atlas.uv(); const auto& texture = make_territory(size); - texture.fill({data.get(), width * height}); + texture.fill({data.get(), static_cast(width) * height}); const auto texture_point = texture.point(); info.texture_atlas_id = atlas_id; @@ -297,49 +297,110 @@ namespace gal::prometheus::gfx auto TextureContext::upload_all_texture(Renderer& renderer) noexcept -> void { std::ranges::for_each( - texture_atlases_, - [&renderer](auto& atlas) noexcept -> void + texture_atlases_, + [&renderer](auto& texture) noexcept -> void + { + if (not texture.uploaded()) + { + texture.create(renderer); + } + else { - if (not atlas.uploaded()) - { - atlas.upload(renderer); - } + texture.upload_if_required(renderer); } + } ); } - RendererContext::~RendererContext() noexcept = default; + RenderContext::~RenderContext() noexcept = default; - RendererContext::RendererContext() noexcept = default; + RenderContext::RenderContext() noexcept = default; - auto RendererContext::initialize() noexcept -> void + auto RenderContext::initialize() noexcept -> void { texture_context_.initialize(render_list_shared_data_); } - auto RendererContext::begin_frame(Renderer& renderer) noexcept -> void + auto RenderContext::begin_frame(Renderer& renderer) noexcept -> void { texture_context_.load_all_font(); texture_context_.upload_all_texture(renderer); } - auto RendererContext::end_frame() noexcept -> void + auto RenderContext::end_frame(Renderer& renderer) noexcept -> void { + std::ignore = renderer; texture_context_.upload_all_font_face(); } - auto RendererContext::texture_context() noexcept -> TextureContext& + auto RenderContext::bind_parser(GlyphParser& parser) noexcept -> void + { + texture_context_.bind_parser(parser); + } + + auto RenderContext::add_font(const std::filesystem::path& path) noexcept -> bool + { + return texture_context_.add_font(path); + } + + auto RenderContext::root_texture() const noexcept -> texture_id_type + { + return texture_context_.root_texture(); + } + + auto RenderContext::atlas_of(const GlyphInfo& info) noexcept -> const Texture& + { + return texture_context_.atlas_of(info); + } + + auto RenderContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* { - return texture_context_; + return texture_context_.glyph_of(codepoint, size, flag); } - auto RendererContext::texture_context() const noexcept -> const TextureContext& + auto RenderContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector { - return texture_context_; + return texture_context_.glyph_of(text, size, flag); } - auto RendererContext::render_list_shared_data() const noexcept -> const RenderListSharedData& + auto RenderContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + { + return texture_context_.size_of(codepoint, size, flag); + } + + auto RenderContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + { + return texture_context_.size_of(text, size, flag); + } + + auto RenderContext::render_list_shared_data() const noexcept -> const RenderListSharedData& { return render_list_shared_data_; } + + auto RenderContext::render_data() const noexcept -> std::vector + { + std::vector all_render_data{}; + all_render_data.reserve(render_lists_.size()); + + std::ranges::for_each( + render_lists_, + [&all_render_data](const auto& render_list) noexcept -> void + { + all_render_data.emplace_back(render_list.render_data()); + } + ); + + return all_render_data; + } + + auto RenderContext::test_render_list() noexcept -> RenderList& + { + if (render_lists_.empty()) + { + render_lists_.emplace_back(RenderListFlag::ANTI_ALIASED_LINE | RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | RenderListFlag::ANTI_ALIASED_FILL, *this); + } + + return render_lists_.front(); + } } // namespace gal::prometheus::gfx diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp index 84b54eb..e52d529 100644 --- a/src/gfx/context.hpp +++ b/src/gfx/context.hpp @@ -35,7 +35,18 @@ namespace gal::prometheus::gfx using territories_type = std::vector; - using font_pending_load_datas = std::vector; + class FontData final + { + public: + using element_type = std::uint8_t; + using data_type = std::unique_ptr; + using size_type = std::uint32_t; + + data_type data; + size_type size; + }; + + using font_data_list_type = std::vector; GlyphParser* parser_; @@ -43,7 +54,7 @@ namespace gal::prometheus::gfx territories_type territories_; font_faces_type font_faces_; - font_pending_load_datas font_pending_load_datas_; + font_data_list_type font_data_list_; /** * @brief Retain at least one texture atlas (root) @@ -106,12 +117,6 @@ namespace gal::prometheus::gfx */ auto add_font(const std::filesystem::path& path) noexcept -> bool; - /** - * @brief Load fonts from the specified path, assuming the path is a valid font file - * @param path Font path - */ - auto add_font(std::string_view path) noexcept -> bool; - /** * @brief Get root (default) texture */ @@ -143,59 +148,118 @@ namespace gal::prometheus::gfx */ [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; - // ============================================================================================================================= - /** - * @brief Load the fonts previously added by @c add_font + * @brief Load the fonts previously added by @c add_font to the @c FontFace + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts */ auto load_all_font() noexcept -> void; /** - * @brief Upload all used glyphs to the texture (if it is not already uploaded) + * @brief Upload all used glyphs (in the @c FontFace) to the texture (if it is not already uploaded) + * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture */ auto upload_all_font_face() noexcept -> void; /** * @brief Write FontFace uploaded glyph data to texture, also set the @c texture_atlas_id and @c uv coordinates for this glyph data + * @note @c GlyphParsedInfo calls this function to upload glyph data to the texture and set its texture atlas ID and UV coordinates */ auto upload_glyph_to_texture(GlyphInfo& info, const GlyphParsedInfo::data_type& data) noexcept -> void; /** * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) + * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) */ auto upload_all_texture(Renderer& renderer) noexcept -> void; }; - class RendererContext final + class RenderContext final { public: using render_lists_type = std::vector; private: TextureContext texture_context_; + RenderListSharedData render_list_shared_data_; render_lists_type render_lists_; public: - RendererContext(const RendererContext&) noexcept = delete; - RendererContext(RendererContext&&) noexcept = default; - auto operator=(const RendererContext&) noexcept -> RendererContext& = delete; - auto operator=(RendererContext&&) noexcept -> RendererContext& = default; + RenderContext(const RenderContext&) noexcept = delete; + RenderContext(RenderContext&&) noexcept = default; + auto operator=(const RenderContext&) noexcept -> RenderContext& = delete; + auto operator=(RenderContext&&) noexcept -> RenderContext& = default; - ~RendererContext() noexcept; + ~RenderContext() noexcept; - RendererContext() noexcept; + RenderContext() noexcept; auto initialize() noexcept -> void; auto begin_frame(Renderer& renderer) noexcept -> void; - auto end_frame() noexcept -> void; + auto end_frame(Renderer& renderer) noexcept -> void; + + // ==================================================================== + // TextureContext + // ==================================================================== + + /** + * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts + */ + auto bind_parser(GlyphParser& parser) noexcept -> void; + + /** + * @brief Load fonts from the specified path, assuming the path is a valid font file + * @param path Font path + */ + auto add_font(const std::filesystem::path& path) noexcept -> bool; + + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root_texture() const noexcept -> texture_id_type; + + /** + * @brief Get texture atlas for glyph information + */ + [[nodiscard]] auto atlas_of(const GlyphInfo& info) noexcept -> const Texture&; - [[nodiscard]] auto texture_context() noexcept -> TextureContext&; - [[nodiscard]] auto texture_context() const noexcept -> const TextureContext&; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + + /** + * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety + * @param codepoint + * @param size + * @param flag + * @return + */ + [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + + /** + * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + * @param text + * @param size + * @param flag + * @return + */ + [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + + // ==================================================================== + // RenderListSharedData + // ==================================================================== [[nodiscard]] auto render_list_shared_data() const noexcept -> const RenderListSharedData&; + + // ==================================================================== + // RenderList + // ==================================================================== + + [[nodiscard]] auto render_data() const noexcept -> std::vector; + + // test + [[nodiscard]] auto test_render_list() noexcept -> RenderList&; }; } // namespace gal::prometheus::gfx diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp index 6fea44a..4ce77c8 100644 --- a/src/gfx/font.cpp +++ b/src/gfx/font.cpp @@ -12,22 +12,6 @@ namespace gal::prometheus::gfx { - FontPendingLoadData::FontPendingLoadData(data_type data, const size_type size) noexcept - : data_{std::move(data)}, - size_{size} - { - } - - auto FontPendingLoadData::data() const noexcept -> data_view_type - { - return {data_.get(), size()}; - } - - auto FontPendingLoadData::size() const noexcept -> size_type - { - return size_; - } - GlyphParsedInfo::GlyphParsedInfo(GlyphInfo& info, data_type data) noexcept : info_{info}, data_{std::move(data)} @@ -45,11 +29,6 @@ namespace gal::prometheus::gfx GlyphParser::~GlyphParser() noexcept = default; - auto GlyphParser::load(const FontPendingLoadData& data) noexcept -> LoadResult - { - return this->load(data.data()); - } - auto GlyphParser::parse(const font_id_type id, const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> ParseResult { return this->parse(id, {.codepoint = codepoint, .size = size, .flag = flag}); @@ -65,10 +44,10 @@ namespace gal::prometheus::gfx } auto& parser = context_.get().parser(); - if (not parser.has_glyph(id_, key.codepoint)) - { - return nullptr; - } + // if (not parser.has_glyph(id_, key.codepoint)) + // { + // return nullptr; + // } auto result = parser.parse(id_, key); if (not result.valid()) @@ -125,7 +104,7 @@ namespace gal::prometheus::gfx if (fallback_glyph_ == nullptr) { // todo - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); } } diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp index f68093b..82f0536 100644 --- a/src/gfx/font.hpp +++ b/src/gfx/font.hpp @@ -5,6 +5,9 @@ #pragma once +#include +#include + #include #include @@ -58,10 +61,10 @@ namespace gal::prometheus::gfx // ============= // Bitmap infos of this glyph - rect_type rect; - value_type advance_x; - bool visible; - bool colored; + rect_type rect{-1, -1, -1, -1}; + value_type advance_x{-1}; + bool visible{false}; + bool colored{false}; // ============= // Data filled when writing texture @@ -69,32 +72,8 @@ namespace gal::prometheus::gfx // The id of the texture atlas where the glyph is located // This id is present if and only if the glyph is in a texture atlas, otherwise it is invalid_texture_atlas_id - texture_atlas_id_type texture_atlas_id; - uv_type uv; - }; - - /** - * @brief Font data to be loaded, then GlyphParser::load will load its bitmap data - */ - class FontPendingLoadData final - { - public: - using element_type = std::uint8_t; - using data_type = std::unique_ptr; - using size_type = std::uint32_t; - - using data_view_type = std::span; - - private: - data_type data_; - size_type size_; - - public: - FontPendingLoadData(data_type data, size_type size) noexcept; - - [[nodiscard]] auto data() const noexcept -> data_view_type; - - [[nodiscard]] auto size() const noexcept -> size_type; + texture_atlas_id_type texture_atlas_id{invalid_texture_atlas_id}; + uv_type uv{-1, -1, -1, -1}; }; /** @@ -114,9 +93,10 @@ namespace gal::prometheus::gfx public: /** * @param info Glyph info - * @param data Glypy bitmap data + * @param data Glyph bitmap data * * @link FontFace::find_or_parse_glyph + * @endlink */ GlyphParsedInfo(GlyphInfo& info, data_type data) noexcept; @@ -159,7 +139,7 @@ namespace gal::prometheus::gfx [[nodiscard]] explicit operator bool() const noexcept { - return data == nullptr; + return data != nullptr; } [[nodiscard]] auto valid() const noexcept -> bool @@ -177,17 +157,31 @@ namespace gal::prometheus::gfx GlyphParser() noexcept = default; - [[nodiscard]] virtual auto initialize() noexcept -> bool = 0; + [[nodiscard]] virtual auto ready() noexcept -> bool = 0; /** - * @brief Load the font data, get all its glyph data, return the id of the font + * @brief Load font data from file, get all glyph data, return id of font + * @param path Font file path + * @return id of the font, or invalid_font_id if failed to load */ - [[nodiscard]] virtual auto load(FontPendingLoadData::data_view_type data) noexcept -> LoadResult = 0; + [[nodiscard]] virtual auto load(const std::filesystem::path& path) noexcept -> LoadResult = 0; /** - * @brief Load the font data, get all its glyph data, return the id of the font + * @brief Load font data from @c data, get all glyph data, return id of font + * @param data Font data + * @param size Font data length + * @return id of the font, or invalid_font_id if failed to load + * @note Transfer ownership of the font data, the caller does not need to free memory */ - [[nodiscard]] auto load(const FontPendingLoadData& data) noexcept -> LoadResult; + [[nodiscard]] virtual auto load(std::unique_ptr data, std::size_t size) noexcept -> LoadResult = 0; + + /** + * @brief Load font data from @c data, get all glyph data, return id of font + * @param data Font data + * @return id of the font, or invalid_font_id if failed to load + * @note Copy font data, caller needs to free memory + */ + [[nodiscard]] virtual auto load(std::span data) noexcept -> LoadResult = 0; /** * @brief Determines whether the target font contains the glyphs of the specified codepoint @@ -290,7 +284,5 @@ namespace gal::prometheus::gfx namespace gal::prometheus::meta::user_defined { template<> - struct enum_is_flag : std::true_type - { - }; + struct enum_is_flag : std::true_type {}; } // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index e257e30..5cbbc29 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -6,6 +6,14 @@ #pragma once #include -#include + +#include #include +#include + +#include + #include +#include + +#include diff --git a/src/gfx/glyph_parser_freetype.cpp b/src/gfx/glyph_parser_freetype.cpp index d9fc0dc..3cdcd71 100644 --- a/src/gfx/glyph_parser_freetype.cpp +++ b/src/gfx/glyph_parser_freetype.cpp @@ -7,6 +7,7 @@ #if defined(GAL_PROMETHEUS_GFX_GLYPH_PARSER_FREETYPE) +#include #include namespace @@ -19,65 +20,108 @@ namespace namespace gal::prometheus::gfx { - auto FreeTypeGlyphParser::FontInfo::set_pixel_height(const std::size_t height) noexcept -> bool + class FreeTypeGlyphParser::Library final { - FT_Size_RequestRec request{.type = FT_SIZE_REQUEST_TYPE_NOMINAL, .width = 0, .height = static_cast(height) * 64, .horiResolution = 0, .vertResolution = 0}; + public: + FT_Library library{nullptr}; + }; - if (const auto error = FT_Request_Size(face, &request); error != FT_Err_Ok) + class FreeTypeGlyphParser::FontInfo final + { + public: + std::unique_ptr font_data{nullptr}; + FT_Face face{nullptr}; + + float ascender{0}; + float descender{0}; + float line_spacing{0}; + float line_gap{0}; + float max_advance_width{0}; + + auto set_pixel_height(const std::size_t height) noexcept -> bool { - return false; - } + FT_Size_RequestRec request{.type = FT_SIZE_REQUEST_TYPE_NOMINAL, .width = 0, .height = static_cast(height) * 64, .horiResolution = 0, .vertResolution = 0}; - const auto& metrics = face->size->metrics; + if (const auto error = FT_Request_Size(face, &request); error != FT_Err_Ok) + { + return false; + } - ascender = ft_size_to_float(metrics.ascender); - descender = ft_size_to_float(metrics.descender); - line_spacing = ft_size_to_float(metrics.height); - line_gap = ft_size_to_float(metrics.height - metrics.ascender + metrics.descender); - max_advance_width = ft_size_to_float(metrics.max_advance); + const auto& metrics = face->size->metrics; - return true; - } + ascender = ft_size_to_float(metrics.ascender); + descender = ft_size_to_float(metrics.descender); + line_spacing = ft_size_to_float(metrics.height); + line_gap = ft_size_to_float(metrics.height - metrics.ascender + metrics.descender); + max_advance_width = ft_size_to_float(metrics.max_advance); + + return true; + } + }; FreeTypeGlyphParser::~FreeTypeGlyphParser() noexcept { std::ranges::for_each( - infos_, - [](auto& info) noexcept -> void - { - ::FT_Done_Face(info.face); - } + infos_, + [](auto& info) noexcept -> void + { + ::FT_Done_Face(info.face); + } ); - FT_Done_FreeType(library_); + FT_Done_FreeType(library_->library); } FreeTypeGlyphParser::FreeTypeGlyphParser() noexcept - : library_{nullptr} + : library_{memory::make_unique()} { + if (const auto error = FT_Init_FreeType(&library_->library); error != FT_Err_Ok) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + } } - auto FreeTypeGlyphParser::initialize() noexcept -> bool + auto FreeTypeGlyphParser::ready() noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(library_ == nullptr); + return library_ != nullptr; + } + + auto FreeTypeGlyphParser::load(const std::filesystem::path& path) noexcept -> LoadResult + { + const auto path_string = path.string(); + + LoadResult invalid_result{.name = {}, .id = invalid_font_id}; - if (const auto error = FT_Init_FreeType(&library_); error != FT_Err_Ok) + FT_Face face = nullptr; + if (const auto error = FT_New_Face(library_->library, path_string.data(), 0, &face); error != FT_Err_Ok) { - return false; + return invalid_result; } - return true; + if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) + { + FT_Done_Face(face); + return invalid_result; + } + + const auto id = infos_.size(); + + auto& info = infos_.emplace_back(); + info.face = face; + + return {.name = face->family_name, .id = static_cast(id)}; } - auto FreeTypeGlyphParser::load(const FontPendingLoadData::data_view_type data) noexcept -> LoadResult + auto FreeTypeGlyphParser::load(std::unique_ptr data, const std::size_t size) noexcept -> LoadResult { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not data.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.get() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size != 0); LoadResult invalid_result{.name = {}, .id = invalid_font_id}; FT_Face face = nullptr; - if (const auto error = FT_New_Memory_Face(library_, data.data(), static_cast(data.size()), 0, &face); error != FT_Err_Ok) + if (const auto error = FT_New_Memory_Face(library_->library, data.get(), static_cast(size), 0, &face); error != FT_Err_Ok) { return invalid_result; } @@ -89,19 +133,31 @@ namespace gal::prometheus::gfx } const auto id = infos_.size(); - infos_.emplace_back(face); + + auto& info = infos_.emplace_back(); + info.font_data = std::move(data); + info.face = face; + return {.name = face->family_name, .id = static_cast(id)}; } + auto FreeTypeGlyphParser::load(const std::span data) noexcept -> LoadResult + { + auto* copy = new std::uint8_t[data.size()]; + std::ranges::copy(data, copy); + + return this->load(std::unique_ptr{copy}, data.size()); + } + auto FreeTypeGlyphParser::has_glyph(const font_id_type id, const std::uint32_t codepoint) const noexcept -> bool { - if (id > infos_.size()) + if (id >= infos_.size()) { return false; } const auto& info = infos_[id]; - if (const auto error = FT_Get_Char_Index(info.face, codepoint); error != FT_Err_Ok) + if (const auto char_index = FT_Get_Char_Index(info.face, codepoint); char_index == 0) { return false; } @@ -118,8 +174,9 @@ namespace gal::prometheus::gfx ParseResult invalid_result{.info = {}, .data = nullptr}; auto& info = infos_[id]; + const auto& face = info.face; - const auto char_index = FT_Get_Char_Index(info.face, key.codepoint); + const auto char_index = FT_Get_Char_Index(face, key.codepoint); if (char_index == 0) { return invalid_result; @@ -127,12 +184,12 @@ namespace gal::prometheus::gfx info.set_pixel_height(key.size); - if (const auto error = FT_Load_Glyph(info.face, char_index, FT_LOAD_DEFAULT); error != FT_Err_Ok) + if (const auto error = FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT); error != FT_Err_Ok) { return invalid_result; } - const auto& slot = info.face->glyph; + const auto& slot = face->glyph; if (std::to_underlying(key.flag) & GlyphFlag::BOLD) { @@ -155,16 +212,16 @@ namespace gal::prometheus::gfx const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; ParseResult result{ - .info = - { + .info = + { .rect = {point, size}, .advance_x = ft_size_to_float(slot->advance.x), .visible = size.width > 0 and size.height > 0, .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, .texture_atlas_id = invalid_texture_atlas_id, .uv = {}, - }, - .data = std::make_unique_for_overwrite(data_length) + }, + .data = std::make_unique_for_overwrite(data_length) }; { diff --git a/src/gfx/glyph_parser_freetype.hpp b/src/gfx/glyph_parser_freetype.hpp index 22463bc..c0b9185 100644 --- a/src/gfx/glyph_parser_freetype.hpp +++ b/src/gfx/glyph_parser_freetype.hpp @@ -14,32 +14,20 @@ #include -#include +#include namespace gal::prometheus::gfx { class FreeTypeGlyphParser final : public GlyphParser { - class FontInfo final - { - public: - FT_Face face{nullptr}; - - float ascender{0}; - float descender{0}; - float line_spacing{0}; - float line_gap{0}; - float max_advance_width{0}; - - auto set_pixel_height(std::size_t height) noexcept -> bool; - }; + class Library; + class FontInfo; public: using infos_type = std::vector; private: - FT_Library library_; - + memory::UniquePointer library_; infos_type infos_; public: @@ -51,9 +39,13 @@ namespace gal::prometheus::gfx FreeTypeGlyphParser() noexcept; - auto initialize() noexcept -> bool override; + auto ready() noexcept -> bool override; + + auto load(const std::filesystem::path& path) noexcept -> LoadResult override; + + auto load(std::unique_ptr data, std::size_t size) noexcept -> LoadResult override; - auto load(FontPendingLoadData::data_view_type data) noexcept -> LoadResult override; + auto load(std::span data) noexcept -> LoadResult override; [[nodiscard]] auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool override; diff --git a/src/gfx/render_list.cpp b/src/gfx/render_list.cpp index 61d8bf7..b0c9436 100644 --- a/src/gfx/render_list.cpp +++ b/src/gfx/render_list.cpp @@ -26,9 +26,9 @@ namespace }; return std::ranges::clamp( - circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), - RenderListSharedData::circle_segments_min, - RenderListSharedData::circle_segments_max + circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), + RenderListSharedData::circle_segments_min, + RenderListSharedData::circle_segments_max ); } @@ -53,7 +53,7 @@ namespace return {math::cos(a), -math::sin(a)}; }; - return { {make_point.template operator()()...} }; + return {{make_point.template operator()()...}}; }(std::make_index_sequence{}); } @@ -233,9 +233,7 @@ namespace RenderDataAppender(command_type& command, vertex_list_type& vertex_list, index_list_type& index_list) noexcept : element_count_{command.element_count}, vertex_list_{vertex_list}, - index_list_{index_list} - { - } + index_list_{index_list} {} [[nodiscard]] auto vertex_count() const noexcept -> size_type { @@ -424,8 +422,8 @@ namespace gal::prometheus::gfx const auto thickness_fractional = thickness - static_cast(thickness_integer); const auto is_use_texture = - ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and - (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); + ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); @@ -479,8 +477,8 @@ namespace gal::prometheus::gfx { const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; const auto vertex_index_for_end = static_cast( - // closed - (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + // closed + (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) ); // Average normals @@ -638,11 +636,11 @@ namespace gal::prometheus::gfx const auto& opaque_uv = shared_data.white_pixel_uv; std::ranges::for_each( - path_point, - [&](const point_type& point) noexcept -> void - { - appender.add_vertex(point, opaque_uv, color); - } + path_point, + [&](const point_type& point) noexcept -> void + { + appender.add_vertex(point, opaque_uv, color); + } ); for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) { @@ -720,14 +718,14 @@ namespace gal::prometheus::gfx GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); appender.add_index( - current_vertex_inner_index + static_cast(n << 1), - current_vertex_inner_index + static_cast(i << 1), - current_vertex_outer_index + static_cast(i << 1) + current_vertex_inner_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(i << 1) ); appender.add_index( - current_vertex_outer_index + static_cast(i << 1), - current_vertex_outer_index + static_cast(n << 1), - current_vertex_inner_index + static_cast(n << 1) + current_vertex_outer_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(n << 1) ); } } @@ -741,7 +739,7 @@ namespace gal::prometheus::gfx const color_type color_left_bottom, const color_type color_right_bottom ) noexcept -> void - // clang-format on + // clang-format on { auto appender = make_appender(); @@ -769,7 +767,7 @@ namespace gal::prometheus::gfx const color_type color_left_bottom, const color_type color_right_bottom ) noexcept -> void - // clang-format on + // clang-format on { const auto& render_list = self.get(); const auto& shared_data = render_list.shared_data(); @@ -786,14 +784,14 @@ namespace gal::prometheus::gfx const color_type color, const float wrap_width ) noexcept -> void - // clang-format on + // clang-format on { std::ignore = wrap_width; auto& render_list = self.get(); - auto& texture_context = render_list.context_.get().texture_context(); + auto& render_context = render_list.render_context_.get(); - const auto& glyphs = texture_context.glyph_of(utf8_text, font_size); + const auto& glyphs = render_context.glyph_of(utf8_text, font_size); for (auto x = p.x; const auto& glyph: glyphs) { if (glyph == nullptr) @@ -803,7 +801,7 @@ namespace gal::prometheus::gfx if (glyph->visible) { - const auto& atlas = texture_context.atlas_of(*glyph); + const auto& atlas = render_context.atlas_of(*glyph); const auto new_texture = render_list.this_command_texture_ != atlas.id(); @@ -860,7 +858,7 @@ namespace gal::prometheus::gfx const uv_type& uv_p4, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { auto& render_list = self.get(); @@ -903,7 +901,7 @@ namespace gal::prometheus::gfx float rounding, RenderFlag flag ) noexcept -> void - // clang-format on + // clang-format on { // @see `path_rect` GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); @@ -923,16 +921,16 @@ namespace gal::prometheus::gfx if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) { draw_image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color ); } else @@ -980,10 +978,10 @@ namespace gal::prometheus::gfx const auto v = uv_min + (it->position - display_rect.left_top()) * scale; it->uv = { - // std::ranges::clamp(v.x, uv_min.x, uv_max.x), - v.x, - // std::ranges::clamp(v.y, uv_min.y, uv_max.y) - v.y + // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + v.x, + // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + v.y }; it += 1; } @@ -1220,8 +1218,8 @@ namespace gal::prometheus::gfx const auto arc_length = to - from; const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); const auto arc_segment_count = std::ranges::max( - static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), - static_cast(std::numbers::pi_v * 2 / arc_length) + static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + static_cast(std::numbers::pi_v * 2 / arc_length) ); path_arc_n(circle, from, to, arc_segment_count); } @@ -1294,7 +1292,7 @@ namespace gal::prometheus::gfx const float tessellation_tolerance, const std::size_t level ) noexcept -> void - // clang-format on + // clang-format on { const auto dx = p4.x - p1.x; const auto dy = p4.y - p1.y; @@ -1327,7 +1325,7 @@ namespace gal::prometheus::gfx const float tessellation_tolerance, const std::size_t level ) noexcept -> void - // clang-format on + // clang-format on { const auto dx = p3.x - p1.x; const auto dy = p3.y - p1.y; @@ -1406,13 +1404,13 @@ namespace gal::prometheus::gfx GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_command_texture_ != invalid_texture_id); command_list_.emplace_back( - command_type{ + command_type{ .scissor = this_command_scissor_, .texture = this_command_texture_, .index_offset = static_cast(index_list_.size()), // set by draw_xxx .element_count = 0 - } + } ); } @@ -1484,29 +1482,40 @@ namespace gal::prometheus::gfx auto RenderList::shared_data() const noexcept -> const RenderListSharedData& { - return context_.get().render_list_shared_data(); + return render_context_.get().render_list_shared_data(); } auto RenderList::default_texture() const noexcept -> texture_id_type { - return context_.get().texture_context().root_texture(); + return render_context_.get().root_texture(); } - RenderList::RenderList(const RenderListFlag flag, RendererContext& context) noexcept + RenderList::RenderList(const RenderListFlag flag, RenderContext& render_context) noexcept : render_list_flag_{flag}, - context_{context}, + render_context_{render_context}, this_command_scissor_{0, 0, 0, 0}, - this_command_texture_{default_texture()} + this_command_texture_{invalid_texture_id} {} + + auto RenderList::reset() noexcept -> void { + command_list_.clear(); + vertex_list_.clear(); + index_list_.clear(); + + // we don't know the size of the clip rect, so we need the user to set it + this_command_scissor_ = {}; + // the first texture is always the (default) font texture + this_command_texture_ = default_texture(); + // we always have a command ready in the buffer command_list_.emplace_back( - command_type{ + command_type{ .scissor = this_command_scissor_, .texture = this_command_texture_, .index_offset = static_cast(index_list_.size()), // set by subsequent draw_xxx - .element_count = 0 - } + .element_count = 0, + } ); } @@ -1561,7 +1570,7 @@ namespace gal::prometheus::gfx const color_type color, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1584,7 +1593,7 @@ namespace gal::prometheus::gfx const color_type color, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1607,7 +1616,7 @@ namespace gal::prometheus::gfx const point_type& c, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1631,7 +1640,7 @@ namespace gal::prometheus::gfx const RenderFlag flag, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1654,7 +1663,7 @@ namespace gal::prometheus::gfx const RenderFlag flag, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { return rect({left_top, right_bottom}, color, rounding, flag, thickness); } @@ -1666,7 +1675,7 @@ namespace gal::prometheus::gfx const float rounding, const RenderFlag flag ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1694,7 +1703,7 @@ namespace gal::prometheus::gfx const float rounding, const RenderFlag flag ) noexcept -> void - // clang-format on + // clang-format on { return rect_filled({left_top, right_bottom}, color, rounding, flag); } @@ -1707,7 +1716,7 @@ namespace gal::prometheus::gfx const color_type color_left_bottom, const color_type color_right_bottom ) noexcept -> void - // clang-format on + // clang-format on { if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) { @@ -1728,7 +1737,7 @@ namespace gal::prometheus::gfx const color_type color_left_bottom, const color_type color_right_bottom ) noexcept -> void - // clang-format on + // clang-format on { return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); } @@ -1742,7 +1751,7 @@ namespace gal::prometheus::gfx const color_type color, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1764,7 +1773,7 @@ namespace gal::prometheus::gfx const point_type& p4, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -1785,7 +1794,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or circle.radius < .5f or segments < 3) { @@ -1807,7 +1816,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { return circle_n({center, radius}, color, segments, thickness); } @@ -1819,7 +1828,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) { @@ -1853,7 +1862,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or circle.radius < .5f or segments < 3) { @@ -1874,7 +1883,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { return circle_n_filled({center, radius}, color, segments); } @@ -1885,7 +1894,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) { @@ -1907,7 +1916,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { return ellipse_n_filled({center, radius, rotation}, color, segments); } @@ -1950,7 +1959,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { return circle({center, radius}, color, segments, thickness); } @@ -1961,7 +1970,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or circle.radius < .5f) { @@ -1991,7 +2000,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { circle_filled({center, radius}, color, segments); } @@ -2003,7 +2012,7 @@ namespace gal::prometheus::gfx std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) { @@ -2028,7 +2037,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { return ellipse({center, radius, rotation}, color, segments, thickness); } @@ -2039,7 +2048,7 @@ namespace gal::prometheus::gfx const color_type color, std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) { @@ -2063,7 +2072,7 @@ namespace gal::prometheus::gfx const color_type color, const std::uint32_t segments ) noexcept -> void - // clang-format on + // clang-format on { return ellipse_filled({center, radius, rotation}, color, segments); } @@ -2078,7 +2087,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -2101,7 +2110,7 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -2123,7 +2132,7 @@ namespace gal::prometheus::gfx const color_type color, const float wrap_width ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -2149,7 +2158,7 @@ namespace gal::prometheus::gfx const uv_type& uv_p4, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -2168,18 +2177,18 @@ namespace gal::prometheus::gfx const rect_type& uv_rect, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { image(texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color); + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color); } // clang-format off @@ -2191,7 +2200,7 @@ namespace gal::prometheus::gfx const uv_type& uv_right_bottom, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); } @@ -2205,7 +2214,7 @@ namespace gal::prometheus::gfx const rect_type& uv_rect, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { if (color.alpha == 0) { @@ -2228,7 +2237,7 @@ namespace gal::prometheus::gfx const uv_type& uv_right_bottom, const color_type color ) noexcept -> void - // clang-format on + // clang-format on { image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); } diff --git a/src/gfx/render_list.hpp b/src/gfx/render_list.hpp index 9a2ea54..d03c500 100644 --- a/src/gfx/render_list.hpp +++ b/src/gfx/render_list.hpp @@ -197,7 +197,7 @@ namespace gal::prometheus::gfx class Drawer; RenderListFlag render_list_flag_; - memory::RefWrapper context_; + memory::RefWrapper render_context_; // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 @@ -222,7 +222,9 @@ namespace gal::prometheus::gfx [[nodiscard]] auto default_texture() const noexcept -> texture_id_type; public: - RenderList(RenderListFlag flag, RendererContext& context) noexcept; + RenderList(RenderListFlag flag, RenderContext& render_context) noexcept; + + auto reset() noexcept -> void; // ---------------------------------------------------------------------------- // RENDER DATA diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index 3a9b8da..e3a7b86 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -28,7 +28,10 @@ namespace gal::prometheus::gfx [[nodiscard]] virtual auto ready() const noexcept -> bool = 0; + virtual auto present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void = 0; + virtual auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type = 0; + virtual auto update_texture(const Texture& texture) noexcept -> void = 0; virtual auto destroy_texture(texture_id_type texture_id) noexcept -> void = 0; }; } // namespace gal::prometheus::gfx diff --git a/src/gfx/renderer_dx11.cpp b/src/gfx/renderer_dx11.cpp index 8021fd9..e50fbf8 100644 --- a/src/gfx/renderer_dx11.cpp +++ b/src/gfx/renderer_dx11.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -28,12 +28,12 @@ namespace using namespace gal::prometheus; using namespace gfx; - [[nodiscard]] auto check_hr_error( + auto check_hr_error( const HRESULT result - #if defined(GAL_PROMETHEUS_GFX_DEBUG) +#if defined(GAL_PROMETHEUS_GFX_DEBUG) , const std::source_location& location = std::source_location::current() - #endif +#endif ) noexcept -> bool { if (SUCCEEDED(result)) @@ -41,18 +41,18 @@ namespace return true; } - #if defined(GAL_PROMETHEUS_GFX_DEBUG) +#if defined(GAL_PROMETHEUS_GFX_DEBUG) const _com_error err{result}; std::println(stderr, "Error: {} --- at {}:{}", err.ErrorMessage(), location.file_name(), location.line()); GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); - #else +#else GAL_PROMETHEUS_COMPILER_UNREACHABLE(); - #endif +#endif return false; } @@ -448,7 +448,8 @@ namespace gal::prometheus::gfx vertex_input_layout_{nullptr}, vertex_projection_matrix_{nullptr}, pixel_shader_{nullptr}, - pixel_font_sampler_{nullptr} {} + pixel_font_sampler_{nullptr}, + render_buffer_{} {} Dx11Renderer::Dx11Renderer(ID3D11Device* device, ID3D11DeviceContext* device_immediate_context) noexcept : Dx11Renderer{} @@ -552,9 +553,251 @@ namespace gal::prometheus::gfx return true; } + auto Dx11Renderer::present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void + { + const auto all_render_data = renderer_context.render_data(); + // const auto [display_x, display_y] = display_area.point; + const auto [display_width, display_height] = display_area.extent; + + auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = render_buffer_; + + const auto [total_vertex_count, total_index_count] = [&]() noexcept + { + struct sum + { + UINT vertex; + UINT index; + }; + + return std::ranges::fold_left( + all_render_data, + sum{.vertex = 0, .index = 0}, + [](const sum s, const RenderData& render_data) noexcept -> sum + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + return {.vertex = s.vertex + static_cast(vertex_list.size()), .index = s.index + static_cast(index_list.size())}; + } + ); + }(); + + if (not this_frame_vertex_buffer or total_vertex_count > this_frame_vertex_count) + { + // todo: grow factor + this_frame_vertex_count = total_vertex_count + 5000; + + const D3D11_BUFFER_DESC buffer_desc{ + .ByteWidth = static_cast(this_frame_vertex_count * sizeof(vertex_type)), + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_VERTEX_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, + .MiscFlags = 0, + .StructureByteStride = 0 + }; + check_hr_error(device_->CreateBuffer(&buffer_desc, nullptr, this_frame_vertex_buffer.ReleaseAndGetAddressOf())); + } + if (not this_frame_index_buffer or total_index_count > this_frame_index_count) + { + // todo: grow factor + this_frame_index_count = total_index_count + 10000; + + const D3D11_BUFFER_DESC buffer_desc{ + .ByteWidth = static_cast(this_frame_index_count * sizeof(index_type)), + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_INDEX_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, + .MiscFlags = 0, + .StructureByteStride = 0 + }; + check_hr_error(device_->CreateBuffer(&buffer_desc, nullptr, this_frame_index_buffer.ReleaseAndGetAddressOf())); + } + + // Upload vertex/index data into a single contiguous GPU buffer + { + D3D11_MAPPED_SUBRESOURCE mapped_vertex_resource; + D3D11_MAPPED_SUBRESOURCE mapped_index_resource; + check_hr_error(device_immediate_context_->Map(this_frame_vertex_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_vertex_resource)); + check_hr_error(device_immediate_context_->Map(this_frame_index_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_index_resource)); + + auto* mapped_vertex = static_cast(mapped_vertex_resource.pData); + auto* mapped_index = static_cast(mapped_index_resource.pData); + + UINT vertex_offset = 0; + UINT index_offset = 0; + + std::ranges::for_each( + all_render_data, + [&](const RenderData& draw_data) noexcept -> void + { + const auto vertex_list = draw_data.vertex_list.get(); + const auto index_list = draw_data.index_list.get(); + + // std::ranges::transform( + // vertex_list, + // mapped_vertex + vertex_offset, + // [](const vertex_type& vertex) noexcept -> vertex_type + // { + // // return { + // // .position = {vertex.position.x, vertex.position.y}, + // // .uv = {vertex.uv.x, vertex.uv.y}, + // // .color = vertex.color.to(primitive::color_format) + // // }; + // return std::bit_cast(vertex); + // } + // ); + std::ranges::copy(vertex_list, mapped_vertex + vertex_offset); + // std::ranges::transform( + // index_list, + // mapped_index + index_offset, + // [vertex_offset](const index_type index) noexcept -> index_type + // { + // return static_cast(index + vertex_offset); + // } + // ); + std::ranges::copy(index_list, mapped_index + index_offset); + + vertex_offset += static_cast(vertex_list.size()); + index_offset += static_cast(index_list.size()); + } + ); + + device_immediate_context_->Unmap(this_frame_vertex_buffer.Get(), 0); + device_immediate_context_->Unmap(this_frame_index_buffer.Get(), 0); + } + + // Setup orthographic projection matrix into our constant buffer + { + D3D11_MAPPED_SUBRESOURCE mapped_resource; + check_hr_error(device_immediate_context_->Map(vertex_projection_matrix_.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource)); + + auto* mapped_projection_matrix = static_cast(mapped_resource.pData); + + constexpr auto left = 0.f; + const auto right = display_width; + constexpr auto top = 0.f; + const auto bottom = display_height; + + const projection_matrix_type mvp{ + {2.0f / (right - left), 0.0f, 0.0f, 0.0f}, + {0.0f, 2.0f / (top - bottom), 0.0f, 0.0f}, + {0.0f, 0.0f, 0.5f, 0.0f}, + {(right + left) / (left - right), (top + bottom) / (bottom - top), 0.5f, 1.0f}, + }; + std::memcpy(mapped_projection_matrix, &mvp, sizeof(projection_matrix_type)); + + device_immediate_context_->Unmap(vertex_projection_matrix_.Get(), 0); + } + + // Setup viewport + { + const D3D11_VIEWPORT viewport{ + .TopLeftX = .0f, + .TopLeftY = .0f, + .Width = display_width, + .Height = display_height, + .MinDepth = 0, + .MaxDepth = 1 + }; + device_immediate_context_->RSSetViewports(1, &viewport); + } + + // Bind shader and vertex buffers + constexpr UINT stride = sizeof(vertex_type); + constexpr UINT offset = 0; + device_immediate_context_->IASetInputLayout(vertex_input_layout_.Get()); + device_immediate_context_->IASetVertexBuffers(0, 1, this_frame_vertex_buffer.GetAddressOf(), &stride, &offset); + device_immediate_context_->IASetIndexBuffer( + this_frame_index_buffer.Get(), + // ReSharper disable once CppUnreachableCode + sizeof(index_type) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, + 0 + ); + device_immediate_context_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + device_immediate_context_->VSSetShader(vertex_shader_.Get(), nullptr, 0); + device_immediate_context_->VSSetConstantBuffers(0, 1, vertex_projection_matrix_.GetAddressOf()); + device_immediate_context_->PSSetShader(pixel_shader_.Get(), nullptr, 0); + device_immediate_context_->PSSetSamplers(0, 1, pixel_font_sampler_.GetAddressOf()); + device_immediate_context_->DSSetShader(nullptr, nullptr, 0); + device_immediate_context_->HSSetShader(nullptr, nullptr, 0); + device_immediate_context_->GSSetShader(nullptr, nullptr, 0); + device_immediate_context_->CSSetShader(nullptr, nullptr, 0); + + // Setup render state + constexpr float blend_factor[]{0, 0, 0, 0}; + device_immediate_context_->OMSetBlendState(blend_state_.Get(), blend_factor, (std::numeric_limits::max)()); + device_immediate_context_->OMSetDepthStencilState(depth_stencil_state_.Get(), 0); + device_immediate_context_->RSSetState(rasterizer_state_.Get()); + + UINT total_index_offset = 0; + std::ranges::for_each( + all_render_data, + [this, &total_index_offset](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + for (const auto& command_list = render_data.command_list.get(); + const auto& [clip_rect, texture, index_offset, element_count]: command_list) + { + const auto [point, extent] = clip_rect; + const D3D11_RECT rect + { + static_cast(point.x), + static_cast(point.y), + static_cast(point.x + extent.width), + static_cast(point.y + extent.height) + }; + device_immediate_context_->RSSetScissorRects(1, &rect); + + assert(texture != 0 and "push_texture_id when create texture view"); + ID3D11ShaderResourceView* textures[]{reinterpret_cast(texture)}; // NOLINT(performance-no-int-to-ptr) + device_immediate_context_->PSSetShaderResources(0, 1, textures); + + const auto this_index_offset = static_cast(total_index_offset + index_offset); + // device_immediate_context_->DrawIndexed(static_cast(element_count), this_index_offset, 0); + device_immediate_context_->DrawIndexedInstanced(static_cast(element_count), 1, this_index_offset, 0, 0); + } + + total_index_offset += static_cast(index_list.size()); + } + ); + } + auto Dx11Renderer::create_texture(const Texture::data_view_type data, const Texture::size_type size) noexcept -> texture_id_type { - return upload_texture(data, size); + return upload_texture(data, size, D3D11_USAGE_DYNAMIC, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, 0); + } + + auto Dx11Renderer::update_texture(const Texture& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.uploaded(), "Create texture first!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty(), "No need to update texture!"); + + auto* srv = id_to_gpu_handle(texture.id()); + const auto it = textures_.find(srv); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != textures_.end(), "Invalid texture id"); + + auto* texture_2d = it->second; + D3D11_MAPPED_SUBRESOURCE mapped_resource{}; + if (const auto result = device_immediate_context_->Map( + texture_2d, + 0, + D3D11_MAP_WRITE_DISCARD, + 0, + &mapped_resource + ); result != S_OK) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + const auto* source = texture.data().data(); + const auto source_length = texture.area_size(); + std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); + + device_immediate_context_->Unmap(texture_2d, 0); } auto Dx11Renderer::destroy_texture(const texture_id_type texture_id) noexcept -> void diff --git a/src/gfx/renderer_dx11.hpp b/src/gfx/renderer_dx11.hpp index 4f1c966..19c6f65 100644 --- a/src/gfx/renderer_dx11.hpp +++ b/src/gfx/renderer_dx11.hpp @@ -27,6 +27,14 @@ namespace gal::prometheus::gfx using textures_type = std::unordered_map; private: + struct render_buffer_type + { + ComPtr index; + UINT index_count; + ComPtr vertex; + UINT vertex_count; + }; + ComPtr device_; ComPtr device_immediate_context_; @@ -43,6 +51,8 @@ namespace gal::prometheus::gfx textures_type textures_; + render_buffer_type render_buffer_; + [[nodiscard]] auto create_blend_state() noexcept -> bool; [[nodiscard]] auto create_rasterizer_state() noexcept -> bool; [[nodiscard]] auto create_depth_stencil_state() noexcept -> bool; @@ -53,10 +63,10 @@ namespace gal::prometheus::gfx [[nodiscard]] auto upload_texture( Texture::data_view_type data, Texture::size_type size, - D3D11_USAGE usage = D3D11_USAGE_DEFAULT, - std::uint32_t bind_flags = D3D11_BIND_SHADER_RESOURCE, - std::uint32_t cpu_access_flags = 0, - std::uint32_t misc_flags = 0, + D3D11_USAGE usage, + std::uint32_t bind_flags, + std::uint32_t cpu_access_flags, + std::uint32_t misc_flags, bool record_resource = true ) noexcept -> texture_id_type; @@ -75,7 +85,10 @@ namespace gal::prometheus::gfx [[nodiscard]] auto ready() const noexcept -> bool override; + auto present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void override; + auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type override; + auto update_texture(const Texture& texture) noexcept -> void override; auto destroy_texture(texture_id_type texture_id) noexcept -> void override; }; } diff --git a/src/gfx/texture.cpp b/src/gfx/texture.cpp index b86bb13..bed7c86 100644 --- a/src/gfx/texture.cpp +++ b/src/gfx/texture.cpp @@ -11,7 +11,7 @@ namespace { -#define STB_RECT_PACK_IMPLEMENTATION + // #define STB_RECT_PACK_IMPLEMENTATION #include } // namespace @@ -24,7 +24,7 @@ namespace gal::prometheus::gfx }; Texture::Texture(Texture&&) noexcept = default; - Texture& Texture::operator=(Texture&&) noexcept = default; + auto Texture::operator=(Texture&&) noexcept -> Texture& = default; Texture::~Texture() noexcept = default; @@ -38,20 +38,40 @@ namespace gal::prometheus::gfx pack_context_->nodes.resize(size.width); stbrp_init_target( - &pack_context_->context, static_cast(size.width), static_cast(size.height), pack_context_->nodes.data(), static_cast(pack_context_->nodes.size()) + &pack_context_->context, + static_cast(size.width), + static_cast(size.height), + pack_context_->nodes.data(), + static_cast(pack_context_->nodes.size()) ); } - auto Texture::upload(Renderer& renderer) noexcept -> void + auto Texture::create(Renderer& renderer) noexcept -> void { - // todo: destroy texture? GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not uploaded()); + const auto id = renderer.create_texture(data(), size_); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id != invalid_texture_id); + dirty_ = false; id_ = id; } + auto Texture::upload(Renderer& renderer) noexcept -> void + { + renderer.update_texture(*this); + dirty_ = false; + } + + auto Texture::upload_if_required(Renderer& renderer) noexcept -> void + { + if (dirty_) + { + renderer.update_texture(*this); + dirty_ = false; + } + } + auto Texture::data() const noexcept -> data_view_type { return {data_.get(), area_size()}; @@ -59,7 +79,7 @@ namespace gal::prometheus::gfx auto Texture::area_size() const noexcept -> std::size_t { - return size_.width * size_.height; + return static_cast(size_.width) * size_.height; } auto Texture::size() const noexcept -> size_type @@ -101,8 +121,8 @@ namespace gal::prometheus::gfx auto* address = data_.get() + (point.y * size.width + point.x); const auto mapping = BorrowTexture::data_type::mapping_type{ - std::dextents{size.height, size.width}, - std::array{size_.width, 1}, + std::dextents{size.height, size.width}, + std::array{size_.width, 1}, }; const auto data = BorrowTexture::data_type{address, mapping}; @@ -115,9 +135,7 @@ namespace gal::prometheus::gfx BorrowTexture::BorrowTexture(const point_type point, const data_type data) noexcept : point_{point}, - data_{data} - { - } + data_{data} {} auto BorrowTexture::valid() const noexcept -> bool { @@ -133,9 +151,12 @@ namespace gal::prometheus::gfx auto BorrowTexture::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n < data_.extent(1)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n <= width); for (size_type::value_type x = offset; x < offset + n; ++x) { @@ -145,9 +166,12 @@ namespace gal::prometheus::gfx auto BorrowTexture::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + data.size() < data_.extent(1)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + data.size() <= width); for (size_type::value_type x = 0; x < data.size(); ++x) { @@ -157,35 +181,45 @@ namespace gal::prometheus::gfx auto BorrowTexture::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(n < data_.extent(1)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(n <= width); fill(y, 0, n, element); } auto BorrowTexture::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() < data_.extent(1)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() <= width); fill(y, 0, data); } auto BorrowTexture::fill(const size_type::value_type y, const element_type element) const noexcept -> void { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - fill(y, data_.extent(1), element); + fill(y, width, element); } auto BorrowTexture::fill(const element_type element) const noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - for (size_type::value_type y = 0; y < data_.extent(0); ++y) + const auto height = data_.extent(0); + for (size_type::value_type y = 0; y < height; ++y) { fill(y, element); } @@ -193,21 +227,27 @@ namespace gal::prometheus::gfx auto BorrowTexture::fill(const data_view_type data) const noexcept -> void { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - for (size_type::value_type y = 0; y < data_.extent(0); ++y) + for (size_type::value_type y = 0; y < height; ++y) { - const data_view_type sub{data.begin() + y * data_.extent(1), data_.extent(1)}; + const data_view_type sub{data.begin() + static_cast(y) * width, width}; fill(y, sub); } } - auto BorrowTexture::operator[](size_type::value_type x, size_type::value_type y) const noexcept -> data_type::reference + auto BorrowTexture::operator[](const size_type::value_type x, const size_type::value_type y) const noexcept -> data_type::reference { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < data_.extent(0)); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < data_.extent(1)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < width); return data_[y, x]; } diff --git a/src/gfx/texture.hpp b/src/gfx/texture.hpp index 3c64342..0b03134 100644 --- a/src/gfx/texture.hpp +++ b/src/gfx/texture.hpp @@ -49,7 +49,7 @@ namespace gal::prometheus::gfx public: Texture(const Texture&) noexcept = delete; - Texture(Texture&&) noexcept; // = default; + Texture(Texture&&) noexcept; // = default; auto operator=(const Texture&) noexcept -> Texture& = delete; auto operator=(Texture&&) noexcept -> Texture&; // = default; @@ -57,8 +57,23 @@ namespace gal::prometheus::gfx explicit Texture(size_type size) noexcept; + /** + * @brief Upload texture atlas data to GPU and get GPU resource handle + */ + auto create(Renderer& renderer) noexcept -> void; + + /** + * @brief Upload new texture atlas data to GPU (overwrite previous texture atlas data) + * @note Not checking if it needs to be re-uploaded + */ auto upload(Renderer& renderer) noexcept -> void; + /** + * @brief Upload new texture atlas data to GPU (overwrite previous texture atlas data) + * @note If no re-upload is required, the upload operation is not performed + */ + auto upload_if_required(Renderer& renderer) noexcept -> void; + /** * @brief Texture atlas data (for upload) */ diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index 1626a94..f65dedc 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -51,8 +51,6 @@ namespace gal::prometheus::gfx class GlyphKey; class GlyphInfo; - class FontPendingLoadData; - class GlyphParsedInfo; class GlyphParser; @@ -76,5 +74,5 @@ namespace gal::prometheus::gfx // ========================================================= class TextureContext; - class RendererContext; + class RenderContext; } // namespace gal::prometheus::gfx From b285de86a85fc7c4dbbc0601bdd87bd7819cadd4 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Sun, 11 May 2025 06:15:27 +0800 Subject: [PATCH 37/54] `fix`: * Move all GFX enumerations to the type.hpp. * Move the `size_of(text)` implementation from `TextureContext` to `RenderList` to ensure that it is highly consistent with the implementation of drawing text --- src/gfx/context.cpp | 128 ++++++++++++++--------- src/gfx/context.hpp | 96 ++++++++++------- src/gfx/font.hpp | 15 --- src/gfx/render_list.cpp | 215 +++++++++++++++++++++++++++++++++----- src/gfx/render_list.hpp | 138 +++++------------------- src/gfx/renderer_dx11.cpp | 10 +- src/gfx/type.hpp | 114 ++++++++++++++++++++ 7 files changed, 469 insertions(+), 247 deletions(-) diff --git a/src/gfx/context.cpp b/src/gfx/context.cpp index 2bd45b7..d72cb64 100644 --- a/src/gfx/context.cpp +++ b/src/gfx/context.cpp @@ -9,7 +9,7 @@ #include -#include +// #include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus::gfx @@ -194,15 +194,13 @@ namespace gal::prometheus::gfx return nullptr; } - auto TextureContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector + auto TextureContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector { - const auto utf32_text = chars::convert(text); - std::vector infos; - infos.reserve(utf32_text.size()); + infos.reserve(text.size()); std::ranges::for_each( - utf32_text, + text, [&infos, this, size, flag](const auto codepoint) noexcept -> void { const auto* info = this->glyph_of(codepoint, size, flag); @@ -213,38 +211,62 @@ namespace gal::prometheus::gfx return infos; } - auto TextureContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type - { - const auto* info = glyph_of(codepoint, size, flag); - if (info == nullptr) - { - return {0, 0}; - } - - return {info->advance_x, info->rect.height()}; - } - - auto TextureContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type - { - const auto infos = glyph_of(text, size, flag); - - extent_type total_size{0, 0}; - std::ranges::for_each( - infos, - [&total_size](const auto* info) noexcept -> void - { - if (info == nullptr) - { - return; - } - - total_size.width += info->advance_x; - total_size.height = std::max(total_size.height, info->rect.height()); - } - ); - - return total_size; - } + // auto TextureContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector + // { + // const auto utf32_text = chars::convert(text); + // + // return this->glyph_of(utf32_text, size, flag); + // } + + // auto TextureContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + // { + // const auto* info = glyph_of(codepoint, size, flag); + // + // if (info == nullptr) + // { + // return {0, 0}; + // } + // + // return {info->advance_x, info->rect.height()}; + // } + // + // auto TextureContext::size_of(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + // { + // const auto infos = glyph_of(text, size, flag); + // + // return std::ranges::fold_left( + // infos, + // extent_type{0, 0}, + // [](const extent_type total, const auto* info) noexcept -> extent_type + // { + // if (info == nullptr) + // { + // return total; + // } + // + // return {total.width + info->advance_x, std::ranges::max(total.height, info->rect.height())}; + // } + // ); + // } + // + // auto TextureContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + // { + // const auto infos = glyph_of(text, size, flag); + // + // return std::ranges::fold_left( + // infos, + // extent_type{0, 0}, + // [](const extent_type total, const auto* info) noexcept -> extent_type + // { + // if (info == nullptr) + // { + // return total; + // } + // + // return {total.width + info->advance_x, std::ranges::max(total.height, info->rect.height())}; + // } + // ); + // } auto TextureContext::load_all_font() noexcept -> void { @@ -358,20 +380,30 @@ namespace gal::prometheus::gfx return texture_context_.glyph_of(codepoint, size, flag); } - auto RenderContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector + auto RenderContext::glyph_of(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector { return texture_context_.glyph_of(text, size, flag); } - auto RenderContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type - { - return texture_context_.size_of(codepoint, size, flag); - } - - auto RenderContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type - { - return texture_context_.size_of(text, size, flag); - } + // auto RenderContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector + // { + // return texture_context_.glyph_of(text, size, flag); + // } + + // auto RenderContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + // { + // return texture_context_.size_of(codepoint, size, flag); + // } + // + // auto RenderContext::size_of(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + // { + // return texture_context_.size_of(text, size, flag); + // } + // + // auto RenderContext::size_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type + // { + // return texture_context_.size_of(text, size, flag); + // } auto RenderContext::render_list_shared_data() const noexcept -> const RenderListSharedData& { diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp index e52d529..31e946e 100644 --- a/src/gfx/context.hpp +++ b/src/gfx/context.hpp @@ -128,25 +128,35 @@ namespace gal::prometheus::gfx [[nodiscard]] auto atlas_of(const GlyphInfo& info) noexcept -> const Texture&; [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; - - /** - * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety - * @param codepoint - * @param size - * @param flag - * @return - */ - [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; - - /** - * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety - * @param text - * @param size - * @param flag - * @return - */ - [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + // [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + + // /** + // * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety + // * @param codepoint + // * @param size + // * @param flag + // * @return + // */ + // [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + // + // /** + // * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + // * @param text + // * @param size + // * @param flag + // * @return + // */ + // [[nodiscard]] auto size_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + // + // /** + // * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + // * @param text + // * @param size + // * @param flag + // * @return + // */ + // [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; /** * @brief Load the fonts previously added by @c add_font to the @c FontFace @@ -227,25 +237,35 @@ namespace gal::prometheus::gfx [[nodiscard]] auto atlas_of(const GlyphInfo& info) noexcept -> const Texture&; [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; - - /** - * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety - * @param codepoint - * @param size - * @param flag - * @return - */ - [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; - - /** - * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety - * @param text - * @param size - * @param flag - * @return - */ - [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + // [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + + // /** + // * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety + // * @param codepoint + // * @param size + // * @param flag + // * @return + // */ + // [[nodiscard]] auto size_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + // + // /** + // * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + // * @param text + // * @param size + // * @param flag + // * @return + // */ + // [[nodiscard]] auto size_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + // + // /** + // * @brief The minimum space to be occupied if the specified text is to be rendered in its entirety + // * @param text + // * @param size + // * @param flag + // * @return + // */ + // [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; // ==================================================================== // RenderListSharedData diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp index 82f0536..836b2f9 100644 --- a/src/gfx/font.hpp +++ b/src/gfx/font.hpp @@ -11,18 +11,10 @@ #include #include -#include #include namespace gal::prometheus::gfx { - enum class GlyphFlag : std::uint8_t - { - NONE = 0, - BOLD = 1 << 0, - ITALIC = 1 << 1, - }; - /** * @brief Glyph */ @@ -279,10 +271,3 @@ namespace gal::prometheus::gfx [[nodiscard]] auto find_glyph_no_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; }; } // namespace gal::prometheus::gfx - -// ReSharper disable once CppRedundantNamespaceDefinition -namespace gal::prometheus::meta::user_defined -{ - template<> - struct enum_is_flag : std::true_type {}; -} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/render_list.cpp b/src/gfx/render_list.cpp index b0c9436..6b1d941 100644 --- a/src/gfx/render_list.cpp +++ b/src/gfx/render_list.cpp @@ -7,6 +7,7 @@ #include +#include #include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE @@ -782,24 +783,76 @@ namespace gal::prometheus::gfx const std::uint32_t font_size, const point_type& p, const color_type color, - const float wrap_width + const float wrap_width, + const GlyphFlag flag ) noexcept -> void // clang-format on { - std::ignore = wrap_width; + // todo: + // The texture used for glyphs may not be the default texture, then we need to switch the texture, + // but at this time, the glyph information may not be written to the texture atlas (e.g. the first frame), then we can't know the ID of the texture atlas + + // note: + // Line break ('\n') have no glyph information (nullptr), meaning we need to skip it auto& render_list = self.get(); auto& render_context = render_list.render_context_.get(); - const auto& glyphs = render_context.glyph_of(utf8_text, font_size); - for (auto x = p.x; const auto& glyph: glyphs) + const auto utf32_text = chars::convert(utf8_text); + const auto& glyphs = render_context.glyph_of(utf32_text, font_size, flag); + + if (std::ranges::any_of( + glyphs, + [](const auto* glyph) noexcept -> bool + { + return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; + } + ) + ) { - if (glyph == nullptr) + // skip this frame? + return; + } + + const auto visible_glyph_count = std::ranges::count_if( + glyphs, + [](const auto* glyph) noexcept -> bool { - return; + if (glyph == nullptr) + { + return false; + } + + if (not glyph->visible) + { + return false; + } + + return true; } + ); - if (glyph->visible) + // two triangle without path + const auto vertex_count = std::size_t{4} * visible_glyph_count; + const auto index_count = std::size_t{6} * visible_glyph_count; + auto appender = make_appender(); + appender.reserve(vertex_count, index_count); + + const auto wrap_pos_x = p.x + wrap_width; + const auto line_height = static_cast(font_size); + auto cursor = p + point_type{0, line_height}; + + for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + { + if (glyph == nullptr) + { + if (codepoint == U'\n') + { + cursor.x = p.x; + cursor.y += line_height; + } + } + else if (glyph->visible) { const auto& atlas = render_context.atlas_of(*glyph); @@ -810,39 +863,103 @@ namespace gal::prometheus::gfx render_list.push_texture(atlas.id()); } - // todo - { - const auto color_may_colored = glyph->colored ? color.transparent() : color; - const auto glyph_point = point_type{x, glyph->rect.point.y}; - const auto glyph_width = glyph->rect.width(); - const auto glyph_height = glyph->rect.height(); - - auto appender = make_appender(); + const auto glyph_advance_x = glyph->advance_x; - // two triangle without path - constexpr size_type vertex_count = 4; - constexpr size_type index_count = 6; - appender.reserve(vertex_count, index_count); + if (cursor.x + glyph_advance_x > wrap_pos_x) + { + cursor.x = p.x; + cursor.y += line_height; + } - const auto current_vertex_index = static_cast(appender.vertex_count()); + const rect_type char_rect + { + cursor + point_type{glyph->rect.left_top().x, -glyph->rect.left_top().y}, + glyph->rect.size() + }; + const auto color_may_colored = glyph->colored ? color.transparent() : color; + const auto current_vertex_index = static_cast(appender.vertex_count()); - appender.add_vertex(glyph_point, glyph->uv.left_top(), color_may_colored); - appender.add_vertex(glyph_point + extent_type{glyph_width, 0}, glyph->uv.right_top(), color_may_colored); - appender.add_vertex(glyph_point + extent_type{0, glyph_height}, glyph->uv.left_bottom(), color_may_colored); - appender.add_vertex(glyph_point + extent_type{glyph_width, glyph_height}, glyph->uv.right_bottom(), color_may_colored); + appender.add_vertex(char_rect.left_top(), glyph->uv.left_top(), color_may_colored); + appender.add_vertex(char_rect.right_top(), glyph->uv.right_top(), color_may_colored); + appender.add_vertex(char_rect.right_bottom(), glyph->uv.right_bottom(), color_may_colored); + appender.add_vertex(char_rect.left_bottom(), glyph->uv.left_bottom(), color_may_colored); - appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - } + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); if (new_texture) { render_list.pop_texture(); } + + cursor.x += glyph_advance_x; } + } + } + + // clang-format off + auto draw_text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const float wrap_width, + const GlyphFlag flag + ) noexcept -> extent_type + // clang-format on + { + auto& render_list = self.get(); + auto& render_context = render_list.render_context_.get(); + + const auto utf32_text = chars::convert(utf8_text); + const auto& glyphs = render_context.glyph_of(utf32_text, font_size, flag); + + if (std::ranges::any_of( + glyphs, + [](const auto* glyph) noexcept -> bool + { + return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; + } + ) + ) + { + // skip this frame? + return {0, 0}; + } + + const auto line_height = static_cast(font_size); + + float max_width = 0; + float current_width = 0; + float total_height = line_height; - x += glyph->advance_x; + for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + { + if (glyph == nullptr) + { + if (codepoint == U'\n') + { + max_width = std::ranges::max(max_width, current_width); + current_width = 0; + total_height += line_height; + } + } + else if (glyph->visible) + { + if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) + { + max_width = std::ranges::max(max_width, current_width); + current_width = glyph_advance_x; + total_height += line_height; + } + else + { + current_width += glyph_advance_x; + } + } } + + max_width = std::ranges::max(max_width, current_width); + + return {max_width, total_height}; } // clang-format off @@ -2133,6 +2250,20 @@ namespace gal::prometheus::gfx const float wrap_width ) noexcept -> void // clang-format on + { + return this->text(utf8_text, font_size, point, color, GlyphFlag::NONE, wrap_width); + } + + // clang-format off + auto RenderList::text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& point, + const color_type color, + const GlyphFlag flag, + const float wrap_width + ) noexcept -> void + // clang-format on { if (color.alpha == 0) { @@ -2142,7 +2273,33 @@ namespace gal::prometheus::gfx GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(wrap_width > 0); Drawer drawer{.self = *this, .path_list = {}}; - drawer.draw_text(utf8_text, font_size, point, color, wrap_width); + + drawer.draw_text(utf8_text, font_size, point, color, wrap_width, flag); + } + + // clang-format off + auto RenderList::text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const float wrap_width + ) noexcept -> extent_type + // clang-format on + { + return this->text_size(utf8_text, font_size, GlyphFlag::NONE, wrap_width); + } + + // clang-format off + auto RenderList::text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const GlyphFlag flag, + const float wrap_width + ) noexcept -> extent_type + // clang-format on + { + Drawer drawer{.self = *this, .path_list = {}}; + + return drawer.draw_text_size(utf8_text, font_size, wrap_width, flag); } // clang-format off diff --git a/src/gfx/render_list.hpp b/src/gfx/render_list.hpp index d03c500..ace212b 100644 --- a/src/gfx/render_list.hpp +++ b/src/gfx/render_list.hpp @@ -7,7 +7,6 @@ #include -#include #include namespace gal::prometheus::gfx @@ -61,96 +60,6 @@ namespace gal::prometheus::gfx auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; }; - enum class RenderListFlag : std::uint8_t - { - NONE = 0, - ANTI_ALIASED_LINE = 1 << 0, - ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, - ANTI_ALIASED_FILL = 1 << 2, - }; - - enum class RenderFlag : std::uint8_t - { - NONE = 0, - // specify that shape should be closed - // @see RenderList::draw_polygon_line - // @see RenderList::draw_polygon_line_aa - // @see RenderList::path_stroke - CLOSED = 1 << 0, - // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_LEFT_TOP = 1 << 1, - // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_RIGHT_TOP = 1 << 2, - // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_LEFT_BOTTOM = 1 << 3, - // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, - // disable rounding on all corners (when rounding > 0.0f) - ROUND_CORNER_NONE = 1 << 5, - - ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, - ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, - ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - - ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, - ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, - }; - - enum class RenderArcFlag : std::uint8_t - { - // [0~3) - Q1 = 1 << 0, - // [3~6) - Q2 = 1 << 1, - // [6~9) - Q3 = 1 << 2, - // [9~12) - Q4 = 1 << 3, - - RIGHT_TOP = Q1, - LEFT_TOP = Q2, - LEFT_BOTTOM = Q3, - RIGHT_BOTTOM = Q4, - TOP = Q1 | Q2, - BOTTOM = Q3 | Q4, - LEFT = Q2 | Q3, - RIGHT = Q1 | Q4, - ALL = Q1 | Q2 | Q3 | Q4, - - // [3, 0) - Q1_CLOCK_WISH = 1 << 4, - // [6, 3) - Q2_CLOCK_WISH = 1 << 5, - // [9, 6) - Q3_CLOCK_WISH = 1 << 6, - // [12, 9) - Q4_CLOCK_WISH = 1 << 7, - - RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, - LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, - LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, - RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, - TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, - BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, - LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, - RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, - ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, - }; - class RenderData final { public: @@ -480,13 +389,37 @@ namespace gal::prometheus::gfx color_type color, float wrap_width = text_no_auto_wrap ) noexcept -> void; + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + GlyphFlag flag, + float wrap_width = text_no_auto_wrap + ) noexcept -> void; + + // fixme: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + float wrap_width = text_no_auto_wrap + ) noexcept -> extent_type; + + // fixme: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + GlyphFlag flag, + float wrap_width = text_no_auto_wrap + ) noexcept -> extent_type; // ---------------------------------------------------------------------------- // IMAGE // p1________ p2 - // | | - // | | + // | | + // | | // p4|_______| p3 auto image( texture_id_type texture_id, @@ -540,22 +473,3 @@ namespace gal::prometheus::gfx // clang-format on }; } // namespace gal::prometheus::gfx - -// ReSharper disable once CppRedundantNamespaceDefinition -namespace gal::prometheus::meta::user_defined -{ - template<> - struct enum_is_flag : std::true_type - { - }; - - template<> - struct enum_is_flag : std::true_type - { - }; - - template<> - struct enum_is_flag : std::true_type - { - }; -} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/renderer_dx11.cpp b/src/gfx/renderer_dx11.cpp index e50fbf8..935efc9 100644 --- a/src/gfx/renderer_dx11.cpp +++ b/src/gfx/renderer_dx11.cpp @@ -738,9 +738,9 @@ namespace gal::prometheus::gfx const auto index_list = render_data.index_list.get(); for (const auto& command_list = render_data.command_list.get(); - const auto& [clip_rect, texture, index_offset, element_count]: command_list) + const auto& [scissor, texture, index_offset, element_count]: command_list) { - const auto [point, extent] = clip_rect; + const auto [point, extent] = scissor; const D3D11_RECT rect { static_cast(point.x), @@ -750,9 +750,9 @@ namespace gal::prometheus::gfx }; device_immediate_context_->RSSetScissorRects(1, &rect); - assert(texture != 0 and "push_texture_id when create texture view"); - ID3D11ShaderResourceView* textures[]{reinterpret_cast(texture)}; // NOLINT(performance-no-int-to-ptr) - device_immediate_context_->PSSetShaderResources(0, 1, textures); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture != invalid_texture_id); + auto* srv = id_to_gpu_handle(texture); + device_immediate_context_->PSSetShaderResources(0, 1, &srv); const auto this_index_offset = static_cast(total_index_offset + index_offset); // device_immediate_context_->DrawIndexed(static_cast(element_count), this_index_offset, 0); diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index f65dedc..492943b 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -13,6 +13,8 @@ #include #include +#include + namespace gal::prometheus::gfx { using point_type = primitive::basic_point_2d; @@ -48,6 +50,13 @@ namespace gal::prometheus::gfx using font_id_type = std::uint32_t; constexpr font_id_type invalid_font_id{std::numeric_limits::max()}; + enum class GlyphFlag : std::uint8_t + { + NONE = 0, + BOLD = 1 << 0, + ITALIC = 1 << 1, + }; + class GlyphKey; class GlyphInfo; @@ -60,6 +69,96 @@ namespace gal::prometheus::gfx // RENDERER LIST // ========================================================= + enum class RenderListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + }; + + enum class RenderFlag : std::uint8_t + { + NONE = 0, + // specify that shape should be closed + // @see RenderList::draw_polygon_line + // @see RenderList::draw_polygon_line_aa + // @see RenderList::path_stroke + CLOSED = 1 << 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + }; + + enum class RenderArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + }; + class RenderListSharedData; class RenderList; @@ -76,3 +175,18 @@ namespace gal::prometheus::gfx class TextureContext; class RenderContext; } // namespace gal::prometheus::gfx + +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; +} // namespace gal::prometheus::meta::user_defined From 056d58bf46c1d55b7ae1515a7119f95920cd8259 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Wed, 14 May 2025 05:19:22 +0800 Subject: [PATCH 38/54] =?UTF-8?q?`fix`:=20Clear=20D3D11=20resource=20leak.?= =?UTF-8?q?=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gfx/renderer_dx11.cpp | 132 +++++++++++++++++++++++--------------- src/gfx/renderer_dx11.hpp | 15 +++-- 2 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/gfx/renderer_dx11.cpp b/src/gfx/renderer_dx11.cpp index 935efc9..59b8bec 100644 --- a/src/gfx/renderer_dx11.cpp +++ b/src/gfx/renderer_dx11.cpp @@ -391,7 +391,7 @@ namespace gal::prometheus::gfx .SysMemSlicePitch = 0 }; - ID3D11Texture2D* texture_2d = nullptr; + ID3D11Texture2D* texture_2d; if (not check_hr_error( device_->CreateTexture2D( &texture_2d_desc, @@ -489,7 +489,7 @@ namespace gal::prometheus::gfx device_immediate_context_ = std::move(device_immediate_context); } - auto Dx11Renderer::create() noexcept -> bool + auto Dx11Renderer::do_create() noexcept -> bool { if (not create_blend_state()) { @@ -515,20 +515,41 @@ namespace gal::prometheus::gfx return true; } - auto Dx11Renderer::destroy() noexcept -> void + auto Dx11Renderer::do_destroy() noexcept -> void { // ComPtr - + blend_state_ = nullptr; + rasterizer_state_ = nullptr; + depth_stencil_state_ = nullptr; + vertex_shader_ = nullptr; + vertex_input_layout_ = nullptr; + vertex_projection_matrix_ = nullptr; + pixel_shader_ = nullptr; + pixel_font_sampler_ = nullptr; + render_buffer_.index = nullptr; + render_buffer_.index_count = 0; + render_buffer_.vertex = nullptr; + render_buffer_.vertex_count = 0; + + // RAW std::ranges::for_each( - textures_ | std::views::values, - [](auto* texture_2d) noexcept -> void + textures_, + [](auto& kv) noexcept -> void { - texture_2d->Release(); + kv.first->Release(); + kv.second->Release(); } ); + textures_.clear(); + + // ComPtr + // device_immediate_context_->ClearState(); + // device_immediate_context_->Flush(); + device_immediate_context_ = nullptr; + device_ = nullptr; } - auto Dx11Renderer::ready() const noexcept -> bool + auto Dx11Renderer::do_ready() const noexcept -> bool { if (device_ == nullptr or device_immediate_context_ == nullptr) { @@ -553,7 +574,57 @@ namespace gal::prometheus::gfx return true; } - auto Dx11Renderer::present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void + auto Dx11Renderer::do_create_texture(const Texture::data_view_type data, const Texture::size_type size) noexcept -> texture_id_type + { + return upload_texture(data, size, D3D11_USAGE_DYNAMIC, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, 0); + } + + auto Dx11Renderer::do_update_texture(const Texture& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.uploaded(), "Create texture first!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty(), "No need to update texture!"); + + auto* srv = id_to_gpu_handle(texture.id()); + const auto it = textures_.find(srv); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != textures_.end(), "Invalid texture id"); + + auto* texture_2d = it->second; + D3D11_MAPPED_SUBRESOURCE mapped_resource{}; + if (const auto result = device_immediate_context_->Map( + texture_2d, + 0, + D3D11_MAP_WRITE_DISCARD, + 0, + &mapped_resource + ); result != S_OK) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + const auto* source = texture.data().data(); + const auto source_length = texture.area_size(); + std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); + + device_immediate_context_->Unmap(texture_2d, 0); + } + + auto Dx11Renderer::do_destroy_texture(const texture_id_type texture_id) noexcept -> void + { + auto* srv = id_to_gpu_handle(texture_id); + + if (const auto it = textures_.find(srv); it != textures_.end()) + { + srv->Release(); + it->first->Release(); + it->second->Release(); + + textures_.erase(it); + } + } + + auto Dx11Renderer::do_present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void { const auto all_render_data = renderer_context.render_data(); // const auto [display_x, display_y] = display_area.point; @@ -763,49 +834,6 @@ namespace gal::prometheus::gfx } ); } - - auto Dx11Renderer::create_texture(const Texture::data_view_type data, const Texture::size_type size) noexcept -> texture_id_type - { - return upload_texture(data, size, D3D11_USAGE_DYNAMIC, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, 0); - } - - auto Dx11Renderer::update_texture(const Texture& texture) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.uploaded(), "Create texture first!"); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty(), "No need to update texture!"); - - auto* srv = id_to_gpu_handle(texture.id()); - const auto it = textures_.find(srv); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != textures_.end(), "Invalid texture id"); - - auto* texture_2d = it->second; - D3D11_MAPPED_SUBRESOURCE mapped_resource{}; - if (const auto result = device_immediate_context_->Map( - texture_2d, - 0, - D3D11_MAP_WRITE_DISCARD, - 0, - &mapped_resource - ); result != S_OK) - { - // todo: error handling - GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); - return; - } - - const auto* source = texture.data().data(); - const auto source_length = texture.area_size(); - std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); - - device_immediate_context_->Unmap(texture_2d, 0); - } - - auto Dx11Renderer::destroy_texture(const texture_id_type texture_id) noexcept -> void - { - auto* srv = id_to_gpu_handle(texture_id); - - textures_.erase(srv); - } } #endif diff --git a/src/gfx/renderer_dx11.hpp b/src/gfx/renderer_dx11.hpp index 19c6f65..37cf27b 100644 --- a/src/gfx/renderer_dx11.hpp +++ b/src/gfx/renderer_dx11.hpp @@ -80,16 +80,17 @@ namespace gal::prometheus::gfx auto bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void; auto bind_device_context(ComPtr device_immediate_context) noexcept -> void; - auto create() noexcept -> bool override; - auto destroy() noexcept -> void override; + private: + auto do_create() noexcept -> bool override; + auto do_destroy() noexcept -> void override; - [[nodiscard]] auto ready() const noexcept -> bool override; + [[nodiscard]] auto do_ready() const noexcept -> bool override; - auto present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void override; + auto do_create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type override; + auto do_update_texture(const Texture& texture) noexcept -> void override; + auto do_destroy_texture(texture_id_type texture_id) noexcept -> void override; - auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type override; - auto update_texture(const Texture& texture) noexcept -> void override; - auto destroy_texture(texture_id_type texture_id) noexcept -> void override; + auto do_present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void override; }; } From b2bd2c228740402f1703d3e68d5647341a0ebf22 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Wed, 14 May 2025 05:24:16 +0800 Subject: [PATCH 39/54] =?UTF-8?q?`fix`:=20*=20TextureContext=20holds=20the?= =?UTF-8?q?=20font=20file=20data=20(once=20held=20by=20the=20GlyphParser)?= =?UTF-8?q?=20*=20Instead=20of=20returning=20GlyphInfo,=20GlyphParser::par?= =?UTF-8?q?se=20returns=20only=20some=20of=20its=20members,=20to=20clarify?= =?UTF-8?q?=20the=20purpose=20of=20the=20interface=20=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 4 + src/gfx/context.cpp | 41 ++++-- src/gfx/context.hpp | 29 ++++- src/gfx/font.cpp | 58 +++------ src/gfx/font.hpp | 201 ++---------------------------- src/gfx/glyph.cpp | 11 ++ src/gfx/glyph.hpp | 150 ++++++++++++++++++++++ src/gfx/glyph_parser_freetype.cpp | 83 +++++------- src/gfx/glyph_parser_freetype.hpp | 4 - src/gfx/renderer.cpp | 40 ++++++ src/gfx/renderer.hpp | 26 ++-- src/gfx/texture.hpp | 6 + src/gfx/type.hpp | 2 - 13 files changed, 349 insertions(+), 306 deletions(-) create mode 100644 src/gfx/glyph.cpp create mode 100644 src/gfx/glyph.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index f25e0cb..1f13016 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -371,6 +371,8 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx/type.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/texture.hpp ${PROJECT_SOURCE_DIR}/src/gfx/font.hpp ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.hpp @@ -438,6 +440,8 @@ set( # GFX # ========================= + ${PROJECT_SOURCE_DIR}/src/gfx/glyph.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/texture.cpp ${PROJECT_SOURCE_DIR}/src/gfx/font.cpp ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.cpp diff --git a/src/gfx/context.cpp b/src/gfx/context.cpp index d72cb64..f57b672 100644 --- a/src/gfx/context.cpp +++ b/src/gfx/context.cpp @@ -51,7 +51,7 @@ namespace gal::prometheus::gfx const auto atlas_id = select_atlas(size); auto& atlas = select_atlas(atlas_id); - const auto texture = atlas.select(size); + auto texture = atlas.select(size); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.valid()); Territory territory{.id = atlas_id, .point = texture.point(), .size = size}; @@ -61,7 +61,8 @@ namespace gal::prometheus::gfx } TextureContext::TextureContext() noexcept - : parser_{nullptr} + : parser_{nullptr}, + font_data_new_add_index_{0} { // root atlas constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; @@ -273,11 +274,11 @@ namespace gal::prometheus::gfx GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); std::ranges::for_each( - font_data_list_, + std::ranges::subrange{font_data_list_.begin() + font_data_new_add_index_, font_data_list_.end()}, [this](FontData& data) noexcept -> void { // note: Transferring ownership of font file data - if (auto result = parser_->load(std::move(data.data), data.size); result.valid()) + if (auto result = parser_->load({data.data.get(), data.size}); result.valid()) { font_faces_.emplace_back(*this, std::move(result.name), result.id); } @@ -288,7 +289,7 @@ namespace gal::prometheus::gfx } } ); - font_data_list_.clear(); + font_data_new_add_index_ = static_cast(font_data_list_.size()); } auto TextureContext::upload_all_font_face() noexcept -> void @@ -296,10 +297,10 @@ namespace gal::prometheus::gfx std::ranges::for_each(font_faces_, &FontFace::upload); } - auto TextureContext::upload_glyph_to_texture(GlyphInfo& info, const GlyphParsedInfo::data_type& data) noexcept -> void + auto TextureContext::upload_parsed_info_to_texture(const GlyphParser::ParseResult& result) noexcept -> parsed_info_upload_result_type { - const auto width = static_cast(info.rect.width()); - const auto height = static_cast(info.rect.height()); + const auto width = static_cast(result.rect.width()); + const auto height = static_cast(result.rect.height()); const auto size = Texture::size_type{width, height}; const auto atlas_id = select_atlas(size); @@ -308,12 +309,13 @@ namespace gal::prometheus::gfx const auto atlas_uv_scale = atlas.uv(); const auto& texture = make_territory(size); - texture.fill({data.get(), static_cast(width) * height}); + texture.fill({result.data.get(), static_cast(width) * height}); const auto texture_point = texture.point(); - info.texture_atlas_id = atlas_id; - info.uv.point = texture_point.to() * atlas_uv_scale; - info.uv.extent = size.to() * atlas_uv_scale; + return { + .texture_atlas_id = atlas_id, + .uv = {texture_point.to() * atlas_uv_scale, size.to() * atlas_uv_scale} + }; } auto TextureContext::upload_all_texture(Renderer& renderer) noexcept -> void @@ -405,6 +407,21 @@ namespace gal::prometheus::gfx // return texture_context_.size_of(text, size, flag); // } + auto RenderContext::load_all_font() noexcept -> void + { + texture_context_.load_all_font(); + } + + auto RenderContext::upload_all_font_face() noexcept -> void + { + texture_context_.upload_all_font_face(); + } + + auto RenderContext::upload_all_texture(Renderer& renderer) noexcept -> void + { + texture_context_.upload_all_texture(renderer); + } + auto RenderContext::render_list_shared_data() const noexcept -> const RenderListSharedData& { return render_list_shared_data_; diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp index 31e946e..b8c536a 100644 --- a/src/gfx/context.hpp +++ b/src/gfx/context.hpp @@ -55,6 +55,7 @@ namespace gal::prometheus::gfx font_faces_type font_faces_; font_data_list_type font_data_list_; + font_data_list_type::difference_type font_data_new_add_index_; /** * @brief Retain at least one texture atlas (root) @@ -170,11 +171,17 @@ namespace gal::prometheus::gfx */ auto upload_all_font_face() noexcept -> void; + struct parsed_info_upload_result_type + { + texture_atlas_id_type texture_atlas_id; + GlyphInfo::uv_type uv; + }; + /** - * @brief Write FontFace uploaded glyph data to texture, also set the @c texture_atlas_id and @c uv coordinates for this glyph data + * @brief Write FontFace uploaded glyph data to texture * @note @c GlyphParsedInfo calls this function to upload glyph data to the texture and set its texture atlas ID and UV coordinates */ - auto upload_glyph_to_texture(GlyphInfo& info, const GlyphParsedInfo::data_type& data) noexcept -> void; + auto upload_parsed_info_to_texture(const GlyphParser::ParseResult& result) noexcept -> parsed_info_upload_result_type; /** * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) @@ -267,6 +274,24 @@ namespace gal::prometheus::gfx // */ // [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; + /** + * @brief Load the fonts previously added by @c add_font to the @c FontFace + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts + */ + auto load_all_font() noexcept -> void; + + /** + * @brief Upload all used glyphs (in the @c FontFace) to the texture (if it is not already uploaded) + * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture + */ + auto upload_all_font_face() noexcept -> void; + + /** + * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) + * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) + */ + auto upload_all_texture(Renderer& renderer) noexcept -> void; + // ==================================================================== // RenderListSharedData // ==================================================================== diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp index 4ce77c8..775ab20 100644 --- a/src/gfx/font.cpp +++ b/src/gfx/font.cpp @@ -5,35 +5,11 @@ #include -#include - #include #include namespace gal::prometheus::gfx { - GlyphParsedInfo::GlyphParsedInfo(GlyphInfo& info, data_type data) noexcept - : info_{info}, - data_{std::move(data)} - { - } - - auto GlyphParsedInfo::upload(TextureContext& texture_context) noexcept -> void - { - auto& info = info_.get(); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id == invalid_texture_atlas_id); - texture_context.upload_glyph_to_texture(info, data_); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.texture_atlas_id != invalid_texture_atlas_id); - } - - GlyphParser::~GlyphParser() noexcept = default; - - auto GlyphParser::parse(const font_id_type id, const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> ParseResult - { - return this->parse(id, {.codepoint = codepoint, .size = size, .flag = flag}); - } - auto FontFace::find_or_parse_glyph(const GlyphKey& key) noexcept -> GlyphInfo* { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_font_id); @@ -55,9 +31,15 @@ namespace gal::prometheus::gfx return nullptr; } - const auto [it, inserted] = glyphs_.emplace(key, result.info); + GlyphInfo info{}; + info.rect = result.rect; + info.advance_x = result.advance_x; + info.visible = result.visible; + info.colored = result.colored; + + const auto [it, inserted] = glyphs_.emplace(key, info); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(inserted); - parsed_infos_upload_queue_.emplace_back(memory::ref(it->second), std::move(result.data)); + parsed_info_queue_.emplace_back(memory::ref(it->second), std::move(result)); return std::addressof(it->second); } @@ -66,14 +48,10 @@ namespace gal::prometheus::gfx : context_{context}, name_{std::move(name)}, id_{id}, - fallback_glyph_{nullptr} - { - } + fallback_glyph_{nullptr} {} FontFace::FontFace(TextureContext& context, const std::string_view name, const font_id_type id) noexcept - : FontFace{context, std::string{name}, id} - { - } + : FontFace{context, std::string{name}, id} {} auto FontFace::name() const noexcept -> std::string_view { @@ -111,13 +89,17 @@ namespace gal::prometheus::gfx auto FontFace::upload() noexcept -> void { std::ranges::for_each( - parsed_infos_upload_queue_, - [&context = context_.get()](GlyphParsedInfo& parsed_info) noexcept -> void - { - parsed_info.upload(context); - } + parsed_info_queue_, + [&context = context_.get()](parsed_info_type& parsed_info) noexcept -> void + { + auto& info = parsed_info.info.get(); + + const auto [texture_atlas_id, uv] = context.upload_parsed_info_to_texture(parsed_info.result); + info.texture_atlas_id = texture_atlas_id; + info.uv = uv; + } ); - parsed_infos_upload_queue_.clear(); + parsed_info_queue_.clear(); } auto FontFace::find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo& diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp index 836b2f9..3b993ae 100644 --- a/src/gfx/font.hpp +++ b/src/gfx/font.hpp @@ -5,203 +5,14 @@ #pragma once -#include -#include - -#include #include +#include +#include #include namespace gal::prometheus::gfx { - /** - * @brief Glyph - */ - class GlyphKey final - { - public: - std::uint32_t codepoint; - std::uint32_t size : 26; - GlyphFlag flag : 6; - - [[nodiscard]] constexpr auto operator==(const GlyphKey& other) const noexcept -> bool - { - return codepoint == other.codepoint and size == other.size and flag == other.flag; - } - - struct hasher - { - [[nodiscard]] auto operator()(const GlyphKey& key) const noexcept -> std::size_t - { - return std::hash{}(key.codepoint) ^ std::hash{}(key.size) ^ std::hash{}(static_cast(key.flag)); - } - }; - }; - - /** - * @brief Information about a glyph - */ - class GlyphInfo final - { - public: - using value_type = extent_type::value_type; - using uv_type = primitive::basic_rect_2d; - - // ============= - // Data filled when loading glyph - // ============= - - // Bitmap infos of this glyph - rect_type rect{-1, -1, -1, -1}; - value_type advance_x{-1}; - bool visible{false}; - bool colored{false}; - - // ============= - // Data filled when writing texture - // ============= - - // The id of the texture atlas where the glyph is located - // This id is present if and only if the glyph is in a texture atlas, otherwise it is invalid_texture_atlas_id - texture_atlas_id_type texture_atlas_id{invalid_texture_atlas_id}; - uv_type uv{-1, -1, -1, -1}; - }; - - /** - * @brief Glyph data parsed by @c GlyphParser::parse, - * which references @c GlyphInfo (to set its @c texture_atlas_id and @c uv) and holds the bitmap data for the glyph (which is automatically released after writing it to the texture atlas) - */ - class GlyphParsedInfo final - { - public: - using info_type = memory::RefWrapper; - using data_type = Texture::data_type; - - private: - info_type info_; - data_type data_; - - public: - /** - * @param info Glyph info - * @param data Glyph bitmap data - * - * @link FontFace::find_or_parse_glyph - * @endlink - */ - GlyphParsedInfo(GlyphInfo& info, data_type data) noexcept; - - /** - * @brief Upload the glyph data to the texture atlas, set its @c texture_atlas_id and @c uv - */ - auto upload(TextureContext& texture_context) noexcept -> void; - }; - - /** - * @brief Parse glyph data from (binary) font data - */ - class GlyphParser - { - public: - class [[nodiscard]] LoadResult final - { - public: - std::string name; - font_id_type id; - - [[nodiscard]] explicit operator bool() const noexcept - { - return id != invalid_font_id; - } - - [[nodiscard]] auto valid() const noexcept -> bool - { - return operator bool(); - } - }; - - class [[nodiscard]] ParseResult final - { - public: - GlyphInfo info; - - // todo: Borrows a memory region from the texture to write to, rather than having it allocated by the parser - GlyphParsedInfo::data_type data; - - [[nodiscard]] explicit operator bool() const noexcept - { - return data != nullptr; - } - - [[nodiscard]] auto valid() const noexcept -> bool - { - return operator bool(); - } - }; - - GlyphParser(const GlyphParser&) noexcept = delete; - GlyphParser(GlyphParser&&) noexcept = default; - auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; - auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; - - virtual ~GlyphParser() noexcept; - - GlyphParser() noexcept = default; - - [[nodiscard]] virtual auto ready() noexcept -> bool = 0; - - /** - * @brief Load font data from file, get all glyph data, return id of font - * @param path Font file path - * @return id of the font, or invalid_font_id if failed to load - */ - [[nodiscard]] virtual auto load(const std::filesystem::path& path) noexcept -> LoadResult = 0; - - /** - * @brief Load font data from @c data, get all glyph data, return id of font - * @param data Font data - * @param size Font data length - * @return id of the font, or invalid_font_id if failed to load - * @note Transfer ownership of the font data, the caller does not need to free memory - */ - [[nodiscard]] virtual auto load(std::unique_ptr data, std::size_t size) noexcept -> LoadResult = 0; - - /** - * @brief Load font data from @c data, get all glyph data, return id of font - * @param data Font data - * @return id of the font, or invalid_font_id if failed to load - * @note Copy font data, caller needs to free memory - */ - [[nodiscard]] virtual auto load(std::span data) noexcept -> LoadResult = 0; - - /** - * @brief Determines whether the target font contains the glyphs of the specified codepoint - * @param id The id returned by loading the font from the previous load - * @param codepoint The codepoint of the glyph to be checked - * @return Exists or not - */ - [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; - - /** - * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer - * @param id The id returned by loading the font from the previous load - * @param key {codepoint, size, flag} - * @return The glyph information of the specified size (and style) of the target codepoint - */ - [[nodiscard]] virtual auto parse(font_id_type id, const GlyphKey& key) noexcept -> ParseResult = 0; - - /** - * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer - * @param id The id returned by loading the font from the previous load - * @param codepoint The codepoint of the glyph - * @param size The size of the glyph - * @param flag The style of the glyph (bold, italic, etc.) - * @return The glyph information of the specified size (and style) of the target codepoint - */ - [[nodiscard]] auto parse(font_id_type id, std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> ParseResult; - }; - /** * @brief All the glyph data used in a font */ @@ -217,7 +28,13 @@ namespace gal::prometheus::gfx std::string name_; font_id_type id_; - std::vector parsed_infos_upload_queue_; + struct parsed_info_type + { + memory::RefWrapper info; + GlyphParser::ParseResult result; + }; + + std::vector parsed_info_queue_; std::unordered_map glyphs_; const GlyphInfo* fallback_glyph_; diff --git a/src/gfx/glyph.cpp b/src/gfx/glyph.cpp new file mode 100644 index 0000000..31d93d3 --- /dev/null +++ b/src/gfx/glyph.cpp @@ -0,0 +1,11 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace gal::prometheus::gfx +{ + GlyphParser::~GlyphParser() noexcept = default; +} diff --git a/src/gfx/glyph.hpp b/src/gfx/glyph.hpp new file mode 100644 index 0000000..3fc89e9 --- /dev/null +++ b/src/gfx/glyph.hpp @@ -0,0 +1,150 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +namespace gal::prometheus::gfx +{ + /** + * @brief Glyph key + */ + class GlyphKey final + { + public: + std::uint32_t codepoint; + std::uint32_t size : 26; + GlyphFlag flag : 6; + + [[nodiscard]] constexpr auto operator==(const GlyphKey& other) const noexcept -> bool + { + return codepoint == other.codepoint and size == other.size and flag == other.flag; + } + + struct hasher + { + [[nodiscard]] auto operator()(const GlyphKey& key) const noexcept -> std::size_t + { + return std::hash{}(key.codepoint) ^ std::hash{}(key.size) ^ std::hash{}(static_cast(key.flag)); + } + }; + }; + + /** + * @brief Glyph info (bitmap info & texture info) + */ + class GlyphInfo final + { + public: + using value_type = extent_type::value_type; + using uv_type = primitive::basic_rect_2d; + + // ============= + // Data filled when loading glyph + // ============= + + // Bitmap infos of this glyph + rect_type rect{-1, -1, -1, -1}; + value_type advance_x{-1}; + bool visible{false}; + bool colored{false}; + + // ============= + // Data filled when writing texture + // ============= + + // The id of the texture atlas where the glyph is located + // This id is present if and only if the glyph is in a texture atlas, otherwise it is invalid_texture_atlas_id + texture_atlas_id_type texture_atlas_id{invalid_texture_atlas_id}; + // The uv coordinate of the glyph in the texture atlas + uv_type uv{-1, -1, -1, -1}; + }; + + class GlyphParser + { + public: + class [[nodiscard]] LoadResult final + { + public: + std::string name; + font_id_type id; + + [[nodiscard]] explicit operator bool() const noexcept + { + return id != invalid_font_id; + } + + [[nodiscard]] auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + class [[nodiscard]] ParseResult final + { + public: + // We could have just returned a GlyphInfo, + // but to make the purpose of the interface clearer, + // we specify that we only return part of the GlyphInfo (see GlyphInfo->'Data filled when loading glyph') + // GlyphInfo info; + + // Bitmap infos of this glyph + rect_type rect{-1, -1, -1, -1}; + GlyphInfo::value_type advance_x{-1}; + bool visible{false}; + bool colored{false}; + + // todo: Borrows a memory region from the texture to write to, rather than having it allocated by the parser + Texture::data_type data{nullptr}; + + [[nodiscard]] explicit operator bool() const noexcept + { + return data != nullptr; + } + + [[nodiscard]] auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + GlyphParser(const GlyphParser&) noexcept = delete; + GlyphParser(GlyphParser&&) noexcept = default; + auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; + auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; + + virtual ~GlyphParser() noexcept; + + GlyphParser() noexcept = default; + + [[nodiscard]] virtual auto ready() noexcept -> bool = 0; + + /** + * @brief Load font data from @c data, get all glyph data, return id of font + * @param data Font data + * @return id of the font, or invalid_font_id if failed to load + * @note Font data *should not* be released unless the target font is no longer needed (to call @c parse) + */ + [[nodiscard]] virtual auto load(std::span data) noexcept -> LoadResult = 0; + + /** + * @brief Determines whether the target font contains the glyphs of the specified codepoint + * @param id The id returned by loading the font from the previous load + * @param codepoint The codepoint of the glyph to be checked + * @return Exists or not + */ + [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; + + /** + * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer + * @param id The id returned by loading the font from the previous load + * @param key {codepoint, size, flag} + * @return The glyph information of the specified size (and style) of the target codepoint + */ + [[nodiscard]] virtual auto parse(font_id_type id, const GlyphKey& key) noexcept -> ParseResult = 0; + }; +} diff --git a/src/gfx/glyph_parser_freetype.cpp b/src/gfx/glyph_parser_freetype.cpp index 3cdcd71..1a4d56a 100644 --- a/src/gfx/glyph_parser_freetype.cpp +++ b/src/gfx/glyph_parser_freetype.cpp @@ -29,7 +29,6 @@ namespace gal::prometheus::gfx class FreeTypeGlyphParser::FontInfo final { public: - std::unique_ptr font_data{nullptr}; FT_Face face{nullptr}; float ascender{0}; @@ -87,41 +86,41 @@ namespace gal::prometheus::gfx return library_ != nullptr; } - auto FreeTypeGlyphParser::load(const std::filesystem::path& path) noexcept -> LoadResult - { - const auto path_string = path.string(); - - LoadResult invalid_result{.name = {}, .id = invalid_font_id}; - - FT_Face face = nullptr; - if (const auto error = FT_New_Face(library_->library, path_string.data(), 0, &face); error != FT_Err_Ok) - { - return invalid_result; - } + // auto FreeTypeGlyphParser::load(const std::filesystem::path& path) noexcept -> LoadResult + // { + // const auto path_string = path.string(); + // + // LoadResult invalid_result{.name = {}, .id = invalid_font_id}; + // + // FT_Face face = nullptr; + // if (const auto error = FT_New_Face(library_->library, path_string.data(), 0, &face); error != FT_Err_Ok) + // { + // return invalid_result; + // } + // + // if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) + // { + // FT_Done_Face(face); + // return invalid_result; + // } + // + // const auto id = infos_.size(); + // + // auto& info = infos_.emplace_back(); + // info.face = face; + // + // return {.name = face->family_name, .id = static_cast(id)}; + // } - if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) - { - FT_Done_Face(face); - return invalid_result; - } - - const auto id = infos_.size(); - - auto& info = infos_.emplace_back(); - info.face = face; - - return {.name = face->family_name, .id = static_cast(id)}; - } - - auto FreeTypeGlyphParser::load(std::unique_ptr data, const std::size_t size) noexcept -> LoadResult + auto FreeTypeGlyphParser::load(const std::span data) noexcept -> LoadResult { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.get() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size != 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not data.empty()); LoadResult invalid_result{.name = {}, .id = invalid_font_id}; FT_Face face = nullptr; - if (const auto error = FT_New_Memory_Face(library_->library, data.get(), static_cast(size), 0, &face); error != FT_Err_Ok) + if (const auto error = FT_New_Memory_Face(library_->library, data.data(), static_cast(data.size()), 0, &face); error != FT_Err_Ok) { return invalid_result; } @@ -135,20 +134,11 @@ namespace gal::prometheus::gfx const auto id = infos_.size(); auto& info = infos_.emplace_back(); - info.font_data = std::move(data); info.face = face; return {.name = face->family_name, .id = static_cast(id)}; } - auto FreeTypeGlyphParser::load(const std::span data) noexcept -> LoadResult - { - auto* copy = new std::uint8_t[data.size()]; - std::ranges::copy(data, copy); - - return this->load(std::unique_ptr{copy}, data.size()); - } - auto FreeTypeGlyphParser::has_glyph(const font_id_type id, const std::uint32_t codepoint) const noexcept -> bool { if (id >= infos_.size()) @@ -171,7 +161,7 @@ namespace gal::prometheus::gfx GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(key.codepoint != 0); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(key.size != 0); - ParseResult invalid_result{.info = {}, .data = nullptr}; + ParseResult invalid_result{}; auto& info = infos_[id]; const auto& face = info.face; @@ -212,15 +202,10 @@ namespace gal::prometheus::gfx const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; ParseResult result{ - .info = - { - .rect = {point, size}, - .advance_x = ft_size_to_float(slot->advance.x), - .visible = size.width > 0 and size.height > 0, - .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, - .texture_atlas_id = invalid_texture_atlas_id, - .uv = {}, - }, + .rect = {point, size}, + .advance_x = ft_size_to_float(slot->advance.x), + .visible = size.width > 0 and size.height > 0, + .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, .data = std::make_unique_for_overwrite(data_length) }; diff --git a/src/gfx/glyph_parser_freetype.hpp b/src/gfx/glyph_parser_freetype.hpp index c0b9185..41a3739 100644 --- a/src/gfx/glyph_parser_freetype.hpp +++ b/src/gfx/glyph_parser_freetype.hpp @@ -41,10 +41,6 @@ namespace gal::prometheus::gfx auto ready() noexcept -> bool override; - auto load(const std::filesystem::path& path) noexcept -> LoadResult override; - - auto load(std::unique_ptr data, std::size_t size) noexcept -> LoadResult override; - auto load(std::span data) noexcept -> LoadResult override; [[nodiscard]] auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool override; diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index e1d00b2..1b413b2 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -4,8 +4,48 @@ // found in the top-level directory of this distribution. #include +#include namespace gal::prometheus::gfx { Renderer::~Renderer() noexcept = default; + + auto Renderer::create() noexcept -> bool + { + return do_create(); + } + + auto Renderer::destroy() noexcept -> void + { + return do_destroy(); + } + + auto Renderer::ready() const noexcept -> bool + { + return do_ready(); + } + + auto Renderer::create_texture(const Texture::data_view_type data, const Texture::size_type size) noexcept -> texture_id_type + { + return do_create_texture(data, size); + } + + auto Renderer::update_texture(const Texture& texture) noexcept -> void + { + do_update_texture(texture); + } + + auto Renderer::destroy_texture(const texture_id_type texture_id) noexcept -> void + { + do_destroy_texture(texture_id); + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + auto Renderer::present(RenderContext& renderer_context, const rect_type& display_area) noexcept -> void + { + // todo: We can explicitly upload all glyphs to texture here, so that we don't need to call TextureContext::upload_all_font_face in RenderContext::end_frame + // renderer_context.upload_all_font_face(); + + do_present(renderer_context, display_area); + } } diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index e3a7b86..489cb35 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -23,15 +23,27 @@ namespace gal::prometheus::gfx Renderer() noexcept = default; public: - [[nodiscard]] virtual auto create() noexcept -> bool = 0; - virtual auto destroy() noexcept -> void = 0; + auto create() noexcept -> bool; + auto destroy() noexcept -> void; - [[nodiscard]] virtual auto ready() const noexcept -> bool = 0; + [[nodiscard]] auto ready() const noexcept -> bool; - virtual auto present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void = 0; + auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type; + auto update_texture(const Texture& texture) noexcept -> void; + auto destroy_texture(texture_id_type texture_id) noexcept -> void; - virtual auto create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type = 0; - virtual auto update_texture(const Texture& texture) noexcept -> void = 0; - virtual auto destroy_texture(texture_id_type texture_id) noexcept -> void = 0; + auto present(RenderContext& renderer_context, const rect_type& display_area) noexcept -> void; + + private: + [[nodiscard]] virtual auto do_create() noexcept -> bool = 0; + virtual auto do_destroy() noexcept -> void = 0; + + [[nodiscard]] virtual auto do_ready() const noexcept -> bool = 0; + + virtual auto do_create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type = 0; + virtual auto do_update_texture(const Texture& texture) noexcept -> void = 0; + virtual auto do_destroy_texture(texture_id_type texture_id) noexcept -> void = 0; + + virtual auto do_present(const RenderContext& render_context, const rect_type& display_area) noexcept -> void = 0; }; } // namespace gal::prometheus::gfx diff --git a/src/gfx/texture.hpp b/src/gfx/texture.hpp index 0b03134..da6e762 100644 --- a/src/gfx/texture.hpp +++ b/src/gfx/texture.hpp @@ -133,6 +133,12 @@ namespace gal::prometheus::gfx data_type data_; public: + BorrowTexture(const BorrowTexture&) noexcept = delete; + BorrowTexture(BorrowTexture&&) noexcept = default; + auto operator=(const BorrowTexture&) noexcept -> BorrowTexture& = delete; + auto operator=(BorrowTexture&&) noexcept -> BorrowTexture& = default; + ~BorrowTexture() noexcept = default; + BorrowTexture(point_type point, data_type data) noexcept; [[nodiscard]] auto valid() const noexcept -> bool; diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index 492943b..4bb4f0a 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -59,8 +59,6 @@ namespace gal::prometheus::gfx class GlyphKey; class GlyphInfo; - - class GlyphParsedInfo; class GlyphParser; class FontFace; From 87407f6598d7a6ae4872575a098c22499ca9c467 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 19 May 2025 17:11:06 +0800 Subject: [PATCH 40/54] =?UTF-8?q?`feat`:=20`TextureContext`=20is=20more=20?= =?UTF-8?q?focused=20on=20textures,=20font-related=20content=20is=20manage?= =?UTF-8?q?d=20by=20`Fonts`,=20and=20`TextureContext`=20only=20proxies=20i?= =?UTF-8?q?ts=20interface.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `todo`: * To keep the internal implementation invisible to the outside world, the current call chain is a bit too cumbersome(RenderContext->TextureContext->Fonts). * `Fonts` obviously not a good name :( --- src/gfx/context.cpp | 209 +++++++++++++++++----------------- src/gfx/context.hpp | 121 ++++++++++++-------- src/gfx/font.cpp | 265 +++++++++++++++++++++++++++++++++++--------- src/gfx/font.hpp | 188 ++++++++++++++++++++++++------- src/gfx/type.hpp | 5 +- 5 files changed, 547 insertions(+), 241 deletions(-) diff --git a/src/gfx/context.cpp b/src/gfx/context.cpp index f57b672..c79af73 100644 --- a/src/gfx/context.cpp +++ b/src/gfx/context.cpp @@ -5,8 +5,6 @@ #include -#include - #include // #include @@ -31,6 +29,13 @@ namespace gal::prometheus::gfx return texture_atlases_[id]; } + auto TextureContext::select_atlas(const texture_atlas_id_type id) const noexcept -> const Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < texture_atlases_.size()); + + return texture_atlases_[id]; + } + auto TextureContext::select_atlas(const Texture::size_type size) const noexcept -> texture_atlas_id_type { // width/height == 0 ==> whitespace @@ -61,8 +66,6 @@ namespace gal::prometheus::gfx } TextureContext::TextureContext() noexcept - : parser_{nullptr}, - font_data_new_add_index_{0} { // root atlas constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; @@ -128,88 +131,65 @@ namespace gal::prometheus::gfx } } - // ======================================== - // FontFace (load fallback glyph) - // ======================================== - std::ranges::for_each( - font_faces_, - [](auto& font_face) noexcept -> void - { - font_face.initialize(); - } - ); + // // ======================================== + // // set fallback glyph + // // ======================================== + // fonts_.set_fallback_glyph(); } - auto TextureContext::bind_parser(GlyphParser& parser) noexcept -> void + auto TextureContext::root_texture() const noexcept -> texture_id_type { - parser_ = std::addressof(parser); + return root_atlas().id(); } - auto TextureContext::parser() const noexcept -> GlyphParser& + auto TextureContext::atlas_of(const GlyphInfo& info) const noexcept -> const Texture& + { + return select_atlas(info.texture_atlas_id); + } + + auto TextureContext::bind_parser(GlyphParser& parser) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); - return *parser_; + fonts_.bind_parser(parser); } auto TextureContext::add_font(const std::filesystem::path& path) noexcept -> bool { - std::ifstream file{path, std::ios::binary}; - if (not file.is_open()) - { - // todo: error handling - return false; - } + return fonts_.add_font(path); + } - file.seekg(0, std::ios::end); - const auto size = file.tellg(); + auto TextureContext::load_all_font() noexcept -> void + { + fonts_.load_all_font(); + } - auto* data = new FontData::element_type[size]; - file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(data), size); - file.close(); + auto TextureContext::set_fallback_glyph() noexcept -> void + { + fonts_.set_fallback_glyph(); + } - font_data_list_.emplace_back(std::unique_ptr{data}, static_cast(size)); - return true; + auto TextureContext::set_fallback_glyph(const GlyphKey& key) noexcept -> void + { + fonts_.set_fallback_glyph(key); } - auto TextureContext::root_texture() const noexcept -> texture_id_type + auto TextureContext::set_fallback_glyph(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> void { - return root_atlas().id(); + fonts_.set_fallback_glyph(codepoint, size, flag); } - auto TextureContext::atlas_of(const GlyphInfo& info) noexcept -> const Texture& + auto TextureContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* { - return select_atlas(info.texture_atlas_id); + return fonts_.glyph_of(key); } auto TextureContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* { - for (auto& face: font_faces_) - { - if (const auto* info = face.find_glyph_no_fallback({.codepoint = codepoint, .size = size, .flag = flag}); info != nullptr) - { - return info; - } - } - - return nullptr; + return fonts_.glyph_of(codepoint, size, flag); } - auto TextureContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector + auto TextureContext::glyph_of(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; + return fonts_.glyph_of(text, size, flag); } // auto TextureContext::glyph_of(const std::string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> std::vector @@ -219,9 +199,24 @@ namespace gal::prometheus::gfx // return this->glyph_of(utf32_text, size, flag); // } + auto TextureContext::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo& + { + return fonts_.glyph_of_or_fallback(key); + } + + auto TextureContext::glyph_of_or_fallback(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) const noexcept -> const GlyphInfo& + { + return fonts_.glyph_of_or_fallback(codepoint, size, flag); + } + + auto TextureContext::glyph_of_or_fallback(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) const noexcept -> std::vector> + { + return fonts_.glyph_of_or_fallback(text, size, flag); + } + // auto TextureContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type // { - // const auto* info = glyph_of(codepoint, size, flag); + // const auto* info = this->glyph_of(codepoint, size, flag); // // if (info == nullptr) // { @@ -230,10 +225,10 @@ namespace gal::prometheus::gfx // // return {info->advance_x, info->rect.height()}; // } - // + // auto TextureContext::size_of(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type // { - // const auto infos = glyph_of(text, size, flag); + // const auto infos = this->glyph_of(text, size, flag); // // return std::ranges::fold_left( // infos, @@ -269,32 +264,9 @@ namespace gal::prometheus::gfx // ); // } - auto TextureContext::load_all_font() noexcept -> void + auto TextureContext::load_all_glyph() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); - - std::ranges::for_each( - std::ranges::subrange{font_data_list_.begin() + font_data_new_add_index_, font_data_list_.end()}, - [this](FontData& data) noexcept -> void - { - // note: Transferring ownership of font file data - if (auto result = parser_->load({data.data.get(), data.size}); result.valid()) - { - font_faces_.emplace_back(*this, std::move(result.name), result.id); - } - else - { - // todo: error handling - GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); - } - } - ); - font_data_new_add_index_ = static_cast(font_data_list_.size()); - } - - auto TextureContext::upload_all_font_face() noexcept -> void - { - std::ranges::for_each(font_faces_, &FontFace::upload); + fonts_.load_all_glyph(*this); } auto TextureContext::upload_parsed_info_to_texture(const GlyphParser::ParseResult& result) noexcept -> parsed_info_upload_result_type @@ -350,11 +322,21 @@ namespace gal::prometheus::gfx texture_context_.load_all_font(); texture_context_.upload_all_texture(renderer); } - + auto RenderContext::end_frame(Renderer& renderer) noexcept -> void { std::ignore = renderer; - texture_context_.upload_all_font_face(); + texture_context_.load_all_glyph(); + } + + auto RenderContext::root_texture() const noexcept -> texture_id_type + { + return texture_context_.root_texture(); + } + + auto RenderContext::atlas_of(const GlyphInfo& info) const noexcept -> const Texture& + { + return texture_context_.atlas_of(info); } auto RenderContext::bind_parser(GlyphParser& parser) noexcept -> void @@ -367,14 +349,29 @@ namespace gal::prometheus::gfx return texture_context_.add_font(path); } - auto RenderContext::root_texture() const noexcept -> texture_id_type + auto RenderContext::load_all_font() noexcept -> void { - return texture_context_.root_texture(); + texture_context_.load_all_font(); } - auto RenderContext::atlas_of(const GlyphInfo& info) noexcept -> const Texture& + auto RenderContext::set_fallback_glyph() noexcept -> void { - return texture_context_.atlas_of(info); + texture_context_.set_fallback_glyph(); + } + + auto RenderContext::set_fallback_glyph(const GlyphKey& key) noexcept -> void + { + texture_context_.set_fallback_glyph(key); + } + + auto RenderContext::set_fallback_glyph(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> void + { + texture_context_.set_fallback_glyph(codepoint, size, flag); + } + + auto RenderContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* + { + return texture_context_.glyph_of(key); } auto RenderContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* @@ -392,6 +389,21 @@ namespace gal::prometheus::gfx // return texture_context_.glyph_of(text, size, flag); // } + auto RenderContext::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo& + { + return texture_context_.glyph_of_or_fallback(key); + } + + auto RenderContext::glyph_of_or_fallback(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) const noexcept -> const GlyphInfo& + { + return texture_context_.glyph_of_or_fallback(codepoint, size, flag); + } + + auto RenderContext::glyph_of_or_fallback(const std::u32string_view text, const std::uint32_t size, const GlyphFlag flag) const noexcept -> std::vector> + { + return texture_context_.glyph_of_or_fallback(text, size, flag); + } + // auto RenderContext::size_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> extent_type // { // return texture_context_.size_of(codepoint, size, flag); @@ -407,14 +419,9 @@ namespace gal::prometheus::gfx // return texture_context_.size_of(text, size, flag); // } - auto RenderContext::load_all_font() noexcept -> void - { - texture_context_.load_all_font(); - } - - auto RenderContext::upload_all_font_face() noexcept -> void + auto RenderContext::load_all_glyph() noexcept -> void { - texture_context_.upload_all_font_face(); + texture_context_.load_all_glyph(); } auto RenderContext::upload_all_texture(Renderer& renderer) noexcept -> void diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp index b8c536a..1ce5e35 100644 --- a/src/gfx/context.hpp +++ b/src/gfx/context.hpp @@ -18,7 +18,6 @@ namespace gal::prometheus::gfx { public: using texture_atlases_type = std::vector; - using font_faces_type = std::vector; private: class Territory final @@ -35,27 +34,10 @@ namespace gal::prometheus::gfx using territories_type = std::vector; - class FontData final - { - public: - using element_type = std::uint8_t; - using data_type = std::unique_ptr; - using size_type = std::uint32_t; - - data_type data; - size_type size; - }; - - using font_data_list_type = std::vector; - - GlyphParser* parser_; - texture_atlases_type texture_atlases_; territories_type territories_; - font_faces_type font_faces_; - font_data_list_type font_data_list_; - font_data_list_type::difference_type font_data_new_add_index_; + Fonts fonts_; /** * @brief Retain at least one texture atlas (root) @@ -74,6 +56,13 @@ namespace gal::prometheus::gfx */ [[nodiscard]] auto select_atlas(texture_atlas_id_type id) noexcept -> Texture&; + /** + * @brief Get the texture atlas for the specified id + * @param id Texture atlas id + * @return Texture atlas + */ + [[nodiscard]] auto select_atlas(texture_atlas_id_type id) const noexcept -> const Texture&; + /** * @brief Gets a texture atlas large enough to hold the specified @c size sub texture * @param size sub texture size @@ -103,35 +92,56 @@ namespace gal::prometheus::gfx auto initialize(RenderListSharedData& shared_data) noexcept -> void; /** - * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts + * @brief Get root (default) texture */ - auto bind_parser(GlyphParser& parser) noexcept -> void; + [[nodiscard]] auto root_texture() const noexcept -> texture_id_type; + + /** + * @brief Get texture atlas for glyph information + */ + [[nodiscard]] auto atlas_of(const GlyphInfo& info) const noexcept -> const Texture&; /** - * @brief Get the current parser, usually used by FontFace to load glyph data + * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts */ - [[nodiscard]] auto parser() const noexcept -> GlyphParser&; + auto bind_parser(GlyphParser& parser) noexcept -> void; /** * @brief Load fonts from the specified path, assuming the path is a valid font file * @param path Font path + * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false */ auto add_font(const std::filesystem::path& path) noexcept -> bool; /** - * @brief Get root (default) texture + * @brief Load the fonts previously added by @c add_font + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts */ - [[nodiscard]] auto root_texture() const noexcept -> texture_id_type; + auto load_all_font() noexcept -> void; + + auto set_fallback_glyph() noexcept -> void; /** - * @brief Get texture atlas for glyph information + * @brief Set the fallback glyph, if we can't find the glyph of the specified codepoint, then use the fallback glyph + * @param key {codepoint, size, flag} + */ + auto set_fallback_glyph(const GlyphKey& key) noexcept -> void; + + /** + * @brief Set the fallback glyph, if we can't find the glyph of the specified codepoint, then use the fallback glyph */ - [[nodiscard]] auto atlas_of(const GlyphInfo& info) noexcept -> const Texture&; + auto set_fallback_glyph(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> void; + [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; // [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) const noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) const noexcept -> std::vector>; + // [[nodiscard]] auto glyph_of_or_fallback(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) const noexcept -> std::vector>; + // /** // * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety // * @param codepoint @@ -160,16 +170,10 @@ namespace gal::prometheus::gfx // [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; /** - * @brief Load the fonts previously added by @c add_font to the @c FontFace - * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts - */ - auto load_all_font() noexcept -> void; - - /** - * @brief Upload all used glyphs (in the @c FontFace) to the texture (if it is not already uploaded) + * @brief Upload all used glyphs to the texture (if it is not already uploaded) * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture */ - auto upload_all_font_face() noexcept -> void; + auto load_all_glyph() noexcept -> void; struct parsed_info_upload_result_type { @@ -178,8 +182,8 @@ namespace gal::prometheus::gfx }; /** - * @brief Write FontFace uploaded glyph data to texture - * @note @c GlyphParsedInfo calls this function to upload glyph data to the texture and set its texture atlas ID and UV coordinates + * @brief Write Font uploaded glyph data to texture + * @note @c FontGlyphQueue calls this function to upload glyph data to the texture and set its texture atlas ID and UV coordinates */ auto upload_parsed_info_to_texture(const GlyphParser::ParseResult& result) noexcept -> parsed_info_upload_result_type; @@ -222,6 +226,16 @@ namespace gal::prometheus::gfx // TextureContext // ==================================================================== + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root_texture() const noexcept -> texture_id_type; + + /** + * @brief Get texture atlas for glyph information + */ + [[nodiscard]] auto atlas_of(const GlyphInfo& info) const noexcept -> const Texture&; + /** * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts */ @@ -234,19 +248,34 @@ namespace gal::prometheus::gfx auto add_font(const std::filesystem::path& path) noexcept -> bool; /** - * @brief Get root (default) texture + * @brief Load the fonts previously added by @c add_font + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts */ - [[nodiscard]] auto root_texture() const noexcept -> texture_id_type; + auto load_all_font() noexcept -> void; + + auto set_fallback_glyph() noexcept -> void; /** - * @brief Get texture atlas for glyph information + * @brief Set the fallback glyph, if we can't find the glyph of the specified codepoint, then use the fallback glyph + * @param key {codepoint, size, flag} */ - [[nodiscard]] auto atlas_of(const GlyphInfo& info) noexcept -> const Texture&; + auto set_fallback_glyph(const GlyphKey& key) noexcept -> void; + /** + * @brief Set the fallback glyph, if we can't find the glyph of the specified codepoint, then use the fallback glyph + */ + auto set_fallback_glyph(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> void; + + [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> const GlyphInfo*; [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; // [[nodiscard]] auto glyph_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> std::vector; + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) const noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) const noexcept -> std::vector>; + // [[nodiscard]] auto glyph_of_or_fallback(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) const noexcept -> std::vector>; + // /** // * @brief The minimum space to be occupied if the specified codepoint is to be rendered in its entirety // * @param codepoint @@ -275,16 +304,10 @@ namespace gal::prometheus::gfx // [[nodiscard]] auto size_of(std::string_view text, std::uint32_t size, GlyphFlag flag = GlyphFlag::NONE) noexcept -> extent_type; /** - * @brief Load the fonts previously added by @c add_font to the @c FontFace - * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts - */ - auto load_all_font() noexcept -> void; - - /** - * @brief Upload all used glyphs (in the @c FontFace) to the texture (if it is not already uploaded) + * @brief Upload all used glyphs to the texture (if it is not already uploaded) * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture */ - auto upload_all_font_face() noexcept -> void; + auto load_all_glyph() noexcept -> void; /** * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp index 775ab20..2362288 100644 --- a/src/gfx/font.cpp +++ b/src/gfx/font.cpp @@ -5,12 +5,51 @@ #include -#include +#include + #include namespace gal::prometheus::gfx { - auto FontFace::find_or_parse_glyph(const GlyphKey& key) noexcept -> GlyphInfo* + auto FontGlyphQueue::push(GlyphInfo& info, GlyphParser::ParseResult&& result) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.valid()); + + list_.emplace_back(memory::ref(info), std::move(result)); + } + + auto FontGlyphQueue::upload(TextureContext& context) noexcept -> void + { + std::ranges::for_each( + list_, + [&context](element_type& element) noexcept -> void + { + auto& info = element.info.get(); + const auto& result = element.result; + + const auto [texture_atlas_id, uv] = context.upload_parsed_info_to_texture(result); + info.texture_atlas_id = texture_atlas_id; + info.uv = uv; + } + ); + list_.clear(); + } + + Font::Font(name_type name, const font_id_type id) noexcept + : name_{std::move(name)}, + id_{id} {} + + auto Font::name() const noexcept -> std::string_view + { + return name_; + } + + auto Font::id() const noexcept -> font_id_type + { + return id_; + } + + auto Font::get_glyph(const GlyphKey& key) const noexcept -> const GlyphInfo* { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_font_id); @@ -19,17 +58,13 @@ namespace gal::prometheus::gfx return std::addressof(it->second); } - auto& parser = context_.get().parser(); - // if (not parser.has_glyph(id_, key.codepoint)) - // { - // return nullptr; - // } + return nullptr; + } - auto result = parser.parse(id_, key); - if (not result.valid()) - { - return nullptr; - } + auto Font::set_glyph(const GlyphKey& key, const GlyphParser::ParseResult& result) noexcept -> GlyphInfo& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_font_id); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.valid()); GlyphInfo info{}; info.rect = result.rect; @@ -37,89 +72,213 @@ namespace gal::prometheus::gfx info.visible = result.visible; info.colored = result.colored; - const auto [it, inserted] = glyphs_.emplace(key, info); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(inserted); - parsed_info_queue_.emplace_back(memory::ref(it->second), std::move(result)); + return glyphs_.insert_or_assign(key, info).first->second; + } + + FontLoadQueue::FontLoadQueue() noexcept + : new_font_index_{0} {} + + auto FontLoadQueue::push(const std::filesystem::path& path) noexcept -> bool + { + std::ifstream file{path, std::ios::binary}; + if (not file.is_open()) + { + // todo: error handling + return false; + } + + file.seekg(0, std::ios::end); + const auto size = file.tellg(); + + auto* data = new font_data_type::element_type[size]; + file.seekg(0, std::ios::beg); + file.read(reinterpret_cast(data), size); + file.close(); - return std::addressof(it->second); + list_.emplace_back(std::unique_ptr{data}, static_cast(size)); + return true; } - FontFace::FontFace(TextureContext& context, std::string name, const font_id_type id) noexcept - : context_{context}, - name_{std::move(name)}, - id_{id}, - fallback_glyph_{nullptr} {} + auto FontLoadQueue::upload(GlyphParser& parser, const functional::function_reference_wrapper font_dest) noexcept -> void + { + std::ranges::for_each( + std::ranges::subrange{list_.begin() + new_font_index_, list_.end()}, + [&](font_data_type& data) noexcept -> void + { + if (auto result = parser.load({data.data.get(), data.size}); result.valid()) + { + font_dest(Font{std::move(result.name), result.id}); + } + else + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + } + } + ); + new_font_index_ = static_cast(list_.size()); + } - FontFace::FontFace(TextureContext& context, const std::string_view name, const font_id_type id) noexcept - : FontFace{context, std::string{name}, id} {} + Fonts::Fonts() noexcept + : fallback_glyph_{nullptr}, + parser_{nullptr} {} - auto FontFace::name() const noexcept -> std::string_view + auto Fonts::bind_parser(GlyphParser& parser) noexcept -> GlyphParser* { - return name_; + return std::exchange(parser_, std::addressof(parser)); } - auto FontFace::id() const noexcept -> font_id_type + auto Fonts::add_font(const std::filesystem::path& path) noexcept -> bool { - return id_; + return font_load_queue_.push(path); } - auto FontFace::initialize() noexcept -> void + auto Fonts::load_all_font() noexcept -> void { - GlyphKey key{.codepoint = u'\xFFFD', .size = 16u, .flag = GlyphFlag::NONE}; - fallback_glyph_ = find_glyph_no_fallback(key); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + + const auto loader = [this](Font&& font) noexcept -> void + { + font_list_.emplace_back(std::move(font)); + }; + + font_load_queue_.upload(*parser_, loader); + } + + auto Fonts::set_fallback_glyph() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not font_list_.empty()); if (fallback_glyph_ == nullptr) { - key.codepoint = u'?'; - fallback_glyph_ = find_glyph_no_fallback(key); + constexpr GlyphKey key{.codepoint = u'\xFFFD', .size = 16u, .flag = GlyphFlag::NONE}; + set_fallback_glyph(key); } + if (fallback_glyph_ == nullptr) { - key.codepoint = u' '; - fallback_glyph_ = find_glyph_no_fallback(key); + constexpr GlyphKey key{.codepoint = u'?', .size = 16u, .flag = GlyphFlag::NONE}; + set_fallback_glyph(key); } if (fallback_glyph_ == nullptr) { - // todo + constexpr GlyphKey key{.codepoint = u' ', .size = 16u, .flag = GlyphFlag::NONE}; + set_fallback_glyph(key); + } + + if (fallback_glyph_ == nullptr) + { + // todo: error handling GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); } } - auto FontFace::upload() noexcept -> void + auto Fonts::set_fallback_glyph(const GlyphKey& key) noexcept -> void { - std::ranges::for_each( - parsed_info_queue_, - [&context = context_.get()](parsed_info_type& parsed_info) noexcept -> void + fallback_glyph_ = this->glyph_of(key); + } + + auto Fonts::set_fallback_glyph(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> void + { + fallback_glyph_ = this->glyph_of(codepoint, size, flag); + } + + auto Fonts::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* + { + for (const auto& font: font_list_) + { + if (const auto* info = font.get_glyph(key); info != nullptr) { - auto& info = parsed_info.info.get(); + return info; + } + } - const auto [texture_atlas_id, uv] = context.upload_parsed_info_to_texture(parsed_info.result); - info.texture_atlas_id = texture_atlas_id; - info.uv = uv; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + + for (auto& font: font_list_) + { + if (auto result = parser_->parse(font.id(), key); result.valid()) + { + auto& queue = font_glyph_queue_[std::addressof(font)]; + auto& inserted_info = font.set_glyph(key, result); + + queue.push(inserted_info, std::move(result)); + return std::addressof(inserted_info); + } + } + + // todo: error handling + return nullptr; + } + + auto Fonts::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo* + { + return this->glyph_of({.codepoint = codepoint, .size = size, .flag = flag}); + } + + auto Fonts::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); } ); - parsed_info_queue_.clear(); + + return infos; } - auto FontFace::find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo& + auto Fonts::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo& { - if (const auto* info = find_or_parse_glyph(key); info != nullptr) + for (const auto& font: font_list_) { - return *info; + if (const auto* info = font.get_glyph(key); info != nullptr) + { + return *info; + } } GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(fallback_glyph_ != nullptr); return *fallback_glyph_; } - auto FontFace::find_glyph_no_fallback(const GlyphKey& key) noexcept -> const GlyphInfo* + auto Fonts::glyph_of_or_fallback(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) const noexcept -> const GlyphInfo& { - if (const auto* info = find_or_parse_glyph(key); info) - { - return info; - } + return this->glyph_of_or_fallback({.codepoint = codepoint, .size = size, .flag = flag}); + } - return nullptr; + auto Fonts::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector> + { + std::vector> infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto& info = this->glyph_of_or_fallback(codepoint, size, flag); + infos.emplace_back(std::ref(info)); + } + ); + + return infos; + } + + auto Fonts::load_all_glyph(TextureContext& context) noexcept -> void + { + std::ranges::for_each( + font_glyph_queue_ | std::views::values, + [&context](auto& queue) noexcept -> void + { + queue.upload(context); + } + ); + font_glyph_queue_.clear(); } } // namespace gal::prometheus::gfx diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp index 3b993ae..632e430 100644 --- a/src/gfx/font.hpp +++ b/src/gfx/font.hpp @@ -5,53 +5,69 @@ #pragma once +#include +#include +#include +#include +#include + #include -#include #include #include +#include namespace gal::prometheus::gfx { - /** - * @brief All the glyph data used in a font - */ - class FontFace final + class FontGlyphQueue final { public: - using value_type = extent_type::value_type; - using uv_type = primitive::basic_rect_2d; - - private: - memory::RefWrapper context_; - - std::string name_; - font_id_type id_; - - struct parsed_info_type + struct element_type { memory::RefWrapper info; GlyphParser::ParseResult result; }; - std::vector parsed_info_queue_; + using list_type = std::vector; - std::unordered_map glyphs_; - const GlyphInfo* fallback_glyph_; + private: + list_type list_; - [[nodiscard]] auto find_or_parse_glyph(const GlyphKey& key) noexcept -> GlyphInfo*; + public: + FontGlyphQueue(const FontGlyphQueue&) noexcept = delete; + FontGlyphQueue(FontGlyphQueue&&) noexcept = default; + auto operator=(const FontGlyphQueue&) noexcept -> FontGlyphQueue& = delete; + auto operator=(FontGlyphQueue&&) noexcept -> FontGlyphQueue& = default; + ~FontGlyphQueue() noexcept = default; + + FontGlyphQueue() noexcept = default; + + auto push(GlyphInfo& info, GlyphParser::ParseResult&& result) noexcept -> void; + + auto upload(TextureContext& context) noexcept -> void; + }; + + class Font final + { public: - FontFace(const FontFace&) noexcept = delete; - FontFace(FontFace&&) noexcept = default; - auto operator=(const FontFace&) noexcept -> FontFace& = delete; - auto operator=(FontFace&&) noexcept -> FontFace& = default; + using name_type = std::string; - ~FontFace() noexcept = default; + private: + name_type name_; + font_id_type id_; - FontFace(TextureContext& context, std::string name, font_id_type id) noexcept; + std::unordered_map glyphs_; - FontFace(TextureContext& context, std::string_view name, font_id_type id) noexcept; + public: + Font(const Font&) noexcept = delete; + Font(Font&&) noexcept = default; + auto operator=(const Font&) noexcept -> Font& = delete; + auto operator=(Font&&) noexcept -> Font& = default; + + ~Font() noexcept = default; + + Font(name_type name, font_id_type id) noexcept; /** * @brief Font name (obtained on GlyphParser::load) @@ -64,27 +80,125 @@ namespace gal::prometheus::gfx [[nodiscard]] auto id() const noexcept -> font_id_type; /** - * @brief Initialization, usually used to load default glyph data (for fallbacks when the desired glyph is not found) + * @brief Get the glyph information of the specified codepoint, if it can't be found, then return a null pointer + * @param key {codepoint, size, flag} + * @return The glyph information of the specified codepoint, or a null pointer if it can't be found + */ + [[nodiscard]] auto get_glyph(const GlyphKey& key) const noexcept -> const GlyphInfo*; + + /** + * @brief Set the glyph information of the specified codepoint, override if it already exists + * @param key {codepoint, size, flag} + * @param result The parse result of the specified codepoint + * @return GlyphInfo after insertion + */ + [[nodiscard]] auto set_glyph(const GlyphKey& key, const GlyphParser::ParseResult& result) noexcept -> GlyphInfo&; + }; + + class FontLoadQueue final + { + public: + struct font_data_type + { + using element_type = std::uint8_t; + using data_type = std::unique_ptr; + using size_type = std::uint32_t; + + data_type data; + size_type size; + }; + + using list_type = std::vector; + + private: + list_type list_; + list_type::difference_type new_font_index_; + + public: + FontLoadQueue(const FontLoadQueue&) noexcept = delete; + FontLoadQueue(FontLoadQueue&&) noexcept = default; + auto operator=(const FontLoadQueue&) noexcept -> FontLoadQueue& = delete; + auto operator=(FontLoadQueue&&) noexcept -> FontLoadQueue& = default; + + ~FontLoadQueue() noexcept = default; + + FontLoadQueue() noexcept; + + auto push(const std::filesystem::path& path) noexcept -> bool; + + auto upload(GlyphParser& parser, functional::function_reference_wrapper font_dest) noexcept -> void; + }; + + class Fonts final + { + public: + using font_list_type = std::vector; + + using font_glyph_queue_list_type = std::unordered_map; + + private: + font_list_type font_list_; + FontLoadQueue font_load_queue_; + + const GlyphInfo* fallback_glyph_; + font_glyph_queue_list_type font_glyph_queue_; + + GlyphParser* parser_; + + public: + Fonts(const Fonts&) noexcept = delete; + Fonts(Fonts&&) noexcept = default; + auto operator=(const Fonts&) noexcept -> Fonts& = delete; + auto operator=(Fonts&&) noexcept -> Fonts& = default; + + ~Fonts() noexcept = default; + + Fonts() noexcept; + + /** + * @brief Bind parser, default parser is null pointer, must bind parser before loading fonts + * @return The previously bound parser, or nullptr if no parser is bound */ - auto initialize() noexcept -> void; + auto bind_parser(GlyphParser& parser) noexcept -> GlyphParser*; /** - * @brief Upload the glyph data used and not uploaded to the texture before to the texture + * @brief Load fonts from the specified path, assuming the path is a valid font file + * @param path Font path + * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false */ - auto upload() noexcept -> void; + auto add_font(const std::filesystem::path& path) noexcept -> bool; /** - * @brief Get the glyph information of the specified codepoint, if it can't be found, then return the fallback glyph information - * @param key {codepoint, size, flag} - * @return The glyph information of the specified codepoint, or the fallback glyph information if it can't be found + * @brief Load the fonts previously added by @c add_font + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts */ - [[nodiscard]] auto find_glyph(const GlyphKey& key) noexcept -> const GlyphInfo&; + auto load_all_font() noexcept -> void; + + auto set_fallback_glyph() noexcept -> void; /** - * @brief Get the glyph information of the specified codepoint, if it can't be found, then return a null pointer + * @brief Set the fallback glyph, if we can't find the glyph of the specified codepoint, then use the fallback glyph * @param key {codepoint, size, flag} - * @return The glyph information of the specified codepoint, or a null pointer if it can't be found */ - [[nodiscard]] auto find_glyph_no_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; + auto set_fallback_glyph(const GlyphKey& key) noexcept -> void; + + /** + * @brief Set the fallback glyph, if we can't find the glyph of the specified codepoint, then use the fallback glyph + */ + auto set_fallback_glyph(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> void; + + [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; + + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector>; + + /** + * @brief Upload all used glyphs to the texture (if it is not already uploaded) + * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture + */ + auto load_all_glyph(TextureContext& context) noexcept -> void; }; } // namespace gal::prometheus::gfx diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index 4bb4f0a..384e59f 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -61,7 +61,10 @@ namespace gal::prometheus::gfx class GlyphInfo; class GlyphParser; - class FontFace; + class FontGlyphQueue; + class Font; + class FontLoadQueue; + class Fonts; // ========================================================= // RENDERER LIST From a0e2a25a354ca4ef728de52ac4bba0fd982ff865 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 19 May 2025 17:12:41 +0800 Subject: [PATCH 41/54] `build`: Blocking annoying WIN32 macros (min/max). --- scripts/library.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/library.cmake b/scripts/library.cmake index 1f13016..2325b29 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -8,7 +8,7 @@ add_library(gal::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) # COMPILE FLAGS if (${PROJECT_NAME_PREFIX}COMPILER_MSVC) - set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS;/utf-8") + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS;/utf-8;/DNOMINMAX") # ==================== # PEDANTIC if (${PROJECT_NAME_PREFIX}PEDANTIC) From cb7d39ed84e9c1c2dee0bbb620c8d22392b046a5 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 19 May 2025 23:12:36 +0800 Subject: [PATCH 42/54] `feat`: gfx::Dx12Renderer (trial version). --- src/gfx/gfx.hpp | 1 + src/gfx/renderer_dx12.cpp | 1026 +++++++++++++++++++++++++++++++++++++ src/gfx/renderer_dx12.hpp | 100 ++++ src/gfx/type.hpp | 2 +- 4 files changed, 1128 insertions(+), 1 deletion(-) create mode 100644 src/gfx/renderer_dx12.cpp create mode 100644 src/gfx/renderer_dx12.hpp diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index 5cbbc29..27860b6 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -15,5 +15,6 @@ #include #include +#include #include diff --git a/src/gfx/renderer_dx12.cpp b/src/gfx/renderer_dx12.cpp new file mode 100644 index 0000000..649f161 --- /dev/null +++ b/src/gfx/renderer_dx12.cpp @@ -0,0 +1,1026 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#if defined(GAL_PROMETHEUS_GFX_RENDER_DX12) + +#include + +#if GAL_PROMETHEUS_COMPILER_DEBUG +#define GAL_PROMETHEUS_GFX_DEBUG +#include +#endif + +#include + +#include +#include +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#include +#include + +namespace +{ + using namespace gal::prometheus; + using namespace gfx; + + auto check_hr_error( + const HRESULT result +#if defined(GAL_PROMETHEUS_GFX_DEBUG) + , + const std::source_location& location = std::source_location::current() +#endif + ) noexcept -> bool + { + if (SUCCEEDED(result)) + { + return true; + } + +#if defined(GAL_PROMETHEUS_GFX_DEBUG) + + const _com_error err{result}; + std::println(stderr, "Error: {} --- at {}:{}", err.ErrorMessage(), location.file_name(), location.line()); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + +#else + + GAL_PROMETHEUS_COMPILER_UNREACHABLE(); + +#endif + + return false; + } + + using projection_matrix_type = float[4][4]; + + constexpr std::size_t gpu_handle_to_id_offset = 0x1000'0000; + + [[nodiscard]] auto id_to_gpu_handle(const texture_id_type id) noexcept -> std::size_t + { + return id - gpu_handle_to_id_offset; + } + + [[nodiscard]] auto gpu_handle_to_id(const std::size_t index) noexcept -> texture_id_type + { + return index + gpu_handle_to_id_offset; + } +} + +namespace gal::prometheus::gfx +{ + auto Dx12Renderer::create_root_signature() noexcept -> bool + { + // [0] projection_matrix + constexpr D3D12_ROOT_PARAMETER param_0 + { + .ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, + .Constants = {.ShaderRegister = 0, .RegisterSpace = 0, .Num32BitValues = sizeof(projection_matrix_type) / sizeof(float)}, + .ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX + }; + // [1] texture + constexpr D3D12_DESCRIPTOR_RANGE range + { + .RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + .NumDescriptors = 1, + .BaseShaderRegister = 0, + .RegisterSpace = 0, + .OffsetInDescriptorsFromTableStart = 0 + }; + const D3D12_ROOT_PARAMETER param_1 + { + .ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, + .DescriptorTable = {.NumDescriptorRanges = 1, .pDescriptorRanges = &range}, + .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL + }; + // @see Dx12Renderer::do_present -> `command_list_->SetGraphicsRootXxx` + const D3D12_ROOT_PARAMETER params[]{param_0, param_1}; + + // Bi-linear sampling is required by default + constexpr D3D12_STATIC_SAMPLER_DESC static_sampler_desc + { + .Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR, + .AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + .AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + .AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + .MipLODBias = .0f, + .MaxAnisotropy = 0, + .ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS, + .BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK, + .MinLOD = .0f, + .MaxLOD = .0f, + .ShaderRegister = 0, + .RegisterSpace = 0, + .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL + }; + + const D3D12_ROOT_SIGNATURE_DESC root_signature_desc + { + .NumParameters = static_cast(std::ranges::size(params)), + .pParameters = params, + .NumStaticSamplers = 1, + .pStaticSamplers = &static_sampler_desc, + .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS + }; + + auto d3d12_dll = GetModuleHandleW(L"d3d12.dll"); + if (d3d12_dll == nullptr) + { + d3d12_dll = LoadLibraryW(L"d3d12.dll"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(d3d12_dll != nullptr); + } + + const auto serialize_root_signature_function = + reinterpret_cast(GetProcAddress(d3d12_dll, "D3D12SerializeRootSignature")); // NOLINT(clang-diagnostic-cast-function-type-strict) + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(serialize_root_signature_function != nullptr); + + ComPtr blob = nullptr; + if (not check_hr_error(serialize_root_signature_function(&root_signature_desc, D3D_ROOT_SIGNATURE_VERSION_1, blob.GetAddressOf(), nullptr))) + { + return false; + } + if (not check_hr_error(device_->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(root_signature_.GetAddressOf())))) + { + return false; + } + + return true; + } + + auto Dx12Renderer::create_pipeline_state() noexcept -> bool + { + // Create the vertex shader + auto vertex_shader_blob = []() -> ComPtr + { + constexpr static char shader[]{ + "cbuffer vertexBuffer : register(b0)" + "{" + " float4x4 ProjectionMatrix;" + "};" + "struct VS_INPUT" + "{" + " float2 pos : POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "PS_INPUT main(VS_INPUT input)" + "{" + " PS_INPUT output;" + " output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));" + " output.col = input.col;" + " output.uv = input.uv;" + " return output;" + "}" + }; + + ComPtr blob; + if (not check_hr_error(D3DCompile(shader, std::ranges::size(shader), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, blob.GetAddressOf(), nullptr))) + { + return nullptr; + } + + return blob; + }(); + + // Create the pixel shader + auto pixel_shader_blob = []() -> ComPtr + { + constexpr static char shader[]{ + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "sampler sampler0;" + "Texture2D texture0;" + "float4 main(PS_INPUT input) : SV_Target" + "{" + " float4 out_col = texture0.Sample(sampler0, input.uv);" + " return input.col * out_col;" + "}" + }; + + ComPtr blob; + if (not check_hr_error(D3DCompile(shader, std::ranges::size(shader), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, blob.GetAddressOf(), nullptr))) + { + return nullptr; + } + + return blob; + }(); + + if (vertex_shader_blob == nullptr or pixel_shader_blob == nullptr) + { + return false; + } + + // Create the blending setup + constexpr D3D12_RENDER_TARGET_BLEND_DESC render_target_blend_desc + { + .BlendEnable = true, + .LogicOpEnable = false, + .SrcBlend = D3D12_BLEND_SRC_ALPHA, + .DestBlend = D3D12_BLEND_INV_SRC_ALPHA, + .BlendOp = D3D12_BLEND_OP_ADD, + .SrcBlendAlpha = D3D12_BLEND_ONE, + .DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA, + .BlendOpAlpha = D3D12_BLEND_OP_ADD, + .LogicOp = D3D12_LOGIC_OP_CLEAR, + .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL + }; + constexpr D3D12_BLEND_DESC blend_desc + { + .AlphaToCoverageEnable = FALSE, + .IndependentBlendEnable = FALSE, + .RenderTarget = {render_target_blend_desc} + }; + + // Create the rasterizer state + constexpr D3D12_RASTERIZER_DESC rasterizer_desc + { + .FillMode = D3D12_FILL_MODE_SOLID, + .CullMode = D3D12_CULL_MODE_NONE, + .FrontCounterClockwise = FALSE, + .DepthBias = D3D12_DEFAULT_DEPTH_BIAS, + .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP, + .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, + .DepthClipEnable = TRUE, + .MultisampleEnable = FALSE, + .AntialiasedLineEnable = FALSE, + .ForcedSampleCount = 0, + .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF + }; + + // Create depth-stencil State + constexpr D3D12_DEPTH_STENCIL_DESC depth_stencil_desc + { + .DepthEnable = FALSE, + .DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL, + .DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS, + .StencilEnable = FALSE, + .StencilReadMask = 0, + .StencilWriteMask = 0, + .FrontFace = + { + .StencilFailOp = D3D12_STENCIL_OP_KEEP, + .StencilDepthFailOp = D3D12_STENCIL_OP_KEEP, + .StencilPassOp = D3D12_STENCIL_OP_KEEP, + .StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS + }, + .BackFace = + { + .StencilFailOp = D3D12_STENCIL_OP_KEEP, + .StencilDepthFailOp = D3D12_STENCIL_OP_KEEP, + .StencilPassOp = D3D12_STENCIL_OP_KEEP, + .StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS + } + }; + + // Create the input layout + constexpr D3D12_INPUT_ELEMENT_DESC input_element_desc[] + { + { + .SemanticName = "POSITION", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, position)), + .InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "TEXCOORD", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, uv)), + .InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "COLOR", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, color)), + .InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + }; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeline_state_desc + { + .pRootSignature = root_signature_.Get(), + .VS = {.pShaderBytecode = vertex_shader_blob->GetBufferPointer(), .BytecodeLength = vertex_shader_blob->GetBufferSize()}, + .PS = {.pShaderBytecode = pixel_shader_blob->GetBufferPointer(), .BytecodeLength = pixel_shader_blob->GetBufferSize()}, + .DS = {.pShaderBytecode = nullptr, .BytecodeLength = 0}, + .HS = {.pShaderBytecode = nullptr, .BytecodeLength = 0}, + .GS = {.pShaderBytecode = nullptr, .BytecodeLength = 0}, + .StreamOutput = {.pSODeclaration = nullptr, .NumEntries = 0, .pBufferStrides = nullptr, .NumStrides = 0, .RasterizedStream = 0}, + .BlendState = blend_desc, + .SampleMask = UINT_MAX, + .RasterizerState = rasterizer_desc, + .DepthStencilState = depth_stencil_desc, + .InputLayout = {.pInputElementDescs = input_element_desc, .NumElements = static_cast(std::ranges::size(input_element_desc))}, + .IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED, + .PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, + .NumRenderTargets = 1, + .RTVFormats = {DXGI_FORMAT_R8G8B8A8_UNORM}, + .DSVFormat = {}, + .SampleDesc = {.Count = 1, .Quality = 0}, + .NodeMask = 1, + .CachedPSO = {.pCachedBlob = nullptr, .CachedBlobSizeInBytes = 0}, + .Flags = D3D12_PIPELINE_STATE_FLAG_NONE + }; + + return check_hr_error(device_->CreateGraphicsPipelineState(&pipeline_state_desc, IID_PPV_ARGS(pipeline_state_.GetAddressOf()))); + } + + auto Dx12Renderer::create_srv_descriptor_heap(const UINT num) noexcept -> bool + { + const D3D12_DESCRIPTOR_HEAP_DESC desc{ + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + .NumDescriptors = num, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + .NodeMask = 0 + }; + return check_hr_error(device_->CreateDescriptorHeap(&desc, IID_PPV_ARGS(srv_descriptor_heap_.ReleaseAndGetAddressOf()))); + } + + auto Dx12Renderer::upload_texture( + const std::size_t index, + const Texture::data_view_type data, + const Texture::size_type size, + const bool record_resource + ) noexcept -> texture_id_type + { + constexpr D3D12_HEAP_PROPERTIES heap_properties{ + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + + const D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Alignment = 0, + .Width = static_cast(size.width), + .Height = static_cast(size.height), + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + ComPtr texture_2d; + if (not check_hr_error( + device_->CreateCommittedResource( + &heap_properties, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(texture_2d.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + const auto upload_pitch = (static_cast(size.width * 4) + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + const auto upload_size = static_cast(size.height) * upload_pitch; + + constexpr D3D12_HEAP_PROPERTIES upload_heap_properties{ + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + + const D3D12_RESOURCE_DESC upload_resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = static_cast(upload_size), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + ComPtr upload_buffer; + check_hr_error( + device_->CreateCommittedResource( + &upload_heap_properties, + D3D12_HEAP_FLAG_NONE, + &upload_resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(upload_buffer.GetAddressOf()) + ) + ); + + void* mapped_data = nullptr; + const D3D12_RANGE range{.Begin = 0, .End = upload_size}; + if (not check_hr_error(upload_buffer->Map(0, &range, &mapped_data))) + { + return invalid_texture_id; + } + for (UINT i = 0; i < static_cast(size.height); ++i) + { + auto* dest = static_cast(mapped_data) + static_cast(upload_pitch * i); + auto* source = reinterpret_cast(data.data()) + static_cast(size.width * i * 4); + const auto length = size.width * 4; + std::memcpy(dest, source, length); + } + upload_buffer->Unmap(0, &range); + + const D3D12_TEXTURE_COPY_LOCATION source_location{ + .pResource = upload_buffer.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + .PlacedFootprint = + { + .Offset = 0, + .Footprint = + { + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .Width = static_cast(size.width), + .Height = static_cast(size.height), + .Depth = 1, + .RowPitch = upload_pitch + } + } + }; + + const D3D12_TEXTURE_COPY_LOCATION dest_location{ + .pResource = texture_2d.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 0 + }; + + const D3D12_RESOURCE_BARRIER barrier{ + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = texture_2d.Get(), + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST, + .StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + } + }; + + ComPtr command_allocator; + if (not check_hr_error( + device_->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(command_allocator.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + ComPtr command_list; + if (not check_hr_error( + device_->CreateCommandList( + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + command_allocator.Get(), + nullptr, + IID_PPV_ARGS(command_list.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + command_list->CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, nullptr); + command_list->ResourceBarrier(1, &barrier); + if (not check_hr_error(command_list->Close())) + { + return invalid_texture_id; + } + + constexpr D3D12_COMMAND_QUEUE_DESC command_queue_desc{ + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = 0, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 1 + }; + + ComPtr command_queue; + if (not check_hr_error( + device_->CreateCommandQueue( + &command_queue_desc, + IID_PPV_ARGS(command_queue.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + ID3D12CommandList* command_lists[]{command_list.Get()}; + command_queue->ExecuteCommandLists(1, command_lists); + + ComPtr fence; + if (not check_hr_error( + device_->CreateFence( + 0, + D3D12_FENCE_FLAG_NONE, + IID_PPV_ARGS(fence.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + constexpr UINT64 fence_value = 1; + if (not check_hr_error(command_queue->Signal(fence.Get(), fence_value))) + { + return invalid_texture_id; + } + if (fence->GetCompletedValue() < fence_value) + { + HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (not check_hr_error(fence->SetEventOnCompletion(fence_value, event))) + { + return invalid_texture_id; + } + WaitForSingleObject(event, INFINITE); + CloseHandle(event); + } + + // Create texture view + const D3D12_SHADER_RESOURCE_VIEW_DESC resource_view_desc{ + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D, + .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + .Texture2D = {.MostDetailedMip = 0, .MipLevels = resource_desc.MipLevels, .PlaneSlice = 0, .ResourceMinLODClamp = .0f} + }; + + const auto increment_size = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + auto cpu_handle = srv_descriptor_heap_->GetCPUDescriptorHandleForHeapStart(); + auto gpu_handle = srv_descriptor_heap_->GetGPUDescriptorHandleForHeapStart(); + + cpu_handle.ptr += index * increment_size; + gpu_handle.ptr += index * increment_size; + + device_->CreateShaderResourceView(texture_2d.Get(), &resource_view_desc, cpu_handle); + + if (record_resource) + { + textures_[index] = {.resource = texture_2d, .cpu = cpu_handle, .gpu = gpu_handle}; + } + else + { + // ComPtr + texture_2d = nullptr; + } + + return gpu_handle_to_id(index); + } + + Dx12Renderer::Dx12Renderer() noexcept + : device_{nullptr}, + command_list_{nullptr}, + root_signature_{nullptr}, + pipeline_state_{nullptr}, + srv_descriptor_heap_{nullptr}, + srv_max_size_{8}, + render_buffer_{} {} + + Dx12Renderer::Dx12Renderer(ID3D12Device* device, ID3D12GraphicsCommandList* command_list) noexcept + { + bind_device(device); + bind_command_list(command_list); + } + + Dx12Renderer::Dx12Renderer(ComPtr device, ComPtr command_list) noexcept + { + bind_device(std::move(device)); + bind_command_list(std::move(command_list)); + } + + auto Dx12Renderer::bind_device(ID3D12Device* device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = device; + } + + auto Dx12Renderer::bind_device(ComPtr device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = std::move(device); + } + + auto Dx12Renderer::bind_command_list(ID3D12GraphicsCommandList* command_list) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list != nullptr); + command_list_ = command_list; + } + + auto Dx12Renderer::bind_command_list(ComPtr command_list) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list != nullptr); + command_list_ = std::move(command_list); + } + + auto Dx12Renderer::do_create() noexcept -> bool + { + if (not create_root_signature()) + { + return false; + } + if (not create_pipeline_state()) + { + return false; + } + if (not create_srv_descriptor_heap(srv_max_size_)) + { + return false; + } + textures_.resize(srv_max_size_); + + return true; + } + + auto Dx12Renderer::do_destroy() noexcept -> void + { + // ComPtr + textures_.clear(); + + render_buffer_.vertex = nullptr; + render_buffer_.index = nullptr; + + // command_list_->ClearState(pipeline_state_.Get()); + // std::ignore = command_list_->Close(); + + srv_descriptor_heap_ = nullptr; + pipeline_state_ = nullptr; + root_signature_ = nullptr; + + command_list_ = nullptr; + device_ = nullptr; + } + + auto Dx12Renderer::do_ready() const noexcept -> bool + { + if (device_ == nullptr or command_list_ == nullptr) + { + return false; + } + + if (root_signature_ == nullptr or pipeline_state_ == nullptr or srv_descriptor_heap_ == nullptr) + { + return false; + } + + return true; + } + + auto Dx12Renderer::do_create_texture(const Texture::data_view_type data, const Texture::size_type size) noexcept -> texture_id_type + { + auto it = std::ranges::find(textures_, nullptr, &texture_type::resource); + if (it == textures_.end()) + { + // full, expand heap + const auto old_size = srv_max_size_; + const auto new_size = static_cast(static_cast(srv_max_size_) * 1.5f); + + srv_max_size_ = new_size; + textures_.resize(srv_max_size_); + it = textures_.begin() + old_size; + + const auto old_heap = srv_descriptor_heap_; + + const auto create_srv_descriptor_heap_result = create_srv_descriptor_heap(srv_max_size_); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(create_srv_descriptor_heap_result); + + const auto source = old_heap->GetCPUDescriptorHandleForHeapStart(); + const auto dest = srv_descriptor_heap_->GetCPUDescriptorHandleForHeapStart(); + + device_->CopyDescriptorsSimple( + old_size, + dest, + source, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV + ); + + const auto increment_size = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + const auto new_cpu_begin = srv_descriptor_heap_->GetCPUDescriptorHandleForHeapStart().ptr; + const auto new_gpu_begin = srv_descriptor_heap_->GetGPUDescriptorHandleForHeapStart().ptr; + + for (std::size_t i = 0; i < old_size; ++i) + { + if (auto& [resource, cpu, gpu] = textures_[i]; resource != nullptr) + { + cpu.ptr = new_cpu_begin + i * increment_size; + gpu.ptr = new_gpu_begin + i * increment_size; + } + } + } + + const auto index = std::ranges::distance(textures_.begin(), it); + + return upload_texture(index, data, size); + } + + auto Dx12Renderer::do_update_texture(const Texture& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.uploaded(), "Create texture first!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty(), "No need to update texture!"); + + const auto index = id_to_gpu_handle(texture.id()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < srv_max_size_); + auto& texture_gpu = textures_[index]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_gpu.resource != nullptr); + + // todo + do_destroy_texture(texture.id()); + std::ignore = upload_texture(index, texture.data(), texture.size()); + } + + auto Dx12Renderer::do_destroy_texture(const texture_id_type texture_id) noexcept -> void + { + const auto index = id_to_gpu_handle(texture_id); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < srv_max_size_); + + auto& texture = textures_[index]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.resource != nullptr); + // ComPtr + texture.resource = nullptr; + } + + auto Dx12Renderer::do_present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void + { + const auto all_render_data = renderer_context.render_data(); + // const auto [display_x, display_y] = display_area.point; + const auto [display_width, display_height] = display_area.extent; + + const auto [total_vertex_count, total_index_count] = [&]() noexcept + { + struct sum + { + UINT vertex; + UINT index; + }; + + return std::ranges::fold_left( + all_render_data, + sum{.vertex = 0, .index = 0}, + [](const sum s, const RenderData& render_data) noexcept -> sum + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + return {.vertex = s.vertex + static_cast(vertex_list.size()), .index = s.index + static_cast(index_list.size())}; + } + ); + }(); + + frame_resource_index_ += 1; + const auto this_frame_index = frame_resource_index_ % num_frames_in_flight; + auto& this_frame = frame_resource_[this_frame_index]; + auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = this_frame; + + constexpr D3D12_HEAP_PROPERTIES heap_properties{ + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + // Create and grow vertex/index buffers if needed + if (this_frame_vertex_buffer == nullptr or this_frame_vertex_count < total_vertex_count) + { + // todo: grow factor + this_frame_vertex_count = total_vertex_count + 5000; + + const D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = this_frame_vertex_count * sizeof(vertex_type), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + check_hr_error( + device_->CreateCommittedResource( + &heap_properties, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(this_frame_vertex_buffer.ReleaseAndGetAddressOf()) + ) + ); + } + if (this_frame_index_buffer == nullptr or this_frame_index_count < total_index_count) + { + // todo: grow factor + this_frame_index_count = total_index_count + 10000; + + const D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = this_frame_index_count * sizeof(index_type), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + check_hr_error( + device_->CreateCommittedResource( + &heap_properties, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(this_frame_index_buffer.ReleaseAndGetAddressOf()) + ) + ); + } + + // Upload vertex/index data into a single contiguous GPU buffer + { + void* mapped_vertex_resource; + void* mapped_index_resource; + constexpr D3D12_RANGE vertex_range{.Begin = 0, .End = 0}; + constexpr D3D12_RANGE index_range{.Begin = 0, .End = 0}; + check_hr_error(this_frame_vertex_buffer->Map(0, &vertex_range, &mapped_vertex_resource)); + check_hr_error(this_frame_index_buffer->Map(0, &index_range, &mapped_index_resource)); + + auto* mapped_vertex = static_cast(mapped_vertex_resource); + auto* mapped_index = static_cast(mapped_index_resource); + + UINT vertex_offset = 0; + UINT index_offset = 0; + + std::ranges::for_each( + all_render_data, + [&](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + // std::ranges::transform( + // vertex_list, + // mapped_vertex + vertex_offset, + // [](const vertex_type& vertex) noexcept -> vertex_type + // { + // // return { + // // .position = {vertex.position.x, vertex.position.y}, + // // .uv = {vertex.uv.x, vertex.uv.y}, + // // .color = vertex.color.to(primitive::color_format) + // // }; + // return std::bit_cast(vertex); + // } + // ); + std::ranges::copy(vertex_list, mapped_vertex + vertex_offset); + // std::ranges::transform( + // index_list, + // mapped_index + index_offset, + // [vertex_offset](const index_type index) noexcept -> index_type + // { + // return static_cast(index + vertex_offset); + // } + // ); + std::ranges::copy(index_list, mapped_index + index_offset); + + vertex_offset += static_cast(vertex_list.size()); + index_offset += static_cast(index_list.size()); + } + ); + + this_frame_vertex_buffer->Unmap(0, &vertex_range); + this_frame_index_buffer->Unmap(0, &index_range); + } + + // Setup orthographic projection matrix into our constant buffer + projection_matrix_type projection_matrix; + { + constexpr auto left = 0.f; + const auto right = display_width; + constexpr auto top = 0.f; + const auto bottom = display_height; + + const float mvp[4][4]{ + {2.0f / (right - left), 0.0f, 0.0f, 0.0f}, + {0.0f, 2.0f / (top - bottom), 0.0f, 0.0f}, + {0.0f, 0.0f, 0.5f, 0.0f}, + {(right + left) / (left - right), (top + bottom) / (bottom - top), 0.5f, 1.0f}, + }; + static_assert(sizeof(mvp) == sizeof(projection_matrix_type)); + std::memcpy(projection_matrix, mvp, sizeof(projection_matrix_type)); + } + + // Setup viewport + { + const D3D12_VIEWPORT viewport{ + .TopLeftX = .0f, + .TopLeftY = .0f, + .Width = static_cast(display_width), + .Height = static_cast(display_height), + .MinDepth = .0f, + .MaxDepth = 1.f + }; + command_list_->RSSetViewports(1, &viewport); + } + + // Bind shader/vertex buffers, root signature and pipeline state + { + ID3D12DescriptorHeap* descriptor_heaps[]{srv_descriptor_heap_.Get()}; + command_list_->SetDescriptorHeaps(1, descriptor_heaps); + + command_list_->SetGraphicsRootSignature(root_signature_.Get()); + command_list_->SetGraphicsRoot32BitConstants(0, sizeof(projection_matrix_type) / sizeof(float), &projection_matrix, 0); + + command_list_->SetPipelineState(pipeline_state_.Get()); + + const D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view{ + .BufferLocation = this_frame_vertex_buffer->GetGPUVirtualAddress(), + .SizeInBytes = this_frame_vertex_count * static_cast(sizeof(vertex_type)), + .StrideInBytes = sizeof(vertex_type) + }; + command_list_->IASetVertexBuffers(0, 1, &vertex_buffer_view); + + const D3D12_INDEX_BUFFER_VIEW index_buffer_view{ + .BufferLocation = this_frame_index_buffer->GetGPUVirtualAddress(), + .SizeInBytes = this_frame_index_count * static_cast(sizeof(index_type)), + // ReSharper disable once CppUnreachableCode + .Format = sizeof(index_type) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT + }; + command_list_->IASetIndexBuffer(&index_buffer_view); + command_list_->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + } + + // Setup blend factor + constexpr float blend_factor[4]{.0f, .0f, .0f, .0f}; + command_list_->OMSetBlendFactor(blend_factor); + + UINT total_index_offset = 0; + std::ranges::for_each( + all_render_data, + [this, &total_index_offset](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + for (const auto& command_list = render_data.command_list.get(); + const auto& [clip_rect, texture_id, index_offset, element_count]: command_list) + { + const auto [point, extent] = clip_rect; + const D3D12_RECT rect + { + static_cast(point.x), + static_cast(point.y), + static_cast(point.x + extent.width), + static_cast(point.y + extent.height) + }; + command_list_->RSSetScissorRects(1, &rect); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_id != invalid_texture_id); + const auto index = id_to_gpu_handle(texture_id); + const auto& texture = textures_[index]; + command_list_->SetGraphicsRootDescriptorTable(1, texture.gpu); + + const auto this_index_offset = static_cast(total_index_offset + index_offset); + command_list_->DrawIndexedInstanced(static_cast(element_count), 1, this_index_offset, 0, 0); + } + + total_index_offset += static_cast(index_list.size()); + } + ); + } +} + +#endif diff --git a/src/gfx/renderer_dx12.hpp b/src/gfx/renderer_dx12.hpp new file mode 100644 index 0000000..2b65476 --- /dev/null +++ b/src/gfx/renderer_dx12.hpp @@ -0,0 +1,100 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +// todo +#define GAL_PROMETHEUS_GFX_RENDER_DX12 + +#if defined(GAL_PROMETHEUS_GFX_RENDER_DX12) + +#include + +#include + +#include +#include + +namespace gal::prometheus::gfx +{ + using Microsoft::WRL::ComPtr; + + class Dx12Renderer final : public Renderer + { + public: + struct texture_type + { + ComPtr resource; + D3D12_CPU_DESCRIPTOR_HANDLE cpu; + D3D12_GPU_DESCRIPTOR_HANDLE gpu; + }; + + using textures_type = std::vector; + + private: + struct render_buffer_type + { + ComPtr index; + UINT index_count; + ComPtr vertex; + UINT vertex_count; + }; + + ComPtr device_; + ComPtr command_list_; + + ComPtr root_signature_; + ComPtr pipeline_state_; + ComPtr srv_descriptor_heap_; + // 8 descriptors are enough, and if not, then 1.5 times more + UINT srv_max_size_; + + textures_type textures_; + + constexpr static UINT num_frames_in_flight = 3; + // note: overflow(max + 1 => 0) + UINT frame_resource_index_ = (std::numeric_limits::max)(); + // render_buffer_type frame_resource_[num_frames_in_flight] = {}; + // num_frames_in_flight < 16 + render_buffer_type frame_resource_[16] = {}; + + render_buffer_type render_buffer_; + + [[nodiscard]] auto create_root_signature() noexcept -> bool; + [[nodiscard]] auto create_pipeline_state() noexcept -> bool; + [[nodiscard]] auto create_srv_descriptor_heap(UINT num) noexcept -> bool; + + [[nodiscard]] auto upload_texture( + std::size_t index, + Texture::data_view_type data, + Texture::size_type size, + bool record_resource = true + ) noexcept -> texture_id_type; + + public: + Dx12Renderer() noexcept; + Dx12Renderer(ID3D12Device* device, ID3D12GraphicsCommandList* command_list) noexcept; + Dx12Renderer(ComPtr device, ComPtr command_list) noexcept; + + auto bind_device(ID3D12Device* device) noexcept -> void; + auto bind_device(ComPtr device) noexcept -> void; + auto bind_command_list(ID3D12GraphicsCommandList* command_list) noexcept -> void; + auto bind_command_list(ComPtr command_list) noexcept -> void; + + private: + auto do_create() noexcept -> bool override; + auto do_destroy() noexcept -> void override; + + [[nodiscard]] auto do_ready() const noexcept -> bool override; + + auto do_create_texture(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type override; + auto do_update_texture(const Texture& texture) noexcept -> void override; + auto do_destroy_texture(texture_id_type texture_id) noexcept -> void override; + + auto do_present(const RenderContext& renderer_context, const rect_type& display_area) noexcept -> void override; + }; +} + +#endif diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp index 384e59f..ac57db4 100644 --- a/src/gfx/type.hpp +++ b/src/gfx/type.hpp @@ -33,7 +33,7 @@ namespace gal::prometheus::gfx // ========================================================= // DX11: ID3D11ShaderResourceView - // DX12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr + // DX12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index using texture_id_type = std::uintptr_t; constexpr texture_id_type invalid_texture_id{0}; From 0c5feee1358bab9441e8a097ad738ef1373028fc Mon Sep 17 00:00:00 2001 From: Life4gal Date: Tue, 20 May 2025 00:53:44 +0800 Subject: [PATCH 43/54] `wip`: Refactoring gfx. --- scripts/library.cmake | 26 +++ src/gfx_new/gfx.cpp | 87 +++++++++ src/gfx_new/gfx.hpp | 151 +++++++++++++++ src/gfx_new/internal/accessor_font.cpp | 6 + src/gfx_new/internal/accessor_font.hpp | 17 ++ src/gfx_new/internal/accessor_render.cpp | 6 + src/gfx_new/internal/accessor_render.hpp | 17 ++ src/gfx_new/internal/accessor_texture.cpp | 124 +++++++++++++ src/gfx_new/internal/accessor_texture.hpp | 117 ++++++++++++ src/gfx_new/internal/renderer_context.cpp | 6 + src/gfx_new/internal/renderer_context.hpp | 17 ++ src/gfx_new/internal/texture.cpp | 214 ++++++++++++++++++++++ src/gfx_new/internal/texture.hpp | 126 +++++++++++++ 13 files changed, 914 insertions(+) create mode 100644 src/gfx_new/gfx.cpp create mode 100644 src/gfx_new/gfx.hpp create mode 100644 src/gfx_new/internal/accessor_font.cpp create mode 100644 src/gfx_new/internal/accessor_font.hpp create mode 100644 src/gfx_new/internal/accessor_render.cpp create mode 100644 src/gfx_new/internal/accessor_render.hpp create mode 100644 src/gfx_new/internal/accessor_texture.cpp create mode 100644 src/gfx_new/internal/accessor_texture.hpp create mode 100644 src/gfx_new/internal/renderer_context.cpp create mode 100644 src/gfx_new/internal/renderer_context.hpp create mode 100644 src/gfx_new/internal/texture.cpp create mode 100644 src/gfx_new/internal/texture.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index 2325b29..c5295f4 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -364,6 +364,18 @@ set( ${PROJECT_SOURCE_DIR}/src/io/inputs.hpp ${PROJECT_SOURCE_DIR}/src/io/io.hpp + + # ========================= + # GFX-NEW + # ========================= + + ${PROJECT_SOURCE_DIR}/src/gfx_new/gfx.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/renderer_context.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_texture.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_font.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_render.hpp + + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.hpp # ========================= # GFX @@ -380,6 +392,7 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx/render_list.hpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer.hpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx12.hpp ${PROJECT_SOURCE_DIR}/src/gfx/context.hpp @@ -435,6 +448,18 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/io/inputs.cpp + + # ========================= + # GFX-NEW + # ========================= + + ${PROJECT_SOURCE_DIR}/src/gfx_new/gfx.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/renderer_context.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_texture.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_font.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_render.cpp + + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.cpp # ========================= # GFX @@ -449,6 +474,7 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx/render_list.cpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer.cpp ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx12.cpp ${PROJECT_SOURCE_DIR}/src/gfx/context.cpp diff --git a/src/gfx_new/gfx.cpp b/src/gfx_new/gfx.cpp new file mode 100644 index 0000000..e956555 --- /dev/null +++ b/src/gfx_new/gfx.cpp @@ -0,0 +1,87 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include +#include + +namespace gal::prometheus::gfx_new +{ + Renderer::~Renderer() noexcept = default; + + Renderer::Renderer() noexcept + : context_{memory::make_unique()} {} + + auto Renderer::construct() noexcept -> bool + { + return do_construct(); + } + + auto Renderer::destruct() noexcept -> void + { + return do_destruct(); + } + + auto Renderer::ready() const noexcept -> bool + { + return do_ready(); + } + + auto Renderer::new_frame() noexcept -> void + { + do_before_new_frame(); + + // + + do_after_new_frame(); + } + + auto Renderer::present() noexcept -> void + { + do_before_present(); + + // + + do_after_present(); + } + + auto Renderer::end_frame() noexcept -> void + { + do_before_end_frame(); + + // + + do_after_end_frame(); + } + + auto Renderer::do_before_new_frame() noexcept -> void + { + // + } + + auto Renderer::do_after_new_frame() noexcept -> void + { + // + } + + auto Renderer::do_before_present() noexcept -> void + { + // + } + + auto Renderer::do_after_present() noexcept -> void + { + // + } + + auto Renderer::do_before_end_frame() noexcept -> void + { + // + } + + auto Renderer::do_after_end_frame() noexcept -> void + { + // + } +} diff --git a/src/gfx_new/gfx.hpp b/src/gfx_new/gfx.hpp new file mode 100644 index 0000000..e888e9c --- /dev/null +++ b/src/gfx_new/gfx.hpp @@ -0,0 +1,151 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace gal::prometheus::gfx_new +{ + // ========================================================= + // PRIMITIVE + // ========================================================= + + using point_type = primitive::basic_point_2d; + using uv_type = primitive::basic_point_2d; + using color_type = primitive::basic_color; + using vertex_type = primitive::basic_vertex; + using index_type = std::uint16_t; + + using extent_type = primitive::basic_extent_2d; + using rect_type = primitive::basic_rect_2d; + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; + + // ========================================================= + // TEXTURE + // ========================================================= + + // DX11: ID3D11ShaderResourceView + // DX12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + constant offset + using texture_id_type = std::uintptr_t; + constexpr texture_id_type invalid_texture_id{0}; + + // ========================================================= + // RENDERER + // ========================================================= + + class Renderer + { + public: + class RendererContext; + + class AccessorTexture; + class AccessorFont; + class AccessorRender; + + class Texture final + { + public: + // 32-bits (RGBA) + using element_type = std::uint32_t; + // size.width * size.height + using data_type = std::unique_ptr; + using data_view_type = std::span; + + using size_type = primitive::basic_extent_2d; + + // ============================== + // CPU side + // ============================== + + data_type data; + size_type size; + + static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); + std::uint64_t dirty : 1; + + // ============================== + // GPU side + // ============================== + + std::uint64_t id : 63; + }; + + private: + memory::UniquePointer context_; + + public: + Renderer(const Renderer&) noexcept = delete; + Renderer(Renderer&&) noexcept = default; + auto operator=(const Renderer&) noexcept -> Renderer& = delete; + auto operator=(Renderer&&) noexcept -> Renderer& = default; + + virtual ~Renderer() noexcept; + + protected: + Renderer() noexcept; + + public: + /** + * @brief Constructs the renderer, which assumes that all the parameters needed for the renderer have been set + */ + [[nodiscard]] auto construct() noexcept -> bool; + + /** + * @brief Destructs the renderer + */ + auto destruct() noexcept -> void; + + /** + * @brief Whether the current renderer is ready (@c construct was called and succeeded) + */ + [[nodiscard]] auto ready() const noexcept -> bool; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + auto new_frame() noexcept -> void; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + auto present() noexcept -> void; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + auto end_frame() noexcept -> void; + + private: + virtual auto do_construct() noexcept -> bool = 0; + virtual auto do_destruct() noexcept -> void = 0; + [[nodiscard]] virtual auto do_ready() const noexcept -> bool = 0; + + [[nodiscard]] virtual auto do_texture_create(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type = 0; + virtual auto do_texture_update(const Texture& texture) noexcept -> void = 0; + virtual auto do_texture_destroy(texture_id_type texture_id) noexcept -> void = 0; + + virtual auto do_before_new_frame() noexcept -> void; + virtual auto do_after_new_frame() noexcept -> void; + virtual auto do_before_present() noexcept -> void; + virtual auto do_after_present() noexcept -> void; + virtual auto do_before_end_frame() noexcept -> void; + virtual auto do_after_end_frame() noexcept -> void; + }; +} diff --git a/src/gfx_new/internal/accessor_font.cpp b/src/gfx_new/internal/accessor_font.cpp new file mode 100644 index 0000000..0063842 --- /dev/null +++ b/src/gfx_new/internal/accessor_font.cpp @@ -0,0 +1,6 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include diff --git a/src/gfx_new/internal/accessor_font.hpp b/src/gfx_new/internal/accessor_font.hpp new file mode 100644 index 0000000..6f8d784 --- /dev/null +++ b/src/gfx_new/internal/accessor_font.hpp @@ -0,0 +1,17 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx_new +{ + // todo + class Renderer::AccessorFont final + { + public: + }; +} diff --git a/src/gfx_new/internal/accessor_render.cpp b/src/gfx_new/internal/accessor_render.cpp new file mode 100644 index 0000000..e03fffc --- /dev/null +++ b/src/gfx_new/internal/accessor_render.cpp @@ -0,0 +1,6 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include diff --git a/src/gfx_new/internal/accessor_render.hpp b/src/gfx_new/internal/accessor_render.hpp new file mode 100644 index 0000000..535ebca --- /dev/null +++ b/src/gfx_new/internal/accessor_render.hpp @@ -0,0 +1,17 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx_new +{ + // todo + class Renderer::AccessorRender final + { + public: + }; +} diff --git a/src/gfx_new/internal/accessor_texture.cpp b/src/gfx_new/internal/accessor_texture.cpp new file mode 100644 index 0000000..2a4c52c --- /dev/null +++ b/src/gfx_new/internal/accessor_texture.cpp @@ -0,0 +1,124 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace gal::prometheus::gfx_new +{ + auto TextureContext::root_id() const noexcept -> texture_atlas_id_type + { + std::ignore = this; + return 0; + } + + TextureContext::TextureContext() noexcept + { + // root atlas + constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; + texture_atlas_list_.emplace_back(root_texture_atlas_size); + } + + auto TextureContext::root() noexcept -> Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); + + return texture_atlas_list_[root_id()]; + } + + auto TextureContext::root() const noexcept -> const Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); + + return texture_atlas_list_[root_id()]; + } + + auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) noexcept -> Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); + + return texture_atlas_list_[texture_atlas_id]; + } + + auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); + + return texture_atlas_list_[texture_atlas_id]; + } + + auto TextureContext::write( + const texture_atlas_id_type texture_atlas_id, + const Texture::data_view_type data, + const Texture::size_type size + ) noexcept -> primitive::basic_rect_2d + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= static_cast(size.width) * size.height); + + auto& texture = this->select(texture_atlas_id); + const auto texture_uv = texture.uv(); + + const auto borrowed_texture = texture.select(size); + borrowed_texture.fill(data); + + const auto position = borrowed_texture.position(); + return {position.to() * texture_uv, size.to() * texture_uv}; + } + + auto TextureContext::write( + const Texture::data_view_type data, + const Texture::size_type size + ) noexcept -> primitive::basic_rect_2d + { + // todo + return this->write(root_id(), data, size); + } + + auto TextureContext::upload(Renderer& renderer) noexcept -> void + { + std::ranges::for_each( + texture_atlas_list_, + [accessor = Renderer::AccessorTexture{renderer}](auto& texture) mutable noexcept -> void + { + if (not texture.uploaded()) + { + accessor.upload(texture); + } + else + { + accessor.update_if_dirty(texture); + } + } + ); + } + + Renderer::AccessorTexture::AccessorTexture(Renderer& renderer) noexcept + : renderer_{renderer} {} + + auto Renderer::AccessorTexture::upload(texture_type& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture.uploaded()); + + auto& renderer = renderer_.get(); + + texture.texture_.id = renderer.do_texture_create(texture.data(), texture.size()); + texture.texture_.dirty = false; + } + + auto Renderer::AccessorTexture::update(texture_type& texture) noexcept -> void + { + auto& renderer = renderer_.get(); + + renderer.do_texture_update(texture.texture_); + texture.texture_.dirty = false; + } + + auto Renderer::AccessorTexture::update_if_dirty(texture_type& texture) noexcept -> void + { + if (texture.dirty()) + { + update(texture); + } + } +} diff --git a/src/gfx_new/internal/accessor_texture.hpp b/src/gfx_new/internal/accessor_texture.hpp new file mode 100644 index 0000000..d9b1195 --- /dev/null +++ b/src/gfx_new/internal/accessor_texture.hpp @@ -0,0 +1,117 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include + +#include + +namespace gal::prometheus::gfx_new +{ + class TextureContext final + { + public: + // index + using texture_atlas_id_type = std::uint32_t; + + using texture_atlas_list_type = std::vector; + + private: + texture_atlas_list_type texture_atlas_list_; + + [[nodiscard]] auto root_id() const noexcept -> texture_atlas_id_type; + + public: + TextureContext() noexcept; + + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root() noexcept -> Texture&; + + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root() const noexcept -> const Texture&; + + /** + * @brief Get texture of id + */ + [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) noexcept -> Texture&; + + /** + * @brief Get texture of id + */ + [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture&; + + /** + * @brief Writes the given glyph data to the specified texture + * @param texture_atlas_id id of the specified texture + * @param data Glyph data to write + * @param size Size of data (rectangle area) + * @return The uv coordinate of the position where the data is written + */ + auto write( + texture_atlas_id_type texture_atlas_id, + Texture::data_view_type data, + Texture::size_type size + ) noexcept -> primitive::basic_rect_2d; + + /** + * @brief Writes the given glyph data to any holdable texture + * @param data Glyph data to write + * @param size Size of data (rectangle area) + * @return The uv coordinate of the position where the data is written + */ + auto write( + Texture::data_view_type data, + Texture::size_type size + ) noexcept -> primitive::basic_rect_2d; + + /** + * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) + * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) + */ + auto upload(Renderer& renderer) noexcept -> void; + }; + + /** + * @brief Proxy class for accessing the Renderer's private interface (this class helps us not to expose too many implementation details to the outside world) + */ + class Renderer::AccessorTexture final + { + public: + using renderer_type = memory::RefWrapper; + + using texture_type = gfx_new::Texture; + + private: + renderer_type renderer_; + + public: + explicit AccessorTexture(Renderer& renderer) noexcept; + + /** + * @brief Upload texture atlas data to GPU and get GPU resource handle + */ + auto upload(texture_type& texture) noexcept -> void; + + /** + * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) + * @note Does not check for the need to update + */ + auto update(texture_type& texture) noexcept -> void; + + /** + * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) + * @note If no update is needed (not dirty) then do nothing + */ + auto update_if_dirty(texture_type& texture) noexcept -> void; + }; +} diff --git a/src/gfx_new/internal/renderer_context.cpp b/src/gfx_new/internal/renderer_context.cpp new file mode 100644 index 0000000..4198a70 --- /dev/null +++ b/src/gfx_new/internal/renderer_context.cpp @@ -0,0 +1,6 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include diff --git a/src/gfx_new/internal/renderer_context.hpp b/src/gfx_new/internal/renderer_context.hpp new file mode 100644 index 0000000..1cdcd2b --- /dev/null +++ b/src/gfx_new/internal/renderer_context.hpp @@ -0,0 +1,17 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx_new +{ + // todo + class Renderer::RendererContext + { + public: + }; +} diff --git a/src/gfx_new/internal/texture.cpp b/src/gfx_new/internal/texture.cpp new file mode 100644 index 0000000..6539b0a --- /dev/null +++ b/src/gfx_new/internal/texture.cpp @@ -0,0 +1,214 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +// #define STB_RECT_PACK_IMPLEMENTATION +// #include + +namespace gal::prometheus::gfx_new +{ + Texture::Texture(const size_type size) noexcept + : rp_context_{}, + texture_{ + .data = std::make_unique_for_overwrite(static_cast(size.width) * size.height), + .size = size, + .dirty = false, + .id = invalid_texture_id, + } + { + rp_nodes_.resize(size.width); + + stbrp_init_target( + &rp_context_, + static_cast(size.width), + static_cast(size.height), + rp_nodes_.data(), + static_cast(rp_nodes_.size()) + ); + } + + auto Texture::data() const noexcept -> data_view_type + { + return {texture_.data.get(), area_size()}; + } + + auto Texture::area_size() const noexcept -> std::size_t + { + return static_cast(texture_.size.width) * texture_.size.height; + } + + auto Texture::size() const noexcept -> size_type + { + return texture_.size; + } + + auto Texture::uv() const noexcept -> uv_type + { + const auto s = size(); + + return {1.f / static_cast(s.width), 1.f / static_cast(s.height)}; + } + + auto Texture::dirty() const noexcept -> bool + { + return texture_.dirty; + } + + auto Texture::uploaded() const noexcept -> bool + { + return texture_.id != invalid_texture_id; + } + + auto Texture::id() const noexcept -> texture_id_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uploaded()); + + return texture_.id; + } + + auto Texture::select(const size_type size) noexcept -> BorrowedTexture + { + stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; + + if (stbrp_pack_rects(&rp_context_, &rect, 1)) + { + const point_type point{static_cast(rect.x), static_cast(rect.y)}; + + auto* address = texture_.data.get() + (point.y * size.width + point.x); + const auto mapping = BorrowedTexture::borrow_data_type::mapping_type{ + std::dextents{size.height, size.width}, + std::array{texture_.size.width, 1}, + }; + const auto data = BorrowedTexture::borrow_data_type{address, mapping}; + + texture_.dirty = true; + return {point, data}; + } + + return {BorrowedTexture::invalid_point, {}}; + } + + BorrowedTexture::BorrowedTexture(const point_type point, const borrow_data_type& data) noexcept + : point_{point}, + data_{data} {} + + auto BorrowedTexture::valid() const noexcept -> bool + { + return point_ != invalid_point; + } + + auto BorrowedTexture::position() const noexcept -> point_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + return point_; + } + + auto BorrowedTexture::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n <= width); + + for (size_type::value_type x = offset; x < offset + n; ++x) + { + data_[y, x] = element; + } + } + + auto BorrowedTexture::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + data.size() <= width); + + for (size_type::value_type x = 0; x < data.size(); ++x) + { + data_[y, x + offset] = data[x]; + } + } + + auto BorrowedTexture::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(n <= width); + + fill(y, 0, n, element); + } + + auto BorrowedTexture::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() <= width); + + fill(y, 0, data); + } + + auto BorrowedTexture::fill(const size_type::value_type y, const element_type element) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + + fill(y, width, element); + } + + auto BorrowedTexture::fill(const element_type element) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + const auto height = data_.extent(0); + for (size_type::value_type y = 0; y < height; ++y) + { + fill(y, element); + } + } + + auto BorrowedTexture::fill(const data_view_type data) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + for (size_type::value_type y = 0; y < height; ++y) + { + const data_view_type sub{data.begin() + static_cast(y) * width, width}; + + fill(y, sub); + } + } + + auto BorrowedTexture::operator[](const size_type::value_type x, const size_type::value_type y) const noexcept -> borrow_data_type::reference + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < width); + + return data_[y, x]; + } +} diff --git a/src/gfx_new/internal/texture.hpp b/src/gfx_new/internal/texture.hpp new file mode 100644 index 0000000..0f64e37 --- /dev/null +++ b/src/gfx_new/internal/texture.hpp @@ -0,0 +1,126 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include + +namespace gal::prometheus::gfx_new +{ + class BorrowedTexture; + + class Texture final + { + // Texture::id and Texture::dirty + friend Renderer::AccessorTexture; + + public: + using element_type = Renderer::Texture::element_type; + using data_type = Renderer::Texture::data_type; + using data_view_type = Renderer::Texture::data_view_type; + + using size_type = Renderer::Texture::size_type; + + using point_type = primitive::basic_point_2d; + using uv_type = primitive::basic_extent_2d; + + private: + stbrp_context rp_context_; + std::vector rp_nodes_; + + Renderer::Texture texture_; + + public: + explicit Texture(size_type size) noexcept; + + /** + * @brief Texture atlas data (for upload) + */ + [[nodiscard]] auto data() const noexcept -> data_view_type; + + /** + * @brief Texture atlas area size + */ + [[nodiscard]] auto area_size() const noexcept -> std::size_t; + + /** + * @brief Texture atlas size + */ + [[nodiscard]] auto size() const noexcept -> size_type; + + /** + * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) + */ + [[nodiscard]] auto uv() const noexcept -> uv_type; + + /** + * @brief Does this texture atlas need to be re-uploaded to the GPU + */ + [[nodiscard]] auto dirty() const noexcept -> bool; + + /** + * @brief Texture atlas uploaded (to GPU) + */ + [[nodiscard]] auto uploaded() const noexcept -> bool; + + /** + * @brief Texture atlas id (usually a GPU resource handle) + */ + [[nodiscard]] auto id() const noexcept -> texture_id_type; + + /** + * @brief Find a region that can hold a (piece of) texture of @c size + * @param size Texture size + */ + [[nodiscard]] auto select(size_type size) noexcept -> BorrowedTexture; + }; + + class BorrowedTexture final + { + friend Texture; + + public: + using element_type = Texture::element_type; + using data_type = Texture::data_type; + using data_view_type = Texture::data_view_type; + + using size_type = Texture::size_type; + + using point_type = Texture::point_type; + using uv_type = Texture::uv_type; + + constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; + + private: + using borrow_data_type = std::mdspan, std::layout_stride>; + + point_type point_; + borrow_data_type data_; + + BorrowedTexture(point_type point, const borrow_data_type& data) noexcept; + + public: + [[nodiscard]] auto valid() const noexcept -> bool; + + [[nodiscard]] auto position() const noexcept -> point_type; + + auto fill(size_type::value_type y, size_type::value_type offset, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, size_type::value_type offset, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, element_type element) const noexcept -> void; + + auto fill(element_type element) const noexcept -> void; + auto fill(data_view_type data) const noexcept -> void; + + auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> borrow_data_type::reference; + }; +} From 3096e8cf25b42bd684e213a06bb8cdb4e3363395 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Wed, 21 May 2025 18:33:19 +0800 Subject: [PATCH 44/54] `wip`: Refactoring gfx. --- scripts/library.cmake | 5 + src/gfx_new/gfx.cpp | 62 +- src/gfx_new/gfx.hpp | 771 ++++++- src/gfx_new/internal/accessor_font.cpp | 216 ++ src/gfx_new/internal/accessor_font.hpp | 83 +- src/gfx_new/internal/accessor_render.cpp | 13 + src/gfx_new/internal/accessor_render.hpp | 24 +- src/gfx_new/internal/accessor_texture.cpp | 26 +- src/gfx_new/internal/accessor_texture.hpp | 27 +- src/gfx_new/internal/font.cpp | 120 + src/gfx_new/internal/font.hpp | 181 ++ src/gfx_new/internal/render_list.cpp | 2411 +++++++++++++++++++++ src/gfx_new/internal/render_list.hpp | 13 + src/gfx_new/internal/renderer_context.hpp | 8 +- src/gfx_new/internal/texture.cpp | 7 +- src/gfx_new/internal/texture.hpp | 14 +- src/gfx_new/internal/type.hpp | 19 + 17 files changed, 3897 insertions(+), 103 deletions(-) create mode 100644 src/gfx_new/internal/font.cpp create mode 100644 src/gfx_new/internal/font.hpp create mode 100644 src/gfx_new/internal/render_list.cpp create mode 100644 src/gfx_new/internal/render_list.hpp create mode 100644 src/gfx_new/internal/type.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index c5295f4..94a180c 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -375,7 +375,10 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_font.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_render.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/type.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.hpp # ========================= # GFX @@ -460,6 +463,8 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_render.cpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.cpp # ========================= # GFX diff --git a/src/gfx_new/gfx.cpp b/src/gfx_new/gfx.cpp index e956555..bb19f8d 100644 --- a/src/gfx_new/gfx.cpp +++ b/src/gfx_new/gfx.cpp @@ -8,6 +8,8 @@ namespace gal::prometheus::gfx_new { + GlyphParser::~GlyphParser() noexcept = default; + Renderer::~Renderer() noexcept = default; Renderer::Renderer() noexcept @@ -30,58 +32,48 @@ namespace gal::prometheus::gfx_new auto Renderer::new_frame() noexcept -> void { - do_before_new_frame(); - - // - - do_after_new_frame(); + // font + { + context_->font_context.load_all_font(); + context_->font_context.set_fallback_glyph(); + } + // texture + { + AccessorTexture accessor{*this}; + context_->texture_context.upload(accessor); + } } auto Renderer::present() noexcept -> void { - do_before_present(); - - // - - do_after_present(); + // glyphs + { + // note: newly added glyph information is not available until the next frame + context_->font_context.upload_all_glyph(context_->texture_context); + } + + // todo + // do_present() } auto Renderer::end_frame() noexcept -> void - { - do_before_end_frame(); - - // - - do_after_end_frame(); - } - - auto Renderer::do_before_new_frame() noexcept -> void - { - // - } - - auto Renderer::do_after_new_frame() noexcept -> void - { - // - } - - auto Renderer::do_before_present() noexcept -> void { // } - auto Renderer::do_after_present() noexcept -> void + auto Renderer::set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser* { - // + return context_->font_context.set_glyph_parser(parser); } - auto Renderer::do_before_end_frame() noexcept -> void + auto Renderer::add_font(const std::filesystem::path& path) noexcept -> bool { - // + return context_->font_context.add_font(path); } - auto Renderer::do_after_end_frame() noexcept -> void + auto Renderer::new_render_list() noexcept -> RenderList& { - // + RenderList new_render_list{*this}; + return context_->render_context.render_lists.emplace_back(std::move(new_render_list)); } } diff --git a/src/gfx_new/gfx.hpp b/src/gfx_new/gfx.hpp index e888e9c..301f40f 100644 --- a/src/gfx_new/gfx.hpp +++ b/src/gfx_new/gfx.hpp @@ -5,7 +5,11 @@ #pragma once +#include #include +#include +#include +#include #include #include @@ -16,6 +20,7 @@ #include #include +#include namespace gal::prometheus::gfx_new { @@ -43,47 +48,706 @@ namespace gal::prometheus::gfx_new using texture_id_type = std::uintptr_t; constexpr texture_id_type invalid_texture_id{0}; + class TextureDescriptor final + { + public: + // 32-bits (RGBA) + using element_type = std::uint32_t; + // size.width * size.height + using data_type = std::unique_ptr; + using data_view_type = std::span; + + using size_type = primitive::basic_extent_2d; + + // ============================== + // CPU side + // ============================== + + data_type data; + size_type size; + + static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); + std::uint64_t dirty : 1; + + // ============================== + // GPU side + // ============================== + + std::uint64_t id : 63; + }; + // ========================================================= - // RENDERER + // FONT // ========================================================= - class Renderer + // index + using font_id_type = std::uint32_t; + constexpr font_id_type invalid_font_id{std::numeric_limits::max()}; + + enum class GlyphFlag : std::uint8_t + { + NONE = 0, + BOLD = 1 << 0, + ITALIC = 1 << 1, + }; + + class GlyphParser { public: - class RendererContext; + class [[nodiscard]] FontDescriptor final + { + public: + using element_type = std::uint8_t; + using data_type = std::unique_ptr; + using data_view_type = std::span; - class AccessorTexture; - class AccessorFont; - class AccessorRender; + using size_type = std::uint32_t; + + std::string_view identifier; + font_id_type id; - class Texture final + [[nodiscard]] constexpr static auto error() noexcept -> FontDescriptor + { + return {.identifier = {}, .id = invalid_font_id}; + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return id != invalid_font_id; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + class GlyphCode final { public: - // 32-bits (RGBA) - using element_type = std::uint32_t; - // size.width * size.height - using data_type = std::unique_ptr; - using data_view_type = std::span; + std::uint32_t codepoint; + std::uint32_t size; + GlyphFlag flag; + + constexpr GlyphCode(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept + : codepoint{codepoint}, + size{size}, + flag{flag} {} + }; + + class [[nodiscard]] GlyphDescriptor final + { + public: + // The offset of the bitmap may be negative (signed) + using rect_type = primitive::basic_rect_2d; + + // Bitmap infos of this glyph + rect_type rect; + float advance_x; + bool visible; + bool colored; + + // FIXME-OPT: + // We could have pre-specified an area to write glyph data to, + // but that would have meant exposing the texture implementation details to the outside world, + // and its implementation will be coupled with the GlyphParser, so we will leave the choice to the GlyphParser, + // which can find a way to reduce the number of dynamic allocations in the internal. + // note: + // We always assume that the data length of @c data is not less than @c rect.size.width * @c rect.size.height + TextureDescriptor::data_type data; + + [[nodiscard]] constexpr static auto error() noexcept -> GlyphDescriptor + { + return {.rect = {-0xbad, -0xbad, 0xc0ffee, 0xc0ffee}, .advance_x = -1, .visible = false, .colored = false, .data = nullptr}; + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return data != nullptr; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + GlyphParser(const GlyphParser&) noexcept = delete; + GlyphParser(GlyphParser&&) noexcept = default; + auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; + auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; + + virtual ~GlyphParser() noexcept; + + GlyphParser() noexcept = default; + + /** + * @brief Load font data from @c data, get all glyph data, return id of font + * @param data Font data + * @return id of the font, or invalid_font_id if failed to load + * @note The parser does not need to store (copy) the font data internally, we make sure that the font data is valid for the lifetime of the parser + */ + [[nodiscard]] virtual auto load(std::span data) noexcept -> FontDescriptor = 0; + + /** + * @brief Determines whether the target font contains the glyphs of the specified codepoint + * @param id The id returned by loading the font from the previous load + * @param codepoint The codepoint of the glyph to be checked + * @return Exists or not + */ + [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; + + /** + * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer + * @param id The id returned by loading the font from the previous load + * @param code {codepoint, size, flag} + * @return The glyph information of the specified size (and style) of the target codepoint + */ + [[nodiscard]] virtual auto parse(font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor = 0; + }; - using size_type = primitive::basic_extent_2d; + // ========================================================= + // RENDERER LIST + // ========================================================= + + enum class RenderListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + + DEFAULT = ANTI_ALIASED_LINE | ANTI_ALIASED_LINE_USE_TEXTURE | ANTI_ALIASED_FILL, + }; + + enum class RenderFlag : std::uint8_t + { + NONE = 0, + // specify that shape should be closed + // @see RenderList::draw_polygon_line + // @see RenderList::draw_polygon_line_aa + // @see RenderList::path_stroke + CLOSED = 1 << 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + }; + + enum class RenderArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + }; + + class RenderListSharedData + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; - // ============================== - // CPU side - // ============================== + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; - data_type data; - size_type size; + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; - static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); - std::uint64_t dirty : 1; + constexpr static std::size_t baked_line_uv_count = 64; + using baked_line_uvs_type = std::array; - // ============================== - // GPU side - // ============================== + circle_segment_counts_type circle_segment_counts; - std::uint64_t id : 63; + vertex_sample_points_type vertex_sample_points; + + baked_line_uvs_type baked_line_uvs; + point_type white_pixel_uv; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + // -------------------------------------------------- + + RenderListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; + + class RenderData final + { + public: + using size_type = std::uint32_t; + + struct [[nodiscard]] command_type + { + rect_type scissor; + texture_id_type texture; + + // ======================= + + // set by RenderList::index_list.size() + // start offset in @c RenderList::index_list + size_type index_offset; + // set by RenderList::draw_xxx + // number of indices (multiple of 3) to be rendered as triangles + size_type element_count; }; + using vertex_list_type = std::vector; + using index_list_type = std::vector; + using command_list_type = std::vector; + + std::reference_wrapper vertex_list; + std::reference_wrapper index_list; + std::reference_wrapper command_list; + }; + + using render_data_list_type = std::vector; + + class RenderList + { + friend class Renderer; + + public: + class RenderListContext; + + private: + memory::UniquePointer context_; + + explicit RenderList(Renderer& renderer) noexcept; + + public: + RenderList(const RenderList&) noexcept = delete; + RenderList(RenderList&&) noexcept; //= default; + auto operator=(const RenderList&) noexcept -> RenderList& = delete; + auto operator=(RenderList&&) noexcept -> RenderList&; // = default; + + ~RenderList() noexcept; + + auto reset() noexcept -> void; + + auto set_flag(RenderListFlag flag) noexcept -> void; + + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE + + auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; + + auto pop_scissor() noexcept -> void; + + auto push_texture(texture_id_type texture) noexcept -> void; + + auto pop_texture() noexcept -> void; + + // ---------------------------------------------------------------------------- + // PRIMITIVE + + auto line( + const point_type& from, + const point_type& to, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color + ) noexcept -> void; + + auto rect( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const point_type& left_top, + const point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderFlag flag = RenderFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color + ) noexcept -> void; + + auto circle_n( + const circle_type& circle, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle_n_filled( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle( + const circle_type& circle, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto circle_filled( + const point_type& center, + float radius, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse_filled( + const point_type& center, + const extent_type& radius, + float rotation, + color_type color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // TEXT + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + float wrap_width = 99999999.f + ) noexcept -> void; + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + GlyphFlag flag, + float wrap_width = 99999999.f + ) noexcept -> void; + + // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + float wrap_width = 99999999.f + ) noexcept -> extent_type; + + // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + GlyphFlag flag, + float wrap_width = 99999999.f + ) noexcept -> extent_type; + + // ---------------------------------------------------------------------------- + // IMAGE + + // p1________ p2 + // | | + // | | + // p4|_______| p3 + auto image( + texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1 = {0, 0}, + const uv_type& uv_p2 = {1, 0}, + const uv_type& uv_p3 = {1, 1}, + const uv_type& uv_p4 = {0, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + float rounding = .0f, + RenderFlag flag = RenderFlag::NONE, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + float rounding = .0f, + RenderFlag flag = RenderFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + }; + + // ========================================================= + // RENDERER + // ========================================================= + + class Renderer + { + public: + class RendererContext; + + class AccessorTexture; + class AccessorFont; + class AccessorRender; + private: memory::UniquePointer context_; @@ -137,15 +801,56 @@ namespace gal::prometheus::gfx_new virtual auto do_destruct() noexcept -> void = 0; [[nodiscard]] virtual auto do_ready() const noexcept -> bool = 0; - [[nodiscard]] virtual auto do_texture_create(Texture::data_view_type data, Texture::size_type size) noexcept -> texture_id_type = 0; - virtual auto do_texture_update(const Texture& texture) noexcept -> void = 0; + [[nodiscard]] virtual auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type = 0; + virtual auto do_texture_update(const TextureDescriptor& texture) noexcept -> void = 0; virtual auto do_texture_destroy(texture_id_type texture_id) noexcept -> void = 0; - virtual auto do_before_new_frame() noexcept -> void; - virtual auto do_after_new_frame() noexcept -> void; - virtual auto do_before_present() noexcept -> void; - virtual auto do_after_present() noexcept -> void; - virtual auto do_before_end_frame() noexcept -> void; - virtual auto do_after_end_frame() noexcept -> void; + virtual auto do_present(const render_data_list_type& render_data_list) noexcept -> void = 0; + + public: + // ============================================================== + // GLYPH + // ============================================================== + + /** + * @brief Setting the glyph parser + * @param parser New glyph parser + * @return Previous glyph parser, or nullptr if newly set + * @note *Must* ensure that the glyph parser is set before calling @c new_frame + */ + auto set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser*; + + // ============================================================== + // FONT + // ============================================================== + + /** + * @brief Load font from the specified path, assuming the path is a valid font file + * @param path Font path + * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false + * @note *Must* ensure that at least one font is added before calling @c new_frame + */ + auto add_font(const std::filesystem::path& path) noexcept -> bool; + + // ============================================================== + // RENDER LIST + // ============================================================== + + auto new_render_list() noexcept -> RenderList&; }; -} +} // namespace gal::prometheus::gfx_new + +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; +} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx_new/internal/accessor_font.cpp b/src/gfx_new/internal/accessor_font.cpp index 0063842..048d64e 100644 --- a/src/gfx_new/internal/accessor_font.cpp +++ b/src/gfx_new/internal/accessor_font.cpp @@ -4,3 +4,219 @@ // found in the top-level directory of this distribution. #include + +#include + +namespace gal::prometheus::gfx_new +{ + auto FontContext::set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser* + { + return std::exchange(parser_, std::addressof(parser)); + } + + auto FontContext::set_fallback_glyph() noexcept -> void + { + if (fallback_glyph_ == nullptr) + { + { + constexpr GlyphKey key{u'\xFFFD', 16u, GlyphFlag::NONE}; + fallback_glyph_ = glyph_of(key); + } + + if (fallback_glyph_ == nullptr) + { + constexpr GlyphKey key{u'?', 16u, GlyphFlag::NONE}; + fallback_glyph_ = glyph_of(key); + } + + if (fallback_glyph_ == nullptr) + { + constexpr GlyphKey key{u' ', 16u, GlyphFlag::NONE}; + fallback_glyph_ = glyph_of(key); + } + } + } + + auto FontContext::add_font(const std::filesystem::path& path) noexcept -> bool + { + return font_load_queue_.push(path); + } + + auto FontContext::load_all_font() noexcept -> void + { + const auto loader = [this](Font&& font) noexcept -> void + { + font_list_.emplace_back(std::move(font)); + }; + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr, "Use Renderer::set_glyph_parser to set a valid glyph parser first!"); + font_load_queue_.upload(*parser_, loader); + } + + auto FontContext::glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo* + { + for (const auto& font: font_list_) + { + if (const auto* info = font.get_glyph(key); info != nullptr) + { + return info; + } + } + + return nullptr; + } + + auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* + { + return glyph_of({codepoint, size, flag}); + } + + auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo* + { + for (const auto& font: font_list_) + { + if (const auto* info = font.get_glyph(key); info != nullptr) + { + return info; + } + } + + return fallback_glyph_; + } + + auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* + { + return this->glyph_of_or_fallback({codepoint, size, flag}); + } + + auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* + { + if (const auto* info = std::as_const(*this).glyph_of(key); info != nullptr) + { + return info; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); + for (auto& font: font_list_) + { + if (auto result = parser_->parse(font.descriptor.id, key); result.valid()) + { + auto& queue = glyph_upload_queue_list_[std::addressof(font)]; + auto& inserted_info = font.set_glyph(key, result); + + queue.push(inserted_info, std::move(result)); + return std::addressof(inserted_info); + } + } + + return nullptr; + } + + auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* + { + return this->glyph_of({codepoint, size, flag}); + } + + auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo* + { + if (const auto* info = glyph_of(key); info != nullptr) + { + return info; + } + + return fallback_glyph_; + } + + auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* + { + return this->glyph_of_or_fallback({codepoint, size, flag}); + } + + auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::upload_all_glyph(TextureContext& context) noexcept -> void + { + std::ranges::for_each( + glyph_upload_queue_list_ | std::views::values, + [&context](auto& queue) noexcept -> void + { + queue.upload(context); + } + ); + glyph_upload_queue_list_.clear(); + } + + Renderer::AccessorFont::AccessorFont(Renderer& renderer) noexcept + : renderer_{renderer} {} + + auto Renderer::AccessorFont::context() noexcept -> FontContext& + { + return renderer_.get().context_->font_context; + } +} diff --git a/src/gfx_new/internal/accessor_font.hpp b/src/gfx_new/internal/accessor_font.hpp index 6f8d784..1709ce9 100644 --- a/src/gfx_new/internal/accessor_font.hpp +++ b/src/gfx_new/internal/accessor_font.hpp @@ -5,13 +5,92 @@ #pragma once -#include +#include + +#include +#include namespace gal::prometheus::gfx_new { - // todo + class FontContext final + { + public: + using font_list_type = std::vector; + using glyph_upload_queue_list_type = std::unordered_map; + + private: + font_list_type font_list_; + FontLoadQueue font_load_queue_; + + glyph_upload_queue_list_type glyph_upload_queue_list_; + + GlyphParser* parser_; + const GlyphInfo* fallback_glyph_; + + public: + FontContext(const FontContext&) noexcept = delete; + FontContext(FontContext&&) noexcept = default; + auto operator=(const FontContext&) noexcept -> FontContext& = delete; + auto operator=(FontContext&&) noexcept -> FontContext& = default; + + ~FontContext() noexcept = default; + + FontContext() noexcept = default; + + auto set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser*; + + auto set_fallback_glyph() noexcept -> void; + + /** + * @brief Load font from the specified path, assuming the path is a valid font file + * @param path Font path + * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false + */ + auto add_font(const std::filesystem::path& path) noexcept -> bool; + + /** + * @brief Load the fonts previously added by @c add_font + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts + */ + auto load_all_font() noexcept -> void; + + [[nodiscard]] auto glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; + + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; + + [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; + + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; + + /** + * @brief Upload all used glyphs to the texture (if it is not already uploaded) + * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture + */ + auto upload_all_glyph(TextureContext& context) noexcept -> void; + }; + + /** + * @brief Proxy class for accessing the Renderer's private interface (this class helps us not to expose too many implementation details to the outside world) + */ class Renderer::AccessorFont final { public: + using renderer_type = memory::RefWrapper; + + private: + renderer_type renderer_; + + public: + explicit AccessorFont(Renderer& renderer) noexcept; + + [[nodiscard]] auto context() noexcept -> FontContext&; }; } diff --git a/src/gfx_new/internal/accessor_render.cpp b/src/gfx_new/internal/accessor_render.cpp index e03fffc..8697169 100644 --- a/src/gfx_new/internal/accessor_render.cpp +++ b/src/gfx_new/internal/accessor_render.cpp @@ -4,3 +4,16 @@ // found in the top-level directory of this distribution. #include + +#include + +namespace gal::prometheus::gfx_new +{ + Renderer::AccessorRender::AccessorRender(Renderer& renderer) noexcept + : renderer_{renderer} {} + + auto Renderer::AccessorRender::context() const noexcept -> const RenderContext& + { + return renderer_.get().context_->render_context; + } +} diff --git a/src/gfx_new/internal/accessor_render.hpp b/src/gfx_new/internal/accessor_render.hpp index 535ebca..bdd04f1 100644 --- a/src/gfx_new/internal/accessor_render.hpp +++ b/src/gfx_new/internal/accessor_render.hpp @@ -5,13 +5,33 @@ #pragma once -#include +#include + +#include namespace gal::prometheus::gfx_new { - // todo + class RenderContext final + { + public: + RenderListSharedData render_list_shared_data; + std::vector render_lists; + }; + + /** + * @brief Proxy class for accessing the Renderer's private interface (this class helps us not to expose too many implementation details to the outside world) + */ class Renderer::AccessorRender final { public: + using renderer_type = memory::RefWrapper; + + private: + renderer_type renderer_; + + public: + explicit AccessorRender(Renderer& renderer) noexcept; + + [[nodiscard]] auto context() const noexcept -> const RenderContext&; }; } diff --git a/src/gfx_new/internal/accessor_texture.cpp b/src/gfx_new/internal/accessor_texture.cpp index 2a4c52c..7e6e1b5 100644 --- a/src/gfx_new/internal/accessor_texture.cpp +++ b/src/gfx_new/internal/accessor_texture.cpp @@ -5,6 +5,8 @@ #include +#include + namespace gal::prometheus::gfx_new { auto TextureContext::root_id() const noexcept -> texture_atlas_id_type @@ -69,17 +71,20 @@ namespace gal::prometheus::gfx_new auto TextureContext::write( const Texture::data_view_type data, const Texture::size_type size - ) noexcept -> primitive::basic_rect_2d + ) noexcept -> random_write_result_type { // todo - return this->write(root_id(), data, size); + const auto id = root_id(); + const auto uv = this->write(id, data, size); + + return {.texture_atlas_id = id, .uv = uv}; } - auto TextureContext::upload(Renderer& renderer) noexcept -> void + auto TextureContext::upload(Renderer::AccessorTexture& accessor) noexcept -> void { std::ranges::for_each( texture_atlas_list_, - [accessor = Renderer::AccessorTexture{renderer}](auto& texture) mutable noexcept -> void + [&accessor](auto& texture) mutable noexcept -> void { if (not texture.uploaded()) { @@ -96,7 +101,14 @@ namespace gal::prometheus::gfx_new Renderer::AccessorTexture::AccessorTexture(Renderer& renderer) noexcept : renderer_{renderer} {} - auto Renderer::AccessorTexture::upload(texture_type& texture) noexcept -> void + auto Renderer::AccessorTexture::context() const noexcept -> const TextureContext& + { + const auto& renderer_context = renderer_.get().context_; + + return renderer_context->texture_context; + } + + auto Renderer::AccessorTexture::upload(Texture& texture) noexcept -> void { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture.uploaded()); @@ -106,7 +118,7 @@ namespace gal::prometheus::gfx_new texture.texture_.dirty = false; } - auto Renderer::AccessorTexture::update(texture_type& texture) noexcept -> void + auto Renderer::AccessorTexture::update(Texture& texture) noexcept -> void { auto& renderer = renderer_.get(); @@ -114,7 +126,7 @@ namespace gal::prometheus::gfx_new texture.texture_.dirty = false; } - auto Renderer::AccessorTexture::update_if_dirty(texture_type& texture) noexcept -> void + auto Renderer::AccessorTexture::update_if_dirty(Texture& texture) noexcept -> void { if (texture.dirty()) { diff --git a/src/gfx_new/internal/accessor_texture.hpp b/src/gfx_new/internal/accessor_texture.hpp index d9b1195..fd906db 100644 --- a/src/gfx_new/internal/accessor_texture.hpp +++ b/src/gfx_new/internal/accessor_texture.hpp @@ -7,7 +7,7 @@ #include -#include +#include #include #include @@ -17,9 +17,6 @@ namespace gal::prometheus::gfx_new class TextureContext final { public: - // index - using texture_atlas_id_type = std::uint32_t; - using texture_atlas_list_type = std::vector; private: @@ -63,22 +60,28 @@ namespace gal::prometheus::gfx_new Texture::size_type size ) noexcept -> primitive::basic_rect_2d; + struct random_write_result_type + { + texture_atlas_id_type texture_atlas_id; + primitive::basic_rect_2d uv; + }; + /** * @brief Writes the given glyph data to any holdable texture * @param data Glyph data to write * @param size Size of data (rectangle area) - * @return The uv coordinate of the position where the data is written + * @return The id of the written texture atlas and the uv coordinate of the position where the data is written */ auto write( Texture::data_view_type data, Texture::size_type size - ) noexcept -> primitive::basic_rect_2d; + ) noexcept -> random_write_result_type; /** * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) */ - auto upload(Renderer& renderer) noexcept -> void; + auto upload(Renderer::AccessorTexture& accessor) noexcept -> void; }; /** @@ -89,29 +92,29 @@ namespace gal::prometheus::gfx_new public: using renderer_type = memory::RefWrapper; - using texture_type = gfx_new::Texture; - private: renderer_type renderer_; public: explicit AccessorTexture(Renderer& renderer) noexcept; + [[nodiscard]] auto context() const noexcept -> const TextureContext&; + /** * @brief Upload texture atlas data to GPU and get GPU resource handle */ - auto upload(texture_type& texture) noexcept -> void; + auto upload(Texture& texture) noexcept -> void; /** * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) * @note Does not check for the need to update */ - auto update(texture_type& texture) noexcept -> void; + auto update(Texture& texture) noexcept -> void; /** * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) * @note If no update is needed (not dirty) then do nothing */ - auto update_if_dirty(texture_type& texture) noexcept -> void; + auto update_if_dirty(Texture& texture) noexcept -> void; }; } diff --git a/src/gfx_new/internal/font.cpp b/src/gfx_new/internal/font.cpp new file mode 100644 index 0000000..1f84b0d --- /dev/null +++ b/src/gfx_new/internal/font.cpp @@ -0,0 +1,120 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::gfx_new +{ + auto GlyphUploadQueue::push(GlyphInfo& info, GlyphParser::GlyphDescriptor&& descriptor) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(descriptor.valid()); + + list_.emplace_back(memory::ref(info), std::move(descriptor)); + } + + auto GlyphUploadQueue::upload(TextureContext& context) noexcept -> void + { + std::ranges::for_each( + list_, + [&context](element_type& element) noexcept -> void + { + auto& info = element.info.get(); + const auto& descriptor = element.descriptor; + + const auto size = descriptor.rect.size(); + const Texture::data_view_type data{descriptor.data.get(), static_cast(size.width) * size.height}; + + // FIXME-OPT: + // Do we need to specify a texture for the upload? + const auto [texture_atlas_id, uv] = context.write(data, size); + info.texture_atlas_id = texture_atlas_id; + info.uv = uv; + } + ); + list_.clear(); + } + + auto Font::get_glyph(const GlyphKey& key) const noexcept -> const GlyphInfo* + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(descriptor.id != invalid_font_id); + + if (const auto it = cached_glyphs.find(key); it != cached_glyphs.end()) + { + return std::addressof(it->second); + } + + return nullptr; + } + + auto Font::set_glyph(const GlyphKey& key, const GlyphParser::GlyphDescriptor& glyph_descriptor) noexcept -> GlyphInfo& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(descriptor.id != invalid_font_id); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_descriptor.valid()); + + GlyphInfo info{}; + info.rect = glyph_descriptor.rect; + info.advance_x = glyph_descriptor.advance_x; + info.visible = glyph_descriptor.visible; + info.colored = glyph_descriptor.colored; + + return cached_glyphs.insert_or_assign(key, info).first->second; + } + + FontLoadQueue::FontLoadQueue() noexcept + : new_font_index_{0} {} + + auto FontLoadQueue::push(const std::filesystem::path& path) noexcept -> bool + { + std::ifstream file{path, std::ios::binary}; + if (not file.is_open()) + { + // todo: error handling + return false; + } + + file.seekg(0, std::ios::end); + const auto size = file.tellg(); + + auto data = std::make_unique_for_overwrite(size); + file.seekg(0, std::ios::beg); + file.read(reinterpret_cast(data.get()), size); + file.close(); + + list_.emplace_back(data_type{data.release()}, static_cast(size)); + return true; + } + + auto FontLoadQueue::upload(GlyphParser& parser, const functional::function_reference_wrapper font_dest) noexcept -> void + { + if (const auto size = static_cast(list_.size()); size > new_font_index_) + { + auto new_fonts = std::ranges::subrange{list_.begin() + new_font_index_, list_.end()}; + + std::ranges::for_each( + new_fonts, + [&](const Descriptor& descriptor) noexcept -> void + { + if (const auto result = parser.load({descriptor.data.get(), descriptor.size}); result.valid()) + { + Font font{.descriptor = {.identifier = result.identifier, .id = result.id}, .cached_glyphs = {}}; + font_dest(std::move(font)); + } + else + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + } + } + ); + + new_font_index_ = size; + } + } +} diff --git a/src/gfx_new/internal/font.hpp b/src/gfx_new/internal/font.hpp new file mode 100644 index 0000000..266a1ad --- /dev/null +++ b/src/gfx_new/internal/font.hpp @@ -0,0 +1,181 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include + +#include +#include + +namespace gal::prometheus::gfx_new +{ + class TextureContext; + + /** + * @brief This class is essentially the same as @c GlyphParser::GlyphCode, but takes up less memory space. + */ + class GlyphKey final + { + public: + std::uint32_t codepoint; + std::uint32_t size : 26; + std::uint32_t flag : 6; + + constexpr GlyphKey(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept + : codepoint{codepoint}, + size{size}, + flag{static_cast(flag)} {} + + [[nodiscard]] constexpr auto operator==(const GlyphKey& other) const noexcept -> bool + { + return codepoint == other.codepoint and size == other.size and flag == other.flag; + } + + [[nodiscard]] constexpr explicit(false) operator GlyphParser::GlyphCode() const noexcept + { + return {codepoint, size, static_cast(flag)}; + } + + struct hasher + { + [[nodiscard]] auto operator()(const GlyphKey& key) const noexcept -> std::size_t + { + return std::hash{}(key.codepoint) ^ std::hash{}(key.size) ^ std::hash{}(static_cast(key.flag)); + } + }; + }; + + static_assert(sizeof(GlyphKey) == sizeof(std::uint32_t) + sizeof(std::uint32_t)); + + /** + * @brief Glyph info (bitmap info & texture info) + */ + class GlyphInfo final + { + public: + using rect_type = GlyphParser::GlyphDescriptor::rect_type; + using uv_type = primitive::basic_rect_2d; + + // ============= + // Data filled when loading glyph + // ============= + + // Bitmap infos of this glyph + rect_type rect; + float advance_x; + bool visible; + bool colored; + + // ============= + // Data filled when writing texture + // ============= + + // The id of the texture atlas where the glyph is located + // This id is present if and only if the glyph is in a texture atlas, otherwise it is invalid_texture_atlas_id + texture_atlas_id_type texture_atlas_id{invalid_texture_atlas_id}; + // The uv coordinate of the glyph in the texture atlas + uv_type uv{-1, -1, -1, -1}; + }; + + /** + * @brief Queue of glyph data to be uploaded to the texture + */ + class GlyphUploadQueue final + { + public: + struct element_type + { + memory::RefWrapper info; + GlyphParser::GlyphDescriptor descriptor; + }; + + using list_type = std::vector; + + private: + list_type list_; + + public: + GlyphUploadQueue(const GlyphUploadQueue&) noexcept = delete; + GlyphUploadQueue(GlyphUploadQueue&&) noexcept = default; + auto operator=(const GlyphUploadQueue&) noexcept -> GlyphUploadQueue& = delete; + auto operator=(GlyphUploadQueue&&) noexcept -> GlyphUploadQueue& = default; + + ~GlyphUploadQueue() noexcept = default; + + GlyphUploadQueue() noexcept = default; + + auto push(GlyphInfo& info, GlyphParser::GlyphDescriptor&& descriptor) noexcept -> void; + + auto upload(TextureContext& context) noexcept -> void; + }; + + class Font final + { + public: + GlyphParser::FontDescriptor descriptor; + std::unordered_map cached_glyphs; + + /** + * @brief Get the glyph information of the specified codepoint, if it can't be found, then return a null pointer + * @param key {codepoint, size, flag} + * @return The glyph information of the specified codepoint, or a null pointer if it can't be found + */ + [[nodiscard]] auto get_glyph(const GlyphKey& key) const noexcept -> const GlyphInfo*; + + /** + * @brief Set the glyph information of the specified codepoint, override if it already exists + * @param key {codepoint, size, flag} + * @param glyph_descriptor The parse result of the specified codepoint + * @return GlyphInfo after insertion + */ + [[nodiscard]] auto set_glyph(const GlyphKey& key, const GlyphParser::GlyphDescriptor& glyph_descriptor) noexcept -> GlyphInfo&; + }; + + /** + * @brief Queue of font data to be loaded to the context + */ + class FontLoadQueue final + { + using descriptor = GlyphParser::FontDescriptor; + + public: + using element_type = descriptor::element_type; + using data_type = descriptor::data_type; + using data_view_type = descriptor::data_view_type; + + using size_type = descriptor::size_type; + + private: + class Descriptor final + { + public: + data_type data; + size_type size; + }; + + using list_type = std::vector; + + list_type list_; + list_type::difference_type new_font_index_; + + public: + FontLoadQueue(const FontLoadQueue&) noexcept = delete; + FontLoadQueue(FontLoadQueue&&) noexcept = default; + auto operator=(const FontLoadQueue&) noexcept -> FontLoadQueue& = delete; + auto operator=(FontLoadQueue&&) noexcept -> FontLoadQueue& = default; + + ~FontLoadQueue() noexcept = default; + + FontLoadQueue() noexcept; + + auto push(const std::filesystem::path& path) noexcept -> bool; + + auto upload(GlyphParser& parser, functional::function_reference_wrapper font_dest) noexcept -> void; + }; +} diff --git a/src/gfx_new/internal/render_list.cpp b/src/gfx_new/internal/render_list.cpp new file mode 100644 index 0000000..70a5d62 --- /dev/null +++ b/src/gfx_new/internal/render_list.cpp @@ -0,0 +1,2411 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include +#include +#include +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace gfx_new; + + // @see https://stackoverflow.com/a/2244088/15194693 + // Number of segments (N) is calculated using equation: + // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r + [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto + { + constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto + { + return (v + 1) / 2 * 2; + }; + + return std::ranges::clamp( + circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), + RenderListSharedData::circle_segments_min, + RenderListSharedData::circle_segments_max + ); + } + + [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto + { + return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); + } + + [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto + { + return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; + } + + template + [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> RenderListSharedData::vertex_sample_points_type + { + return [](std::index_sequence) noexcept -> RenderListSharedData::vertex_sample_points_type + { + constexpr auto make_point = []() noexcept -> point_type + { + const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; + return {math::cos(a), -math::sin(a)}; + }; + + return {{make_point.template operator()()...}}; + }(std::make_index_sequence{}); + } + + [[nodiscard]] constexpr auto range_of_arc(const RenderArcFlag flag) noexcept -> std::pair + { + static_assert(RenderListSharedData::vertex_sample_points_count % 12 == 0); + constexpr auto factor = static_cast(RenderListSharedData::vertex_sample_points_count / 12); + + switch (flag) + { + case RenderArcFlag::Q1: + { + return std::make_pair(0 * factor, 3 * factor); + } + case RenderArcFlag::Q2: + { + return std::make_pair(3 * factor, 6 * factor); + } + case RenderArcFlag::Q3: + { + return std::make_pair(6 * factor, 9 * factor); + } + case RenderArcFlag::Q4: + { + return std::make_pair(9 * factor, 12 * factor); + } + case RenderArcFlag::TOP: + { + return std::make_pair(0 * factor, 6 * factor); + } + case RenderArcFlag::BOTTOM: + { + return std::make_pair(6 * factor, 12 * factor); + } + case RenderArcFlag::LEFT: + { + return std::make_pair(3 * factor, 9 * factor); + } + case RenderArcFlag::RIGHT: + { + return std::make_pair(9 * factor, 15 * factor); + } + case RenderArcFlag::ALL: + { + return std::make_pair(0 * factor, 12 * factor); + } + case RenderArcFlag::Q1_CLOCK_WISH: + { + return std::make_pair(3 * factor, 0 * factor); + } + case RenderArcFlag::Q2_CLOCK_WISH: + { + return std::make_pair(6 * factor, 3 * factor); + } + case RenderArcFlag::Q3_CLOCK_WISH: + { + return std::make_pair(9 * factor, 6 * factor); + } + case RenderArcFlag::Q4_CLOCK_WISH: + { + return std::make_pair(12 * factor, 9 * factor); + } + case RenderArcFlag::TOP_CLOCK_WISH: + { + return std::make_pair(6 * factor, 0 * factor); + } + case RenderArcFlag::BOTTOM_CLOCK_WISH: + { + return std::make_pair(12 * factor, 6 * factor); + } + case RenderArcFlag::LEFT_CLOCK_WISH: + { + return std::make_pair(9 * factor, 3 * factor); + } + case RenderArcFlag::RIGHT_CLOCK_WISH: + { + return std::make_pair(15 * factor, 9 * factor); + } + case RenderArcFlag::ALL_CLOCK_WISH: + { + return std::make_pair(12 * factor, 0 * factor); + } + default: // NOLINT(clang-diagnostic-covered-switch-default) + { + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + } + } + } + + [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const RenderFlag flag) noexcept -> RenderFlag + { + using enum RenderFlag; + + if ((flag & ROUND_CORNER_MASK) == NONE) + { + return ROUND_CORNER_ALL | flag; + } + + return flag; + } + + [[nodiscard]] constexpr auto to_fixed_normal(const float x, const float y) noexcept -> std::pair + { + if (const auto d = math::pow(x, 2) + math::pow(y, 2); d > 1e-6f) + { + // fixme + const auto inv_len = [d] + { + // #if defined(__AVX512F__) + // __m512 d_v = _mm512_set1_ps(d); + // __m512 inv_len_v = _mm512_rcp14_ps(d_v); + // return _mm512_cvtss_f32(inv_len_v); + // #elif defined(__AVX__) + // __m256 d_v = _mm256_set1_ps(d); + // __m256 inv_len_v = _mm256_rcp_ps(d_v); + // return _mm256_cvtss_f32(inv_len_v); + // #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) + // __m128 d_v = _mm_set_ss(d); + // __m128 inv_len_v = _mm_rcp_ss(d_v); + // return _mm_cvtss_f32(inv_len_v); + // #else + return 1.0f / d; + // #endif + }(); + + return {x * inv_len, y * inv_len}; + } + + return {x, y}; + } + + // fixme + constexpr std::size_t bezier_curve_casteljau_max_level = 10; + + constexpr auto bezier_cubic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 3); + const auto w2 = 3 * math::pow(u, 2) * tolerance; + const auto w3 = 3 * u * math::pow(tolerance, 2); + const auto w4 = math::pow(tolerance, 3); + + return {p1.x * w1 + p2.x * w2 + p3.x * w3 + p4.x * w4, p1.y * w1 + p2.y * w2 + p3.y * w3 + p4.y * w4}; + }; + + constexpr auto bezier_quadratic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 2); + const auto w2 = 2 * u * tolerance; + const auto w3 = math::pow(tolerance, 2); + + return {p1.x * w1 + p2.x * w2 + p3.x * w3, p1.y * w1 + p2.y * w2 + p3.y * w3}; + }; + + class RenderDataAppender final + { + public: + using size_type = RenderData::size_type; + + using command_type = RenderData::command_type; + + using vertex_list_type = RenderData::vertex_list_type; + using index_list_type = RenderData::index_list_type; + + private: + // command_type::element_count + memory::RefWrapper element_count_; + // RenderList::vertex_list + memory::RefWrapper vertex_list_; + // RenderList::index_list_; + memory::RefWrapper index_list_; + + public: + RenderDataAppender(command_type& command, vertex_list_type& vertex_list, index_list_type& index_list) noexcept + : element_count_{command.element_count}, + vertex_list_{vertex_list}, + index_list_{index_list} {} + + [[nodiscard]] auto vertex_count() const noexcept -> size_type + { + const auto& list = vertex_list_.get(); + const auto size = list.size(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size < std::numeric_limits::max(), "Too many vertex!"); + + return static_cast(size); + } + + auto reserve(const size_type vertex_count, const size_type index_count) noexcept -> void + { + auto& element_count = element_count_.get(); + auto& vertex = vertex_list_.get(); + auto& index = index_list_.get(); + + element_count += index_count; + vertex.reserve(vertex.size() + vertex_count); + index.reserve(index.size() + index_count); + } + + auto reserve(const std::size_t vertex_count, const std::size_t index_count) noexcept -> void + { + reserve(static_cast(vertex_count), static_cast(index_count)); + } + + auto add_vertex(const point_type& point, const uv_type& uv, color_type color) noexcept -> void + { + auto& list = vertex_list_.get(); + + list.emplace_back(point, uv, color); + } + + auto add_index(const index_type a, const index_type b, const index_type c) noexcept -> void + { + auto& list = index_list_.get(); + + list.push_back(a); + list.push_back(b); + list.push_back(c); + } + }; + + class RenderListDrawer final + { + public: + memory::RefWrapper self; + + using size_type = RenderData::size_type; + + using path_list_type = std::vector; + + path_list_type path_list; + + [[nodiscard]] auto make_appender() noexcept -> RenderDataAppender; + + // ============================================================= + // DRAW + // ============================================================= + + auto draw_polygon_line(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; + auto draw_polygon_line_aa(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; + auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; + auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; + auto draw_rect_filled( + const rect_type& rect, + const uv_type& uv, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + auto draw_rect_filled( + const rect_type& rect, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + auto draw_text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& p, + color_type color, + float wrap_width, + GlyphFlag flag + ) noexcept -> void; + auto draw_text_size( + std::string_view utf8_text, + std::uint32_t font_size, + float wrap_width, + GlyphFlag flag + ) noexcept -> extent_type; + auto draw_image( + texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + color_type color + ) noexcept -> void; + auto draw_image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + color_type color, + float rounding, + RenderFlag flag + ) noexcept -> void; + + // ============================================================= + // PATH + // ============================================================= + + auto path_clear() noexcept -> void; + auto path_reserve(std::size_t size) noexcept -> void; + auto path_reserve_extra(std::size_t size) noexcept -> void; + auto path_pin(const point_type& point) noexcept -> void; + auto path_stroke(color_type color, RenderFlag flag, float thickness) noexcept -> void; + auto path_stroke(color_type color) noexcept -> void; + auto path_arc_fast(const circle_type& circle, int from, int to) noexcept -> void; + auto path_arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> void; + auto path_arc_n(const circle_type& circle, float from, float to, std::uint32_t segments) noexcept -> void; + auto path_arc(const circle_type& circle, float from, float to) noexcept -> void; + auto path_arc_elliptical_n(const ellipse_type& ellipse, float from, float to, std::uint32_t segments) noexcept -> void; + auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void; + auto path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void; + auto path_bezier_cubic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + float tessellation_tolerance, + std::size_t level + ) noexcept -> void; + auto path_bezier_quadratic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + float tessellation_tolerance, + std::size_t level + ) noexcept -> void; + auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void; + auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void; + }; +} + +namespace gal::prometheus::gfx_new +{ + RenderListSharedData::RenderListSharedData() noexcept + : circle_segment_counts{}, + vertex_sample_points{vertex_sample_points_calc()}, + circle_segment_max_error{0}, + arc_fast_radius_cutoff{0}, + curve_tessellation_tolerance{1.25f} + { + set_circle_tessellation_max_error(.3f); + } + + auto RenderListSharedData::circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type + { + // ceil to never reduce accuracy + if (const auto radius_index = static_cast(radius + .999999f); radius_index < circle_segment_counts.size()) + { + return circle_segment_counts[radius_index]; + } + return static_cast(circle_segments_calc(radius, circle_segment_max_error)); + } + + auto RenderListSharedData::vertex_sample_point(const std::size_t index) const noexcept -> const point_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points.size()); + + return vertex_sample_points[index]; + } + + auto RenderListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); + + if (circle_segment_max_error == max_error) // NOLINT(clang-diagnostic-float-equal) + { + return; + } + + for (auto [index, count]: circle_segment_counts | std::views::enumerate) + { + const auto radius = static_cast(index); + count = static_cast(circle_segments_calc(radius, max_error)); + } + + circle_segment_max_error = max_error; + arc_fast_radius_cutoff = circle_segments_calc_radius(vertex_sample_points_count, max_error); + } + + auto RenderListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); + + curve_tessellation_tolerance = tolerance; + } + + class RenderList::RenderListContext final + { + friend RenderListDrawer; + + public: + using size_type = RenderData::size_type; + + using command_type = RenderData::command_type; + + using vertex_list_type = RenderData::vertex_list_type; + using index_list_type = RenderData::index_list_type; + using command_list_type = RenderData::command_list_type; + + mutable memory::RefWrapper renderer; + + RenderListFlag render_list_flag; + + // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) + // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 + // command_list: + // 0: .scissor = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) + // 1: .scissor = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) + // 2: .scissor = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) + command_list_type command_list; + vertex_list_type vertex_list; + index_list_type index_list; + + rect_type this_command_scissor; + texture_id_type this_command_texture; + + auto push_command() noexcept -> void; + + auto on_scissor_changed() noexcept -> void; + auto on_texture_changed() noexcept -> void; + + [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData& + { + const Renderer::AccessorRender accessor{renderer.get()}; + + return accessor.context().render_list_shared_data; + } + + [[nodiscard]] auto default_texture() const noexcept -> texture_id_type + { + const Renderer::AccessorTexture accessor{renderer.get()}; + + return accessor.context().root().id(); + } + + auto reset() noexcept -> void + { + command_list.clear(); + vertex_list.clear(); + index_list.clear(); + + // we don't know the size of the clip rect, so we need the user to set it + this_command_scissor = {}; + // the first texture is always the (default) font texture + this_command_texture = default_texture(); + + // we always have a command ready in the buffer + command_list.emplace_back( + command_type{ + .scissor = this_command_scissor, + .texture = this_command_texture, + .index_offset = static_cast(index_list.size()), + // set by subsequent draw_xxx + .element_count = 0, + } + ); + } + + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE + + auto push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& + { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); + + this_command_scissor = intersect_with_current_scissor ? rect.combine_min(current_scissor) : rect; + + on_scissor_changed(); + return command_list.back().scissor; + } + + auto pop_scissor() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); + this_command_scissor = command_list[command_list.size() - 2].scissor; + + on_scissor_changed(); + } + + auto push_texture(const texture_id_type texture) noexcept -> void + { + this_command_texture = texture; + + on_texture_changed(); + } + + auto pop_texture() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); + this_command_texture = command_list[command_list.size() - 2].texture; + + on_texture_changed(); + } + }; + + RenderList::RenderList(Renderer& renderer) noexcept + : context_{memory::make_unique(renderer, RenderListFlag::DEFAULT)} {} + + RenderList::RenderList(RenderList&&) noexcept = default; + + auto RenderList::operator=(RenderList&&) noexcept -> RenderList& = default; + + RenderList::~RenderList() noexcept = default; + + auto RenderList::reset() noexcept -> void + { + context_->reset(); + } + + auto RenderList::set_flag(const RenderListFlag flag) noexcept -> void + { + context_->render_list_flag = flag; + } + + auto RenderList::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& + { + return context_->push_scissor(rect, intersect_with_current_scissor); + } + + auto RenderList::pop_scissor() noexcept -> void + { + context_->pop_scissor(); + } + + auto RenderList::push_texture(const texture_id_type texture) noexcept -> void + { + context_->push_texture(texture); + } + + auto RenderList::pop_texture() noexcept -> void + { + context_->pop_texture(); + } + + auto RenderList::line( + const point_type& from, + const point_type& to, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_pin(from); + drawer.path_pin(to); + + drawer.path_stroke(color, RenderFlag::NONE, thickness); + } + + auto RenderList::triangle( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_pin(a); + drawer.path_pin(b); + drawer.path_pin(c); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + auto RenderList::triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_pin(a); + drawer.path_pin(b); + drawer.path_pin(c); + + drawer.path_stroke(color); + } + + auto RenderList::rect( + const rect_type& rect, + const color_type color, + const float rounding, + const RenderFlag flag, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_rect(rect, rounding, flag); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + auto RenderList::rect( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const RenderFlag flag, + const float thickness + ) noexcept -> void + { + return rect({left_top, right_bottom}, color, rounding, flag, thickness); + } + + auto RenderList::rect_filled( + const rect_type& rect, + const color_type color, + const float rounding, + const RenderFlag flag + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + { + drawer.draw_rect_filled(rect, color, color, color, color); + } + else + { + drawer.path_rect(rect, rounding, flag); + drawer.path_stroke(color); + } + } + + auto RenderList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const RenderFlag flag + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color, rounding, flag); + } + + auto RenderList::rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto RenderList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto RenderList::quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_quadrilateral(p1, p2, p3, p4); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + auto RenderList::quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_quadrilateral(p1, p2, p3, p4); + + drawer.path_stroke(color); + } + + auto RenderList::circle_n( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + auto RenderList::circle_n( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle_n({center, radius}, color, segments, thickness); + } + + auto RenderList::ellipse_n( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + + void RenderList::ellipse_n( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + { + return ellipse_n({center, radius, rotation}, color, segments, thickness); + } + + auto RenderList::circle_n_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color); + } + + auto RenderList::circle_n_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return circle_n_filled({center, radius}, color, segments); + } + + auto RenderList::ellipse_n_filled( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + drawer.path_stroke(color); + } + + auto RenderList::ellipse_n_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_n_filled({center, radius, rotation}, color, segments); + } + + void RenderList::circle( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + + drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, RenderListSharedData::circle_segments_min, RenderListSharedData::circle_segments_max); + + circle_n(circle, color, clamped_segments, thickness); + } + } + + auto RenderList::circle( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle({center, radius}, color, segments, thickness); + } + + auto RenderList::circle_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + + drawer.path_stroke(color); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, RenderListSharedData::circle_segments_min, RenderListSharedData::circle_segments_max); + + circle_n_filled(circle, color, clamped_segments); + } + } + + auto RenderList::circle_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + circle_filled({center, radius}, color, segments); + } + + auto RenderList::ellipse( + const ellipse_type& ellipse, + const color_type color, + std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + if (segments == 0) + { + // fixme + segments = context_->shared_data().circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n(ellipse, color, segments, thickness); + } + + auto RenderList::ellipse( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return ellipse({center, radius, rotation}, color, segments, thickness); + } + + auto RenderList::ellipse_filled( + const ellipse_type& ellipse, + const color_type color, + std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + if (segments == 0) + { + // fixme + segments = context_->shared_data().circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n_filled(ellipse, color, segments); + } + + auto RenderList::ellipse_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_filled({center, radius, rotation}, color, segments); + } + + auto RenderList::bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_bezier_curve(p1, p2, p3, p4, segments); + + drawer.path_stroke(color, RenderFlag::NONE, thickness); + } + + auto RenderList::bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.path_bezier_quadratic_curve(p1, p2, p3, segments); + + drawer.path_stroke(color, RenderFlag::NONE, thickness); + } + + auto RenderList::text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& point, + const color_type color, + const float wrap_width + ) noexcept -> void + { + return this->text(utf8_text, font_size, point, color, GlyphFlag::NONE, wrap_width); + } + + auto RenderList::text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& point, + const color_type color, + const GlyphFlag flag, + const float wrap_width + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(wrap_width > 0); + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.draw_text(utf8_text, font_size, point, color, wrap_width, flag); + } + + auto RenderList::text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const float wrap_width + ) noexcept -> extent_type + { + return this->text_size(utf8_text, font_size, GlyphFlag::NONE, wrap_width); + } + + auto RenderList::text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const GlyphFlag flag, + const float wrap_width + ) noexcept -> extent_type + { + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + return drawer.draw_text_size(utf8_text, font_size, wrap_width, flag); + } + + auto RenderList::image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); + } + + auto RenderList::image( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void + { + this->image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + + auto RenderList::image( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + this->image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); + } + + auto RenderList::image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const float rounding, + const RenderFlag flag, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + RenderListDrawer drawer{.self = *context_, .path_list = {}}; + + drawer.draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); + } + + auto RenderList::image_rounded( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const float rounding, + const RenderFlag flag, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + this->image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + } +} + +namespace +{ + auto RenderListDrawer::make_appender() noexcept -> RenderDataAppender + { + auto& render_list = self.get(); + + return {render_list.command_list.back(), render_list.vertex_list, render_list.index_list}; + } + + auto RenderListDrawer::draw_polygon_line(const color_type color, const RenderFlag render_flag, const float thickness) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + auto appender = make_appender(); + + const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + + const auto vertex_count = segments_count * 4; + const auto index_count = segments_count * 6; + appender.reserve(vertex_count, index_count); + + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + + const auto& p1 = path_point[i]; + const auto& p2 = path_point[n]; + + auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); + normalized_x *= (thickness * .5f); + normalized_y *= (thickness * .5f); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = shared_data.white_pixel_uv; + + appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + } + + auto RenderListDrawer::draw_polygon_line_aa(const color_type color, const RenderFlag render_flag, float thickness) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& render_list = self.get(); + const auto render_list_flag = render_list.render_list_flag; + const auto& shared_data = render_list.shared_data(); + auto appender = make_appender(); + + const auto& opaque_uv = shared_data.white_pixel_uv; + const auto transparent_color = color.transparent(); + + const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + const auto is_thick_line = thickness > 1.f; + + thickness = std::ranges::max(thickness, 1.f); + const auto thickness_integer = static_cast(thickness); + const auto thickness_fractional = thickness - static_cast(thickness_integer); + + const auto is_use_texture = + ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); + + const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); + const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); + appender.reserve(vertex_cont, index_count); + + // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; + + // Calculate normals (tangents) for each line segment + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + + if (not is_closed) + { + temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + } + + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + if (is_use_texture or not is_thick_line) + { + // [PATH 1] Texture-based lines (thick or non-thick) + + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA + const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; + } + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + // closed + (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + ); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // dm_x, dm_y are offset to the outer edge of the AA area + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= half_draw_size; + dm_y *= half_draw_size; + + // Add temporary vertexes for the outer edges + temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; + temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; + + if (is_use_texture) + { + // Add indices for two triangles + + // right + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); + // left + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); + } + else + { + // Add indexes for four triangles + + // right 1 + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); + // right 2 + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); + // left 1 + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // left 2 + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + } + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertexes for each point on the line + if (is_use_texture) + { + const auto& uv = shared_data.baked_line_uvs[thickness_integer]; + + const auto uv0 = uv.left_top(); + const auto uv1 = uv.right_bottom(); + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); + } + } + else + { + // If we're not using a texture, we need the center vertex as well + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // center of line + appender.add_vertex(path_point[i], opaque_uv, color); + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); + } + } + } + else + { + // [PATH 2] Non-texture-based lines (non-thick) + + // we need to draw the solid line core and thus require four vertices per point + const auto half_inner_thickness = (thickness - 1.f) * .5f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + const auto point_last = path_point_count - 1; + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + } + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast((first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + 4)); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); + const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); + const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); + const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); + + // Add temporary vertices + temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; + temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + + // Add indexes + appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); + appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertices + for (std::decay_t i = 0; i < path_point_count; ++i) + { + appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); + appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); + } + } + } + + auto RenderListDrawer::draw_convex_polygon_line_filled(const color_type color) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + auto appender = make_appender(); + + const auto vertex_count = path_point_count; + const auto index_count = (path_point_count - 2) * 3; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = shared_data.white_pixel_uv; + + std::ranges::for_each( + path_point, + [&](const point_type& point) noexcept -> void + { + appender.add_vertex(point, opaque_uv, color); + } + ); + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); + + appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); + } + } + + auto RenderListDrawer::draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void + { + const auto path_point_count = path_list.size(); + const auto& path_point = path_list; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + auto appender = make_appender(); + + const auto& opaque_uv = shared_data.white_pixel_uv; + const auto transparent_color = color.transparent(); + + const auto vertex_count = path_point_count * 2; + const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_inner_index = static_cast(appender.vertex_count()); + const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); + + // Add indexes for fill + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + + appender.add_index(current_vertex_inner_index + 0, current_vertex_inner_index + static_cast((i - 1) << 1), current_vertex_inner_index + static_cast(i << 1)); + } + + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + // Average normals + const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= .5f; + dm_y *= .5f; + + // inner + appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); + // outer + appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + + // Add indexes for fringes + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + + appender.add_index( + current_vertex_inner_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(i << 1) + ); + appender.add_index( + current_vertex_outer_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(n << 1) + ); + } + } + + auto RenderListDrawer::draw_rect_filled( + const rect_type& rect, + const uv_type& uv, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(rect.left_top(), uv, color_left_top); + appender.add_vertex(rect.right_top(), uv, color_right_top); + appender.add_vertex(rect.right_bottom(), uv, color_right_bottom); + appender.add_vertex(rect.left_bottom(), uv, color_left_bottom); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + + auto RenderListDrawer::draw_rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + const auto& opaque_uv = shared_data.white_pixel_uv; + + draw_rect_filled(rect, opaque_uv, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto RenderListDrawer::draw_text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& p, + const color_type color, + const float wrap_width, + const GlyphFlag flag + ) noexcept -> void + { + // todo: + // The texture used for glyphs may not be the default texture, then we need to switch the texture, + // but at this time, the glyph information may not be written to the texture atlas (e.g. the first frame), then we can't know the ID of the texture atlas + + // note: + // Line break ('\n') have no glyph information (nullptr), meaning we need to skip it + + auto& render_list = self.get(); + + const Renderer::AccessorTexture texture_accessor{render_list.renderer.get()}; + const auto& texture_context = texture_accessor.context(); + + Renderer::AccessorFont font_accessor{render_list.renderer.get()}; + auto& font_context = font_accessor.context(); + + const auto utf32_text = chars::convert(utf8_text); + const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + + if (std::ranges::any_of( + glyphs, + [](const auto* glyph) noexcept -> bool + { + return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; + } + )) + { + // skip this frame? + return; + } + + const auto visible_glyph_count = std::ranges::count_if( + glyphs, + [](const auto* glyph) noexcept -> bool + { + if (glyph == nullptr) + { + return false; + } + + if (not glyph->visible) + { + return false; + } + + return true; + } + ); + + // two triangle without path + const auto vertex_count = std::size_t{4} * visible_glyph_count; + const auto index_count = std::size_t{6} * visible_glyph_count; + auto appender = make_appender(); + appender.reserve(vertex_count, index_count); + + const auto wrap_pos_x = p.x + wrap_width; + const auto line_height = static_cast(font_size); + auto cursor = p + point_type{0, line_height}; + + for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + { + if (glyph == nullptr) + { + if (codepoint == U'\n') + { + cursor.x = p.x; + cursor.y += line_height; + } + } + else if (glyph->visible) + { + const auto& texture = texture_context.select(glyph->texture_atlas_id); + + const auto new_texture = render_list.this_command_texture != texture.id(); + + if (new_texture) + { + render_list.push_texture(texture.id()); + } + + const auto& glyph_rect = glyph->rect; + const auto& glyph_uv = glyph->uv; + const auto glyph_advance_x = glyph->advance_x; + const auto glyph_colored = glyph->colored; + + if (cursor.x + glyph_advance_x > wrap_pos_x) + { + cursor.x = p.x; + cursor.y += line_height; + } + + const auto left_top = glyph_rect.left_top().to(); + const rect_type char_rect + { + cursor + point_type{left_top.x, -left_top.y}, + glyph_rect.size().to() + }; + const auto color_may_colored = glyph_colored ? color.transparent() : color; + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(char_rect.left_top(), glyph_uv.left_top(), color_may_colored); + appender.add_vertex(char_rect.right_top(), glyph_uv.right_top(), color_may_colored); + appender.add_vertex(char_rect.right_bottom(), glyph_uv.right_bottom(), color_may_colored); + appender.add_vertex(char_rect.left_bottom(), glyph_uv.left_bottom(), color_may_colored); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + + if (new_texture) + { + render_list.pop_texture(); + } + + cursor.x += glyph_advance_x; + } + } + } + + auto RenderListDrawer::draw_text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const float wrap_width, + const GlyphFlag flag + ) noexcept -> extent_type + { + const auto& render_list = self.get(); + + Renderer::AccessorFont font_accessor{render_list.renderer.get()}; + auto& font_context = font_accessor.context(); + + const auto utf32_text = chars::convert(utf8_text); + const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + + if (std::ranges::any_of( + glyphs, + [](const auto& glyph) noexcept -> bool + { + return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; + } + )) + { + // skip this frame? + return {0, 0}; + } + + const auto line_height = static_cast(font_size); + + float max_width = 0; + float current_width = 0; + float total_height = line_height; + + for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + { + if (glyph == nullptr) + { + if (codepoint == U'\n') + { + max_width = std::ranges::max(max_width, current_width); + current_width = 0; + total_height += line_height; + } + } + else if (glyph->visible) + { + if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) + { + max_width = std::ranges::max(max_width, current_width); + current_width = glyph_advance_x; + total_height += line_height; + } + else + { + current_width += glyph_advance_x; + } + } + } + + max_width = std::ranges::max(max_width, current_width); + + return {max_width, total_height}; + } + + auto RenderListDrawer::draw_image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void + { + auto& render_list = self.get(); + + const auto new_texture = render_list.this_command_texture != texture_id; + + if (new_texture) + { + render_list.push_texture(texture_id); + } + + auto appender = make_appender(); + + // two triangle without path + constexpr size_type vertex_count = 4; + constexpr size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(display_p1, uv_p1, color); + appender.add_vertex(display_p2, uv_p2, color); + appender.add_vertex(display_p3, uv_p3, color); + appender.add_vertex(display_p4, uv_p4, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + + if (new_texture) + { + render_list.pop_texture(); + } + } + + auto RenderListDrawer::draw_image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color, + float rounding, + RenderFlag flag + ) noexcept -> void + { + // @see `path_rect` + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; + const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + { + draw_image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + else + { + auto& render_list = self.get(); + + const auto new_texture = render_list.this_command_texture != texture_id; + + if (new_texture) + { + render_list.push_texture(texture_id); + } + + const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + + path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + + const auto before_vertex_count = render_list.vertex_list.size(); + // draw + path_stroke(color); + const auto after_vertex_count = render_list.vertex_list.size(); + + // set uv manually + + const auto display_size = display_rect.size(); + const auto uv_size = uv_rect.size(); + const auto scale = uv_size / display_size; + + auto it = render_list.vertex_list.begin() + static_cast(before_vertex_count); + const auto end = render_list.vertex_list.begin() + static_cast(after_vertex_count); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == render_list.vertex_list.end()); + + // note: linear uv + const auto uv_min = uv_rect.left_top(); + // const auto uv_max = uv_rect.right_bottom(); + while (it != end) + { + const auto v = uv_min + (it->position - display_rect.left_top()) * scale; + + it->uv = { + // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + v.x, + // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + v.y + }; + it += 1; + } + + if (new_texture) + { + render_list.pop_texture(); + } + } + } + + auto RenderListDrawer::path_clear() noexcept -> void + { + path_list.clear(); + } + + auto RenderListDrawer::path_reserve(const std::size_t size) noexcept -> void + { + path_list.reserve(size); + } + + auto RenderListDrawer::path_reserve_extra(const std::size_t size) noexcept -> void + { + path_reserve(path_list.size() + size); + } + + auto RenderListDrawer::path_pin(const point_type& point) noexcept -> void + { + path_list.push_back(point); + } + + auto RenderListDrawer::path_stroke(const color_type color, const RenderFlag flag, const float thickness) noexcept -> void + { + if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) + { + draw_polygon_line_aa(color, flag, thickness); + } + else + { + draw_polygon_line(color, flag, thickness); + } + + path_clear(); + } + + auto RenderListDrawer::path_stroke(const color_type color) noexcept -> void + { + if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) + { + draw_convex_polygon_line_filled_aa(color); + } + else + { + draw_convex_polygon_line_filled(color); + } + + path_clear(); + } + + auto RenderListDrawer::path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void + { + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + + // Calculate arc auto segment step size + auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); + // Make sure we never do steps larger than one quarter of the circle + step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); + + const auto sample_range = math::abs(to - from); + const auto next_step = step; + + auto extra_max_sample = false; + if (step > 1) + { + const auto overstep = sample_range % step; + if (overstep > 0) + { + extra_max_sample = true; + + // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + step -= (step - overstep) / 2; + } + + path_reserve_extra(sample_range / step + 1 + (overstep > 0)); + } + else + { + path_reserve_extra(sample_range + 1); + } + + auto sample_index = from; + if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + { + sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); + if (sample_index < 0) + { + sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + } + } + + if (to >= from) + { + for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + { + sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = shared_data.vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + else + { + for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + { + sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = shared_data.vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + + if (extra_max_sample) + { + auto normalized_max_sample_index = to % static_cast(RenderListSharedData::vertex_sample_points_count); + if (normalized_max_sample_index < 0) + { + normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; + } + + const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); + + path_pin({center + sample_point * radius}); + } + } + + auto RenderListDrawer::path_arc_fast(const circle_type& circle, const RenderArcFlag flag) noexcept -> void + { + const auto [from, to] = range_of_arc(flag); + + return path_arc_fast(circle, from, to); + } + + auto RenderListDrawer::path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); + + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); + } + } + + auto RenderListDrawer::path_arc(const circle_type& circle, const float from, const float to) noexcept -> void + { + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + + // Automatic segment count + if (radius <= shared_data.arc_fast_radius_cutoff) + { + const auto is_reversed = to < from; + + // We are going to use precomputed values for mid-samples. + // Determine first and last sample in lookup table that belong to the arc + const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); + const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); + + const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); + const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); + const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); + + const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + + const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; + const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; + + if (emit_start) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); + } + if (sample_mid > 0) + { + path_arc_fast(circle, sample_from, sample_to); + } + if (emit_end) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); + } + } + else + { + const auto arc_length = to - from; + const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); + const auto arc_segment_count = std::ranges::max( + static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + static_cast(std::numbers::pi_v * 2 / arc_length) + ); + path_arc_n(circle, from, to, arc_segment_count); + } + } + + auto RenderListDrawer::path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + const auto& [center, radius, rotation] = ellipse; + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + const auto offset = point_type{math::cos(a), math::sin(a)} * radius; + const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; + const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; + path_pin({center + point_type{prime_x, prime_y}}); + } + } + + auto RenderListDrawer::path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void + { + path_pin(p1); + path_pin(p2); + path_pin(p3); + path_pin(p4); + } + + auto RenderListDrawer::path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; + const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + { + path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + } + else + { + const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + + path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + } + } + + auto RenderListDrawer::path_bezier_cubic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + { + const auto dx = p4.x - p1.x; + const auto dy = p4.y - p1.y; + const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); + const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); + + if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p4); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_34 = (p3 + p4) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + const auto p_234 = (p_23 + p_34) * .5f; + const auto p_1234 = (p_123 + p_234) * .5f; + + path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); + path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); + } + } + + auto RenderListDrawer::path_bezier_quadratic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + { + const auto dx = p3.x - p1.x; + const auto dy = p3.y - p1.y; + const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; + + if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p3); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + + path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); + path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); + } + } + + auto RenderListDrawer::path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void + { + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data.curve_tessellation_tolerance, 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); + } + } + } + + auto RenderListDrawer::path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void + { + const auto& render_list = self.get(); + const auto& shared_data = render_list.shared_data(); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data.curve_tessellation_tolerance, 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + } + } + } +}; diff --git a/src/gfx_new/internal/render_list.hpp b/src/gfx_new/internal/render_list.hpp new file mode 100644 index 0000000..74bd260 --- /dev/null +++ b/src/gfx_new/internal/render_list.hpp @@ -0,0 +1,13 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx_new +{ + // +} diff --git a/src/gfx_new/internal/renderer_context.hpp b/src/gfx_new/internal/renderer_context.hpp index 1cdcd2b..0ae91bd 100644 --- a/src/gfx_new/internal/renderer_context.hpp +++ b/src/gfx_new/internal/renderer_context.hpp @@ -5,13 +5,17 @@ #pragma once -#include +#include +#include +#include namespace gal::prometheus::gfx_new { - // todo class Renderer::RendererContext { public: + FontContext font_context; + TextureContext texture_context; + RenderContext render_context; }; } diff --git a/src/gfx_new/internal/texture.cpp b/src/gfx_new/internal/texture.cpp index 6539b0a..08f5e22 100644 --- a/src/gfx_new/internal/texture.cpp +++ b/src/gfx_new/internal/texture.cpp @@ -19,7 +19,8 @@ namespace gal::prometheus::gfx_new .size = size, .dirty = false, .id = invalid_texture_id, - } + }, + uv_{1.f / static_cast(size.width), 1.f / static_cast(size.height)} { rp_nodes_.resize(size.width); @@ -49,9 +50,7 @@ namespace gal::prometheus::gfx_new auto Texture::uv() const noexcept -> uv_type { - const auto s = size(); - - return {1.f / static_cast(s.width), 1.f / static_cast(s.height)}; + return uv_; } auto Texture::dirty() const noexcept -> bool diff --git a/src/gfx_new/internal/texture.hpp b/src/gfx_new/internal/texture.hpp index 0f64e37..386cca9 100644 --- a/src/gfx_new/internal/texture.hpp +++ b/src/gfx_new/internal/texture.hpp @@ -7,7 +7,7 @@ #include -#include +#include #include @@ -21,11 +21,11 @@ namespace gal::prometheus::gfx_new friend Renderer::AccessorTexture; public: - using element_type = Renderer::Texture::element_type; - using data_type = Renderer::Texture::data_type; - using data_view_type = Renderer::Texture::data_view_type; + using element_type = TextureDescriptor::element_type; + using data_type = TextureDescriptor::data_type; + using data_view_type = TextureDescriptor::data_view_type; - using size_type = Renderer::Texture::size_type; + using size_type = TextureDescriptor::size_type; using point_type = primitive::basic_point_2d; using uv_type = primitive::basic_extent_2d; @@ -34,7 +34,9 @@ namespace gal::prometheus::gfx_new stbrp_context rp_context_; std::vector rp_nodes_; - Renderer::Texture texture_; + TextureDescriptor texture_; + // It's much more cost-effective to keep a member variable than to compute it every time + uv_type uv_; public: explicit Texture(size_type size) noexcept; diff --git a/src/gfx_new/internal/type.hpp b/src/gfx_new/internal/type.hpp new file mode 100644 index 0000000..dd89152 --- /dev/null +++ b/src/gfx_new/internal/type.hpp @@ -0,0 +1,19 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx_new +{ + // ========================================================= + // FONT + // ========================================================= + + // index + using texture_atlas_id_type = std::uint32_t; + constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; +} From db2caec148e0d1e721c9c839d21ca68eccd5bda1 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 26 May 2025 00:17:43 +0800 Subject: [PATCH 45/54] `wip`: Refactoring gfx. --- scripts/library.cmake | 14 +- src/gfx_new/gfx.cpp | 79 - src/gfx_new/gfx.hpp | 373 +- src/gfx_new/internal/accessor_font.cpp | 222 -- src/gfx_new/internal/accessor_font.hpp | 96 - src/gfx_new/internal/accessor_render.cpp | 19 - src/gfx_new/internal/accessor_render.hpp | 37 - src/gfx_new/internal/accessor_texture.cpp | 136 - src/gfx_new/internal/accessor_texture.hpp | 120 - src/gfx_new/internal/context.cpp | 511 +++ src/gfx_new/internal/context.hpp | 237 ++ src/gfx_new/internal/font.cpp | 3 +- src/gfx_new/internal/font.hpp | 6 +- src/gfx_new/internal/render_list.cpp | 4353 +++++++++++++-------- src/gfx_new/internal/render_list.hpp | 55 +- src/gfx_new/internal/renderer_context.cpp | 6 - src/gfx_new/internal/renderer_context.hpp | 21 - src/gfx_new/internal/texture.cpp | 2 - src/gfx_new/internal/texture.hpp | 4 +- src/gfx_new/internal/type.hpp | 19 - 20 files changed, 3839 insertions(+), 2474 deletions(-) delete mode 100644 src/gfx_new/gfx.cpp delete mode 100644 src/gfx_new/internal/accessor_font.cpp delete mode 100644 src/gfx_new/internal/accessor_font.hpp delete mode 100644 src/gfx_new/internal/accessor_render.cpp delete mode 100644 src/gfx_new/internal/accessor_render.hpp delete mode 100644 src/gfx_new/internal/accessor_texture.cpp delete mode 100644 src/gfx_new/internal/accessor_texture.hpp create mode 100644 src/gfx_new/internal/context.cpp create mode 100644 src/gfx_new/internal/context.hpp delete mode 100644 src/gfx_new/internal/renderer_context.cpp delete mode 100644 src/gfx_new/internal/renderer_context.hpp delete mode 100644 src/gfx_new/internal/type.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index 94a180c..191101c 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -370,15 +370,10 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/gfx_new/gfx.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/renderer_context.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_texture.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_font.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_render.hpp - - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/type.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/context.hpp # ========================= # GFX @@ -456,15 +451,10 @@ set( # GFX-NEW # ========================= - ${PROJECT_SOURCE_DIR}/src/gfx_new/gfx.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/renderer_context.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_texture.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_font.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/accessor_render.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.cpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.cpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/context.cpp # ========================= # GFX diff --git a/src/gfx_new/gfx.cpp b/src/gfx_new/gfx.cpp deleted file mode 100644 index bb19f8d..0000000 --- a/src/gfx_new/gfx.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include -#include - -namespace gal::prometheus::gfx_new -{ - GlyphParser::~GlyphParser() noexcept = default; - - Renderer::~Renderer() noexcept = default; - - Renderer::Renderer() noexcept - : context_{memory::make_unique()} {} - - auto Renderer::construct() noexcept -> bool - { - return do_construct(); - } - - auto Renderer::destruct() noexcept -> void - { - return do_destruct(); - } - - auto Renderer::ready() const noexcept -> bool - { - return do_ready(); - } - - auto Renderer::new_frame() noexcept -> void - { - // font - { - context_->font_context.load_all_font(); - context_->font_context.set_fallback_glyph(); - } - // texture - { - AccessorTexture accessor{*this}; - context_->texture_context.upload(accessor); - } - } - - auto Renderer::present() noexcept -> void - { - // glyphs - { - // note: newly added glyph information is not available until the next frame - context_->font_context.upload_all_glyph(context_->texture_context); - } - - // todo - // do_present() - } - - auto Renderer::end_frame() noexcept -> void - { - // - } - - auto Renderer::set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser* - { - return context_->font_context.set_glyph_parser(parser); - } - - auto Renderer::add_font(const std::filesystem::path& path) noexcept -> bool - { - return context_->font_context.add_font(path); - } - - auto Renderer::new_render_list() noexcept -> RenderList& - { - RenderList new_render_list{*this}; - return context_->render_context.render_lists.emplace_back(std::move(new_render_list)); - } -} diff --git a/src/gfx_new/gfx.hpp b/src/gfx_new/gfx.hpp index 301f40f..1e2fa84 100644 --- a/src/gfx_new/gfx.hpp +++ b/src/gfx_new/gfx.hpp @@ -20,10 +20,17 @@ #include #include +#include #include namespace gal::prometheus::gfx_new { + // ========================================================= + // CONTEXT + // ========================================================= + + class Context; + // ========================================================= // PRIMITIVE // ========================================================= @@ -43,8 +50,8 @@ namespace gal::prometheus::gfx_new // TEXTURE // ========================================================= - // DX11: ID3D11ShaderResourceView - // DX12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + constant offset + // D3D11: ID3D11ShaderResourceView + // D3D12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + constant offset using texture_id_type = std::uintptr_t; constexpr texture_id_type invalid_texture_id{0}; @@ -77,7 +84,7 @@ namespace gal::prometheus::gfx_new }; // ========================================================= - // FONT + // GLYPH / FONT // ========================================================= // index @@ -220,14 +227,9 @@ namespace gal::prometheus::gfx_new DEFAULT = ANTI_ALIASED_LINE | ANTI_ALIASED_LINE_USE_TEXTURE | ANTI_ALIASED_FILL, }; - enum class RenderFlag : std::uint8_t + enum class RenderRectFlag : std::uint8_t { NONE = 0, - // specify that shape should be closed - // @see RenderList::draw_polygon_line - // @see RenderList::draw_polygon_line_aa - // @see RenderList::path_stroke - CLOSED = 1 << 0, // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) // @see RenderList::path_rect // @see RenderList::rect @@ -384,15 +386,85 @@ namespace gal::prometheus::gfx_new class RenderList { + friend class Context; friend class Renderer; public: class RenderListContext; + class Painter final + { + public: + using path_list_type = std::vector; + + private: + memory::RefWrapper render_list_; + path_list_type path_list_; + + auto draw_polygon_line(color_type color, float thickness, bool close) noexcept -> void; + auto draw_polygon_line_aa(color_type color, float thickness, bool close) noexcept -> void; + auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; + auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; + + public: + Painter(const Painter&) noexcept = delete; + Painter(Painter&&) noexcept = default; + auto operator=(const Painter&) noexcept -> Painter& = delete; + auto operator=(Painter&&) noexcept -> Painter& = default; + + ~Painter() noexcept; + + explicit Painter(RenderList& render_list, std::size_t reserve_point) noexcept; + + auto clear() noexcept -> Painter&; + auto reserve_extra(std::size_t size) noexcept -> Painter&; + auto reserve(std::size_t size) noexcept -> Painter&; + + auto pin(const point_type& point) noexcept -> Painter&; + + auto line(const point_type& from, const point_type& to) noexcept -> Painter&; + auto triangle(const point_type& a, const point_type& b, const point_type& c) noexcept -> Painter&; + auto quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; + auto rect(const rect_type& rect, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto rect(const rect_type::point_type& left_top, const rect_type::extent_type& extent, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto rect(const rect_type::point_type& left_top, const rect_type::point_type& right_bottom, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto circle_n(const circle_type& circle, std::uint32_t segments) noexcept -> Painter&; + auto circle_n(const circle_type::point_type& center, circle_type::radius_value_type radius, std::uint32_t segments) noexcept -> Painter&; + auto circle(const circle_type& circle) noexcept -> Painter&; + auto circle(const circle_type::point_type& center, circle_type::radius_value_type radius) noexcept -> Painter&; + auto ellipse_n(const ellipse_type& ellipse, std::uint32_t segments) noexcept -> Painter&; + auto ellipse_n(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation, std::uint32_t segments) noexcept -> Painter&; + auto ellipse(const ellipse_type& ellipse) noexcept -> Painter&; + auto ellipse(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation) noexcept -> Painter&; + auto arc_fast(const circle_type& circle, int sample_point_from, int sample_point_to) noexcept -> Painter&; + auto arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> Painter&; + auto arc_n(const circle_type& circle, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; + auto arc(const circle_type& circle, float degree_from, float degree_to) noexcept -> Painter&; + auto arc_n(const ellipse_type& ellipse, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; + auto bezier_cubic_n(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, std::uint32_t segments) noexcept -> Painter&; + auto bezier_cubic(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; + auto bezier_quadratic_n(const point_type& p1, const point_type& p2, const point_type& p3, std::uint32_t segments) noexcept -> Painter&; + auto bezier_quadratic(const point_type& p1, const point_type& p2, const point_type& p3) noexcept -> Painter&; + + /** + * @brief Draws the fill shape according to the specified path points + * @param color Shape color + */ + auto stroke(color_type color) noexcept -> void; + + /** + * @brief Plot the corresponding lines/shapes according to the specified path points + * @param color Line/shape color + * @param thickness Line thickness + * @param close Whether the graph is closed, in other words, whether the first point should be connected to the last point + */ + auto stroke(color_type color, float thickness, bool close) noexcept -> void; + }; + private: memory::UniquePointer context_; - explicit RenderList(Renderer& renderer) noexcept; + explicit RenderList(Context& context, RenderListFlag flag) noexcept; public: RenderList(const RenderList&) noexcept = delete; @@ -417,6 +489,14 @@ namespace gal::prometheus::gfx_new auto pop_texture() noexcept -> void; + // ---------------------------------------------------------------------------- + // PAINTER + + [[nodiscard]] auto painter(const Painter::path_list_type::size_type reserve_point = 0) noexcept -> Painter + { + return Painter{*this, reserve_point}; + } + // ---------------------------------------------------------------------------- // PRIMITIVE @@ -442,20 +522,46 @@ namespace gal::prometheus::gfx_new color_type color ) noexcept -> void; + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color + ) noexcept -> void; + auto rect( const rect_type& rect, color_type color, float rounding = .0f, - RenderFlag flag = RenderFlag::ROUND_CORNER_ALL, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, float thickness = 1.f ) noexcept -> void; auto rect( const point_type& left_top, - const point_type& right_bottom, + const rect_type::extent_type& extent, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, color_type color, float rounding = .0f, - RenderFlag flag = RenderFlag::ROUND_CORNER_ALL, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, float thickness = 1.f ) noexcept -> void; @@ -463,7 +569,15 @@ namespace gal::prometheus::gfx_new const rect_type& rect, color_type color, float rounding = .0f, - RenderFlag flag = RenderFlag::ROUND_CORNER_ALL + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL ) noexcept -> void; auto rect_filled( @@ -471,7 +585,7 @@ namespace gal::prometheus::gfx_new const point_type& right_bottom, color_type color, float rounding = .0f, - RenderFlag flag = RenderFlag::ROUND_CORNER_ALL + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL ) noexcept -> void; auto rect_filled( @@ -484,28 +598,20 @@ namespace gal::prometheus::gfx_new auto rect_filled( const point_type& left_top, - const point_type& right_bottom, + const rect_type::extent_type& extent, color_type color_left_top, color_type color_right_top, color_type color_left_bottom, color_type color_right_bottom ) noexcept -> void; - auto quadrilateral( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto quadrilateral_filled( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom ) noexcept -> void; auto circle_n( @@ -516,24 +622,8 @@ namespace gal::prometheus::gfx_new ) noexcept -> void; auto circle_n( - const point_type& center, - float radius, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n( - const ellipse_type& ellipse, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n( - const point_type& center, - const extent_type& radius, - float rotation, + const circle_type::point_type& center, + circle_type::radius_value_type radius, color_type color, std::uint32_t segments, float thickness = 1.f @@ -546,82 +636,100 @@ namespace gal::prometheus::gfx_new ) noexcept -> void; auto circle_n_filled( - const point_type& center, - float radius, + const circle_type::point_type& center, + circle_type::radius_value_type radius, color_type color, std::uint32_t segments ) noexcept -> void; - auto ellipse_n_filled( - const ellipse_type& ellipse, + auto circle( + const circle_type& circle, color_type color, - std::uint32_t segments + float thickness = 1.f ) noexcept -> void; - auto ellipse_n_filled( - const point_type& center, - const extent_type& radius, - float rotation, + auto circle( + const circle_type::point_type& center, + circle_type::radius_value_type radius, color_type color, - std::uint32_t segments + float thickness = 1.f ) noexcept -> void; - auto circle( + auto circle_filled( const circle_type& circle, + color_type color + ) noexcept -> void; + + auto circle_filled( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, color_type color, - std::uint32_t segments = 0, + std::uint32_t segments, float thickness = 1.f ) noexcept -> void; - auto circle( - const point_type& center, - float radius, + auto ellipse_n( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, color_type color, - std::uint32_t segments = 0, + std::uint32_t segments, float thickness = 1.f ) noexcept -> void; - auto circle_filled( - const circle_type& circle, + auto ellipse_n_filled( + const ellipse_type& ellipse, color_type color, - std::uint32_t segments = 0 + std::uint32_t segments ) noexcept -> void; - auto circle_filled( - const point_type& center, - float radius, + auto ellipse_n_filled( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, color_type color, - std::uint32_t segments = 0 + std::uint32_t segments ) noexcept -> void; auto ellipse( const ellipse_type& ellipse, color_type color, - std::uint32_t segments = 0, float thickness = 1.f ) noexcept -> void; auto ellipse( - const point_type& center, - const extent_type& radius, - float rotation, + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, color_type color, - std::uint32_t segments = 0, float thickness = 1.f ) noexcept -> void; auto ellipse_filled( const ellipse_type& ellipse, - color_type color, - std::uint32_t segments = 0 + color_type color ) noexcept -> void; auto ellipse_filled( - const point_type& center, - const extent_type& radius, - float rotation, + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color + ) noexcept -> void; + + auto bezier_cubic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, color_type color, - std::uint32_t segments = 0 + std::uint32_t segments, + float thickness = 1.f ) noexcept -> void; auto bezier_cubic( @@ -630,7 +738,15 @@ namespace gal::prometheus::gfx_new const point_type& p3, const point_type& p4, color_type color, - std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + std::uint32_t segments, float thickness = 1.f ) noexcept -> void; @@ -639,7 +755,6 @@ namespace gal::prometheus::gfx_new const point_type& p2, const point_type& p3, color_type color, - std::uint32_t segments = 0, float thickness = 1.f ) noexcept -> void; @@ -718,7 +833,7 @@ namespace gal::prometheus::gfx_new texture_id_type texture_id, const rect_type& display_rect, float rounding = .0f, - RenderFlag flag = RenderFlag::NONE, + RenderRectFlag flag = RenderRectFlag::NONE, const rect_type& uv_rect = {0, 0, 1, 1}, color_type color = primitive::colors::white ) noexcept -> void; @@ -728,7 +843,7 @@ namespace gal::prometheus::gfx_new const point_type& display_left_top, const point_type& display_right_bottom, float rounding = .0f, - RenderFlag flag = RenderFlag::NONE, + RenderRectFlag flag = RenderRectFlag::NONE, const uv_type& uv_left_top = {0, 0}, const uv_type& uv_right_bottom = {1, 1}, color_type color = primitive::colors::white @@ -741,15 +856,7 @@ namespace gal::prometheus::gfx_new class Renderer { - public: - class RendererContext; - - class AccessorTexture; - class AccessorFont; - class AccessorRender; - - private: - memory::UniquePointer context_; + friend Context; public: Renderer(const Renderer&) noexcept = delete; @@ -782,19 +889,19 @@ namespace gal::prometheus::gfx_new * @brief * @note @c new_frame -> @c present -> @c end_frame */ - auto new_frame() noexcept -> void; + auto new_frame(Context& context) noexcept -> void; /** * @brief * @note @c new_frame -> @c present -> @c end_frame */ - auto present() noexcept -> void; + auto present(Context& context) noexcept -> void; /** * @brief * @note @c new_frame -> @c present -> @c end_frame */ - auto end_frame() noexcept -> void; + auto end_frame(Context& context) noexcept -> void; private: virtual auto do_construct() noexcept -> bool = 0; @@ -806,38 +913,52 @@ namespace gal::prometheus::gfx_new virtual auto do_texture_destroy(texture_id_type texture_id) noexcept -> void = 0; virtual auto do_present(const render_data_list_type& render_data_list) noexcept -> void = 0; + }; - public: - // ============================================================== - // GLYPH - // ============================================================== + // ========================================================= + // CONTEXT + // ========================================================= - /** - * @brief Setting the glyph parser - * @param parser New glyph parser - * @return Previous glyph parser, or nullptr if newly set - * @note *Must* ensure that the glyph parser is set before calling @c new_frame - */ - auto set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser*; + [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context*; + auto destroy_context(Context& context) noexcept -> void; + auto destroy_context(Context* context) noexcept -> void; + + // ========================================================= + // GLYPH PARSER + // ========================================================= - // ============================================================== - // FONT - // ============================================================== + /** + * @brief Setting the glyph parser of context + * @return Previous glyph parser, or nullptr if newly set + */ + auto set_glyph_parser(Context& context, std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; - /** - * @brief Load font from the specified path, assuming the path is a valid font file - * @param path Font path - * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false - * @note *Must* ensure that at least one font is added before calling @c new_frame - */ - auto add_font(const std::filesystem::path& path) noexcept -> bool; + // ========================================================= + // RENDERER + // ========================================================= - // ============================================================== - // RENDER LIST - // ============================================================== + /** + * @brief Setting the renderer of context + * @return Previous renderer, or nullptr if newly set + */ + auto set_renderer(Context& context, std::shared_ptr renderer) noexcept -> std::shared_ptr; - auto new_render_list() noexcept -> RenderList&; - }; + // ========================================================= + // FONT + // ========================================================= + + /** + * @brief Load font from the specified path, assuming the path is a valid font file + * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false + * @note *Must* ensure that at least one font is added before calling @c Renderer::new_frame + */ + auto add_font(Context& context, const std::filesystem::path& path) noexcept -> bool; + + // ========================================================= + // RENDER LIST + // ========================================================= + + auto new_render_list(Context& context, RenderListFlag flag = RenderListFlag::DEFAULT) noexcept -> RenderList&; } // namespace gal::prometheus::gfx_new namespace gal::prometheus::meta::user_defined @@ -849,7 +970,7 @@ namespace gal::prometheus::meta::user_defined struct enum_is_flag : std::true_type {}; template<> - struct enum_is_flag : std::true_type {}; + struct enum_is_flag : std::true_type {}; template<> struct enum_is_flag : std::true_type {}; diff --git a/src/gfx_new/internal/accessor_font.cpp b/src/gfx_new/internal/accessor_font.cpp deleted file mode 100644 index 048d64e..0000000 --- a/src/gfx_new/internal/accessor_font.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include - -namespace gal::prometheus::gfx_new -{ - auto FontContext::set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser* - { - return std::exchange(parser_, std::addressof(parser)); - } - - auto FontContext::set_fallback_glyph() noexcept -> void - { - if (fallback_glyph_ == nullptr) - { - { - constexpr GlyphKey key{u'\xFFFD', 16u, GlyphFlag::NONE}; - fallback_glyph_ = glyph_of(key); - } - - if (fallback_glyph_ == nullptr) - { - constexpr GlyphKey key{u'?', 16u, GlyphFlag::NONE}; - fallback_glyph_ = glyph_of(key); - } - - if (fallback_glyph_ == nullptr) - { - constexpr GlyphKey key{u' ', 16u, GlyphFlag::NONE}; - fallback_glyph_ = glyph_of(key); - } - } - } - - auto FontContext::add_font(const std::filesystem::path& path) noexcept -> bool - { - return font_load_queue_.push(path); - } - - auto FontContext::load_all_font() noexcept -> void - { - const auto loader = [this](Font&& font) noexcept -> void - { - font_list_.emplace_back(std::move(font)); - }; - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr, "Use Renderer::set_glyph_parser to set a valid glyph parser first!"); - font_load_queue_.upload(*parser_, loader); - } - - auto FontContext::glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo* - { - for (const auto& font: font_list_) - { - if (const auto* info = font.get_glyph(key); info != nullptr) - { - return info; - } - } - - return nullptr; - } - - auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* - { - return glyph_of({codepoint, size, flag}); - } - - auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo* - { - for (const auto& font: font_list_) - { - if (const auto* info = font.get_glyph(key); info != nullptr) - { - return info; - } - } - - return fallback_glyph_; - } - - auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* - { - return this->glyph_of_or_fallback({codepoint, size, flag}); - } - - auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* - { - if (const auto* info = std::as_const(*this).glyph_of(key); info != nullptr) - { - return info; - } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(parser_ != nullptr); - for (auto& font: font_list_) - { - if (auto result = parser_->parse(font.descriptor.id, key); result.valid()) - { - auto& queue = glyph_upload_queue_list_[std::addressof(font)]; - auto& inserted_info = font.set_glyph(key, result); - - queue.push(inserted_info, std::move(result)); - return std::addressof(inserted_info); - } - } - - return nullptr; - } - - auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* - { - return this->glyph_of({codepoint, size, flag}); - } - - auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo* - { - if (const auto* info = glyph_of(key); info != nullptr) - { - return info; - } - - return fallback_glyph_; - } - - auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* - { - return this->glyph_of_or_fallback({codepoint, size, flag}); - } - - auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::upload_all_glyph(TextureContext& context) noexcept -> void - { - std::ranges::for_each( - glyph_upload_queue_list_ | std::views::values, - [&context](auto& queue) noexcept -> void - { - queue.upload(context); - } - ); - glyph_upload_queue_list_.clear(); - } - - Renderer::AccessorFont::AccessorFont(Renderer& renderer) noexcept - : renderer_{renderer} {} - - auto Renderer::AccessorFont::context() noexcept -> FontContext& - { - return renderer_.get().context_->font_context; - } -} diff --git a/src/gfx_new/internal/accessor_font.hpp b/src/gfx_new/internal/accessor_font.hpp deleted file mode 100644 index 1709ce9..0000000 --- a/src/gfx_new/internal/accessor_font.hpp +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -#include -#include - -namespace gal::prometheus::gfx_new -{ - class FontContext final - { - public: - using font_list_type = std::vector; - using glyph_upload_queue_list_type = std::unordered_map; - - private: - font_list_type font_list_; - FontLoadQueue font_load_queue_; - - glyph_upload_queue_list_type glyph_upload_queue_list_; - - GlyphParser* parser_; - const GlyphInfo* fallback_glyph_; - - public: - FontContext(const FontContext&) noexcept = delete; - FontContext(FontContext&&) noexcept = default; - auto operator=(const FontContext&) noexcept -> FontContext& = delete; - auto operator=(FontContext&&) noexcept -> FontContext& = default; - - ~FontContext() noexcept = default; - - FontContext() noexcept = default; - - auto set_glyph_parser(GlyphParser& parser) noexcept -> GlyphParser*; - - auto set_fallback_glyph() noexcept -> void; - - /** - * @brief Load font from the specified path, assuming the path is a valid font file - * @param path Font path - * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false - */ - auto add_font(const std::filesystem::path& path) noexcept -> bool; - - /** - * @brief Load the fonts previously added by @c add_font - * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts - */ - auto load_all_font() noexcept -> void; - - [[nodiscard]] auto glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; - - [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; - - [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; - - [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; - - /** - * @brief Upload all used glyphs to the texture (if it is not already uploaded) - * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture - */ - auto upload_all_glyph(TextureContext& context) noexcept -> void; - }; - - /** - * @brief Proxy class for accessing the Renderer's private interface (this class helps us not to expose too many implementation details to the outside world) - */ - class Renderer::AccessorFont final - { - public: - using renderer_type = memory::RefWrapper; - - private: - renderer_type renderer_; - - public: - explicit AccessorFont(Renderer& renderer) noexcept; - - [[nodiscard]] auto context() noexcept -> FontContext&; - }; -} diff --git a/src/gfx_new/internal/accessor_render.cpp b/src/gfx_new/internal/accessor_render.cpp deleted file mode 100644 index 8697169..0000000 --- a/src/gfx_new/internal/accessor_render.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include - -namespace gal::prometheus::gfx_new -{ - Renderer::AccessorRender::AccessorRender(Renderer& renderer) noexcept - : renderer_{renderer} {} - - auto Renderer::AccessorRender::context() const noexcept -> const RenderContext& - { - return renderer_.get().context_->render_context; - } -} diff --git a/src/gfx_new/internal/accessor_render.hpp b/src/gfx_new/internal/accessor_render.hpp deleted file mode 100644 index bdd04f1..0000000 --- a/src/gfx_new/internal/accessor_render.hpp +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -#include - -namespace gal::prometheus::gfx_new -{ - class RenderContext final - { - public: - RenderListSharedData render_list_shared_data; - std::vector render_lists; - }; - - /** - * @brief Proxy class for accessing the Renderer's private interface (this class helps us not to expose too many implementation details to the outside world) - */ - class Renderer::AccessorRender final - { - public: - using renderer_type = memory::RefWrapper; - - private: - renderer_type renderer_; - - public: - explicit AccessorRender(Renderer& renderer) noexcept; - - [[nodiscard]] auto context() const noexcept -> const RenderContext&; - }; -} diff --git a/src/gfx_new/internal/accessor_texture.cpp b/src/gfx_new/internal/accessor_texture.cpp deleted file mode 100644 index 7e6e1b5..0000000 --- a/src/gfx_new/internal/accessor_texture.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include - -namespace gal::prometheus::gfx_new -{ - auto TextureContext::root_id() const noexcept -> texture_atlas_id_type - { - std::ignore = this; - return 0; - } - - TextureContext::TextureContext() noexcept - { - // root atlas - constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; - texture_atlas_list_.emplace_back(root_texture_atlas_size); - } - - auto TextureContext::root() noexcept -> Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); - - return texture_atlas_list_[root_id()]; - } - - auto TextureContext::root() const noexcept -> const Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); - - return texture_atlas_list_[root_id()]; - } - - auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) noexcept -> Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); - - return texture_atlas_list_[texture_atlas_id]; - } - - auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); - - return texture_atlas_list_[texture_atlas_id]; - } - - auto TextureContext::write( - const texture_atlas_id_type texture_atlas_id, - const Texture::data_view_type data, - const Texture::size_type size - ) noexcept -> primitive::basic_rect_2d - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= static_cast(size.width) * size.height); - - auto& texture = this->select(texture_atlas_id); - const auto texture_uv = texture.uv(); - - const auto borrowed_texture = texture.select(size); - borrowed_texture.fill(data); - - const auto position = borrowed_texture.position(); - return {position.to() * texture_uv, size.to() * texture_uv}; - } - - auto TextureContext::write( - const Texture::data_view_type data, - const Texture::size_type size - ) noexcept -> random_write_result_type - { - // todo - const auto id = root_id(); - const auto uv = this->write(id, data, size); - - return {.texture_atlas_id = id, .uv = uv}; - } - - auto TextureContext::upload(Renderer::AccessorTexture& accessor) noexcept -> void - { - std::ranges::for_each( - texture_atlas_list_, - [&accessor](auto& texture) mutable noexcept -> void - { - if (not texture.uploaded()) - { - accessor.upload(texture); - } - else - { - accessor.update_if_dirty(texture); - } - } - ); - } - - Renderer::AccessorTexture::AccessorTexture(Renderer& renderer) noexcept - : renderer_{renderer} {} - - auto Renderer::AccessorTexture::context() const noexcept -> const TextureContext& - { - const auto& renderer_context = renderer_.get().context_; - - return renderer_context->texture_context; - } - - auto Renderer::AccessorTexture::upload(Texture& texture) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture.uploaded()); - - auto& renderer = renderer_.get(); - - texture.texture_.id = renderer.do_texture_create(texture.data(), texture.size()); - texture.texture_.dirty = false; - } - - auto Renderer::AccessorTexture::update(Texture& texture) noexcept -> void - { - auto& renderer = renderer_.get(); - - renderer.do_texture_update(texture.texture_); - texture.texture_.dirty = false; - } - - auto Renderer::AccessorTexture::update_if_dirty(Texture& texture) noexcept -> void - { - if (texture.dirty()) - { - update(texture); - } - } -} diff --git a/src/gfx_new/internal/accessor_texture.hpp b/src/gfx_new/internal/accessor_texture.hpp deleted file mode 100644 index fd906db..0000000 --- a/src/gfx_new/internal/accessor_texture.hpp +++ /dev/null @@ -1,120 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -#include -#include - -#include - -namespace gal::prometheus::gfx_new -{ - class TextureContext final - { - public: - using texture_atlas_list_type = std::vector; - - private: - texture_atlas_list_type texture_atlas_list_; - - [[nodiscard]] auto root_id() const noexcept -> texture_atlas_id_type; - - public: - TextureContext() noexcept; - - /** - * @brief Get root (default) texture - */ - [[nodiscard]] auto root() noexcept -> Texture&; - - /** - * @brief Get root (default) texture - */ - [[nodiscard]] auto root() const noexcept -> const Texture&; - - /** - * @brief Get texture of id - */ - [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) noexcept -> Texture&; - - /** - * @brief Get texture of id - */ - [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture&; - - /** - * @brief Writes the given glyph data to the specified texture - * @param texture_atlas_id id of the specified texture - * @param data Glyph data to write - * @param size Size of data (rectangle area) - * @return The uv coordinate of the position where the data is written - */ - auto write( - texture_atlas_id_type texture_atlas_id, - Texture::data_view_type data, - Texture::size_type size - ) noexcept -> primitive::basic_rect_2d; - - struct random_write_result_type - { - texture_atlas_id_type texture_atlas_id; - primitive::basic_rect_2d uv; - }; - - /** - * @brief Writes the given glyph data to any holdable texture - * @param data Glyph data to write - * @param size Size of data (rectangle area) - * @return The id of the written texture atlas and the uv coordinate of the position where the data is written - */ - auto write( - Texture::data_view_type data, - Texture::size_type size - ) noexcept -> random_write_result_type; - - /** - * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) - * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) - */ - auto upload(Renderer::AccessorTexture& accessor) noexcept -> void; - }; - - /** - * @brief Proxy class for accessing the Renderer's private interface (this class helps us not to expose too many implementation details to the outside world) - */ - class Renderer::AccessorTexture final - { - public: - using renderer_type = memory::RefWrapper; - - private: - renderer_type renderer_; - - public: - explicit AccessorTexture(Renderer& renderer) noexcept; - - [[nodiscard]] auto context() const noexcept -> const TextureContext&; - - /** - * @brief Upload texture atlas data to GPU and get GPU resource handle - */ - auto upload(Texture& texture) noexcept -> void; - - /** - * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) - * @note Does not check for the need to update - */ - auto update(Texture& texture) noexcept -> void; - - /** - * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) - * @note If no update is needed (not dirty) then do nothing - */ - auto update_if_dirty(Texture& texture) noexcept -> void; - }; -} diff --git a/src/gfx_new/internal/context.cpp b/src/gfx_new/internal/context.cpp new file mode 100644 index 0000000..734c6e9 --- /dev/null +++ b/src/gfx_new/internal/context.cpp @@ -0,0 +1,511 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include "render_list.hpp" + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::gfx_new +{ + // ========================================================= + // TEXTURE + // ========================================================= + + auto TextureContext::root_id() const noexcept -> texture_atlas_id_type + { + std::ignore = this; + return 0; + } + + TextureContext::TextureContext() noexcept + { + // root atlas + constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; + texture_atlas_list_.emplace_back(root_texture_atlas_size); + } + + auto TextureContext::root() noexcept -> Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); + + return texture_atlas_list_[root_id()]; + } + + auto TextureContext::root() const noexcept -> const Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); + + return texture_atlas_list_[root_id()]; + } + + auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) noexcept -> Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); + + return texture_atlas_list_[texture_atlas_id]; + } + + auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); + + return texture_atlas_list_[texture_atlas_id]; + } + + auto TextureContext::write( + const texture_atlas_id_type texture_atlas_id, + const Texture::data_view_type data, + const Texture::size_type size + ) noexcept -> primitive::basic_rect_2d + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= static_cast(size.width) * size.height); + + auto& texture = this->select(texture_atlas_id); + const auto texture_uv = texture.uv(); + + const auto borrowed_texture = texture.select(size); + borrowed_texture.fill(data); + + const auto position = borrowed_texture.position(); + return {position.to() * texture_uv, size.to() * texture_uv}; + } + + auto TextureContext::write( + const Texture::data_view_type data, + const Texture::size_type size + ) noexcept -> random_write_result_type + { + // todo + const auto id = root_id(); + const auto uv = this->write(id, data, size); + + return {.texture_atlas_id = id, .uv = uv}; + } + + auto TextureContext::upload(const Context& context) noexcept -> void + { + auto renderer = context.get_renderer(); + + std::ranges::for_each( + texture_atlas_list_, + [&renderer](auto& texture) mutable noexcept -> void + { + if (not texture.uploaded()) + { + renderer.upload(texture); + } + else + { + renderer.update_if_dirty(texture); + } + } + ); + } + + // ========================================================= + // FONT + // ========================================================= + + auto FontContext::set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> void + { + glyph_parser_ = std::move(glyph_parser); + } + + auto FontContext::set_fallback_glyph() noexcept -> void + { + if (fallback_glyph_ == nullptr) + { + { + constexpr GlyphKey key{u'\xFFFD', 16u, GlyphFlag::NONE}; + fallback_glyph_ = glyph_of(key); + } + + if (fallback_glyph_ == nullptr) + { + constexpr GlyphKey key{u'?', 16u, GlyphFlag::NONE}; + fallback_glyph_ = glyph_of(key); + } + + if (fallback_glyph_ == nullptr) + { + constexpr GlyphKey key{u' ', 16u, GlyphFlag::NONE}; + fallback_glyph_ = glyph_of(key); + } + } + } + + auto FontContext::add_font(const std::filesystem::path& path) noexcept -> bool + { + return font_load_queue_.push(path); + } + + auto FontContext::load_all_font() noexcept -> void + { + const auto loader = [this](Font&& font) noexcept -> void + { + font_list_.emplace_back(std::move(font)); + }; + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser_ != nullptr, "Use Renderer::set_glyph_parser to set a valid glyph parser first!"); + font_load_queue_.upload(*glyph_parser_, loader); + } + + auto FontContext::glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo* + { + for (const auto& font: font_list_) + { + if (const auto* info = font.get_glyph(key); info != nullptr) + { + return info; + } + } + + return nullptr; + } + + auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* + { + return glyph_of({codepoint, size, flag}); + } + + auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo* + { + for (const auto& font: font_list_) + { + if (const auto* info = font.get_glyph(key); info != nullptr) + { + return info; + } + } + + return fallback_glyph_; + } + + auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* + { + return this->glyph_of_or_fallback({codepoint, size, flag}); + } + + auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* + { + if (const auto* info = std::as_const(*this).glyph_of(key); info != nullptr) + { + return info; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser_ != nullptr); + for (auto& font: font_list_) + { + if (auto result = glyph_parser_->parse(font.descriptor.id, key); result.valid()) + { + auto& queue = glyph_upload_queue_list_[std::addressof(font)]; + auto& inserted_info = font.set_glyph(key, result); + + queue.push(inserted_info, std::move(result)); + return std::addressof(inserted_info); + } + } + + return nullptr; + } + + auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* + { + return this->glyph_of({codepoint, size, flag}); + } + + auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo* + { + if (const auto* info = glyph_of(key); info != nullptr) + { + return info; + } + + return fallback_glyph_; + } + + auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* + { + return this->glyph_of_or_fallback({codepoint, size, flag}); + } + + auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector + { + std::vector infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); + infos.emplace_back(info); + } + ); + + return infos; + } + + auto FontContext::upload_all_glyph(TextureContext& context) noexcept -> void + { + std::ranges::for_each( + glyph_upload_queue_list_ | std::views::values, + [&context](auto& queue) noexcept -> void + { + queue.upload(context); + } + ); + glyph_upload_queue_list_.clear(); + } + + // ========================================================= + // CONTEXT + // ========================================================= + + Context::RendererAccessor::RendererAccessor(Renderer& renderer) noexcept + : renderer_{renderer} {} + + auto Context::RendererAccessor::upload(Texture& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture.uploaded()); + + auto& renderer = renderer_.get(); + + texture.texture_.id = renderer.do_texture_create(texture.data(), texture.size()); + texture.texture_.dirty = false; + } + + auto Context::RendererAccessor::update(Texture& texture) noexcept -> void + { + auto& renderer = renderer_.get(); + + renderer.do_texture_update(texture.texture_); + texture.texture_.dirty = false; + } + + auto Context::RendererAccessor::update_if_dirty(Texture& texture) noexcept -> void + { + if (texture.dirty()) + { + update(texture); + } + } + + Context::Context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept + : glyph_parser_{std::move(glyph_parser)}, + renderer_{std::move(renderer)}, + texture_context_{}, + font_context_{} {} + + auto Context::get_glyph_parser() const noexcept -> std::shared_ptr + { + return glyph_parser_; + } + + auto Context::set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> std::shared_ptr + { + auto old = std::exchange(glyph_parser_, glyph_parser); + font_context_.set_glyph_parser(glyph_parser_); + + return old; + } + + // auto Context::get_renderer() noexcept -> std::shared_ptr + // { + // return renderer_; + // } + + auto Context::get_renderer() const noexcept -> RendererAccessor + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer_ != nullptr); + + return RendererAccessor{*renderer_}; + } + + auto Context::set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr + { + return std::exchange(renderer_, renderer); + } + + auto Context::get_texture_context() const noexcept -> const TextureContext& + { + return texture_context_; + } + + auto Context::get_font_context() noexcept -> FontContext& + { + return font_context_; + } + + auto Context::get_render_list_shared_data() const noexcept -> const RenderListSharedData& + { + return render_list_shared_data_; + } + + auto Context::new_render_list(const RenderListFlag flag) noexcept -> RenderList& + { + RenderList render_list{*this, flag}; + return render_lists_.emplace_back(std::move(render_list)); + } + + auto Context::render_data() const noexcept -> render_data_list_type + { + render_data_list_type all_render_data{}; + all_render_data.reserve(render_lists_.size()); + + std::ranges::transform( + render_lists_, + std::back_inserter(all_render_data), + [](const RenderList& render_list) noexcept -> RenderData + { + auto& context = *render_list.context_; + + return {.vertex_list = context.vertex_list, .index_list = context.index_list, .command_list = context.command_list}; + } + ); + + return all_render_data; + } + + [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context* + { + auto* context = new Context{std::move(glyph_parser), std::move(renderer)}; + return context; + } + + auto destroy_context(Context& context) noexcept -> void + { + delete std::addressof(context); + } + + auto destroy_context(Context* context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context != nullptr); + destroy_context(*context); + } + + auto set_glyph_parser(Context& context, std::shared_ptr glyph_parser) noexcept -> std::shared_ptr + { + return context.set_glyph_parser(std::move(glyph_parser)); + } + + auto set_renderer(Context& context, std::shared_ptr renderer) noexcept -> std::shared_ptr + { + return context.set_renderer(std::move(renderer)); + } + + auto add_font(Context& context, const std::filesystem::path& path) noexcept -> bool + { + return context.get_font_context().add_font(path); + } + + auto new_render_list(Context& context, const RenderListFlag flag) noexcept -> RenderList& + { + return context.new_render_list(flag); + } + + GlyphParser::~GlyphParser() noexcept = default; + + Renderer::~Renderer() noexcept = default; + + Renderer::Renderer() noexcept = default; + + auto Renderer::construct() noexcept -> bool + { + return do_construct(); + } + + auto Renderer::destruct() noexcept -> void + { + return do_destruct(); + } + + auto Renderer::ready() const noexcept -> bool + { + return do_ready(); + } + + auto Renderer::new_frame(Context& context) noexcept -> void + { + // font + { + context.font_context_.load_all_font(); + context.font_context_.set_fallback_glyph(); + } + // texture + { + context.texture_context_.upload(context); + } + } + + auto Renderer::present(Context& context) noexcept -> void + { + // glyphs + { + // note: newly added glyph information is not available until the next frame + context.font_context_.upload_all_glyph(context.texture_context_); + } + + do_present(context.render_data()); + } + + auto Renderer::end_frame(Context& context) noexcept -> void + { + std::ignore = context; + } +} diff --git a/src/gfx_new/internal/context.hpp b/src/gfx_new/internal/context.hpp new file mode 100644 index 0000000..c609b42 --- /dev/null +++ b/src/gfx_new/internal/context.hpp @@ -0,0 +1,237 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +namespace gal::prometheus::gfx_new +{ + // ========================================================= + // TEXTURE + // ========================================================= + + class TextureContext final + { + public: + using texture_atlas_list_type = std::vector; + + private: + texture_atlas_list_type texture_atlas_list_; + + [[nodiscard]] auto root_id() const noexcept -> texture_atlas_id_type; + + public: + TextureContext(const TextureContext&) noexcept = delete; + TextureContext(TextureContext&&) noexcept = default; + auto operator=(const TextureContext&) noexcept -> TextureContext& = delete; + auto operator=(TextureContext&&) noexcept -> TextureContext& = default; + + ~TextureContext() noexcept = default; + + TextureContext() noexcept; + + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root() noexcept -> Texture&; + + /** + * @brief Get root (default) texture + */ + [[nodiscard]] auto root() const noexcept -> const Texture&; + + /** + * @brief Get texture of id + */ + [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) noexcept -> Texture&; + + /** + * @brief Get texture of id + */ + [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture&; + + /** + * @brief Writes the given glyph data to the specified texture + * @param texture_atlas_id id of the specified texture + * @param data Glyph data to write + * @param size Size of data (rectangle area) + * @return The uv coordinate of the position where the data is written + */ + auto write( + texture_atlas_id_type texture_atlas_id, + Texture::data_view_type data, + Texture::size_type size + ) noexcept -> primitive::basic_rect_2d; + + struct random_write_result_type + { + texture_atlas_id_type texture_atlas_id; + primitive::basic_rect_2d uv; + }; + + /** + * @brief Writes the given glyph data to any holdable texture + * @param data Glyph data to write + * @param size Size of data (rectangle area) + * @return The id of the written texture atlas and the uv coordinate of the position where the data is written + */ + auto write( + Texture::data_view_type data, + Texture::size_type size + ) noexcept -> random_write_result_type; + + /** + * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) + * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) + */ + auto upload(const Context& context) noexcept -> void; + }; + + // ========================================================= + // FONT + // ========================================================= + + class FontContext final + { + public: + using font_list_type = std::vector; + using glyph_upload_queue_list_type = std::unordered_map; + + private: + font_list_type font_list_; + FontLoadQueue font_load_queue_; + + glyph_upload_queue_list_type glyph_upload_queue_list_; + + std::shared_ptr glyph_parser_; + const GlyphInfo* fallback_glyph_; + + public: + FontContext(const FontContext&) noexcept = delete; + FontContext(FontContext&&) noexcept = default; + auto operator=(const FontContext&) noexcept -> FontContext& = delete; + auto operator=(FontContext&&) noexcept -> FontContext& = default; + + ~FontContext() noexcept = default; + + FontContext() noexcept = default; + + auto set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> void; + + auto set_fallback_glyph() noexcept -> void; + + /** + * @brief Load font from the specified path, assuming the path is a valid font file + * @param path Font path + * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false + */ + auto add_font(const std::filesystem::path& path) noexcept -> bool; + + /** + * @brief Load the fonts previously added by @c add_font + * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts + */ + auto load_all_font() noexcept -> void; + + [[nodiscard]] auto glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; + + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; + + [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; + + [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; + [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; + + /** + * @brief Upload all used glyphs to the texture (if it is not already uploaded) + * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture + */ + auto upload_all_glyph(TextureContext& context) noexcept -> void; + }; + + // ========================================================= + // CONTEXT + // ========================================================= + + class Context final + { + // xxx_context + friend Renderer; + + public: + class RendererAccessor final + { + public: + using renderer_type = memory::RefWrapper; + + private: + renderer_type renderer_; + + public: + explicit RendererAccessor(Renderer& renderer) noexcept; + + /** + * @brief Upload texture atlas data to GPU and get GPU resource handle + */ + auto upload(Texture& texture) noexcept -> void; + + /** + * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) + * @note Does not check for the need to update + */ + auto update(Texture& texture) noexcept -> void; + + /** + * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) + * @note If no update is needed (not dirty) then do nothing + */ + auto update_if_dirty(Texture& texture) noexcept -> void; + }; + + private: + std::shared_ptr glyph_parser_; + std::shared_ptr renderer_; + + TextureContext texture_context_; + FontContext font_context_; + + RenderListSharedData render_list_shared_data_; + std::vector render_lists_; + + public: + Context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept; + + [[nodiscard]] auto get_glyph_parser() const noexcept -> std::shared_ptr; + + auto set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; + + // [[nodiscard]] auto get_renderer() noexcept -> std::shared_ptr; + [[nodiscard]] auto get_renderer() const noexcept -> RendererAccessor; + + auto set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr; + + [[nodiscard]] auto get_texture_context() const noexcept -> const TextureContext&; + + [[nodiscard]] auto get_font_context() noexcept -> FontContext&; + + [[nodiscard]] auto get_render_list_shared_data() const noexcept -> const RenderListSharedData&; + + auto new_render_list(RenderListFlag flag) noexcept -> RenderList&; + + [[nodiscard]] auto render_data() const noexcept -> render_data_list_type; + }; +} diff --git a/src/gfx_new/internal/font.cpp b/src/gfx_new/internal/font.cpp index 1f84b0d..fa3de85 100644 --- a/src/gfx_new/internal/font.cpp +++ b/src/gfx_new/internal/font.cpp @@ -7,7 +7,8 @@ #include -#include +#include +#include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus::gfx_new diff --git a/src/gfx_new/internal/font.hpp b/src/gfx_new/internal/font.hpp index 266a1ad..9b7c089 100644 --- a/src/gfx_new/internal/font.hpp +++ b/src/gfx_new/internal/font.hpp @@ -8,13 +8,17 @@ #include #include -#include +#include #include #include namespace gal::prometheus::gfx_new { + // index + using texture_atlas_id_type = std::uint32_t; + constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; + class TextureContext; /** diff --git a/src/gfx_new/internal/render_list.cpp b/src/gfx_new/internal/render_list.cpp index 70a5d62..17dda63 100644 --- a/src/gfx_new/internal/render_list.cpp +++ b/src/gfx_new/internal/render_list.cpp @@ -5,10 +5,10 @@ #include -#include -#include -#include +#include + #include +#include #include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE @@ -145,9 +145,9 @@ namespace } } - [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const RenderFlag flag) noexcept -> RenderFlag + [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const RenderRectFlag flag) noexcept -> RenderRectFlag { - using enum RenderFlag; + using enum RenderRectFlag; if ((flag & ROUND_CORNER_MASK) == NONE) { @@ -237,6 +237,9 @@ namespace vertex_list_{vertex_list}, index_list_{index_list} {} + explicit RenderDataAppender(RenderList::RenderListContext& context) noexcept + : RenderDataAppender{context.command_list.back(), context.vertex_list, context.index_list} {} + [[nodiscard]] auto vertex_count() const noexcept -> size_type { const auto& list = vertex_list_.get(); @@ -280,112 +283,112 @@ namespace } }; - class RenderListDrawer final - { - public: - memory::RefWrapper self; - - using size_type = RenderData::size_type; - - using path_list_type = std::vector; - - path_list_type path_list; - - [[nodiscard]] auto make_appender() noexcept -> RenderDataAppender; - - // ============================================================= - // DRAW - // ============================================================= - - auto draw_polygon_line(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; - auto draw_polygon_line_aa(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; - auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; - auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; - auto draw_rect_filled( - const rect_type& rect, - const uv_type& uv, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - auto draw_rect_filled( - const rect_type& rect, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - auto draw_text( - std::string_view utf8_text, - std::uint32_t font_size, - const point_type& p, - color_type color, - float wrap_width, - GlyphFlag flag - ) noexcept -> void; - auto draw_text_size( - std::string_view utf8_text, - std::uint32_t font_size, - float wrap_width, - GlyphFlag flag - ) noexcept -> extent_type; - auto draw_image( - texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - color_type color - ) noexcept -> void; - auto draw_image_rounded( - texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - color_type color, - float rounding, - RenderFlag flag - ) noexcept -> void; - - // ============================================================= - // PATH - // ============================================================= - - auto path_clear() noexcept -> void; - auto path_reserve(std::size_t size) noexcept -> void; - auto path_reserve_extra(std::size_t size) noexcept -> void; - auto path_pin(const point_type& point) noexcept -> void; - auto path_stroke(color_type color, RenderFlag flag, float thickness) noexcept -> void; - auto path_stroke(color_type color) noexcept -> void; - auto path_arc_fast(const circle_type& circle, int from, int to) noexcept -> void; - auto path_arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> void; - auto path_arc_n(const circle_type& circle, float from, float to, std::uint32_t segments) noexcept -> void; - auto path_arc(const circle_type& circle, float from, float to) noexcept -> void; - auto path_arc_elliptical_n(const ellipse_type& ellipse, float from, float to, std::uint32_t segments) noexcept -> void; - auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void; - auto path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void; - auto path_bezier_cubic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - float tessellation_tolerance, - std::size_t level - ) noexcept -> void; - auto path_bezier_quadratic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - float tessellation_tolerance, - std::size_t level - ) noexcept -> void; - auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void; - auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void; - }; + // class RenderListDrawer final + // { + // public: + // memory::RefWrapper self; + // + // using size_type = RenderData::size_type; + // + // using path_list_type = std::vector; + // + // path_list_type path_list; + // + // [[nodiscard]] auto make_appender() noexcept -> RenderDataAppender; + // + // // ============================================================= + // // DRAW + // // ============================================================= + // + // auto draw_polygon_line(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; + // auto draw_polygon_line_aa(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; + // auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; + // auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; + // auto draw_rect_filled( + // const rect_type& rect, + // const uv_type& uv, + // color_type color_left_top, + // color_type color_right_top, + // color_type color_left_bottom, + // color_type color_right_bottom + // ) noexcept -> void; + // auto draw_rect_filled( + // const rect_type& rect, + // color_type color_left_top, + // color_type color_right_top, + // color_type color_left_bottom, + // color_type color_right_bottom + // ) noexcept -> void; + // auto draw_text( + // std::string_view utf8_text, + // std::uint32_t font_size, + // const point_type& p, + // color_type color, + // float wrap_width, + // GlyphFlag flag + // ) noexcept -> void; + // auto draw_text_size( + // std::string_view utf8_text, + // std::uint32_t font_size, + // float wrap_width, + // GlyphFlag flag + // ) noexcept -> extent_type; + // auto draw_image( + // texture_id_type texture_id, + // const point_type& display_p1, + // const point_type& display_p2, + // const point_type& display_p3, + // const point_type& display_p4, + // const uv_type& uv_p1, + // const uv_type& uv_p2, + // const uv_type& uv_p3, + // const uv_type& uv_p4, + // color_type color + // ) noexcept -> void; + // auto draw_image_rounded( + // texture_id_type texture_id, + // const rect_type& display_rect, + // const rect_type& uv_rect, + // color_type color, + // float rounding, + // RenderFlag flag + // ) noexcept -> void; + // + // // ============================================================= + // // PATH + // // ============================================================= + // + // auto path_clear() noexcept -> void; + // auto path_reserve(std::size_t size) noexcept -> void; + // auto path_reserve_extra(std::size_t size) noexcept -> void; + // auto path_pin(const point_type& point) noexcept -> void; + // auto path_stroke(color_type color, RenderFlag flag, float thickness) noexcept -> void; + // auto path_stroke(color_type color) noexcept -> void; + // auto path_arc_fast(const circle_type& circle, int from, int to) noexcept -> void; + // auto path_arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> void; + // auto path_arc_n(const circle_type& circle, float from, float to, std::uint32_t segments) noexcept -> void; + // auto path_arc(const circle_type& circle, float from, float to) noexcept -> void; + // auto path_arc_elliptical_n(const ellipse_type& ellipse, float from, float to, std::uint32_t segments) noexcept -> void; + // auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void; + // auto path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void; + // auto path_bezier_cubic_curve_casteljau( + // const point_type& p1, + // const point_type& p2, + // const point_type& p3, + // const point_type& p4, + // float tessellation_tolerance, + // std::size_t level + // ) noexcept -> void; + // auto path_bezier_quadratic_curve_casteljau( + // const point_type& p1, + // const point_type& p2, + // const point_type& p3, + // float tessellation_tolerance, + // std::size_t level + // ) noexcept -> void; + // auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void; + // auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void; + // }; } namespace gal::prometheus::gfx_new @@ -443,1248 +446,1622 @@ namespace gal::prometheus::gfx_new curve_tessellation_tolerance = tolerance; } - class RenderList::RenderListContext final + auto RenderList::RenderListContext::push_command() noexcept -> void { - friend RenderListDrawer; - - public: - using size_type = RenderData::size_type; - - using command_type = RenderData::command_type; - - using vertex_list_type = RenderData::vertex_list_type; - using index_list_type = RenderData::index_list_type; - using command_list_type = RenderData::command_list_type; + // Fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_command_texture != invalid_texture_id); - mutable memory::RefWrapper renderer; - - RenderListFlag render_list_flag; - - // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) - // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 - // command_list: - // 0: .scissor = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) - // 1: .scissor = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) - // 2: .scissor = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) - command_list_type command_list; - vertex_list_type vertex_list; - index_list_type index_list; - - rect_type this_command_scissor; - texture_id_type this_command_texture; - - auto push_command() noexcept -> void; - - auto on_scissor_changed() noexcept -> void; - auto on_texture_changed() noexcept -> void; + command_list.emplace_back( + command_type{ + .scissor = this_command_scissor, + .texture = this_command_texture, + .index_offset = static_cast(index_list.size()), + // set by draw_xxx + .element_count = 0 + } + ); + } - [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData& - { - const Renderer::AccessorRender accessor{renderer.get()}; + auto RenderList::RenderListContext::on_scissor_changed() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - return accessor.context().render_list_shared_data; - } + const auto command_count = command_list.size(); + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); - [[nodiscard]] auto default_texture() const noexcept -> texture_id_type + if (current_element_count != 0) { - const Renderer::AccessorTexture accessor{renderer.get()}; + if (current_scissor != this_command_scissor) + { + push_command(); - return accessor.context().root().id(); + return; + } } - auto reset() noexcept -> void + // try to merge with previous command if it matches, else use current command + if (command_count > 1) { - command_list.clear(); - vertex_list.clear(); - index_list.clear(); - - // we don't know the size of the clip rect, so we need the user to set it - this_command_scissor = {}; - // the first texture is always the (default) font texture - this_command_texture = default_texture(); - - // we always have a command ready in the buffer - command_list.emplace_back( - command_type{ - .scissor = this_command_scissor, - .texture = this_command_texture, - .index_offset = static_cast(index_list.size()), - // set by subsequent draw_xxx - .element_count = 0, - } - ); + if (const auto& [previous_scissor, previous_texture, previous_index_offset, previous_element_count] = command_list[command_count - 2]; + current_element_count == 0 and (this_command_scissor == previous_scissor and this_command_texture == previous_texture) and + // sequential + current_index_offset == previous_index_offset + previous_element_count) + { + command_list.pop_back(); + return; + } } - // ---------------------------------------------------------------------------- - // SCISSOR & TEXTURE - - auto push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& - { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - - const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); - - this_command_scissor = intersect_with_current_scissor ? rect.combine_min(current_scissor) : rect; - - on_scissor_changed(); - return command_list.back().scissor; - } + command_list.back().scissor = this_command_scissor; + } - auto pop_scissor() noexcept -> void - { - // at least one command - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); - this_command_scissor = command_list[command_list.size() - 2].scissor; + auto RenderList::RenderListContext::on_texture_changed() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - on_scissor_changed(); - } + const auto command_count = command_list.size(); + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); - auto push_texture(const texture_id_type texture) noexcept -> void + if (current_element_count != 0) { - this_command_texture = texture; + if (current_texture != this_command_texture) + { + push_command(); - on_texture_changed(); + return; + } } - auto pop_texture() noexcept -> void + // try to merge with previous command if it matches, else use current command + if (command_count > 1) { - // at least one command - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); - this_command_texture = command_list[command_list.size() - 2].texture; - - on_texture_changed(); + if (const auto& [previous_scissor, previous_texture, previous_index_offset, previous_element_count] = command_list[command_count - 2]; + current_element_count == 0 and (this_command_scissor == previous_scissor and this_command_texture == previous_texture) and + // sequential + current_index_offset == previous_index_offset + previous_element_count) + { + command_list.pop_back(); + return; + } } - }; - - RenderList::RenderList(Renderer& renderer) noexcept - : context_{memory::make_unique(renderer, RenderListFlag::DEFAULT)} {} - - RenderList::RenderList(RenderList&&) noexcept = default; - - auto RenderList::operator=(RenderList&&) noexcept -> RenderList& = default; - - RenderList::~RenderList() noexcept = default; - auto RenderList::reset() noexcept -> void - { - context_->reset(); + command_list.back().texture = this_command_texture; } - auto RenderList::set_flag(const RenderListFlag flag) noexcept -> void + auto RenderList::RenderListContext::shared_data() const noexcept -> const RenderListSharedData& { - context_->render_list_flag = flag; + return context.get().get_render_list_shared_data(); } - auto RenderList::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& + auto RenderList::RenderListContext::default_texture() const noexcept -> texture_id_type { - return context_->push_scissor(rect, intersect_with_current_scissor); + return context.get().get_texture_context().root().id(); } - auto RenderList::pop_scissor() noexcept -> void + auto RenderList::RenderListContext::reset() noexcept -> void { - context_->pop_scissor(); - } + command_list.clear(); + vertex_list.clear(); + index_list.clear(); - auto RenderList::push_texture(const texture_id_type texture) noexcept -> void - { - context_->push_texture(texture); - } + // we don't know the size of the clip rect, so we need the user to set it + this_command_scissor = {}; + // the first texture is always the (default) font texture + this_command_texture = default_texture(); - auto RenderList::pop_texture() noexcept -> void - { - context_->pop_texture(); + // we always have a command ready in the buffer + command_list.emplace_back( + command_type{ + .scissor = this_command_scissor, + .texture = this_command_texture, + .index_offset = static_cast(index_list.size()), + // set by subsequent draw_xxx + .element_count = 0, + } + ); } - auto RenderList::line( - const point_type& from, - const point_type& to, - const color_type color, - const float thickness - ) noexcept -> void + auto RenderList::RenderListContext::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& { - if (color.alpha == 0) - { - return; - } + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - drawer.path_pin(from); - drawer.path_pin(to); + const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); - drawer.path_stroke(color, RenderFlag::NONE, thickness); + this_command_scissor = intersect_with_current_scissor ? rect.combine_min(current_scissor) : rect; + + on_scissor_changed(); + return command_list.back().scissor; } - auto RenderList::triangle( - const point_type& a, - const point_type& b, - const point_type& c, - const color_type color, - const float thickness - ) noexcept -> void + auto RenderList::RenderListContext::pop_scissor() noexcept -> void { - if (color.alpha == 0) - { - return; - } + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); + this_command_scissor = command_list[command_list.size() - 2].scissor; - RenderListDrawer drawer{.self = *context_, .path_list = {}}; - - drawer.path_pin(a); - drawer.path_pin(b); - drawer.path_pin(c); - - drawer.path_stroke(color, RenderFlag::CLOSED, thickness); + on_scissor_changed(); } - auto RenderList::triangle_filled( - const point_type& a, - const point_type& b, - const point_type& c, - const color_type color - ) noexcept -> void + auto RenderList::RenderListContext::push_texture(const texture_id_type texture) noexcept -> void { - if (color.alpha == 0) - { - return; - } + this_command_texture = texture; - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + on_texture_changed(); + } - drawer.path_pin(a); - drawer.path_pin(b); - drawer.path_pin(c); + auto RenderList::RenderListContext::pop_texture() noexcept -> void + { + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); + this_command_texture = command_list[command_list.size() - 2].texture; - drawer.path_stroke(color); + on_texture_changed(); } - auto RenderList::rect( - const rect_type& rect, - const color_type color, - const float rounding, - const RenderFlag flag, - const float thickness - ) noexcept -> void + auto RenderList::Painter::draw_polygon_line(const color_type color, const float thickness, const bool close) noexcept -> void { - if (color.alpha == 0) + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; + + if (path_point_count < 2 or color.alpha == 0) { return; } - RenderListDrawer drawer{.self = *context_, .path_list = {}}; - - drawer.path_rect(rect, rounding, flag); + auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); + RenderDataAppender appender{render_list_context}; - drawer.path_stroke(color, RenderFlag::CLOSED, thickness); - } + const auto segments_count = close ? path_point_count : path_point_count - 1; - auto RenderList::rect( - const point_type& left_top, - const point_type& right_bottom, - const color_type color, - const float rounding, - const RenderFlag flag, - const float thickness - ) noexcept -> void - { - return rect({left_top, right_bottom}, color, rounding, flag, thickness); - } + const auto vertex_count = segments_count * 4; + const auto index_count = segments_count * 6; + appender.reserve(vertex_count, index_count); - auto RenderList::rect_filled( - const rect_type& rect, - const color_type color, - const float rounding, - const RenderFlag flag - ) noexcept -> void - { - if (color.alpha == 0) + for (std::decay_t i = 0; i < segments_count; ++i) { - return; - } + const auto n = (i + 1) % path_point_count; - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + const auto& p1 = path_point[i]; + const auto& p2 = path_point[n]; - if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) - { - drawer.draw_rect_filled(rect, color, color, color, color); - } - else - { - drawer.path_rect(rect, rounding, flag); - drawer.path_stroke(color); + auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); + normalized_x *= (thickness * .5f); + normalized_y *= (thickness * .5f); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = shared_data.white_pixel_uv; + + appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); } } - auto RenderList::rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type color, - const float rounding, - const RenderFlag flag - ) noexcept -> void + auto RenderList::Painter::draw_polygon_line_aa(const color_type color, float thickness, const bool close) noexcept -> void { - return rect_filled({left_top, right_bottom}, color, rounding, flag); - } + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; - auto RenderList::rect_filled( - const rect_type& rect, - const color_type color_left_top, - const color_type color_right_top, - const color_type color_left_bottom, - const color_type color_right_bottom - ) noexcept -> void - { - if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) + if (path_point_count < 2 or color.alpha == 0) { return; } - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + auto& render_list_context = *render_list_.get().context_; + const auto render_list_flag = render_list_context.render_list_flag; + const auto& shared_data = render_list_context.shared_data(); + RenderDataAppender appender{render_list_context}; - drawer.draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - } + const auto& opaque_uv = shared_data.white_pixel_uv; + const auto transparent_color = color.transparent(); - auto RenderList::rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type color_left_top, - const color_type color_right_top, - const color_type color_left_bottom, - const color_type color_right_bottom - ) noexcept -> void - { - return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - } + const auto segments_count = close ? path_point_count : path_point_count - 1; + const auto is_thick_line = thickness > 1.f; - auto RenderList::quadrilateral( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type color, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0) - { - return; - } + thickness = std::ranges::max(thickness, 1.f); + const auto thickness_integer = static_cast(thickness); + const auto thickness_fractional = thickness - static_cast(thickness_integer); - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + const auto is_use_texture = + ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); - drawer.path_quadrilateral(p1, p2, p3, p4); + const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); + const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); + appender.reserve(vertex_cont, index_count); - drawer.path_stroke(color, RenderFlag::CLOSED, thickness); - } + // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; - auto RenderList::quadrilateral_filled( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type color - ) noexcept -> void - { - if (color.alpha == 0) + // Calculate normals (tangents) for each line segment + for (std::decay_t i = 0; i < segments_count; ++i) { - return; + const auto n = (i + 1) % path_point_count; + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; } - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + if (not close) + { + temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + } - drawer.path_quadrilateral(p1, p2, p3, p4); + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + if (is_use_texture or not is_thick_line) + { + // [PATH 1] Texture-based lines (thick or non-thick) - drawer.path_stroke(color); - } + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA + const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; - auto RenderList::circle_n( - const circle_type& circle, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f or segments < 3) - { - return; - } + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not close) + { + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; + } - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + const auto current_vertex_index = static_cast(appender.vertex_count()); - drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + // closed + (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + ); - drawer.path_stroke(color, RenderFlag::CLOSED, thickness); - } + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // dm_x, dm_y are offset to the outer edge of the AA area + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= half_draw_size; + dm_y *= half_draw_size; - auto RenderList::circle_n( - const point_type& center, - const float radius, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - return circle_n({center, radius}, color, segments, thickness); - } + // Add temporary vertexes for the outer edges + temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; + temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; - auto RenderList::ellipse_n( - const ellipse_type& ellipse, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) - { - return; - } + if (is_use_texture) + { + // Add indices for two triangles - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + // right + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); + // left + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); + } + else + { + // Add indexes for four triangles - drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + // right 1 + appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); + // right 2 + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); + // left 1 + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // left 2 + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + } - drawer.path_stroke(color, RenderFlag::CLOSED, thickness); - } + vertex_index_for_start = vertex_index_for_end; + } - void RenderList::ellipse_n( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept - { - return ellipse_n({center, radius, rotation}, color, segments, thickness); - } + // Add vertexes for each point on the line + if (is_use_texture) + { + const auto& uv = shared_data.baked_line_uvs[thickness_integer]; - auto RenderList::circle_n_filled( - const circle_type& circle, - const color_type color, - const std::uint32_t segments - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f or segments < 3) - { - return; + const auto uv0 = uv.left_top(); + const auto uv1 = uv.right_bottom(); + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); + } + } + else + { + // If we're not using a texture, we need the center vertex as well + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // center of line + appender.add_vertex(path_point[i], opaque_uv, color); + // left-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); + // right-side outer edge + appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); + } + } } + else + { + // [PATH 2] Non-texture-based lines (non-thick) - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + // we need to draw the solid line core and thus require four vertices per point + const auto half_inner_thickness = (thickness - 1.f) * .5f; - drawer.path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not close) + { + const auto point_last = path_point_count - 1; + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + } - drawer.path_stroke(color); - } + const auto current_vertex_index = static_cast(appender.vertex_count()); - auto RenderList::circle_n_filled( - const point_type& center, - const float radius, - const color_type color, - const std::uint32_t segments - ) noexcept -> void - { - return circle_n_filled({center, radius}, color, segments); - } + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast((first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + 4)); - auto RenderList::ellipse_n_filled( - const ellipse_type& ellipse, - const color_type color, - const std::uint32_t segments - ) noexcept -> void - { - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) - { - return; - } + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); + const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); + const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); + const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + // Add temporary vertices + temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; + temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + + // Add indexes + appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); + appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); + appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); - drawer.path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + vertex_index_for_start = vertex_index_for_end; + } - drawer.path_stroke(color); + // Add vertices + for (std::decay_t i = 0; i < path_point_count; ++i) + { + appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); + appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); + appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); + } + } } - auto RenderList::ellipse_n_filled( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type color, - const std::uint32_t segments - ) noexcept -> void + auto RenderList::Painter::draw_convex_polygon_line_filled(const color_type color) noexcept -> void { - return ellipse_n_filled({center, radius, rotation}, color, segments); - } + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; - void RenderList::circle( - const circle_type& circle, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept - { - if (color.alpha == 0 or circle.radius < .5f) + if (path_point_count < 3 or color.alpha == 0) { return; } - if (segments == 0) - { - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); + RenderDataAppender appender{render_list_context}; - drawer.path_arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + const auto vertex_count = path_point_count; + const auto index_count = (path_point_count - 2) * 3; + appender.reserve(vertex_count, index_count); - drawer.path_stroke(color, RenderFlag::CLOSED, thickness); - } - else + const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& opaque_uv = shared_data.white_pixel_uv; + + std::ranges::for_each( + path_point, + [&](const point_type& point) noexcept -> void + { + appender.add_vertex(point, opaque_uv, color); + } + ); + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) { - const auto clamped_segments = std::ranges::clamp(segments, RenderListSharedData::circle_segments_min, RenderListSharedData::circle_segments_max); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); - circle_n(circle, color, clamped_segments, thickness); + appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); } } - auto RenderList::circle( - const point_type& center, - const float radius, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void + auto RenderList::Painter::draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void { - return circle({center, radius}, color, segments, thickness); - } + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; - auto RenderList::circle_filled( - const circle_type& circle, - const color_type color, - const std::uint32_t segments - ) noexcept -> void - { - if (color.alpha == 0 or circle.radius < .5f) + if (path_point_count < 3 or color.alpha == 0) { return; } - if (segments == 0) - { - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); + RenderDataAppender appender{render_list_context}; + + const auto& opaque_uv = shared_data.white_pixel_uv; + const auto transparent_color = color.transparent(); - drawer.path_arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + const auto vertex_count = path_point_count * 2; + const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; + appender.reserve(vertex_count, index_count); - drawer.path_stroke(color); - } - else + const auto current_vertex_inner_index = static_cast(appender.vertex_count()); + const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); + + // Add indexes for fill + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) { - const auto clamped_segments = std::ranges::clamp(segments, RenderListSharedData::circle_segments_min, RenderListSharedData::circle_segments_max); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); - circle_n_filled(circle, color, clamped_segments); + appender.add_index(current_vertex_inner_index + 0, current_vertex_inner_index + static_cast((i - 1) << 1), current_vertex_inner_index + static_cast(i << 1)); } - } - auto RenderList::circle_filled( - const point_type& center, - const float radius, - const color_type color, - const std::uint32_t segments - ) noexcept -> void - { - circle_filled({center, radius}, color, segments); - } + path_list_type temp_buffer{}; + temp_buffer.resize(path_point_count); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - auto RenderList::ellipse( - const ellipse_type& ellipse, - const color_type color, - std::uint32_t segments, - const float thickness - ) noexcept -> void - { - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) { - return; + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; } - if (segments == 0) + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) { - // fixme - segments = context_->shared_data().circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - } + // Average normals + const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= .5f; + dm_y *= .5f; + + // inner + appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); + // outer + appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + + // Add indexes for fringes + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); - ellipse_n(ellipse, color, segments, thickness); + appender.add_index( + current_vertex_inner_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(i << 1) + ); + appender.add_index( + current_vertex_outer_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(n << 1) + ); + } } - auto RenderList::ellipse( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void + RenderList::Painter::~Painter() noexcept +#if GAL_PROMETHEUS_COMPILER_DEBUG { - return ellipse({center, radius, rotation}, color, segments, thickness); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(path_list_.empty(), "Call `stroke` to draw or call `clear` to discard!"); } +#else + = default; +#endif - auto RenderList::ellipse_filled( - const ellipse_type& ellipse, - const color_type color, - std::uint32_t segments - ) noexcept -> void + RenderList::Painter::Painter(RenderList& render_list, const std::size_t reserve_point) noexcept + : render_list_{render_list} { - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) - { - return; - } + reserve(reserve_point); + } - if (segments == 0) - { - // fixme - segments = context_->shared_data().circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - } + auto RenderList::Painter::clear() noexcept -> Painter& + { + path_list_.clear(); - ellipse_n_filled(ellipse, color, segments); + return *this; } - auto RenderList::ellipse_filled( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type color, - const std::uint32_t segments - ) noexcept -> void + auto RenderList::Painter::reserve_extra(const std::size_t size) noexcept -> Painter& { - return ellipse_filled({center, radius, rotation}, color, segments); + return reserve(path_list_.size() + size); } - auto RenderList::bezier_cubic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void + auto RenderList::Painter::reserve(const std::size_t size) noexcept -> Painter& { - if (color.alpha == 0) - { - return; - } + path_list_.reserve(size); - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + return *this; + } - drawer.path_bezier_curve(p1, p2, p3, p4, segments); + auto RenderList::Painter::pin(const point_type& point) noexcept -> Painter& + { + path_list_.emplace_back(point); - drawer.path_stroke(color, RenderFlag::NONE, thickness); + return *this; } - auto RenderList::bezier_quadratic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const color_type color, - const std::uint32_t segments, - const float thickness - ) noexcept -> void - // clang-format on + auto RenderList::Painter::line(const point_type& from, const point_type& to) noexcept -> Painter& { - if (color.alpha == 0) - { - return; - } + pin(from); + pin(to); - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + return *this; + } - drawer.path_bezier_quadratic_curve(p1, p2, p3, segments); + auto RenderList::Painter::triangle(const point_type& a, const point_type& b, const point_type& c) noexcept -> Painter& + { + pin(a); + pin(b); + pin(c); - drawer.path_stroke(color, RenderFlag::NONE, thickness); + return *this; } - auto RenderList::text( - const std::string_view utf8_text, - const std::uint32_t font_size, - const point_type& point, - const color_type color, - const float wrap_width - ) noexcept -> void + auto RenderList::Painter::quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter& { - return this->text(utf8_text, font_size, point, color, GlyphFlag::NONE, wrap_width); + pin(p1); + pin(p2); + pin(p3); + pin(p4); + + return *this; } - auto RenderList::text( - const std::string_view utf8_text, - const std::uint32_t font_size, - const point_type& point, - const color_type color, - const GlyphFlag flag, - const float wrap_width - ) noexcept -> void - // clang-format on + auto RenderList::Painter::rect(const rect_type& rect, float rounding, RenderRectFlag flag) noexcept -> Painter& { - if (color.alpha == 0) + if (rounding >= .5f) { - return; + flag = to_fixed_rect_corner_flag(flag); + + const auto v = (flag & RenderRectFlag::ROUND_CORNER_TOP) == RenderRectFlag::ROUND_CORNER_TOP or (flag & RenderRectFlag::ROUND_CORNER_BOTTOM) == RenderRectFlag::ROUND_CORNER_BOTTOM; + const auto h = (flag & RenderRectFlag::ROUND_CORNER_LEFT) == RenderRectFlag::ROUND_CORNER_LEFT or (flag & RenderRectFlag::ROUND_CORNER_RIGHT) == RenderRectFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); } - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(wrap_width > 0); + if (rounding < .5f or (RenderRectFlag::ROUND_CORNER_MASK & flag) == RenderRectFlag::ROUND_CORNER_NONE) + { + reserve_extra(4); + quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + } + else + { + const auto rounding_left_top = (flag & RenderRectFlag::ROUND_CORNER_LEFT_TOP) != RenderRectFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & RenderRectFlag::ROUND_CORNER_RIGHT_TOP) != RenderRectFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & RenderRectFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderRectFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & RenderRectFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderRectFlag::NONE ? rounding : 0; - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + } - drawer.draw_text(utf8_text, font_size, point, color, wrap_width, flag); + return *this; } - auto RenderList::text_size( - const std::string_view utf8_text, - const std::uint32_t font_size, - const float wrap_width - ) noexcept -> extent_type + auto RenderList::Painter::rect(const rect_type::point_type& left_top, const rect_type::extent_type& extent, const float rounding, const RenderRectFlag flag) noexcept -> Painter& { - return this->text_size(utf8_text, font_size, GlyphFlag::NONE, wrap_width); + return rect({left_top, extent}, rounding, flag); } - auto RenderList::text_size( - const std::string_view utf8_text, - const std::uint32_t font_size, - const GlyphFlag flag, - const float wrap_width - ) noexcept -> extent_type + auto RenderList::Painter::rect(const rect_type::point_type& left_top, const rect_type::point_type& right_bottom, const float rounding, const RenderRectFlag flag) noexcept -> Painter& { - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + return rect({left_top, right_bottom}, rounding, flag); + } - return drawer.draw_text_size(utf8_text, font_size, wrap_width, flag); + auto RenderList::Painter::circle_n(const circle_type& circle, const std::uint32_t segments) noexcept -> Painter& + { + return arc_n(circle, 0, std::numbers::pi_v * 2, segments); } - auto RenderList::image( - const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - const color_type color - ) noexcept -> void + auto RenderList::Painter::circle_n(const circle_type::point_type& center, const circle_type::radius_value_type radius, const std::uint32_t segments) noexcept -> Painter& { - if (color.alpha == 0) - { - return; - } + return circle_n({center, radius}, segments); + } - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + auto RenderList::Painter::circle(const circle_type& circle) noexcept -> Painter& + { + return arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + } - drawer.draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); + auto RenderList::Painter::circle(const circle_type::point_type& center, const circle_type::radius_value_type radius) noexcept -> Painter& + { + return circle({center, radius}); } - auto RenderList::image( - const texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - const color_type color - ) noexcept -> void + auto RenderList::Painter::ellipse_n(const ellipse_type& ellipse, const std::uint32_t segments) noexcept -> Painter& { - this->image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); + return arc_n(ellipse, 0, std::numbers::pi_v * 2, segments); } - auto RenderList::image( - const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const uv_type& uv_left_top, - const uv_type& uv_right_bottom, - const color_type color - ) noexcept -> void + auto RenderList::Painter::ellipse_n( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + const ellipse_type::rotation_value_type rotation, + const std::uint32_t segments + ) noexcept -> Painter& { - this->image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); + return ellipse_n({center, radius, rotation}, segments); } - auto RenderList::image_rounded( - const texture_id_type texture_id, - const rect_type& display_rect, - const float rounding, - const RenderFlag flag, - const rect_type& uv_rect, - const color_type color - ) noexcept -> void + auto RenderList::Painter::ellipse(const ellipse_type& ellipse) noexcept -> Painter& { - if (color.alpha == 0) - { - return; - } + // FIXME-OPT + const auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); - RenderListDrawer drawer{.self = *context_, .path_list = {}}; + const auto segments = shared_data.circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - drawer.draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); + return ellipse_n(ellipse, segments); } - auto RenderList::image_rounded( - const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const float rounding, - const RenderFlag flag, - const uv_type& uv_left_top, - const uv_type& uv_right_bottom, - const color_type color - ) noexcept -> void + auto RenderList::Painter::ellipse(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, const ellipse_type::rotation_value_type rotation) noexcept -> Painter& { - this->image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + return ellipse({center, radius, rotation}); } -} -namespace -{ - auto RenderListDrawer::make_appender() noexcept -> RenderDataAppender - { - auto& render_list = self.get(); - - return {render_list.command_list.back(), render_list.vertex_list, render_list.index_list}; - } - - auto RenderListDrawer::draw_polygon_line(const color_type color, const RenderFlag render_flag, const float thickness) noexcept -> void + auto RenderList::Painter::arc_fast(const circle_type& circle, const int sample_point_from, const int sample_point_to) noexcept -> Painter& { - const auto path_point_count = path_list.size(); - const auto& path_point = path_list; + const auto& [center, radius] = circle; - if (path_point_count < 2 or color.alpha == 0) + if (radius < .5f) { - return; + return pin(center); } - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); - auto appender = make_appender(); + const auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); - const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; - const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + // Calculate arc auto segment step size + auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); + // Make sure we never do steps larger than one quarter of the circle + step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); - const auto vertex_count = segments_count * 4; - const auto index_count = segments_count * 6; - appender.reserve(vertex_count, index_count); + const auto sample_range = math::abs(sample_point_to - sample_point_from); + const auto next_step = step; - for (std::decay_t i = 0; i < segments_count; ++i) + auto extra_max_sample = false; + if (step > 1) { - const auto n = (i + 1) % path_point_count; + const auto overstep = sample_range % step; + if (overstep > 0) + { + extra_max_sample = true; - const auto& p1 = path_point[i]; - const auto& p2 = path_point[n]; + // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + step -= (step - overstep) / 2; + } - auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); - normalized_x *= (thickness * .5f); - normalized_y *= (thickness * .5f); + reserve_extra(sample_range / step + 1 + (overstep > 0)); + } + else + { + reserve_extra(sample_range + 1); + } - const auto current_vertex_index = static_cast(appender.vertex_count()); - const auto& opaque_uv = shared_data.white_pixel_uv; + auto sample_index = sample_point_from; + if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + { + sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); + if (sample_index < 0) + { + sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + } + } - appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + if (sample_point_to >= sample_point_from) + { + for (int i = sample_point_from; i <= sample_point_to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + { + sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); + } - appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + const auto& sample_point = shared_data.vertex_sample_point(sample_index); + + pin({center + sample_point * radius}); + } } - } + else + { + for (int i = sample_point_from; i >= sample_point_to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + { + sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + } - auto RenderListDrawer::draw_polygon_line_aa(const color_type color, const RenderFlag render_flag, float thickness) noexcept -> void - { - const auto path_point_count = path_list.size(); - const auto& path_point = path_list; + const auto& sample_point = shared_data.vertex_sample_point(sample_index); - if (path_point_count < 2 or color.alpha == 0) - { - return; + pin({center + sample_point * radius}); + } } - const auto& render_list = self.get(); - const auto render_list_flag = render_list.render_list_flag; - const auto& shared_data = render_list.shared_data(); - auto appender = make_appender(); - - const auto& opaque_uv = shared_data.white_pixel_uv; - const auto transparent_color = color.transparent(); + if (extra_max_sample) + { + auto normalized_max_sample_index = sample_point_to % static_cast(RenderListSharedData::vertex_sample_points_count); + if (normalized_max_sample_index < 0) + { + normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; + } - const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; - const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - const auto is_thick_line = thickness > 1.f; + const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); - thickness = std::ranges::max(thickness, 1.f); - const auto thickness_integer = static_cast(thickness); - const auto thickness_fractional = thickness - static_cast(thickness_integer); + pin({center + sample_point * radius}); + } - const auto is_use_texture = - ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and - (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); + return *this; + } - const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); - const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); - appender.reserve(vertex_cont, index_count); + auto RenderList::Painter::arc_fast(const circle_type& circle, const RenderArcFlag flag) noexcept -> Painter& + { + const auto [from, to] = range_of_arc(flag); - // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - path_list_type temp_buffer{}; - temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); - auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; + return arc_fast(circle, from, to); + } - // Calculate normals (tangents) for each line segment - for (std::decay_t i = 0; i < segments_count; ++i) - { - const auto n = (i + 1) % path_point_count; - const auto d = path_point[n] - path_point[i]; + auto RenderList::Painter::arc_n(const circle_type& circle, const float degree_from, const float degree_to, const std::uint32_t segments) noexcept -> Painter& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(degree_to > degree_from); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(degree_from >= 0); - const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); - temp_buffer_normals[i].x = normalized_y; - temp_buffer_normals[i].y = -normalized_x; - } + const auto& [center, radius] = circle; - if (not is_closed) + if (radius < .5f) { - temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + return pin(center); } - // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point - if (is_use_texture or not is_thick_line) + reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) { - // [PATH 1] Texture-based lines (thick or non-thick) + const auto a = degree_from + static_cast(i) / static_cast(segments) * (degree_to - degree_from); + pin({center + point_type{math::cos(a), math::sin(a)} * radius}); + } - // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA - const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; + return *this; + } - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (not is_closed) - { - temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; - temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; - temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; - temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; - } + auto RenderList::Painter::arc(const circle_type& circle, const float degree_from, const float degree_to) noexcept -> Painter& + { + const auto& [center, radius] = circle; - const auto current_vertex_index = static_cast(appender.vertex_count()); + if (radius < .5f) + { + return pin(center); + } - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - auto vertex_index_for_start = current_vertex_index; - for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - { - const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - const auto vertex_index_for_end = static_cast( - // closed - (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) - ); + const auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); - // Average normals - const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - // dm_x, dm_y are offset to the outer edge of the AA area - auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - dm_x *= half_draw_size; - dm_y *= half_draw_size; + // Automatic segment count + if (radius <= shared_data.arc_fast_radius_cutoff) + { + const auto is_reversed = degree_to < degree_from; - // Add temporary vertexes for the outer edges - temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; - temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; + // We are going to use precomputed values for mid-samples. + // Determine first and last sample in lookup table that belong to the arc + const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * degree_from / (std::numbers::pi_v * 2); + const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * degree_to / (std::numbers::pi_v * 2); - if (is_use_texture) - { - // Add indices for two triangles + const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); + const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); + const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); - // right - appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); - // left - appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); - } - else - { - // Add indexes for four triangles + const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; - // right 1 - appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); - // right 2 - appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); - // left 1 - appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); - // left 2 - appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); - } + const auto emit_start = math::abs(segment_from_angle - degree_from) >= 1e-5f; + const auto emit_end = math::abs(degree_to - segment_to_angle) >= 1e-5f; - vertex_index_for_start = vertex_index_for_end; + if (emit_start) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `arc_fast`. + pin({center + point_type{math::cos(degree_from), -math::sin(degree_from)} * radius}); } - - // Add vertexes for each point on the line - if (is_use_texture) + if (sample_mid > 0) { - const auto& uv = shared_data.baked_line_uvs[thickness_integer]; - - const auto uv0 = uv.left_top(); - const auto uv1 = uv.right_bottom(); - for (std::decay_t i = 0; i < path_point_count; ++i) - { - // left-side outer edge - appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); - // right-side outer edge - appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); - } + arc_fast(circle, sample_from, sample_to); } - else + if (emit_end) { - // If we're not using a texture, we need the center vertex as well - for (std::decay_t i = 0; i < path_point_count; ++i) - { - // center of line - appender.add_vertex(path_point[i], opaque_uv, color); - // left-side outer edge - appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); - // right-side outer edge - appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); - } + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `arc_fast`. + pin({center + point_type{math::cos(degree_to), -math::sin(degree_to)} * radius}); } } else { - // [PATH 2] Non-texture-based lines (non-thick) + const auto arc_length = degree_to - degree_from; + const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); + const auto arc_segment_count = std::ranges::max( + static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + static_cast(std::numbers::pi_v * 2 / arc_length) + ); + arc_n(circle, degree_from, degree_to, arc_segment_count); + } - // we need to draw the solid line core and thus require four vertices per point - const auto half_inner_thickness = (thickness - 1.f) * .5f; + return *this; + } - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (not is_closed) - { - const auto point_last = path_point_count - 1; - temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); - temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); - temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); - temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); - temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - } + auto RenderList::Painter::arc_n(const ellipse_type& ellipse, const float degree_from, const float degree_to, const std::uint32_t segments) noexcept -> Painter& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(degree_to > degree_from); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(degree_from >= 0); - const auto current_vertex_index = static_cast(appender.vertex_count()); + const auto& [center, radius, rotation] = ellipse; - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - auto vertex_index_for_start = current_vertex_index; - for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - { - const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - const auto vertex_index_for_end = static_cast((first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + 4)); + if (radius.width < .5f or radius.height < .5f) + { + return pin(center); + } - // Average normals - const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); - const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); - const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); - const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); - // Add temporary vertices - temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; - temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; - temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; - temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = degree_from + static_cast(i) / static_cast(segments) * (degree_to - degree_from); + const auto offset = point_type{math::cos(a), math::sin(a)} * radius; + const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; + const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; + pin({center + point_type{prime_x, prime_y}}); + } - // Add indexes - appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); - appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); - appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); - appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); - appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); - appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); + return *this; + } - vertex_index_for_start = vertex_index_for_end; - } + auto RenderList::Painter::bezier_cubic_n(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> Painter& + { + reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); + } - // Add vertices - for (std::decay_t i = 0; i < path_point_count; ++i) - { - appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); - appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); - appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); - appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); - } + return *this; + } + + auto RenderList::Painter::bezier_cubic(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter& + { + // auto-tessellated + const auto bezier_cubic_curve_casteljau = functional::y_combinator + { + [this]( + auto& self, + const point_type& ip1, + const point_type& ip2, + const point_type& ip3, + const point_type& ip4, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + { + const auto dx = ip4.x - ip1.x; + const auto dy = ip4.y - ip1.y; + const auto d2 = math::abs((ip2.x - ip4.x) * dy - (ip2.y - ip4.y) * dx); + const auto d3 = math::abs((ip3.x - ip4.x) * dy - (ip3.y - ip4.y) * dx); + + if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + pin(ip4); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (ip1 + ip2) * .5f; + const auto p_23 = (ip2 + ip3) * .5f; + const auto p_34 = (ip3 + ip4) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + const auto p_234 = (p_23 + p_34) * .5f; + const auto p_1234 = (p_123 + p_234) * .5f; + + self(ip1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); + self(p_1234, p_234, p_34, ip4, tessellation_tolerance, level + 1); + } + } + }; + + const auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + + reserve_extra(bezier_curve_casteljau_max_level * 2); + bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data.curve_tessellation_tolerance, 0); + + return *this; + } + + auto RenderList::Painter::bezier_quadratic_n(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> Painter& + { + reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + } + + return *this; + } + + auto RenderList::Painter::bezier_quadratic(const point_type& p1, const point_type& p2, const point_type& p3) noexcept -> Painter& + { + // auto-tessellated + const auto bezier_quadratic_curve_casteljau = functional::y_combinator{ + [this]( + auto& self, + const point_type& ip1, + const point_type& ip2, + const point_type& ip3, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + { + const auto dx = ip3.x - ip1.x; + const auto dy = ip3.y - ip1.y; + const auto det = (ip2.x - ip3.x) * dy - (ip2.y - ip3.y) * dx; + + if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + pin(ip3); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (ip1 + ip2) * .5f; + const auto p_23 = (ip2 + ip3) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + + self(ip1, p_12, p_123, tessellation_tolerance, level + 1); + self(p_123, p_23, ip3, tessellation_tolerance, level + 1); + } + } + }; + + const auto& render_list_context = *render_list_.get().context_; + const auto& shared_data = render_list_context.shared_data(); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + + reserve_extra(bezier_curve_casteljau_max_level * 2); + bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data.curve_tessellation_tolerance, 0); + + return *this; + } + + auto RenderList::Painter::stroke(const color_type color) noexcept -> void + { + const auto& render_list_context = *render_list_.get().context_; + + if (const auto render_list_flag = render_list_context.render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) + { + draw_convex_polygon_line_filled_aa(color); + } + else + { + draw_convex_polygon_line_filled(color); + } + + clear(); + } + + auto RenderList::Painter::stroke(const color_type color, const float thickness, const bool close) noexcept -> void + { + const auto& render_list_context = *render_list_.get().context_; + + if (const auto render_list_flag = render_list_context.render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) + { + draw_polygon_line_aa(color, thickness, close); + } + else + { + draw_polygon_line(color, thickness, close); + } + + clear(); + } + + RenderList::RenderList(Context& context, RenderListFlag flag) noexcept + : context_{memory::make_unique(context, flag)} {} + + RenderList::RenderList(RenderList&&) noexcept = default; + + auto RenderList::operator=(RenderList&&) noexcept -> RenderList& = default; + + RenderList::~RenderList() noexcept = default; + + auto RenderList::reset() noexcept -> void + { + context_->reset(); + } + + auto RenderList::set_flag(const RenderListFlag flag) noexcept -> void + { + context_->render_list_flag = flag; + } + + auto RenderList::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& + { + return context_->push_scissor(rect, intersect_with_current_scissor); + } + + auto RenderList::pop_scissor() noexcept -> void + { + context_->pop_scissor(); + } + + auto RenderList::push_texture(const texture_id_type texture) noexcept -> void + { + context_->push_texture(texture); + } + + auto RenderList::pop_texture() noexcept -> void + { + context_->pop_texture(); + } + + auto RenderList::line( + const point_type& from, + const point_type& to, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + painter(2).line(from, to).stroke(color, thickness, false); + } + + auto RenderList::triangle( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + painter(3).triangle(a, b, c).stroke(color, thickness, true); + } + + auto RenderList::triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + painter(3).triangle(a, b, c).stroke(color); + } + + auto RenderList::quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + painter(4).quadrilateral(p1, p2, p3, p4).stroke(color, thickness, true); + } + + auto RenderList::quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + painter(4).quadrilateral(p1, p2, p3, p4).stroke(color); + } + + auto RenderList::rect( + const rect_type& rect, + const color_type color, + const float rounding, + const RenderRectFlag flag, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + painter().rect(rect, rounding, flag).stroke(color, thickness, true); + } + + auto RenderList::rect( + const point_type& left_top, + const rect_type::extent_type& extent, + const color_type color, + const float rounding, + const RenderRectFlag flag, + const float thickness + ) noexcept -> void + { + return rect({left_top, extent}, color, rounding, flag, thickness); + } + + auto RenderList::rect( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const RenderRectFlag flag, + const float thickness + ) noexcept -> void + { + return rect({left_top, right_bottom}, color, rounding, flag, thickness); + } + + auto RenderList::rect_filled( + const rect_type& rect, + const color_type color, + const float rounding, + const RenderRectFlag flag + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + if (rounding < .5f or (RenderRectFlag::ROUND_CORNER_MASK & flag) == RenderRectFlag::ROUND_CORNER_NONE) + { + rect_filled(rect, color, color, color, color); + } + else + { + painter().rect(rect, rounding, flag).stroke(color); + } + } + + auto RenderList::rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + const color_type color, + const float rounding, + const RenderRectFlag flag + ) noexcept -> void + { + return rect_filled({left_top, extent}, color, rounding, flag); + } + + auto RenderList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color, + const float rounding, + const RenderRectFlag flag + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color, rounding, flag); + } + + auto RenderList::rect_filled( + const rect_type& rect, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) + { + return; + } + + const auto& shared_data = context_->shared_data(); + const auto& opaque_uv = shared_data.white_pixel_uv; + + RenderDataAppender appender{*context_}; + + // two triangle without path + constexpr RenderDataAppender::size_type vertex_count = 4; + constexpr RenderDataAppender::size_type index_count = 6; + appender.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(appender.vertex_count()); + + appender.add_vertex(rect.left_top(), opaque_uv, color_left_top); + appender.add_vertex(rect.right_top(), opaque_uv, color_right_top); + appender.add_vertex(rect.right_bottom(), opaque_uv, color_left_bottom); + appender.add_vertex(rect.left_bottom(), opaque_uv, color_right_bottom); + + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + + auto RenderList::rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + return rect_filled({left_top, extent}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto RenderList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type color_left_top, + const color_type color_right_top, + const color_type color_left_bottom, + const color_type color_right_bottom + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto RenderList::circle_n( + const circle_type& circle, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + painter().circle_n(circle, segments).stroke(color, thickness, true); + } + + auto RenderList::circle_n( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle_n({center, radius}, color, segments, thickness); + } + + auto RenderList::circle_n_filled( + const circle_type& circle, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; } + + painter().circle_n(circle, segments).stroke(color); } - auto RenderListDrawer::draw_convex_polygon_line_filled(const color_type color) noexcept -> void + auto RenderList::circle_n_filled( + const point_type& center, + const float radius, + const color_type color, + const std::uint32_t segments + ) noexcept -> void { - const auto path_point_count = path_list.size(); - const auto& path_point = path_list; + return circle_n_filled({center, radius}, color, segments); + } - if (path_point_count < 3 or color.alpha == 0) + void RenderList::circle( + const circle_type& circle, + const color_type color, + const float thickness + ) noexcept + { + if (color.alpha == 0 or circle.radius < .5f) { return; } - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); - auto appender = make_appender(); - - const auto vertex_count = path_point_count; - const auto index_count = (path_point_count - 2) * 3; - appender.reserve(vertex_count, index_count); + painter().circle(circle).stroke(color, thickness, true); + } - const auto current_vertex_index = static_cast(appender.vertex_count()); - const auto& opaque_uv = shared_data.white_pixel_uv; + auto RenderList::circle( + const point_type& center, + const float radius, + const color_type color, + const float thickness + ) noexcept -> void + { + return circle({center, radius}, color, thickness); + } - std::ranges::for_each( - path_point, - [&](const point_type& point) noexcept -> void - { - appender.add_vertex(point, opaque_uv, color); - } - ); - for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + auto RenderList::circle_filled( + const circle_type& circle, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f) { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); - - appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); + return; } + + painter().circle(circle).stroke(color); } - auto RenderListDrawer::draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void + auto RenderList::circle_filled( + const point_type& center, + const float radius, + const color_type color + ) noexcept -> void { - const auto path_point_count = path_list.size(); - const auto& path_point = path_list; + circle_filled({center, radius}, color); + } - if (path_point_count < 3 or color.alpha == 0) + auto RenderList::ellipse_n( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) { return; } - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); - auto appender = make_appender(); + painter().ellipse_n(ellipse, segments).stroke(color, thickness, true); + } - const auto& opaque_uv = shared_data.white_pixel_uv; - const auto transparent_color = color.transparent(); + void RenderList::ellipse_n( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept + { + return ellipse_n({center, radius, rotation}, color, segments, thickness); + } - const auto vertex_count = path_point_count * 2; - const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; - appender.reserve(vertex_count, index_count); + auto RenderList::ellipse_n_filled( + const ellipse_type& ellipse, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } - const auto current_vertex_inner_index = static_cast(appender.vertex_count()); - const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); + painter().ellipse_n(ellipse, segments).stroke(color); + } - // Add indexes for fill - for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + auto RenderList::ellipse_n_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_n_filled({center, radius, rotation}, color, segments); + } - appender.add_index(current_vertex_inner_index + 0, current_vertex_inner_index + static_cast((i - 1) << 1), current_vertex_inner_index + static_cast(i << 1)); + auto RenderList::ellipse( + const ellipse_type& ellipse, + const color_type color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; } - path_list_type temp_buffer{}; - temp_buffer.resize(path_point_count); - auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + painter().ellipse(ellipse).stroke(color, thickness, true); + } - for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - { - const auto d = path_point[n] - path_point[i]; + auto RenderList::ellipse( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color, + const float thickness + ) noexcept -> void + { + return ellipse({center, radius, rotation}, color, thickness); + } - const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); - temp_buffer_normals[i].x = normalized_y; - temp_buffer_normals[i].y = -normalized_x; - } - for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + auto RenderList::ellipse_filled( + const ellipse_type& ellipse, + const color_type color + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) { - // Average normals - const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; - auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - dm_x *= .5f; - dm_y *= .5f; + return; + } - // inner - appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); - // outer - appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + painter().ellipse(ellipse).stroke(color); + } - // Add indexes for fringes - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + auto RenderList::ellipse_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type color + ) noexcept -> void + { + return ellipse_filled({center, radius, rotation}, color); + } - appender.add_index( - current_vertex_inner_index + static_cast(n << 1), - current_vertex_inner_index + static_cast(i << 1), - current_vertex_outer_index + static_cast(i << 1) - ); - appender.add_index( - current_vertex_outer_index + static_cast(i << 1), - current_vertex_outer_index + static_cast(n << 1), - current_vertex_inner_index + static_cast(n << 1) - ); + auto RenderList::bezier_cubic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; } + + painter().bezier_cubic_n(p1, p2, p3, p4, segments).stroke(color, thickness, false); } - auto RenderListDrawer::draw_rect_filled( - const rect_type& rect, - const uv_type& uv, - const color_type color_left_top, - const color_type color_right_top, - const color_type color_left_bottom, - const color_type color_right_bottom + auto RenderList::bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type color, + const float thickness ) noexcept -> void { - auto appender = make_appender(); - - // two triangle without path - constexpr size_type vertex_count = 4; - constexpr size_type index_count = 6; - appender.reserve(vertex_count, index_count); + if (color.alpha == 0) + { + return; + } - const auto current_vertex_index = static_cast(appender.vertex_count()); + painter().bezier_cubic(p1, p2, p3, p4).stroke(color, thickness, false); + } - appender.add_vertex(rect.left_top(), uv, color_left_top); - appender.add_vertex(rect.right_top(), uv, color_right_top); - appender.add_vertex(rect.right_bottom(), uv, color_right_bottom); - appender.add_vertex(rect.left_bottom(), uv, color_left_bottom); + auto RenderList::bezier_quadratic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } - appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + painter().bezier_quadratic_n(p1, p2, p3, segments).stroke(color, thickness, false); } - auto RenderListDrawer::draw_rect_filled( - const rect_type& rect, - const color_type color_left_top, - const color_type color_right_top, - const color_type color_left_bottom, - const color_type color_right_bottom + auto RenderList::bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type color, + const float thickness + ) noexcept -> void + // clang-format on + { + if (color.alpha == 0) + { + return; + } + + painter().bezier_quadratic(p1, p2, p3).stroke(color, thickness, false); + } + + auto RenderList::text( + const std::string_view utf8_text, + const std::uint32_t font_size, + const point_type& point, + const color_type color, + const float wrap_width ) noexcept -> void { - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); - const auto& opaque_uv = shared_data.white_pixel_uv; - - draw_rect_filled(rect, opaque_uv, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + return this->text(utf8_text, font_size, point, color, GlyphFlag::NONE, wrap_width); } - auto RenderListDrawer::draw_text( + auto RenderList::text( const std::string_view utf8_text, const std::uint32_t font_size, - const point_type& p, + const point_type& point, const color_type color, - const float wrap_width, - const GlyphFlag flag + const GlyphFlag flag, + const float wrap_width ) noexcept -> void + // clang-format on { + if (color.alpha == 0) + { + return; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(wrap_width > 0); + // todo: // The texture used for glyphs may not be the default texture, then we need to switch the texture, // but at this time, the glyph information may not be written to the texture atlas (e.g. the first frame), then we can't know the ID of the texture atlas @@ -1692,13 +2069,10 @@ namespace // note: // Line break ('\n') have no glyph information (nullptr), meaning we need to skip it - auto& render_list = self.get(); - - const Renderer::AccessorTexture texture_accessor{render_list.renderer.get()}; - const auto& texture_context = texture_accessor.context(); - - Renderer::AccessorFont font_accessor{render_list.renderer.get()}; - auto& font_context = font_accessor.context(); + auto& render_list_context = *context_; + auto& context = render_list_context.context.get(); + auto& font_context = context.get_font_context(); + const auto& texture_context = context.get_texture_context(); const auto utf32_text = chars::convert(utf8_text); const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); @@ -1736,12 +2110,12 @@ namespace // two triangle without path const auto vertex_count = std::size_t{4} * visible_glyph_count; const auto index_count = std::size_t{6} * visible_glyph_count; - auto appender = make_appender(); + RenderDataAppender appender{render_list_context}; appender.reserve(vertex_count, index_count); - const auto wrap_pos_x = p.x + wrap_width; + const auto wrap_pos_x = point.x + wrap_width; const auto line_height = static_cast(font_size); - auto cursor = p + point_type{0, line_height}; + auto cursor = point + point_type{0, line_height}; for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) { @@ -1749,7 +2123,7 @@ namespace { if (codepoint == U'\n') { - cursor.x = p.x; + cursor.x = point.x; cursor.y += line_height; } } @@ -1757,11 +2131,11 @@ namespace { const auto& texture = texture_context.select(glyph->texture_atlas_id); - const auto new_texture = render_list.this_command_texture != texture.id(); + const auto new_texture = render_list_context.this_command_texture != texture.id(); if (new_texture) { - render_list.push_texture(texture.id()); + render_list_context.push_texture(texture.id()); } const auto& glyph_rect = glyph->rect; @@ -1771,7 +2145,7 @@ namespace if (cursor.x + glyph_advance_x > wrap_pos_x) { - cursor.x = p.x; + cursor.x = point.x; cursor.y += line_height; } @@ -1794,7 +2168,7 @@ namespace if (new_texture) { - render_list.pop_texture(); + render_list_context.pop_texture(); } cursor.x += glyph_advance_x; @@ -1802,17 +2176,25 @@ namespace } } - auto RenderListDrawer::draw_text_size( + auto RenderList::text_size( const std::string_view utf8_text, const std::uint32_t font_size, - const float wrap_width, - const GlyphFlag flag + const float wrap_width ) noexcept -> extent_type { - const auto& render_list = self.get(); + return this->text_size(utf8_text, font_size, GlyphFlag::NONE, wrap_width); + } - Renderer::AccessorFont font_accessor{render_list.renderer.get()}; - auto& font_context = font_accessor.context(); + auto RenderList::text_size( + const std::string_view utf8_text, + const std::uint32_t font_size, + const GlyphFlag flag, + const float wrap_width + ) noexcept -> extent_type + { + const auto& render_list_context = *context_; + auto& context = render_list_context.context.get(); + auto& font_context = context.get_font_context(); const auto utf32_text = chars::convert(utf8_text); const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); @@ -1831,581 +2213,1406 @@ namespace const auto line_height = static_cast(font_size); - float max_width = 0; - float current_width = 0; - float total_height = line_height; - - for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) - { - if (glyph == nullptr) - { - if (codepoint == U'\n') - { - max_width = std::ranges::max(max_width, current_width); - current_width = 0; - total_height += line_height; - } - } - else if (glyph->visible) - { - if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) - { - max_width = std::ranges::max(max_width, current_width); - current_width = glyph_advance_x; - total_height += line_height; - } - else - { - current_width += glyph_advance_x; - } - } - } - - max_width = std::ranges::max(max_width, current_width); - - return {max_width, total_height}; - } - - auto RenderListDrawer::draw_image( - const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - const color_type color - ) noexcept -> void - { - auto& render_list = self.get(); - - const auto new_texture = render_list.this_command_texture != texture_id; - - if (new_texture) - { - render_list.push_texture(texture_id); - } - - auto appender = make_appender(); - - // two triangle without path - constexpr size_type vertex_count = 4; - constexpr size_type index_count = 6; - appender.reserve(vertex_count, index_count); - - const auto current_vertex_index = static_cast(appender.vertex_count()); - - appender.add_vertex(display_p1, uv_p1, color); - appender.add_vertex(display_p2, uv_p2, color); - appender.add_vertex(display_p3, uv_p3, color); - appender.add_vertex(display_p4, uv_p4, color); - - appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - - if (new_texture) - { - render_list.pop_texture(); - } - } - - auto RenderListDrawer::draw_image_rounded( - const texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - const color_type color, - float rounding, - RenderFlag flag - ) noexcept -> void - { - // @see `path_rect` - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); - - if (rounding >= .5f) - { - flag = to_fixed_rect_corner_flag(flag); - - const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; - const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; - - rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); - rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); - } - - if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) - { - draw_image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); - } - else - { - auto& render_list = self.get(); - - const auto new_texture = render_list.this_command_texture != texture_id; - - if (new_texture) - { - render_list.push_texture(texture_id); - } - - const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; - const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; - const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - - path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); - path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); - path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); - path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); - - const auto before_vertex_count = render_list.vertex_list.size(); - // draw - path_stroke(color); - const auto after_vertex_count = render_list.vertex_list.size(); - - // set uv manually - - const auto display_size = display_rect.size(); - const auto uv_size = uv_rect.size(); - const auto scale = uv_size / display_size; - - auto it = render_list.vertex_list.begin() + static_cast(before_vertex_count); - const auto end = render_list.vertex_list.begin() + static_cast(after_vertex_count); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == render_list.vertex_list.end()); - - // note: linear uv - const auto uv_min = uv_rect.left_top(); - // const auto uv_max = uv_rect.right_bottom(); - while (it != end) - { - const auto v = uv_min + (it->position - display_rect.left_top()) * scale; - - it->uv = { - // std::ranges::clamp(v.x, uv_min.x, uv_max.x), - v.x, - // std::ranges::clamp(v.y, uv_min.y, uv_max.y) - v.y - }; - it += 1; - } - - if (new_texture) - { - render_list.pop_texture(); - } - } - } - - auto RenderListDrawer::path_clear() noexcept -> void - { - path_list.clear(); - } - - auto RenderListDrawer::path_reserve(const std::size_t size) noexcept -> void - { - path_list.reserve(size); - } - - auto RenderListDrawer::path_reserve_extra(const std::size_t size) noexcept -> void - { - path_reserve(path_list.size() + size); - } - - auto RenderListDrawer::path_pin(const point_type& point) noexcept -> void - { - path_list.push_back(point); - } - - auto RenderListDrawer::path_stroke(const color_type color, const RenderFlag flag, const float thickness) noexcept -> void - { - if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) - { - draw_polygon_line_aa(color, flag, thickness); - } - else - { - draw_polygon_line(color, flag, thickness); - } - - path_clear(); - } - - auto RenderListDrawer::path_stroke(const color_type color) noexcept -> void - { - if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) - { - draw_convex_polygon_line_filled_aa(color); - } - else - { - draw_convex_polygon_line_filled(color); - } - - path_clear(); - } - - auto RenderListDrawer::path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void - { - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); - - // Calculate arc auto segment step size - auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); - // Make sure we never do steps larger than one quarter of the circle - step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); - - const auto sample_range = math::abs(to - from); - const auto next_step = step; - - auto extra_max_sample = false; - if (step > 1) - { - const auto overstep = sample_range % step; - if (overstep > 0) - { - extra_max_sample = true; - - // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, - // distribute first step range evenly between them by reducing first step size. - step -= (step - overstep) / 2; - } - - path_reserve_extra(sample_range / step + 1 + (overstep > 0)); - } - else - { - path_reserve_extra(sample_range + 1); - } - - auto sample_index = from; - if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) - { - sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); - if (sample_index < 0) - { - sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); - } - } - - if (to >= from) + float max_width = 0; + float current_width = 0; + float total_height = line_height; + + for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) { - for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + if (glyph == nullptr) { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + if (codepoint == U'\n') { - sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); + max_width = std::ranges::max(max_width, current_width); + current_width = 0; + total_height += line_height; } - - const auto& sample_point = shared_data.vertex_sample_point(sample_index); - - path_pin({center + sample_point * radius}); } - } - else - { - for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + else if (glyph->visible) { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (sample_index < 0) + if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) { - sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + max_width = std::ranges::max(max_width, current_width); + current_width = glyph_advance_x; + total_height += line_height; + } + else + { + current_width += glyph_advance_x; } - - const auto& sample_point = shared_data.vertex_sample_point(sample_index); - - path_pin({center + sample_point * radius}); - } - } - - if (extra_max_sample) - { - auto normalized_max_sample_index = to % static_cast(RenderListSharedData::vertex_sample_points_count); - if (normalized_max_sample_index < 0) - { - normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; } - - const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); - - path_pin({center + sample_point * radius}); } - } - auto RenderListDrawer::path_arc_fast(const circle_type& circle, const RenderArcFlag flag) noexcept -> void - { - const auto [from, to] = range_of_arc(flag); + max_width = std::ranges::max(max_width, current_width); - return path_arc_fast(circle, from, to); + return {max_width, total_height}; } - auto RenderListDrawer::path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void + auto RenderList::image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type color + ) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); - - const auto& [center, radius] = circle; - - if (radius < .5f) + if (color.alpha == 0) { - path_pin(center); return; } - path_reserve_extra(segments); - for (std::uint32_t i = 0; i < segments; ++i) - { - const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); - } - } + auto& render_list_context = *context_; - auto RenderListDrawer::path_arc(const circle_type& circle, const float from, const float to) noexcept -> void - { - const auto& [center, radius] = circle; + const auto new_texture = render_list_context.this_command_texture != texture_id; - if (radius < .5f) + if (new_texture) { - path_pin(center); - return; + render_list_context.push_texture(texture_id); } - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); - - // Automatic segment count - if (radius <= shared_data.arc_fast_radius_cutoff) - { - const auto is_reversed = to < from; + RenderDataAppender appender{render_list_context}; - // We are going to use precomputed values for mid-samples. - // Determine first and last sample in lookup table that belong to the arc - const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); - const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); + // two triangle without path + constexpr RenderDataAppender::size_type vertex_count = 4; + constexpr RenderDataAppender::size_type index_count = 6; + appender.reserve(vertex_count, index_count); - const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); - const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); - const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); + const auto current_vertex_index = static_cast(appender.vertex_count()); - const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; - const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + appender.add_vertex(display_p1, uv_p1, color); + appender.add_vertex(display_p2, uv_p2, color); + appender.add_vertex(display_p3, uv_p3, color); + appender.add_vertex(display_p4, uv_p4, color); - const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; - const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; + appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - if (emit_start) - { - // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); - } - if (sample_mid > 0) - { - path_arc_fast(circle, sample_from, sample_to); - } - if (emit_end) - { - // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); - } - } - else + if (new_texture) { - const auto arc_length = to - from; - const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); - const auto arc_segment_count = std::ranges::max( - static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), - static_cast(std::numbers::pi_v * 2 / arc_length) - ); - path_arc_n(circle, from, to, arc_segment_count); + render_list_context.pop_texture(); } } - auto RenderListDrawer::path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void + auto RenderList::image( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void { - const auto& [center, radius, rotation] = ellipse; - const auto cos_theta = math::cos(rotation); - const auto sin_theta = math::sin(rotation); - - path_reserve_extra(segments); - for (std::uint32_t i = 0; i < segments; ++i) - { - const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - const auto offset = point_type{math::cos(a), math::sin(a)} * radius; - const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; - const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; - path_pin({center + point_type{prime_x, prime_y}}); - } + this->image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); } - auto RenderListDrawer::path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void + auto RenderList::image( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void { - path_pin(p1); - path_pin(p2); - path_pin(p3); - path_pin(p4); + this->image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); } - auto RenderListDrawer::path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void + auto RenderList::image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + float rounding, + RenderRectFlag flag, + const rect_type& uv_rect, + const color_type color + ) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); + if (color.alpha == 0) + { + return; + } + + // @see `path_rect` + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); if (rounding >= .5f) { flag = to_fixed_rect_corner_flag(flag); - const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; - const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + const auto v = (flag & RenderRectFlag::ROUND_CORNER_TOP) == RenderRectFlag::ROUND_CORNER_TOP or (flag & RenderRectFlag::ROUND_CORNER_BOTTOM) == RenderRectFlag::ROUND_CORNER_BOTTOM; + const auto h = (flag & RenderRectFlag::ROUND_CORNER_LEFT) == RenderRectFlag::ROUND_CORNER_LEFT or (flag & RenderRectFlag::ROUND_CORNER_RIGHT) == RenderRectFlag::ROUND_CORNER_RIGHT; - rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); - rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); } - if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + if (rounding < .5f or (RenderRectFlag::ROUND_CORNER_MASK & flag) == RenderRectFlag::ROUND_CORNER_NONE) { - path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + this->image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); } else { - const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; - const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; - const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + auto& render_list_context = *context_; - path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); - path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); - path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); - path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); - } - } + const auto new_texture = render_list_context.this_command_texture != texture_id; - auto RenderListDrawer::path_bezier_cubic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const float tessellation_tolerance, - const std::size_t level - ) noexcept -> void - { - const auto dx = p4.x - p1.x; - const auto dy = p4.y - p1.y; - const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); - const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); + if (new_texture) + { + render_list_context.push_texture(texture_id); + } - if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) - { - path_pin(p4); - } - else if (level < bezier_curve_casteljau_max_level) - { - const auto p_12 = (p1 + p2) * .5f; - const auto p_23 = (p2 + p3) * .5f; - const auto p_34 = (p3 + p4) * .5f; - const auto p_123 = (p_12 + p_23) * .5f; - const auto p_234 = (p_23 + p_34) * .5f; - const auto p_1234 = (p_123 + p_234) * .5f; + const auto rounding_left_top = (flag & RenderRectFlag::ROUND_CORNER_LEFT_TOP) != RenderRectFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & RenderRectFlag::ROUND_CORNER_RIGHT_TOP) != RenderRectFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & RenderRectFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderRectFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & RenderRectFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderRectFlag::NONE ? rounding : 0; - path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); - path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); - } - } + const auto before_vertex_count = render_list_context.vertex_list.size(); - auto RenderListDrawer::path_bezier_quadratic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const float tessellation_tolerance, - const std::size_t level - ) noexcept -> void - { - const auto dx = p3.x - p1.x; - const auto dy = p3.y - p1.y; - const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; + // draw + painter() + .arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH) + .arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH) + .arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH) + .arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH) + .stroke(color); - if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) - { - path_pin(p3); - } - else if (level < bezier_curve_casteljau_max_level) - { - const auto p_12 = (p1 + p2) * .5f; - const auto p_23 = (p2 + p3) * .5f; - const auto p_123 = (p_12 + p_23) * .5f; + const auto after_vertex_count = render_list_context.vertex_list.size(); - path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); - path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); - } - } + // set uv manually - auto RenderListDrawer::path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void - { - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); + const auto display_size = display_rect.size(); + const auto uv_size = uv_rect.size(); + const auto scale = uv_size / display_size; - path_pin(p1); - if (segments == 0) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + auto it = render_list_context.vertex_list.begin() + static_cast(before_vertex_count); + const auto end = render_list_context.vertex_list.begin() + static_cast(after_vertex_count); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == render_list_context.vertex_list.end()); - path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // auto-tessellated - path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data.curve_tessellation_tolerance, 0); - } - else - { - path_reserve_extra(segments); - const auto step = 1.f / static_cast(segments); - for (std::uint32_t i = 1; i <= segments; ++i) + // FIXME-OPT: linear uv + const auto uv_min = uv_rect.left_top(); + // const auto uv_max = uv_rect.right_bottom(); + while (it != end) { - path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); - } - } - } - - auto RenderListDrawer::path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void - { - const auto& render_list = self.get(); - const auto& shared_data = render_list.shared_data(); + const auto v = uv_min + (it->position - display_rect.left_top()) * scale; - path_pin(p1); - if (segments == 0) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + it->uv = { + // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + v.x, + // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + v.y + }; + it += 1; + } - path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // auto-tessellated - path_bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data.curve_tessellation_tolerance, 0); - } - else - { - path_reserve_extra(segments); - const auto step = 1.f / static_cast(segments); - for (std::uint32_t i = 1; i <= segments; ++i) + if (new_texture) { - path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + render_list_context.pop_texture(); } } } + + auto RenderList::image_rounded( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const float rounding, + const RenderRectFlag flag, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + this->image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + } +} + +namespace +{ + // auto RenderListDrawer::make_appender() noexcept -> RenderDataAppender + // { + // auto& render_list = self.get(); + // + // return {render_list.command_list.back(), render_list.vertex_list, render_list.index_list}; + // } + // + // auto RenderListDrawer::draw_polygon_line(const color_type color, const RenderFlag render_flag, const float thickness) noexcept -> void + // { + // const auto path_point_count = path_list.size(); + // const auto& path_point = path_list; + // + // if (path_point_count < 2 or color.alpha == 0) + // { + // return; + // } + // + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // auto appender = make_appender(); + // + // const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + // const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + // + // const auto vertex_count = segments_count * 4; + // const auto index_count = segments_count * 6; + // appender.reserve(vertex_count, index_count); + // + // for (std::decay_t i = 0; i < segments_count; ++i) + // { + // const auto n = (i + 1) % path_point_count; + // + // const auto& p1 = path_point[i]; + // const auto& p2 = path_point[n]; + // + // auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); + // normalized_x *= (thickness * .5f); + // normalized_y *= (thickness * .5f); + // + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // const auto& opaque_uv = shared_data.white_pixel_uv; + // + // appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + // appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + // appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + // appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + // + // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + // } + // } + // + // auto RenderListDrawer::draw_polygon_line_aa(const color_type color, const RenderFlag render_flag, float thickness) noexcept -> void + // { + // const auto path_point_count = path_list.size(); + // const auto& path_point = path_list; + // + // if (path_point_count < 2 or color.alpha == 0) + // { + // return; + // } + // + // const auto& render_list = self.get(); + // const auto render_list_flag = render_list.render_list_flag; + // const auto& shared_data = render_list.shared_data(); + // auto appender = make_appender(); + // + // const auto& opaque_uv = shared_data.white_pixel_uv; + // const auto transparent_color = color.transparent(); + // + // const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; + // const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + // const auto is_thick_line = thickness > 1.f; + // + // thickness = std::ranges::max(thickness, 1.f); + // const auto thickness_integer = static_cast(thickness); + // const auto thickness_fractional = thickness - static_cast(thickness_integer); + // + // const auto is_use_texture = + // ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + // (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); + // + // const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); + // const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); + // appender.reserve(vertex_cont, index_count); + // + // // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + // path_list_type temp_buffer{}; + // temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); + // auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + // auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; + // + // // Calculate normals (tangents) for each line segment + // for (std::decay_t i = 0; i < segments_count; ++i) + // { + // const auto n = (i + 1) % path_point_count; + // const auto d = path_point[n] - path_point[i]; + // + // const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + // temp_buffer_normals[i].x = normalized_y; + // temp_buffer_normals[i].y = -normalized_x; + // } + // + // if (not is_closed) + // { + // temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + // } + // + // // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + // if (is_use_texture or not is_thick_line) + // { + // // [PATH 1] Texture-based lines (thick or non-thick) + // + // // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA + // const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; + // + // // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + // if (not is_closed) + // { + // temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; + // temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; + // temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; + // temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; + // } + // + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // + // // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + // auto vertex_index_for_start = current_vertex_index; + // for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + // { + // const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + // const auto vertex_index_for_end = static_cast( + // // closed + // (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + // ); + // + // // Average normals + // const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // // dm_x, dm_y are offset to the outer edge of the AA area + // auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + // dm_x *= half_draw_size; + // dm_y *= half_draw_size; + // + // // Add temporary vertexes for the outer edges + // temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; + // temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; + // + // if (is_use_texture) + // { + // // Add indices for two triangles + // + // // right + // appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); + // // left + // appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); + // } + // else + // { + // // Add indexes for four triangles + // + // // right 1 + // appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); + // // right 2 + // appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); + // // left 1 + // appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // // left 2 + // appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + // } + // + // vertex_index_for_start = vertex_index_for_end; + // } + // + // // Add vertexes for each point on the line + // if (is_use_texture) + // { + // const auto& uv = shared_data.baked_line_uvs[thickness_integer]; + // + // const auto uv0 = uv.left_top(); + // const auto uv1 = uv.right_bottom(); + // for (std::decay_t i = 0; i < path_point_count; ++i) + // { + // // left-side outer edge + // appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); + // // right-side outer edge + // appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); + // } + // } + // else + // { + // // If we're not using a texture, we need the center vertex as well + // for (std::decay_t i = 0; i < path_point_count; ++i) + // { + // // center of line + // appender.add_vertex(path_point[i], opaque_uv, color); + // // left-side outer edge + // appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); + // // right-side outer edge + // appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); + // } + // } + // } + // else + // { + // // [PATH 2] Non-texture-based lines (non-thick) + // + // // we need to draw the solid line core and thus require four vertices per point + // const auto half_inner_thickness = (thickness - 1.f) * .5f; + // + // // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + // if (not is_closed) + // { + // const auto point_last = path_point_count - 1; + // temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); + // temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); + // temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); + // temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); + // temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + // temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + // temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + // temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + // } + // + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // + // // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + // auto vertex_index_for_start = current_vertex_index; + // for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + // { + // const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + // const auto vertex_index_for_end = static_cast((first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + 4)); + // + // // Average normals + // const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + // const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); + // const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); + // const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); + // const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); + // + // // Add temporary vertices + // temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; + // temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; + // temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; + // temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + // + // // Add indexes + // appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); + // appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); + // appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + // appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); + // appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); + // + // vertex_index_for_start = vertex_index_for_end; + // } + // + // // Add vertices + // for (std::decay_t i = 0; i < path_point_count; ++i) + // { + // appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); + // appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); + // appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); + // appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); + // } + // } + // } + // + // auto RenderListDrawer::draw_convex_polygon_line_filled(const color_type color) noexcept -> void + // { + // const auto path_point_count = path_list.size(); + // const auto& path_point = path_list; + // + // if (path_point_count < 3 or color.alpha == 0) + // { + // return; + // } + // + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // auto appender = make_appender(); + // + // const auto vertex_count = path_point_count; + // const auto index_count = (path_point_count - 2) * 3; + // appender.reserve(vertex_count, index_count); + // + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // const auto& opaque_uv = shared_data.white_pixel_uv; + // + // std::ranges::for_each( + // path_point, + // [&](const point_type& point) noexcept -> void + // { + // appender.add_vertex(point, opaque_uv, color); + // } + // ); + // for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); + // + // appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); + // } + // } + // + // auto RenderListDrawer::draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void + // { + // const auto path_point_count = path_list.size(); + // const auto& path_point = path_list; + // + // if (path_point_count < 3 or color.alpha == 0) + // { + // return; + // } + // + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // auto appender = make_appender(); + // + // const auto& opaque_uv = shared_data.white_pixel_uv; + // const auto transparent_color = color.transparent(); + // + // const auto vertex_count = path_point_count * 2; + // const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; + // appender.reserve(vertex_count, index_count); + // + // const auto current_vertex_inner_index = static_cast(appender.vertex_count()); + // const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); + // + // // Add indexes for fill + // for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + // + // appender.add_index(current_vertex_inner_index + 0, current_vertex_inner_index + static_cast((i - 1) << 1), current_vertex_inner_index + static_cast(i << 1)); + // } + // + // path_list_type temp_buffer{}; + // temp_buffer.resize(path_point_count); + // auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + // + // for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + // { + // const auto d = path_point[n] - path_point[i]; + // + // const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + // temp_buffer_normals[i].x = normalized_y; + // temp_buffer_normals[i].y = -normalized_x; + // } + // for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + // { + // // Average normals + // const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; + // auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + // dm_x *= .5f; + // dm_y *= .5f; + // + // // inner + // appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); + // // outer + // appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + // + // // Add indexes for fringes + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + // + // appender.add_index( + // current_vertex_inner_index + static_cast(n << 1), + // current_vertex_inner_index + static_cast(i << 1), + // current_vertex_outer_index + static_cast(i << 1) + // ); + // appender.add_index( + // current_vertex_outer_index + static_cast(i << 1), + // current_vertex_outer_index + static_cast(n << 1), + // current_vertex_inner_index + static_cast(n << 1) + // ); + // } + // } + // + // auto RenderListDrawer::draw_rect_filled( + // const rect_type& rect, + // const uv_type& uv, + // const color_type color_left_top, + // const color_type color_right_top, + // const color_type color_left_bottom, + // const color_type color_right_bottom + // ) noexcept -> void + // { + // auto appender = make_appender(); + // + // // two triangle without path + // constexpr size_type vertex_count = 4; + // constexpr size_type index_count = 6; + // appender.reserve(vertex_count, index_count); + // + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // + // appender.add_vertex(rect.left_top(), uv, color_left_top); + // appender.add_vertex(rect.right_top(), uv, color_right_top); + // appender.add_vertex(rect.right_bottom(), uv, color_right_bottom); + // appender.add_vertex(rect.left_bottom(), uv, color_left_bottom); + // + // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + // } + // + // auto RenderListDrawer::draw_rect_filled( + // const rect_type& rect, + // const color_type color_left_top, + // const color_type color_right_top, + // const color_type color_left_bottom, + // const color_type color_right_bottom + // ) noexcept -> void + // { + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // const auto& opaque_uv = shared_data.white_pixel_uv; + // + // draw_rect_filled(rect, opaque_uv, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + // } + // + // auto RenderListDrawer::draw_text( + // const std::string_view utf8_text, + // const std::uint32_t font_size, + // const point_type& p, + // const color_type color, + // const float wrap_width, + // const GlyphFlag flag + // ) noexcept -> void + // { + // // todo: + // // The texture used for glyphs may not be the default texture, then we need to switch the texture, + // // but at this time, the glyph information may not be written to the texture atlas (e.g. the first frame), then we can't know the ID of the texture atlas + // + // // note: + // // Line break ('\n') have no glyph information (nullptr), meaning we need to skip it + // + // auto& render_list = self.get(); + // + // const Renderer::AccessorTexture texture_accessor{render_list.renderer.get()}; + // const auto& texture_context = texture_accessor.context(); + // + // Renderer::AccessorFont font_accessor{render_list.renderer.get()}; + // auto& font_context = font_accessor.context(); + // + // const auto utf32_text = chars::convert(utf8_text); + // const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + // + // if (std::ranges::any_of( + // glyphs, + // [](const auto* glyph) noexcept -> bool + // { + // return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; + // } + // )) + // { + // // skip this frame? + // return; + // } + // + // const auto visible_glyph_count = std::ranges::count_if( + // glyphs, + // [](const auto* glyph) noexcept -> bool + // { + // if (glyph == nullptr) + // { + // return false; + // } + // + // if (not glyph->visible) + // { + // return false; + // } + // + // return true; + // } + // ); + // + // // two triangle without path + // const auto vertex_count = std::size_t{4} * visible_glyph_count; + // const auto index_count = std::size_t{6} * visible_glyph_count; + // auto appender = make_appender(); + // appender.reserve(vertex_count, index_count); + // + // const auto wrap_pos_x = p.x + wrap_width; + // const auto line_height = static_cast(font_size); + // auto cursor = p + point_type{0, line_height}; + // + // for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + // { + // if (glyph == nullptr) + // { + // if (codepoint == U'\n') + // { + // cursor.x = p.x; + // cursor.y += line_height; + // } + // } + // else if (glyph->visible) + // { + // const auto& texture = texture_context.select(glyph->texture_atlas_id); + // + // const auto new_texture = render_list.this_command_texture != texture.id(); + // + // if (new_texture) + // { + // render_list.push_texture(texture.id()); + // } + // + // const auto& glyph_rect = glyph->rect; + // const auto& glyph_uv = glyph->uv; + // const auto glyph_advance_x = glyph->advance_x; + // const auto glyph_colored = glyph->colored; + // + // if (cursor.x + glyph_advance_x > wrap_pos_x) + // { + // cursor.x = p.x; + // cursor.y += line_height; + // } + // + // const auto left_top = glyph_rect.left_top().to(); + // const rect_type char_rect + // { + // cursor + point_type{left_top.x, -left_top.y}, + // glyph_rect.size().to() + // }; + // const auto color_may_colored = glyph_colored ? color.transparent() : color; + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // + // appender.add_vertex(char_rect.left_top(), glyph_uv.left_top(), color_may_colored); + // appender.add_vertex(char_rect.right_top(), glyph_uv.right_top(), color_may_colored); + // appender.add_vertex(char_rect.right_bottom(), glyph_uv.right_bottom(), color_may_colored); + // appender.add_vertex(char_rect.left_bottom(), glyph_uv.left_bottom(), color_may_colored); + // + // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + // + // if (new_texture) + // { + // render_list.pop_texture(); + // } + // + // cursor.x += glyph_advance_x; + // } + // } + // } + // + // auto RenderListDrawer::draw_text_size( + // const std::string_view utf8_text, + // const std::uint32_t font_size, + // const float wrap_width, + // const GlyphFlag flag + // ) noexcept -> extent_type + // { + // const auto& render_list = self.get(); + // + // Renderer::AccessorFont font_accessor{render_list.renderer.get()}; + // auto& font_context = font_accessor.context(); + // + // const auto utf32_text = chars::convert(utf8_text); + // const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + // + // if (std::ranges::any_of( + // glyphs, + // [](const auto& glyph) noexcept -> bool + // { + // return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; + // } + // )) + // { + // // skip this frame? + // return {0, 0}; + // } + // + // const auto line_height = static_cast(font_size); + // + // float max_width = 0; + // float current_width = 0; + // float total_height = line_height; + // + // for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + // { + // if (glyph == nullptr) + // { + // if (codepoint == U'\n') + // { + // max_width = std::ranges::max(max_width, current_width); + // current_width = 0; + // total_height += line_height; + // } + // } + // else if (glyph->visible) + // { + // if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) + // { + // max_width = std::ranges::max(max_width, current_width); + // current_width = glyph_advance_x; + // total_height += line_height; + // } + // else + // { + // current_width += glyph_advance_x; + // } + // } + // } + // + // max_width = std::ranges::max(max_width, current_width); + // + // return {max_width, total_height}; + // } + // + // auto RenderListDrawer::draw_image( + // const texture_id_type texture_id, + // const point_type& display_p1, + // const point_type& display_p2, + // const point_type& display_p3, + // const point_type& display_p4, + // const uv_type& uv_p1, + // const uv_type& uv_p2, + // const uv_type& uv_p3, + // const uv_type& uv_p4, + // const color_type color + // ) noexcept -> void + // { + // auto& render_list = self.get(); + // + // const auto new_texture = render_list.this_command_texture != texture_id; + // + // if (new_texture) + // { + // render_list.push_texture(texture_id); + // } + // + // auto appender = make_appender(); + // + // // two triangle without path + // constexpr size_type vertex_count = 4; + // constexpr size_type index_count = 6; + // appender.reserve(vertex_count, index_count); + // + // const auto current_vertex_index = static_cast(appender.vertex_count()); + // + // appender.add_vertex(display_p1, uv_p1, color); + // appender.add_vertex(display_p2, uv_p2, color); + // appender.add_vertex(display_p3, uv_p3, color); + // appender.add_vertex(display_p4, uv_p4, color); + // + // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + // + // if (new_texture) + // { + // render_list.pop_texture(); + // } + // } + // + // auto RenderListDrawer::draw_image_rounded( + // const texture_id_type texture_id, + // const rect_type& display_rect, + // const rect_type& uv_rect, + // const color_type color, + // float rounding, + // RenderFlag flag + // ) noexcept -> void + // { + // // @see `path_rect` + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); + // + // if (rounding >= .5f) + // { + // flag = to_fixed_rect_corner_flag(flag); + // + // const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; + // const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + // + // rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); + // rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); + // } + // + // if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + // { + // draw_image( + // texture_id, + // display_rect.left_top(), + // display_rect.right_top(), + // display_rect.right_bottom(), + // display_rect.left_bottom(), + // uv_rect.left_top(), + // uv_rect.right_top(), + // uv_rect.right_bottom(), + // uv_rect.left_bottom(), + // color + // ); + // } + // else + // { + // auto& render_list = self.get(); + // + // const auto new_texture = render_list.this_command_texture != texture_id; + // + // if (new_texture) + // { + // render_list.push_texture(texture_id); + // } + // + // const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; + // const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; + // const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + // const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + // + // path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + // path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + // path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + // path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + // + // const auto before_vertex_count = render_list.vertex_list.size(); + // // draw + // path_stroke(color); + // const auto after_vertex_count = render_list.vertex_list.size(); + // + // // set uv manually + // + // const auto display_size = display_rect.size(); + // const auto uv_size = uv_rect.size(); + // const auto scale = uv_size / display_size; + // + // auto it = render_list.vertex_list.begin() + static_cast(before_vertex_count); + // const auto end = render_list.vertex_list.begin() + static_cast(after_vertex_count); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == render_list.vertex_list.end()); + // + // // note: linear uv + // const auto uv_min = uv_rect.left_top(); + // // const auto uv_max = uv_rect.right_bottom(); + // while (it != end) + // { + // const auto v = uv_min + (it->position - display_rect.left_top()) * scale; + // + // it->uv = { + // // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + // v.x, + // // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + // v.y + // }; + // it += 1; + // } + // + // if (new_texture) + // { + // render_list.pop_texture(); + // } + // } + // } + // + // auto RenderListDrawer::path_clear() noexcept -> void + // { + // path_list.clear(); + // } + // + // auto RenderListDrawer::path_reserve(const std::size_t size) noexcept -> void + // { + // path_list.reserve(size); + // } + // + // auto RenderListDrawer::path_reserve_extra(const std::size_t size) noexcept -> void + // { + // path_reserve(path_list.size() + size); + // } + // + // auto RenderListDrawer::path_pin(const point_type& point) noexcept -> void + // { + // path_list.push_back(point); + // } + // + // auto RenderListDrawer::path_stroke(const color_type color, const RenderFlag flag, const float thickness) noexcept -> void + // { + // if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) + // { + // draw_polygon_line_aa(color, flag, thickness); + // } + // else + // { + // draw_polygon_line(color, flag, thickness); + // } + // + // path_clear(); + // } + // + // auto RenderListDrawer::path_stroke(const color_type color) noexcept -> void + // { + // if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) + // { + // draw_convex_polygon_line_filled_aa(color); + // } + // else + // { + // draw_convex_polygon_line_filled(color); + // } + // + // path_clear(); + // } + // + // auto RenderListDrawer::path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void + // { + // const auto& [center, radius] = circle; + // + // if (radius < .5f) + // { + // path_pin(center); + // return; + // } + // + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // + // // Calculate arc auto segment step size + // auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); + // // Make sure we never do steps larger than one quarter of the circle + // step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); + // + // const auto sample_range = math::abs(to - from); + // const auto next_step = step; + // + // auto extra_max_sample = false; + // if (step > 1) + // { + // const auto overstep = sample_range % step; + // if (overstep > 0) + // { + // extra_max_sample = true; + // + // // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, + // // distribute first step range evenly between them by reducing first step size. + // step -= (step - overstep) / 2; + // } + // + // path_reserve_extra(sample_range / step + 1 + (overstep > 0)); + // } + // else + // { + // path_reserve_extra(sample_range + 1); + // } + // + // auto sample_index = from; + // if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + // { + // sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); + // if (sample_index < 0) + // { + // sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + // } + // } + // + // if (to >= from) + // { + // for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + // { + // // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + // if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + // { + // sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); + // } + // + // const auto& sample_point = shared_data.vertex_sample_point(sample_index); + // + // path_pin({center + sample_point * radius}); + // } + // } + // else + // { + // for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + // { + // // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + // if (sample_index < 0) + // { + // sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + // } + // + // const auto& sample_point = shared_data.vertex_sample_point(sample_index); + // + // path_pin({center + sample_point * radius}); + // } + // } + // + // if (extra_max_sample) + // { + // auto normalized_max_sample_index = to % static_cast(RenderListSharedData::vertex_sample_points_count); + // if (normalized_max_sample_index < 0) + // { + // normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; + // } + // + // const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); + // + // path_pin({center + sample_point * radius}); + // } + // } + // + // auto RenderListDrawer::path_arc_fast(const circle_type& circle, const RenderArcFlag flag) noexcept -> void + // { + // const auto [from, to] = range_of_arc(flag); + // + // return path_arc_fast(circle, from, to); + // } + // + // auto RenderListDrawer::path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); + // + // const auto& [center, radius] = circle; + // + // if (radius < .5f) + // { + // path_pin(center); + // return; + // } + // + // path_reserve_extra(segments); + // for (std::uint32_t i = 0; i < segments; ++i) + // { + // const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + // path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); + // } + // } + // + // auto RenderListDrawer::path_arc(const circle_type& circle, const float from, const float to) noexcept -> void + // { + // const auto& [center, radius] = circle; + // + // if (radius < .5f) + // { + // path_pin(center); + // return; + // } + // + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // + // // Automatic segment count + // if (radius <= shared_data.arc_fast_radius_cutoff) + // { + // const auto is_reversed = to < from; + // + // // We are going to use precomputed values for mid-samples. + // // Determine first and last sample in lookup table that belong to the arc + // const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); + // const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); + // + // const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); + // const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); + // const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); + // + // const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + // const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + // + // const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; + // const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; + // + // if (emit_start) + // { + // // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + // path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); + // } + // if (sample_mid > 0) + // { + // path_arc_fast(circle, sample_from, sample_to); + // } + // if (emit_end) + // { + // // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + // path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); + // } + // } + // else + // { + // const auto arc_length = to - from; + // const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); + // const auto arc_segment_count = std::ranges::max( + // static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + // static_cast(std::numbers::pi_v * 2 / arc_length) + // ); + // path_arc_n(circle, from, to, arc_segment_count); + // } + // } + // + // auto RenderListDrawer::path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void + // { + // const auto& [center, radius, rotation] = ellipse; + // const auto cos_theta = math::cos(rotation); + // const auto sin_theta = math::sin(rotation); + // + // path_reserve_extra(segments); + // for (std::uint32_t i = 0; i < segments; ++i) + // { + // const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + // const auto offset = point_type{math::cos(a), math::sin(a)} * radius; + // const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; + // const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; + // path_pin({center + point_type{prime_x, prime_y}}); + // } + // } + // + // auto RenderListDrawer::path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void + // { + // path_pin(p1); + // path_pin(p2); + // path_pin(p3); + // path_pin(p4); + // } + // + // auto RenderListDrawer::path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); + // + // if (rounding >= .5f) + // { + // flag = to_fixed_rect_corner_flag(flag); + // + // const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; + // const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; + // + // rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); + // rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); + // } + // + // if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) + // { + // path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + // } + // else + // { + // const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; + // const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; + // const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + // const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; + // + // path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); + // path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); + // path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); + // path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); + // } + // } + // + // auto RenderListDrawer::path_bezier_cubic_curve_casteljau( + // const point_type& p1, + // const point_type& p2, + // const point_type& p3, + // const point_type& p4, + // const float tessellation_tolerance, + // const std::size_t level + // ) noexcept -> void + // { + // const auto dx = p4.x - p1.x; + // const auto dy = p4.y - p1.y; + // const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); + // const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); + // + // if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + // { + // path_pin(p4); + // } + // else if (level < bezier_curve_casteljau_max_level) + // { + // const auto p_12 = (p1 + p2) * .5f; + // const auto p_23 = (p2 + p3) * .5f; + // const auto p_34 = (p3 + p4) * .5f; + // const auto p_123 = (p_12 + p_23) * .5f; + // const auto p_234 = (p_23 + p_34) * .5f; + // const auto p_1234 = (p_123 + p_234) * .5f; + // + // path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); + // path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); + // } + // } + // + // auto RenderListDrawer::path_bezier_quadratic_curve_casteljau( + // const point_type& p1, + // const point_type& p2, + // const point_type& p3, + // const float tessellation_tolerance, + // const std::size_t level + // ) noexcept -> void + // { + // const auto dx = p3.x - p1.x; + // const auto dy = p3.y - p1.y; + // const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; + // + // if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + // { + // path_pin(p3); + // } + // else if (level < bezier_curve_casteljau_max_level) + // { + // const auto p_12 = (p1 + p2) * .5f; + // const auto p_23 = (p2 + p3) * .5f; + // const auto p_123 = (p_12 + p_23) * .5f; + // + // path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); + // path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); + // } + // } + // + // auto RenderListDrawer::path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void + // { + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // + // path_pin(p1); + // if (segments == 0) + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + // + // path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // // auto-tessellated + // path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data.curve_tessellation_tolerance, 0); + // } + // else + // { + // path_reserve_extra(segments); + // const auto step = 1.f / static_cast(segments); + // for (std::uint32_t i = 1; i <= segments; ++i) + // { + // path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); + // } + // } + // } + // + // auto RenderListDrawer::path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void + // { + // const auto& render_list = self.get(); + // const auto& shared_data = render_list.shared_data(); + // + // path_pin(p1); + // if (segments == 0) + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); + // + // path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // // auto-tessellated + // path_bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data.curve_tessellation_tolerance, 0); + // } + // else + // { + // path_reserve_extra(segments); + // const auto step = 1.f / static_cast(segments); + // for (std::uint32_t i = 1; i <= segments; ++i) + // { + // path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + // } + // } + // } }; diff --git a/src/gfx_new/internal/render_list.hpp b/src/gfx_new/internal/render_list.hpp index 74bd260..db57504 100644 --- a/src/gfx_new/internal/render_list.hpp +++ b/src/gfx_new/internal/render_list.hpp @@ -5,9 +5,60 @@ #pragma once -#include +#include namespace gal::prometheus::gfx_new { - // + class RenderList::RenderListContext final + { + public: + using size_type = RenderData::size_type; + + using command_type = RenderData::command_type; + + using vertex_list_type = RenderData::vertex_list_type; + using index_list_type = RenderData::index_list_type; + using command_list_type = RenderData::command_list_type; + + mutable memory::RefWrapper context; + + RenderListFlag render_list_flag; + + // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) + // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 + // command_list: + // 0: .scissor = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) + // 1: .scissor = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) + // 2: .scissor = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) + command_list_type command_list; + vertex_list_type vertex_list; + index_list_type index_list; + + rect_type this_command_scissor; + texture_id_type this_command_texture; + + private: + auto push_command() noexcept -> void; + + auto on_scissor_changed() noexcept -> void; + auto on_texture_changed() noexcept -> void; + + public: + [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData&; + + [[nodiscard]] auto default_texture() const noexcept -> texture_id_type; + + auto reset() noexcept -> void; + + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE + + auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; + + auto pop_scissor() noexcept -> void; + + auto push_texture(texture_id_type texture) noexcept -> void; + + auto pop_texture() noexcept -> void; + }; } diff --git a/src/gfx_new/internal/renderer_context.cpp b/src/gfx_new/internal/renderer_context.cpp deleted file mode 100644 index 4198a70..0000000 --- a/src/gfx_new/internal/renderer_context.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include diff --git a/src/gfx_new/internal/renderer_context.hpp b/src/gfx_new/internal/renderer_context.hpp deleted file mode 100644 index 0ae91bd..0000000 --- a/src/gfx_new/internal/renderer_context.hpp +++ /dev/null @@ -1,21 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include - -namespace gal::prometheus::gfx_new -{ - class Renderer::RendererContext - { - public: - FontContext font_context; - TextureContext texture_context; - RenderContext render_context; - }; -} diff --git a/src/gfx_new/internal/texture.cpp b/src/gfx_new/internal/texture.cpp index 08f5e22..7c77cfc 100644 --- a/src/gfx_new/internal/texture.cpp +++ b/src/gfx_new/internal/texture.cpp @@ -5,8 +5,6 @@ #include -#include - // #define STB_RECT_PACK_IMPLEMENTATION // #include diff --git a/src/gfx_new/internal/texture.hpp b/src/gfx_new/internal/texture.hpp index 386cca9..34d87ef 100644 --- a/src/gfx_new/internal/texture.hpp +++ b/src/gfx_new/internal/texture.hpp @@ -7,7 +7,7 @@ #include -#include +#include #include @@ -18,7 +18,7 @@ namespace gal::prometheus::gfx_new class Texture final { // Texture::id and Texture::dirty - friend Renderer::AccessorTexture; + friend Context; public: using element_type = TextureDescriptor::element_type; diff --git a/src/gfx_new/internal/type.hpp b/src/gfx_new/internal/type.hpp deleted file mode 100644 index dd89152..0000000 --- a/src/gfx_new/internal/type.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -namespace gal::prometheus::gfx_new -{ - // ========================================================= - // FONT - // ========================================================= - - // index - using texture_atlas_id_type = std::uint32_t; - constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; -} From 5a2c3e52c671e0a7253e4462441d46648a2b596e Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 26 May 2025 01:12:44 +0800 Subject: [PATCH 46/54] `wip`: Refactoring gfx(extension). --- scripts/library.cmake | 7 + .../extension/glyph_parser_freetype.cpp | 272 ++++++ .../extension/glyph_parser_freetype.hpp | 44 + src/gfx_new/extension/renderer_d3d11.cpp | 837 ++++++++++++++++++ src/gfx_new/extension/renderer_d3d11.hpp | 92 ++ src/gfx_new/gfx.hpp | 10 +- src/gfx_new/internal/context.cpp | 20 +- src/gfx_new/internal/context.hpp | 3 +- src/gfx_new/internal/font.cpp | 38 +- src/gfx_new/internal/font.hpp | 12 +- 10 files changed, 1279 insertions(+), 56 deletions(-) create mode 100644 src/gfx_new/extension/glyph_parser_freetype.cpp create mode 100644 src/gfx_new/extension/glyph_parser_freetype.hpp create mode 100644 src/gfx_new/extension/renderer_d3d11.cpp create mode 100644 src/gfx_new/extension/renderer_d3d11.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index 191101c..f2a2f74 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -370,11 +370,15 @@ set( # ========================= ${PROJECT_SOURCE_DIR}/src/gfx_new/gfx.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.hpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/context.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/glyph_parser_freetype.hpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/renderer_d3d11.hpp + # ========================= # GFX # ========================= @@ -456,6 +460,9 @@ set( ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.cpp ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/context.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/glyph_parser_freetype.cpp + ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/renderer_d3d11.cpp + # ========================= # GFX # ========================= diff --git a/src/gfx_new/extension/glyph_parser_freetype.cpp b/src/gfx_new/extension/glyph_parser_freetype.cpp new file mode 100644 index 0000000..5e7df03 --- /dev/null +++ b/src/gfx_new/extension/glyph_parser_freetype.cpp @@ -0,0 +1,272 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include + +namespace +{ + [[nodiscard]] auto ft_size_to_float(const FT_Pos size) noexcept -> float + { + return static_cast((size + 63) >> 6); + } +} // namespace + +namespace gal::prometheus::gfx_new +{ + class GlyphParserFreeType::Library final + { + public: + FT_Library library{nullptr}; + }; + + class GlyphParserFreeType::FontInfo final + { + public: + std::filesystem::path path; + FT_Face face{nullptr}; + + float ascender{0}; + float descender{0}; + float line_spacing{0}; + float line_gap{0}; + float max_advance_width{0}; + + auto set_pixel_height(const std::size_t height) noexcept -> bool + { + FT_Size_RequestRec request{.type = FT_SIZE_REQUEST_TYPE_NOMINAL, .width = 0, .height = static_cast(height) * 64, .horiResolution = 0, .vertResolution = 0}; + + if (const auto error = FT_Request_Size(face, &request); error != FT_Err_Ok) + { + return false; + } + + const auto& metrics = face->size->metrics; + + ascender = ft_size_to_float(metrics.ascender); + descender = ft_size_to_float(metrics.descender); + line_spacing = ft_size_to_float(metrics.height); + line_gap = ft_size_to_float(metrics.height - metrics.ascender + metrics.descender); + max_advance_width = ft_size_to_float(metrics.max_advance); + + return true; + } + }; + + GlyphParserFreeType::~GlyphParserFreeType() noexcept + { + std::ranges::for_each( + infos_, + [](auto& info) noexcept -> void + { + ::FT_Done_Face(info.face); + } + ); + + FT_Done_FreeType(library_->library); + } + + GlyphParserFreeType::GlyphParserFreeType() noexcept + : library_{memory::make_unique()} + { + if (const auto error = FT_Init_FreeType(&library_->library); error != FT_Err_Ok) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + } + } + + auto GlyphParserFreeType::load(const std::filesystem::path& path) noexcept -> FontDescriptor + { + if (const auto it = std::ranges::find(infos_, path, &FontInfo::path); it != infos_.end()) + { + const auto& info = it.operator*(); + const auto index = std::ranges::distance(infos_.begin(), it); + + return {.identifier = info.face->family_name, .id = static_cast(index)}; + } + + const auto path_string = path.string(); + + FT_Face face = nullptr; + if (const auto error = FT_New_Face(library_->library, path_string.data(), 0, &face); error != FT_Err_Ok) + { + return FontDescriptor::error(); + } + + if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) + { + FT_Done_Face(face); + return FontDescriptor::error(); + } + + const auto id = infos_.size(); + auto& info = infos_.emplace_back(); + info.face = face; + + return {.identifier = face->family_name, .id = static_cast(id)}; + } + + // auto GlyphParserFreeType::load(const std::span data) noexcept -> FontDescriptor + // { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.data() != nullptr); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not data.empty()); + // + // FT_Face face = nullptr; + // if (const auto error = FT_New_Memory_Face(library_->library, data.data(), static_cast(data.size()), 0, &face); error != FT_Err_Ok) + // { + // return FontDescriptor::error(); + // } + // + // if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) + // { + // FT_Done_Face(face); + // return FontDescriptor::error(); + // } + // + // const auto id = infos_.size(); + // + // auto& info = infos_.emplace_back(); + // info.face = face; + // + // return {.identifier = face->family_name, .id = static_cast(id)}; + // } + + auto GlyphParserFreeType::has_glyph(const font_id_type id, const std::uint32_t codepoint) const noexcept -> bool + { + if (id >= infos_.size()) + { + return false; + } + + const auto& info = infos_[id]; + if (const auto char_index = FT_Get_Char_Index(info.face, codepoint); char_index == 0) + { + return false; + } + + return true; + } + + auto GlyphParserFreeType::parse(const font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < infos_.size()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(code.codepoint != 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(code.size != 0); + + auto& info = infos_[id]; + const auto& face = info.face; + + const auto char_index = FT_Get_Char_Index(face, code.codepoint); + if (char_index == 0) + { + return GlyphDescriptor::error(); + } + + info.set_pixel_height(code.size); + + if (const auto error = FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT); error != FT_Err_Ok) + { + return GlyphDescriptor::error(); + } + + const auto& slot = face->glyph; + + if (std::to_underlying(code.flag) & GlyphFlag::BOLD) + { + FT_GlyphSlot_Embolden(slot); + } + if (std::to_underlying(code.flag) & GlyphFlag::ITALIC) + { + FT_GlyphSlot_Oblique(slot); + } + + if (const auto error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); error != FT_Err_Ok) + { + return GlyphDescriptor::error(); + } + + const auto& bitmap = face->glyph->bitmap; + + const GlyphDescriptor::rect_type::point_type point{slot->bitmap_left, slot->bitmap_top}; + const GlyphDescriptor::rect_type::extent_type size{bitmap.width, bitmap.rows}; + const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; + + GlyphDescriptor result{ + .rect = {point, size}, + .advance_x = ft_size_to_float(slot->advance.x), + .visible = size.width > 0 and size.height > 0, + .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, + .data = std::make_unique_for_overwrite(data_length) + }; + + { + const auto* source = bitmap.buffer; + auto* dest = result.data.get(); + + if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) + { + for (std::uint32_t y = 0; y < bitmap.rows; ++y) + { + for (std::uint32_t x = 0; x < bitmap.width; ++x) + { + const auto a = source[x]; + const auto color = + // A + a << 24 | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + dest[x] = color; + } + + source += bitmap.pitch; + dest += bitmap.width; + } + } + else if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + for (std::uint32_t y = 0; y < bitmap.rows; ++y) + { + const std::uint8_t* p = source; + std::uint8_t bits = 0; + + for (std::uint32_t x = 0; x < bitmap.width; ++x) + { + if ((x & 7) == 0) + { + bits = *p; + p += 1; + } + + const auto a = (bits & 0x80) ? std::uint32_t{0xff} : std::uint32_t{0}; + const auto color = + // A + a << 24 | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + dest[x] = color; + + bits <<= 1; + } + + source += bitmap.pitch; + dest += bitmap.width; + } + } + } + + return result; + } +} diff --git a/src/gfx_new/extension/glyph_parser_freetype.hpp b/src/gfx_new/extension/glyph_parser_freetype.hpp new file mode 100644 index 0000000..369ca25 --- /dev/null +++ b/src/gfx_new/extension/glyph_parser_freetype.hpp @@ -0,0 +1,44 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include + +namespace gal::prometheus::gfx_new +{ + class GlyphParserFreeType final : public GlyphParser + { + class Library; + class FontInfo; + + public: + using infos_type = std::vector; + + private: + memory::UniquePointer library_; + infos_type infos_; + + public: + GlyphParserFreeType(const GlyphParserFreeType&) noexcept = delete; + GlyphParserFreeType(GlyphParserFreeType&&) noexcept = default; + auto operator=(const GlyphParserFreeType&) noexcept -> GlyphParserFreeType& = delete; + auto operator=(GlyphParserFreeType&&) noexcept -> GlyphParserFreeType& = default; + + ~GlyphParserFreeType() noexcept override; + + GlyphParserFreeType() noexcept; + + auto load(const std::filesystem::path& path) noexcept -> FontDescriptor override; + + [[nodiscard]] auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool override; + + auto parse(font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor override; + }; +} diff --git a/src/gfx_new/extension/renderer_d3d11.cpp b/src/gfx_new/extension/renderer_d3d11.cpp new file mode 100644 index 0000000..d71408e --- /dev/null +++ b/src/gfx_new/extension/renderer_d3d11.cpp @@ -0,0 +1,837 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#if GAL_PROMETHEUS_COMPILER_DEBUG +#define GAL_PROMETHEUS_GFX_DEBUG +#include +#endif + +#include + +#include +#include +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#include +#include + +namespace +{ + using namespace gal::prometheus; + using namespace gfx; + + auto check_hr_error( + const HRESULT result +#if defined(GAL_PROMETHEUS_GFX_DEBUG) + , + const std::source_location& location = std::source_location::current() +#endif + ) noexcept -> bool + { + if (SUCCEEDED(result)) + { + return true; + } + +#if defined(GAL_PROMETHEUS_GFX_DEBUG) + + const _com_error err{result}; + std::println(stderr, "Error: {} --- at {}:{}", err.ErrorMessage(), location.file_name(), location.line()); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + +#else + + GAL_PROMETHEUS_COMPILER_UNREACHABLE(); + +#endif + + return false; + } + + using projection_matrix_type = float[4][4]; + + [[nodiscard]] auto id_to_gpu_handle(const texture_id_type id) noexcept -> ID3D11ShaderResourceView* + { + return reinterpret_cast(id); // NOLINT(performance-no-int-to-ptr) + } + + [[nodiscard]] auto gpu_handle_to_id(const ID3D11ShaderResourceView* srv) noexcept -> texture_id_type + { + return reinterpret_cast(srv); + } +} + +namespace gal::prometheus::gfx_new +{ + auto RendererD3D11::create_blend_state() noexcept -> bool + { + constexpr D3D11_RENDER_TARGET_BLEND_DESC render_target + { + .BlendEnable = TRUE, + .SrcBlend = D3D11_BLEND_SRC_ALPHA, + .DestBlend = D3D11_BLEND_INV_SRC_ALPHA, + .BlendOp = D3D11_BLEND_OP_ADD, + .SrcBlendAlpha = D3D11_BLEND_ONE, + .DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA, + .BlendOpAlpha = D3D11_BLEND_OP_ADD, + .RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL + }; + constexpr D3D11_BLEND_DESC blend_desc + { + .AlphaToCoverageEnable = FALSE, + .IndependentBlendEnable = FALSE, + .RenderTarget = + { + render_target, + } + }; + + return check_hr_error(device_->CreateBlendState(&blend_desc, blend_state_.ReleaseAndGetAddressOf())); + } + + auto RendererD3D11::create_rasterizer_state() noexcept -> bool + { + constexpr D3D11_RASTERIZER_DESC rasterizer_desc + { + .FillMode = D3D11_FILL_SOLID, + .CullMode = D3D11_CULL_NONE, + .FrontCounterClockwise = FALSE, + .DepthBias = 0, + .DepthBiasClamp = 0, + .SlopeScaledDepthBias = 0, + .DepthClipEnable = TRUE, + .ScissorEnable = TRUE, + .MultisampleEnable = TRUE, + .AntialiasedLineEnable = TRUE + }; + + return check_hr_error(device_->CreateRasterizerState(&rasterizer_desc, rasterizer_state_.ReleaseAndGetAddressOf())); + } + + auto RendererD3D11::create_depth_stencil_state() noexcept -> bool + { + constexpr D3D11_DEPTH_STENCIL_DESC depth_stencil_desc + { + .DepthEnable = FALSE, + .DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL, + .DepthFunc = D3D11_COMPARISON_ALWAYS, + .StencilEnable = FALSE, + .StencilReadMask = 0, + .StencilWriteMask = 0, + .FrontFace = {.StencilFailOp = D3D11_STENCIL_OP_KEEP, .StencilDepthFailOp = D3D11_STENCIL_OP_KEEP, .StencilPassOp = D3D11_STENCIL_OP_KEEP, .StencilFunc = D3D11_COMPARISON_ALWAYS}, + .BackFace = {.StencilFailOp = D3D11_STENCIL_OP_KEEP, .StencilDepthFailOp = D3D11_STENCIL_OP_KEEP, .StencilPassOp = D3D11_STENCIL_OP_KEEP, .StencilFunc = D3D11_COMPARISON_ALWAYS} + }; + + return check_hr_error(device_->CreateDepthStencilState(&depth_stencil_desc, depth_stencil_state_.ReleaseAndGetAddressOf())); + } + + auto RendererD3D11::create_vertex_shader() noexcept -> bool + { + constexpr char shader_code[] + { + "cbuffer vertexBuffer : register(b0)" + "{" + " float4x4 ProjectionMatrix;" + "};" + "struct VS_INPUT" + "{" + " float2 pos : POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "PS_INPUT main(VS_INPUT input)" + "{" + " PS_INPUT output;" + " output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));" + " output.col = input.col;" + " output.uv = input.uv;" + " return output;" + "}" + }; + + ComPtr shader_blob; + ComPtr error_message; + + if (const auto result = D3DCompile( + shader_code, + sizeof(shader_code), + nullptr, + nullptr, + nullptr, + "main", + "vs_5_0", + 0, + 0, + shader_blob.GetAddressOf(), + error_message.GetAddressOf() + ); + FAILED(result)) + { + std::println( + stderr, + "D3DCompile failed: {} -- at {}:{}", + static_cast(error_message->GetBufferPointer()), + std::source_location::current().file_name(), + std::source_location::current().line() + ); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return false; + } + + if (not check_hr_error( + device_->CreateVertexShader( + shader_blob->GetBufferPointer(), + shader_blob->GetBufferSize(), + nullptr, + vertex_shader_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + // vertex input layout + constexpr D3D11_INPUT_ELEMENT_DESC input_element_desc[]{ + { + .SemanticName = "POSITION", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, position)), + .InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "COLOR", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, color)), + .InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "TEXCOORD", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, uv)), + .InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + }; + if (not check_hr_error( + device_->CreateInputLayout( + input_element_desc, + static_cast(std::ranges::size(input_element_desc)), + shader_blob->GetBufferPointer(), + shader_blob->GetBufferSize(), + vertex_input_layout_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + // constant buffer + constexpr D3D11_BUFFER_DESC constant_buffer_desc{ + .ByteWidth = sizeof(projection_matrix_type), + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_CONSTANT_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, + .MiscFlags = 0, + .StructureByteStride = 0 + }; + if (not check_hr_error( + device_->CreateBuffer( + &constant_buffer_desc, + nullptr, + vertex_projection_matrix_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + return true; + } + + auto RendererD3D11::create_pixel_shader() noexcept -> bool + { + constexpr char shader_code[] + { + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "sampler sampler0;" + "Texture2D texture0;" + "float4 main(PS_INPUT input) : SV_Target" + "{" + " float4 out_col = texture0.Sample(sampler0, input.uv);" + " return input.col * out_col;" + "}" + }; + + ComPtr shader_blob; + ComPtr error_message; + + if (const auto result = D3DCompile( + shader_code, + sizeof(shader_code), + nullptr, + nullptr, + nullptr, + "main", + "ps_5_0", + 0, + 0, + shader_blob.GetAddressOf(), + error_message.GetAddressOf() + ); + FAILED(result)) + { + std::println( + stderr, + "D3DCompile failed: {} -- at {}:{}", + static_cast(error_message->GetBufferPointer()), + std::source_location::current().file_name(), + std::source_location::current().line() + ); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return false; + } + + if (not check_hr_error( + device_->CreatePixelShader( + shader_blob->GetBufferPointer(), + shader_blob->GetBufferSize(), + nullptr, + pixel_shader_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + // Create pixel shader texture sampler + constexpr D3D11_SAMPLER_DESC sampler_desc + { + .Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR, + .AddressU = D3D11_TEXTURE_ADDRESS_WRAP, + .AddressV = D3D11_TEXTURE_ADDRESS_WRAP, + .AddressW = D3D11_TEXTURE_ADDRESS_WRAP, + .MipLODBias = 0, + .MaxAnisotropy = 0, + .ComparisonFunc = D3D11_COMPARISON_ALWAYS, + .BorderColor = {0, 0, 0, 0}, + .MinLOD = 0, + .MaxLOD = 0 + }; + if (not check_hr_error( + device_->CreateSamplerState( + &sampler_desc, + pixel_font_sampler_.ReleaseAndGetAddressOf() + ) + )) + { + return false; + } + + return true; + } + + auto RendererD3D11::upload_texture( + const Texture::data_view_type data, + const Texture::size_type size, + const D3D11_USAGE usage, + const std::uint32_t bind_flags, + const std::uint32_t cpu_access_flags, + const std::uint32_t misc_flags, + const bool record_resource + ) noexcept -> texture_id_type + { + const D3D11_TEXTURE2D_DESC texture_2d_desc + { + .Width = static_cast(size.width), + .Height = static_cast(size.height), + .MipLevels = 1, + .ArraySize = 1, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Usage = usage, + .BindFlags = bind_flags, + .CPUAccessFlags = cpu_access_flags, + .MiscFlags = misc_flags + }; + + const D3D11_SUBRESOURCE_DATA subresource_data + { + .pSysMem = data.data(), + .SysMemPitch = static_cast(size.width * 4), + .SysMemSlicePitch = 0 + }; + + ID3D11Texture2D* texture_2d; + if (not check_hr_error( + device_->CreateTexture2D( + &texture_2d_desc, + &subresource_data, + &texture_2d + ) + )) + { + return invalid_texture_id; + } + + const D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_view_desc + { + .Format = texture_2d_desc.Format, + .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, + .Texture2D = + { + .MostDetailedMip = 0, + .MipLevels = texture_2d_desc.MipLevels + } + }; + + ID3D11ShaderResourceView* srv = nullptr; + if (not check_hr_error( + device_->CreateShaderResourceView( + texture_2d, + &shader_resource_view_desc, + &srv + ) + )) + { + return false; + } + + if (record_resource) + { + textures_.insert_or_assign(srv, texture_2d); + } + else + { + texture_2d->Release(); + } + + return gpu_handle_to_id(srv); + } + + RendererD3D11::RendererD3D11() noexcept + : device_{nullptr}, + device_immediate_context_{nullptr}, + blend_state_{nullptr}, + rasterizer_state_{nullptr}, + depth_stencil_state_{nullptr}, + vertex_shader_{nullptr}, + vertex_input_layout_{nullptr}, + vertex_projection_matrix_{nullptr}, + pixel_shader_{nullptr}, + pixel_font_sampler_{nullptr}, + render_buffer_{} {} + + RendererD3D11::RendererD3D11(ID3D11Device* device, ID3D11DeviceContext* device_immediate_context) noexcept + : RendererD3D11{} + { + bind_device(device); + bind_device_context(device_immediate_context); + } + + RendererD3D11::RendererD3D11(ComPtr device, ComPtr device_immediate_context) noexcept + : RendererD3D11{} + { + bind_device(std::move(device)); + bind_device_context(std::move(device_immediate_context)); + } + + auto RendererD3D11::bind_device(ID3D11Device* device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = device; + } + + auto RendererD3D11::bind_device(ComPtr device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = std::move(device); + } + + auto RendererD3D11::bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device_immediate_context != nullptr); + device_immediate_context_ = device_immediate_context; + } + + auto RendererD3D11::bind_device_context(ComPtr device_immediate_context) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device_immediate_context != nullptr); + device_immediate_context_ = std::move(device_immediate_context); + } + + auto RendererD3D11::set_display_size(const extent_type& display_size) noexcept -> void + { + display_size_ = display_size; + } + + auto RendererD3D11::do_construct() noexcept -> bool + { + if (not create_blend_state()) + { + return false; + } + if (not create_rasterizer_state()) + { + return false; + } + if (not create_depth_stencil_state()) + { + return false; + } + if (not create_vertex_shader()) + { + return false; + } + if (not create_pixel_shader()) + { + return false; + } + + return true; + } + + auto RendererD3D11::do_destruct() noexcept -> void + { + // ComPtr + blend_state_ = nullptr; + rasterizer_state_ = nullptr; + depth_stencil_state_ = nullptr; + vertex_shader_ = nullptr; + vertex_input_layout_ = nullptr; + vertex_projection_matrix_ = nullptr; + pixel_shader_ = nullptr; + pixel_font_sampler_ = nullptr; + render_buffer_.index = nullptr; + render_buffer_.index_count = 0; + render_buffer_.vertex = nullptr; + render_buffer_.vertex_count = 0; + + // RAW + std::ranges::for_each( + textures_, + [](auto& kv) noexcept -> void + { + kv.first->Release(); + kv.second->Release(); + } + ); + textures_.clear(); + + // ComPtr + // device_immediate_context_->ClearState(); + // device_immediate_context_->Flush(); + device_immediate_context_ = nullptr; + device_ = nullptr; + } + + auto RendererD3D11::do_ready() const noexcept -> bool + { + if (device_ == nullptr or device_immediate_context_ == nullptr) + { + return false; + } + + if (blend_state_ == nullptr or rasterizer_state_ == nullptr or depth_stencil_state_ == nullptr) + { + return false; + } + + if (vertex_shader_ == nullptr or vertex_input_layout_ == nullptr or vertex_projection_matrix_ == nullptr) + { + return false; + } + + if (pixel_shader_ == nullptr or pixel_font_sampler_ == nullptr) + { + return false; + } + + return true; + } + + auto RendererD3D11::do_texture_create(const TextureDescriptor::data_view_type data, const TextureDescriptor::size_type size) noexcept -> texture_id_type + { + return upload_texture(data, size, D3D11_USAGE_DYNAMIC, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, 0); + } + + auto RendererD3D11::do_texture_update(const TextureDescriptor& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.id != invalid_texture_id, "Create texture first!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty != 0, "No need to update texture!"); + + auto* srv = id_to_gpu_handle(texture.id); + const auto it = textures_.find(srv); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != textures_.end(), "Invalid texture id"); + + auto* texture_2d = it->second; + D3D11_MAPPED_SUBRESOURCE mapped_resource{}; + if (const auto result = device_immediate_context_->Map( + texture_2d, + 0, + D3D11_MAP_WRITE_DISCARD, + 0, + &mapped_resource + ); result != S_OK) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + const auto* source = texture.data.get(); + const auto source_length = static_cast(texture.size.width) * texture.size.height; + std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); + + device_immediate_context_->Unmap(texture_2d, 0); + } + + auto RendererD3D11::do_texture_destroy(const texture_id_type texture_id) noexcept -> void + { + auto* srv = id_to_gpu_handle(texture_id); + + if (const auto it = textures_.find(srv); it != textures_.end()) + { + srv->Release(); + it->first->Release(); + it->second->Release(); + + textures_.erase(it); + } + } + + auto RendererD3D11::do_present(const render_data_list_type& render_data_list) noexcept -> void + { + auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = render_buffer_; + + const auto [total_vertex_count, total_index_count] = [&]() noexcept + { + struct sum + { + UINT vertex; + UINT index; + }; + + return std::ranges::fold_left( + render_data_list, + sum{.vertex = 0, .index = 0}, + [](const sum s, const RenderData& render_data) noexcept -> sum + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + return {.vertex = s.vertex + static_cast(vertex_list.size()), .index = s.index + static_cast(index_list.size())}; + } + ); + }(); + + if (not this_frame_vertex_buffer or total_vertex_count > this_frame_vertex_count) + { + // todo: grow factor + this_frame_vertex_count = total_vertex_count + 5000; + + const D3D11_BUFFER_DESC buffer_desc{ + .ByteWidth = static_cast(this_frame_vertex_count * sizeof(vertex_type)), + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_VERTEX_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, + .MiscFlags = 0, + .StructureByteStride = 0 + }; + check_hr_error(device_->CreateBuffer(&buffer_desc, nullptr, this_frame_vertex_buffer.ReleaseAndGetAddressOf())); + } + if (not this_frame_index_buffer or total_index_count > this_frame_index_count) + { + // todo: grow factor + this_frame_index_count = total_index_count + 10000; + + const D3D11_BUFFER_DESC buffer_desc{ + .ByteWidth = static_cast(this_frame_index_count * sizeof(index_type)), + .Usage = D3D11_USAGE_DYNAMIC, + .BindFlags = D3D11_BIND_INDEX_BUFFER, + .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, + .MiscFlags = 0, + .StructureByteStride = 0 + }; + check_hr_error(device_->CreateBuffer(&buffer_desc, nullptr, this_frame_index_buffer.ReleaseAndGetAddressOf())); + } + + // Upload vertex/index data into a single contiguous GPU buffer + { + D3D11_MAPPED_SUBRESOURCE mapped_vertex_resource; + D3D11_MAPPED_SUBRESOURCE mapped_index_resource; + check_hr_error(device_immediate_context_->Map(this_frame_vertex_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_vertex_resource)); + check_hr_error(device_immediate_context_->Map(this_frame_index_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_index_resource)); + + auto* mapped_vertex = static_cast(mapped_vertex_resource.pData); + auto* mapped_index = static_cast(mapped_index_resource.pData); + + UINT vertex_offset = 0; + UINT index_offset = 0; + + std::ranges::for_each( + render_data_list, + [&](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + // std::ranges::transform( + // vertex_list, + // mapped_vertex + vertex_offset, + // [](const vertex_type& vertex) noexcept -> vertex_type + // { + // // return { + // // .position = {vertex.position.x, vertex.position.y}, + // // .uv = {vertex.uv.x, vertex.uv.y}, + // // .color = vertex.color.to(primitive::color_format) + // // }; + // return std::bit_cast(vertex); + // } + // ); + std::ranges::copy(vertex_list, mapped_vertex + vertex_offset); + // std::ranges::transform( + // index_list, + // mapped_index + index_offset, + // [vertex_offset](const index_type index) noexcept -> index_type + // { + // return static_cast(index + vertex_offset); + // } + // ); + std::ranges::copy(index_list, mapped_index + index_offset); + + vertex_offset += static_cast(vertex_list.size()); + index_offset += static_cast(index_list.size()); + } + ); + + device_immediate_context_->Unmap(this_frame_vertex_buffer.Get(), 0); + device_immediate_context_->Unmap(this_frame_index_buffer.Get(), 0); + } + + // Setup orthographic projection matrix into our constant buffer + { + D3D11_MAPPED_SUBRESOURCE mapped_resource; + check_hr_error(device_immediate_context_->Map(vertex_projection_matrix_.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource)); + + auto* mapped_projection_matrix = static_cast(mapped_resource.pData); + + constexpr auto left = 0.f; + const auto right = display_size_.width; + constexpr auto top = 0.f; + const auto bottom = display_size_.height; + + const projection_matrix_type mvp{ + {2.0f / (right - left), 0.0f, 0.0f, 0.0f}, + {0.0f, 2.0f / (top - bottom), 0.0f, 0.0f}, + {0.0f, 0.0f, 0.5f, 0.0f}, + {(right + left) / (left - right), (top + bottom) / (bottom - top), 0.5f, 1.0f}, + }; + std::memcpy(mapped_projection_matrix, &mvp, sizeof(projection_matrix_type)); + + device_immediate_context_->Unmap(vertex_projection_matrix_.Get(), 0); + } + + // Setup viewport + { + const D3D11_VIEWPORT viewport{ + .TopLeftX = .0f, + .TopLeftY = .0f, + .Width = display_size_.width, + .Height = display_size_.height, + .MinDepth = 0, + .MaxDepth = 1 + }; + device_immediate_context_->RSSetViewports(1, &viewport); + } + + // Bind shader and vertex buffers + constexpr UINT stride = sizeof(vertex_type); + constexpr UINT offset = 0; + device_immediate_context_->IASetInputLayout(vertex_input_layout_.Get()); + device_immediate_context_->IASetVertexBuffers(0, 1, this_frame_vertex_buffer.GetAddressOf(), &stride, &offset); + device_immediate_context_->IASetIndexBuffer( + this_frame_index_buffer.Get(), + // ReSharper disable once CppUnreachableCode + sizeof(index_type) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, + 0 + ); + device_immediate_context_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + device_immediate_context_->VSSetShader(vertex_shader_.Get(), nullptr, 0); + device_immediate_context_->VSSetConstantBuffers(0, 1, vertex_projection_matrix_.GetAddressOf()); + device_immediate_context_->PSSetShader(pixel_shader_.Get(), nullptr, 0); + device_immediate_context_->PSSetSamplers(0, 1, pixel_font_sampler_.GetAddressOf()); + device_immediate_context_->DSSetShader(nullptr, nullptr, 0); + device_immediate_context_->HSSetShader(nullptr, nullptr, 0); + device_immediate_context_->GSSetShader(nullptr, nullptr, 0); + device_immediate_context_->CSSetShader(nullptr, nullptr, 0); + + // Setup render state + constexpr float blend_factor[]{0, 0, 0, 0}; + device_immediate_context_->OMSetBlendState(blend_state_.Get(), blend_factor, (std::numeric_limits::max)()); + device_immediate_context_->OMSetDepthStencilState(depth_stencil_state_.Get(), 0); + device_immediate_context_->RSSetState(rasterizer_state_.Get()); + + UINT total_index_offset = 0; + std::ranges::for_each( + render_data_list, + [this, &total_index_offset](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + for (const auto& command_list = render_data.command_list.get(); + const auto& [scissor, texture, index_offset, element_count]: command_list) + { + const auto [point, extent] = scissor; + const D3D11_RECT rect + { + static_cast(point.x), + static_cast(point.y), + static_cast(point.x + extent.width), + static_cast(point.y + extent.height) + }; + device_immediate_context_->RSSetScissorRects(1, &rect); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture != invalid_texture_id); + auto* srv = id_to_gpu_handle(texture); + device_immediate_context_->PSSetShaderResources(0, 1, &srv); + + const auto this_index_offset = static_cast(total_index_offset + index_offset); + // device_immediate_context_->DrawIndexed(static_cast(element_count), this_index_offset, 0); + device_immediate_context_->DrawIndexedInstanced(static_cast(element_count), 1, this_index_offset, 0, 0); + } + + total_index_offset += static_cast(index_list.size()); + } + ); + } +} diff --git a/src/gfx_new/extension/renderer_d3d11.hpp b/src/gfx_new/extension/renderer_d3d11.hpp new file mode 100644 index 0000000..5ce257d --- /dev/null +++ b/src/gfx_new/extension/renderer_d3d11.hpp @@ -0,0 +1,92 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include +#include + +namespace gal::prometheus::gfx_new +{ + using Microsoft::WRL::ComPtr; + + class RendererD3D11 final : public Renderer + { + public: + using textures_type = std::unordered_map; + + private: + struct render_buffer_type + { + ComPtr index; + UINT index_count; + ComPtr vertex; + UINT vertex_count; + }; + + ComPtr device_; + ComPtr device_immediate_context_; + + ComPtr blend_state_; + ComPtr rasterizer_state_; + ComPtr depth_stencil_state_; + + ComPtr vertex_shader_; + ComPtr vertex_input_layout_; + ComPtr vertex_projection_matrix_; + + ComPtr pixel_shader_; + ComPtr pixel_font_sampler_; + + textures_type textures_; + + render_buffer_type render_buffer_; + extent_type display_size_; + + [[nodiscard]] auto create_blend_state() noexcept -> bool; + [[nodiscard]] auto create_rasterizer_state() noexcept -> bool; + [[nodiscard]] auto create_depth_stencil_state() noexcept -> bool; + + [[nodiscard]] auto create_vertex_shader() noexcept -> bool; + [[nodiscard]] auto create_pixel_shader() noexcept -> bool; + + [[nodiscard]] auto upload_texture( + TextureDescriptor::data_view_type data, + TextureDescriptor::size_type size, + D3D11_USAGE usage, + std::uint32_t bind_flags, + std::uint32_t cpu_access_flags, + std::uint32_t misc_flags, + bool record_resource = true + ) noexcept -> texture_id_type; + + public: + RendererD3D11() noexcept; + RendererD3D11(ID3D11Device* device, ID3D11DeviceContext* device_immediate_context) noexcept; + RendererD3D11(ComPtr device, ComPtr device_immediate_context) noexcept; + + auto bind_device(ID3D11Device* device) noexcept -> void; + auto bind_device(ComPtr device) noexcept -> void; + auto bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void; + auto bind_device_context(ComPtr device_immediate_context) noexcept -> void; + + auto set_display_size(const extent_type& display_size) noexcept -> void; + + private: + auto do_construct() noexcept -> bool override; + auto do_destruct() noexcept -> void override; + [[nodiscard]] auto do_ready() const noexcept -> bool override; + + auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type override; + auto do_texture_update(const TextureDescriptor& texture) noexcept -> void override; + auto do_texture_destroy(texture_id_type texture_id) noexcept -> void override; + + auto do_present(const render_data_list_type& render_data_list) noexcept -> void override; + }; +} diff --git a/src/gfx_new/gfx.hpp b/src/gfx_new/gfx.hpp index 1e2fa84..584487c 100644 --- a/src/gfx_new/gfx.hpp +++ b/src/gfx_new/gfx.hpp @@ -189,12 +189,11 @@ namespace gal::prometheus::gfx_new GlyphParser() noexcept = default; /** - * @brief Load font data from @c data, get all glyph data, return id of font - * @param data Font data + * @brief Load font data from @c path, return id of font + * @param path Font path * @return id of the font, or invalid_font_id if failed to load - * @note The parser does not need to store (copy) the font data internally, we make sure that the font data is valid for the lifetime of the parser */ - [[nodiscard]] virtual auto load(std::span data) noexcept -> FontDescriptor = 0; + [[nodiscard]] virtual auto load(const std::filesystem::path& path) noexcept -> FontDescriptor = 0; /** * @brief Determines whether the target font contains the glyphs of the specified codepoint @@ -949,10 +948,9 @@ namespace gal::prometheus::gfx_new /** * @brief Load font from the specified path, assuming the path is a valid font file - * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false * @note *Must* ensure that at least one font is added before calling @c Renderer::new_frame */ - auto add_font(Context& context, const std::filesystem::path& path) noexcept -> bool; + auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void; // ========================================================= // RENDER LIST diff --git a/src/gfx_new/internal/context.cpp b/src/gfx_new/internal/context.cpp index 734c6e9..ed39f57 100644 --- a/src/gfx_new/internal/context.cpp +++ b/src/gfx_new/internal/context.cpp @@ -138,9 +138,9 @@ namespace gal::prometheus::gfx_new } } - auto FontContext::add_font(const std::filesystem::path& path) noexcept -> bool + auto FontContext::add_font(const std::filesystem::path& path) noexcept -> void { - return font_load_queue_.push(path); + font_load_queue_.push(path); } auto FontContext::load_all_font() noexcept -> void @@ -347,10 +347,14 @@ namespace gal::prometheus::gfx_new } Context::Context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept - : glyph_parser_{std::move(glyph_parser)}, - renderer_{std::move(renderer)}, + : glyph_parser_{nullptr}, + renderer_{nullptr}, texture_context_{}, - font_context_{} {} + font_context_{} + { + set_glyph_parser(std::move(glyph_parser)); + set_renderer(std::move(renderer)); + } auto Context::get_glyph_parser() const noexcept -> std::shared_ptr { @@ -449,9 +453,9 @@ namespace gal::prometheus::gfx_new return context.set_renderer(std::move(renderer)); } - auto add_font(Context& context, const std::filesystem::path& path) noexcept -> bool + auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void { - return context.get_font_context().add_font(path); + context.get_font_context().add_font(path); } auto new_render_list(Context& context, const RenderListFlag flag) noexcept -> RenderList& @@ -506,6 +510,6 @@ namespace gal::prometheus::gfx_new auto Renderer::end_frame(Context& context) noexcept -> void { - std::ignore = context; + context.render_lists_.clear(); } } diff --git a/src/gfx_new/internal/context.hpp b/src/gfx_new/internal/context.hpp index c609b42..2ed8670 100644 --- a/src/gfx_new/internal/context.hpp +++ b/src/gfx_new/internal/context.hpp @@ -130,9 +130,8 @@ namespace gal::prometheus::gfx_new /** * @brief Load font from the specified path, assuming the path is a valid font file * @param path Font path - * @return Returns true if the file exists and was opened successfully (without checking if it is a valid font file), otherwise returns false */ - auto add_font(const std::filesystem::path& path) noexcept -> bool; + auto add_font(const std::filesystem::path& path) noexcept -> void; /** * @brief Load the fonts previously added by @c add_font diff --git a/src/gfx_new/internal/font.cpp b/src/gfx_new/internal/font.cpp index fa3de85..9aa84c6 100644 --- a/src/gfx_new/internal/font.cpp +++ b/src/gfx_new/internal/font.cpp @@ -5,8 +5,6 @@ #include -#include - #include #include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE @@ -68,41 +66,22 @@ namespace gal::prometheus::gfx_new return cached_glyphs.insert_or_assign(key, info).first->second; } - FontLoadQueue::FontLoadQueue() noexcept - : new_font_index_{0} {} + FontLoadQueue::FontLoadQueue() noexcept = default; - auto FontLoadQueue::push(const std::filesystem::path& path) noexcept -> bool + auto FontLoadQueue::push(const std::filesystem::path& path) noexcept -> void { - std::ifstream file{path, std::ios::binary}; - if (not file.is_open()) - { - // todo: error handling - return false; - } - - file.seekg(0, std::ios::end); - const auto size = file.tellg(); - - auto data = std::make_unique_for_overwrite(size); - file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(data.get()), size); - file.close(); - - list_.emplace_back(data_type{data.release()}, static_cast(size)); - return true; + list_.emplace_back(path); } auto FontLoadQueue::upload(GlyphParser& parser, const functional::function_reference_wrapper font_dest) noexcept -> void { - if (const auto size = static_cast(list_.size()); size > new_font_index_) + if (not list_.empty()) { - auto new_fonts = std::ranges::subrange{list_.begin() + new_font_index_, list_.end()}; - std::ranges::for_each( - new_fonts, - [&](const Descriptor& descriptor) noexcept -> void + list_, + [&](const auto& path) noexcept -> void { - if (const auto result = parser.load({descriptor.data.get(), descriptor.size}); result.valid()) + if (const auto result = parser.load(path); result.valid()) { Font font{.descriptor = {.identifier = result.identifier, .id = result.id}, .cached_glyphs = {}}; font_dest(std::move(font)); @@ -114,8 +93,7 @@ namespace gal::prometheus::gfx_new } } ); - - new_font_index_ = size; + list_.clear(); } } } diff --git a/src/gfx_new/internal/font.hpp b/src/gfx_new/internal/font.hpp index 9b7c089..d831404 100644 --- a/src/gfx_new/internal/font.hpp +++ b/src/gfx_new/internal/font.hpp @@ -156,17 +156,9 @@ namespace gal::prometheus::gfx_new using size_type = descriptor::size_type; private: - class Descriptor final - { - public: - data_type data; - size_type size; - }; - - using list_type = std::vector; + using list_type = std::vector; list_type list_; - list_type::difference_type new_font_index_; public: FontLoadQueue(const FontLoadQueue&) noexcept = delete; @@ -178,7 +170,7 @@ namespace gal::prometheus::gfx_new FontLoadQueue() noexcept; - auto push(const std::filesystem::path& path) noexcept -> bool; + auto push(const std::filesystem::path& path) noexcept -> void; auto upload(GlyphParser& parser, functional::function_reference_wrapper font_dest) noexcept -> void; }; From b713d65676d2d7705e2ffa233e484697957299ce Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 26 May 2025 15:39:42 +0800 Subject: [PATCH 47/54] =?UTF-8?q?`fix`:=20*=20Allow=20`RefWrapper`=20to=20be=20implicitly=20converted=20to=20`const=20T&`.=20*?= =?UTF-8?q?=20Allow=20`const=20RefWrapper`=20to=20be=20implicitly=20con?= =?UTF-8?q?verted=20to=20`const=20T&`.=20=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/memory/reference_wrapper.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/memory/reference_wrapper.hpp b/src/memory/reference_wrapper.hpp index 470b576..1bb0390 100644 --- a/src/memory/reference_wrapper.hpp +++ b/src/memory/reference_wrapper.hpp @@ -54,14 +54,24 @@ namespace gal::prometheus::memory pointer_ = std::addressof(ref); } + // RefWrapper --(implicit)--> T& // ReSharper disable once CppNonExplicitConversionOperator constexpr explicit(false) operator type&() noexcept { return *pointer_; } + // RefWrapper --(implicit)--> const T& // ReSharper disable once CppNonExplicitConversionOperator - constexpr explicit (false) operator const type&() noexcept // + constexpr explicit(false) operator type&() const noexcept + requires(std::is_const_v) // avoid redefinition (RefWrapper) + { + return *pointer_; + } + + // const RefWrapper --(implicit)--> const T& + // ReSharper disable once CppNonExplicitConversionOperator + constexpr explicit (false) operator const type&() const noexcept // requires (not std::is_const_v) // avoid redefinition (RefWrapper) { return *pointer_; From 7220644e8a261d7ed3d7dad56cc03ba2abaa95f8 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 26 May 2025 17:54:19 +0800 Subject: [PATCH 48/54] =?UTF-8?q?`fix`:=20Remove=20assertions=20from=20som?= =?UTF-8?q?e=20lightweight=20modules.=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/io/inputs.cpp | 1 + src/math/cmath.hpp | 8 ++++---- src/primitive/circle.hpp | 2 +- src/primitive/extent.hpp | 12 ++++++------ src/primitive/point.hpp | 12 ++++++------ src/primitive/rect.hpp | 14 +++++++------- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/io/inputs.cpp b/src/io/inputs.cpp index 53f30ac..f93f16e 100644 --- a/src/io/inputs.cpp +++ b/src/io/inputs.cpp @@ -6,6 +6,7 @@ #include #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace { diff --git a/src/math/cmath.hpp b/src/math/cmath.hpp index f8a12ea..27871ad 100644 --- a/src/math/cmath.hpp +++ b/src/math/cmath.hpp @@ -12,7 +12,7 @@ #include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +// #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE #if not defined(__cpp_lib_constexpr_cmath) or __cpp_lib_constexpr_cmath < 202306L #define CMATH_WORKAROUND_REQUIRED @@ -120,7 +120,7 @@ namespace gal::prometheus::math // ReSharper disable once IdentifierTypo [[nodiscard]] constexpr auto tgamma(const T value) noexcept -> T { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= T{0}); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= T{0}); if constexpr (not std::is_floating_point_v) { @@ -149,7 +149,7 @@ namespace gal::prometheus::math requires std::is_arithmetic_v [[nodiscard]] constexpr auto pow(const T base, const int exp) noexcept -> T { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(exp >= 0); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(exp >= 0); #if defined(CMATH_WORKAROUND_REQUIRED) GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED @@ -170,7 +170,7 @@ namespace gal::prometheus::math requires std::is_arithmetic_v [[nodiscard]] constexpr auto sqrt(const T value) noexcept -> T { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= 0); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= 0); #if defined(CMATH_WORKAROUND_REQUIRED) GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED diff --git a/src/primitive/circle.hpp b/src/primitive/circle.hpp index b09c6ee..bfd5c92 100644 --- a/src/primitive/circle.hpp +++ b/src/primitive/circle.hpp @@ -16,7 +16,7 @@ #include #include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +// #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus { diff --git a/src/primitive/extent.hpp b/src/primitive/extent.hpp index 5c45655..081261b 100644 --- a/src/primitive/extent.hpp +++ b/src/primitive/extent.hpp @@ -13,7 +13,7 @@ #include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +// #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus { @@ -96,8 +96,8 @@ namespace gal::prometheus const basic_extent<2, High>& high ) const noexcept -> basic_extent { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.width < high.width); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.height < high.height); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.width < high.width); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.height < high.height); return { @@ -189,9 +189,9 @@ namespace gal::prometheus const basic_extent<3, High>& high ) const noexcept -> basic_extent { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.width < high.width); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.height < high.height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.depth < high.depth); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.width < high.width); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.height < high.height); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.depth < high.depth); return { diff --git a/src/primitive/point.hpp b/src/primitive/point.hpp index 98baad6..f309e35 100644 --- a/src/primitive/point.hpp +++ b/src/primitive/point.hpp @@ -14,7 +14,7 @@ #include #include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +// #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus { @@ -103,8 +103,8 @@ namespace gal::prometheus const basic_point<2, High>& high ) const noexcept -> basic_point { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); return { @@ -235,9 +235,9 @@ namespace gal::prometheus const basic_point<3, High>& high ) const noexcept -> basic_point { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.z < high.z); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.z < high.z); return { diff --git a/src/primitive/rect.hpp b/src/primitive/rect.hpp index c71d27b..35c3ab8 100644 --- a/src/primitive/rect.hpp +++ b/src/primitive/rect.hpp @@ -15,7 +15,7 @@ #include #include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +// #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus { @@ -380,16 +380,16 @@ namespace gal::prometheus [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); return p.between(left_top_near(), right_bottom_near()); } [[nodiscard]] constexpr auto includes(const basic_rect& rect) const noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(width() > rect.width() and height() > rect.height() and depth() > rect.depth()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(width() > rect.width() and height() > rect.height() and depth() > rect.depth()); return rect.point.x >= point.x and @@ -402,8 +402,8 @@ namespace gal::prometheus [[nodiscard]] constexpr auto intersects(const basic_rect& rect) const noexcept -> bool { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); return not( rect.point.x >= point.x + width() or From 552e667b29128a2060f11ee3cde44956ee871e84 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Mon, 26 May 2025 17:55:10 +0800 Subject: [PATCH 49/54] =?UTF-8?q?`feat`:=20New=20gfx=20module.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 92 +- .../extension/glyph_parser_freetype.cpp | 4 +- .../extension/glyph_parser_freetype.hpp | 4 +- .../extension/renderer_d3d11.cpp | 27 +- .../extension/renderer_d3d11.hpp | 9 +- src/gfx/extension/renderer_d3d12.cpp | 1027 +++++++++++++++++ src/gfx/extension/renderer_d3d12.hpp | 87 ++ src/gfx/gfx.hpp | 993 +++++++++++++++- src/{gfx_new => gfx}/internal/context.cpp | 165 ++- src/{gfx_new => gfx}/internal/context.hpp | 94 +- src/{gfx_new => gfx}/internal/font.cpp | 8 +- src/{gfx_new => gfx}/internal/font.hpp | 4 +- src/{gfx_new => gfx}/internal/render_list.cpp | 136 ++- src/{gfx_new => gfx}/internal/render_list.hpp | 4 +- src/{gfx_new => gfx}/internal/texture.cpp | 4 +- src/{gfx_new => gfx}/internal/texture.hpp | 4 +- src/gfx/{ => old}/context.cpp | 0 src/gfx/{ => old}/context.hpp | 7 + src/gfx/{ => old}/font.cpp | 0 src/gfx/{ => old}/font.hpp | 0 src/gfx/old/gfx.hpp | 20 + src/gfx/{ => old}/glyph.cpp | 0 src/gfx/{ => old}/glyph.hpp | 0 src/gfx/{ => old}/glyph_parser_freetype.cpp | 2 +- src/gfx/{ => old}/glyph_parser_freetype.hpp | 0 src/gfx/{ => old}/render_list.cpp | 0 src/gfx/{ => old}/render_list.hpp | 0 src/gfx/{ => old}/renderer.cpp | 0 src/gfx/{ => old}/renderer.hpp | 0 src/gfx/{ => old}/renderer_dx11.cpp | 7 +- src/gfx/{ => old}/renderer_dx11.hpp | 0 src/gfx/{ => old}/renderer_dx12.cpp | 0 src/gfx/{ => old}/renderer_dx12.hpp | 0 src/gfx/{ => old}/texture.cpp | 0 src/gfx/{ => old}/texture.hpp | 0 src/gfx/{ => old}/type.hpp | 0 src/gfx_new/gfx.hpp | 975 ---------------- 37 files changed, 2487 insertions(+), 1186 deletions(-) rename src/{gfx_new => gfx}/extension/glyph_parser_freetype.cpp (98%) rename src/{gfx_new => gfx}/extension/glyph_parser_freetype.hpp (95%) rename src/{gfx_new => gfx}/extension/renderer_d3d11.cpp (97%) rename src/{gfx_new => gfx}/extension/renderer_d3d11.hpp (91%) create mode 100644 src/gfx/extension/renderer_d3d12.cpp create mode 100644 src/gfx/extension/renderer_d3d12.hpp rename src/{gfx_new => gfx}/internal/context.cpp (72%) rename src/{gfx_new => gfx}/internal/context.hpp (77%) rename src/{gfx_new => gfx}/internal/font.cpp (94%) rename src/{gfx_new => gfx}/internal/font.hpp (98%) rename src/{gfx_new => gfx}/internal/render_list.cpp (97%) rename src/{gfx_new => gfx}/internal/render_list.hpp (97%) rename src/{gfx_new => gfx}/internal/texture.cpp (98%) rename src/{gfx_new => gfx}/internal/texture.hpp (98%) rename src/gfx/{ => old}/context.cpp (100%) rename src/gfx/{ => old}/context.hpp (98%) rename src/gfx/{ => old}/font.cpp (100%) rename src/gfx/{ => old}/font.hpp (100%) create mode 100644 src/gfx/old/gfx.hpp rename src/gfx/{ => old}/glyph.cpp (100%) rename src/gfx/{ => old}/glyph.hpp (100%) rename src/gfx/{ => old}/glyph_parser_freetype.cpp (99%) rename src/gfx/{ => old}/glyph_parser_freetype.hpp (100%) rename src/gfx/{ => old}/render_list.cpp (100%) rename src/gfx/{ => old}/render_list.hpp (100%) rename src/gfx/{ => old}/renderer.cpp (100%) rename src/gfx/{ => old}/renderer.hpp (100%) rename src/gfx/{ => old}/renderer_dx11.cpp (99%) rename src/gfx/{ => old}/renderer_dx11.hpp (100%) rename src/gfx/{ => old}/renderer_dx12.cpp (100%) rename src/gfx/{ => old}/renderer_dx12.hpp (100%) rename src/gfx/{ => old}/texture.cpp (100%) rename src/gfx/{ => old}/texture.hpp (100%) rename src/gfx/{ => old}/type.hpp (100%) delete mode 100644 src/gfx_new/gfx.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index f2a2f74..2f4f761 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -366,39 +366,40 @@ set( ${PROJECT_SOURCE_DIR}/src/io/io.hpp # ========================= - # GFX-NEW + # GFX # ========================= - ${PROJECT_SOURCE_DIR}/src/gfx_new/gfx.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/gfx.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/context.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/texture.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/font.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/render_list.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/context.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/glyph_parser_freetype.hpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/renderer_d3d11.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/extension/glyph_parser_freetype.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d11.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d12.hpp # ========================= - # GFX + # GFX-OLD # ========================= - ${PROJECT_SOURCE_DIR}/src/gfx/type.hpp - - ${PROJECT_SOURCE_DIR}/src/gfx/glyph.hpp - - ${PROJECT_SOURCE_DIR}/src/gfx/texture.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/font.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.hpp - - ${PROJECT_SOURCE_DIR}/src/gfx/render_list.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/renderer.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx12.hpp - - ${PROJECT_SOURCE_DIR}/src/gfx/context.hpp - - ${PROJECT_SOURCE_DIR}/src/gfx/gfx.hpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/type.hpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph.hpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/texture.hpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/font.hpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph_parser_freetype.hpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/render_list.hpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer.hpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx11.hpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx12.hpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/context.hpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/gfx.hpp # ========================= # GUI @@ -452,33 +453,34 @@ set( ${PROJECT_SOURCE_DIR}/src/io/inputs.cpp # ========================= - # GFX-NEW + # GFX # ========================= - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/texture.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/font.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/render_list.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/internal/context.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/texture.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/font.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/render_list.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/context.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/glyph_parser_freetype.cpp - ${PROJECT_SOURCE_DIR}/src/gfx_new/extension/renderer_d3d11.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/extension/glyph_parser_freetype.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d11.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d12.cpp # ========================= - # GFX + # GFX-OLD # ========================= - ${PROJECT_SOURCE_DIR}/src/gfx/glyph.cpp - - ${PROJECT_SOURCE_DIR}/src/gfx/texture.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/font.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/glyph_parser_freetype.cpp - - ${PROJECT_SOURCE_DIR}/src/gfx/render_list.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/renderer.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx11.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/renderer_dx12.cpp - - ${PROJECT_SOURCE_DIR}/src/gfx/context.cpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph.cpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/texture.cpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/font.cpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph_parser_freetype.cpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/render_list.cpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer.cpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx11.cpp +# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx12.cpp +# +# ${PROJECT_SOURCE_DIR}/src/gfx/old/context.cpp # ========================= # GUI diff --git a/src/gfx_new/extension/glyph_parser_freetype.cpp b/src/gfx/extension/glyph_parser_freetype.cpp similarity index 98% rename from src/gfx_new/extension/glyph_parser_freetype.cpp rename to src/gfx/extension/glyph_parser_freetype.cpp index 5e7df03..9b7895b 100644 --- a/src/gfx_new/extension/glyph_parser_freetype.cpp +++ b/src/gfx/extension/glyph_parser_freetype.cpp @@ -3,7 +3,7 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include +#include #include #include @@ -16,7 +16,7 @@ namespace } } // namespace -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { class GlyphParserFreeType::Library final { diff --git a/src/gfx_new/extension/glyph_parser_freetype.hpp b/src/gfx/extension/glyph_parser_freetype.hpp similarity index 95% rename from src/gfx_new/extension/glyph_parser_freetype.hpp rename to src/gfx/extension/glyph_parser_freetype.hpp index 369ca25..7f083d1 100644 --- a/src/gfx_new/extension/glyph_parser_freetype.hpp +++ b/src/gfx/extension/glyph_parser_freetype.hpp @@ -7,11 +7,11 @@ #include -#include +#include #include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { class GlyphParserFreeType final : public GlyphParser { diff --git a/src/gfx_new/extension/renderer_d3d11.cpp b/src/gfx/extension/renderer_d3d11.cpp similarity index 97% rename from src/gfx_new/extension/renderer_d3d11.cpp rename to src/gfx/extension/renderer_d3d11.cpp index d71408e..972e918 100644 --- a/src/gfx_new/extension/renderer_d3d11.cpp +++ b/src/gfx/extension/renderer_d3d11.cpp @@ -3,7 +3,7 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include +#include #include @@ -15,8 +15,6 @@ #include #include -#include -#include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE #include @@ -69,7 +67,7 @@ namespace } } -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { auto RendererD3D11::create_blend_state() noexcept -> bool { @@ -360,8 +358,8 @@ namespace gal::prometheus::gfx_new } auto RendererD3D11::upload_texture( - const Texture::data_view_type data, - const Texture::size_type size, + const TextureDescriptor::data_view_type data, + const TextureDescriptor::size_type size, const D3D11_USAGE usage, const std::uint32_t bind_flags, const std::uint32_t cpu_access_flags, @@ -488,11 +486,6 @@ namespace gal::prometheus::gfx_new device_immediate_context_ = std::move(device_immediate_context); } - auto RendererD3D11::set_display_size(const extent_type& display_size) noexcept -> void - { - display_size_ = display_size; - } - auto RendererD3D11::do_construct() noexcept -> bool { if (not create_blend_state()) @@ -609,7 +602,7 @@ namespace gal::prometheus::gfx_new const auto* source = texture.data.get(); const auto source_length = static_cast(texture.size.width) * texture.size.height; - std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); + std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); device_immediate_context_->Unmap(texture_2d, 0); } @@ -628,7 +621,7 @@ namespace gal::prometheus::gfx_new } } - auto RendererD3D11::do_present(const render_data_list_type& render_data_list) noexcept -> void + auto RendererD3D11::do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void { auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = render_buffer_; @@ -745,9 +738,9 @@ namespace gal::prometheus::gfx_new auto* mapped_projection_matrix = static_cast(mapped_resource.pData); constexpr auto left = 0.f; - const auto right = display_size_.width; + const auto right = display_size.width; constexpr auto top = 0.f; - const auto bottom = display_size_.height; + const auto bottom = display_size.height; const projection_matrix_type mvp{ {2.0f / (right - left), 0.0f, 0.0f, 0.0f}, @@ -765,8 +758,8 @@ namespace gal::prometheus::gfx_new const D3D11_VIEWPORT viewport{ .TopLeftX = .0f, .TopLeftY = .0f, - .Width = display_size_.width, - .Height = display_size_.height, + .Width = display_size.width, + .Height = display_size.height, .MinDepth = 0, .MaxDepth = 1 }; diff --git a/src/gfx_new/extension/renderer_d3d11.hpp b/src/gfx/extension/renderer_d3d11.hpp similarity index 91% rename from src/gfx_new/extension/renderer_d3d11.hpp rename to src/gfx/extension/renderer_d3d11.hpp index 5ce257d..5d081d7 100644 --- a/src/gfx_new/extension/renderer_d3d11.hpp +++ b/src/gfx/extension/renderer_d3d11.hpp @@ -7,12 +7,12 @@ #include -#include +#include #include #include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { using Microsoft::WRL::ComPtr; @@ -47,7 +47,6 @@ namespace gal::prometheus::gfx_new textures_type textures_; render_buffer_type render_buffer_; - extent_type display_size_; [[nodiscard]] auto create_blend_state() noexcept -> bool; [[nodiscard]] auto create_rasterizer_state() noexcept -> bool; @@ -76,8 +75,6 @@ namespace gal::prometheus::gfx_new auto bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void; auto bind_device_context(ComPtr device_immediate_context) noexcept -> void; - auto set_display_size(const extent_type& display_size) noexcept -> void; - private: auto do_construct() noexcept -> bool override; auto do_destruct() noexcept -> void override; @@ -87,6 +84,6 @@ namespace gal::prometheus::gfx_new auto do_texture_update(const TextureDescriptor& texture) noexcept -> void override; auto do_texture_destroy(texture_id_type texture_id) noexcept -> void override; - auto do_present(const render_data_list_type& render_data_list) noexcept -> void override; + auto do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void override; }; } diff --git a/src/gfx/extension/renderer_d3d12.cpp b/src/gfx/extension/renderer_d3d12.cpp new file mode 100644 index 0000000..e9cb431 --- /dev/null +++ b/src/gfx/extension/renderer_d3d12.cpp @@ -0,0 +1,1027 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#if GAL_PROMETHEUS_COMPILER_DEBUG +#define GAL_PROMETHEUS_GFX_DEBUG +#include +#endif + +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#include +#include + +namespace +{ + using namespace gal::prometheus; + using namespace gfx; + + auto check_hr_error( + const HRESULT result +#if defined(GAL_PROMETHEUS_GFX_DEBUG) + , + const std::source_location& location = std::source_location::current() +#endif + ) noexcept -> bool + { + if (SUCCEEDED(result)) + { + return true; + } + +#if defined(GAL_PROMETHEUS_GFX_DEBUG) + + const _com_error err{result}; + std::println(stderr, "Error: {} --- at {}:{}", err.ErrorMessage(), location.file_name(), location.line()); + + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + +#else + + GAL_PROMETHEUS_COMPILER_UNREACHABLE(); + +#endif + + return false; + } + + using projection_matrix_type = float[4][4]; + + constexpr std::size_t gpu_handle_to_id_offset = 0x1000'0000; + + [[nodiscard]] auto id_to_gpu_handle(const texture_id_type id) noexcept -> std::size_t + { + return id - gpu_handle_to_id_offset; + } + + [[nodiscard]] auto gpu_handle_to_id(const std::size_t index) noexcept -> texture_id_type + { + return index + gpu_handle_to_id_offset; + } +} + +namespace gal::prometheus::gfx +{ + auto RendererD3D12::create_root_signature() noexcept -> bool + { + // [0] projection_matrix + constexpr D3D12_ROOT_PARAMETER param_0 + { + .ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, + .Constants = {.ShaderRegister = 0, .RegisterSpace = 0, .Num32BitValues = sizeof(projection_matrix_type) / sizeof(float)}, + .ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX + }; + // [1] texture + constexpr D3D12_DESCRIPTOR_RANGE range + { + .RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + .NumDescriptors = 1, + .BaseShaderRegister = 0, + .RegisterSpace = 0, + .OffsetInDescriptorsFromTableStart = 0 + }; + const D3D12_ROOT_PARAMETER param_1 + { + .ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, + .DescriptorTable = {.NumDescriptorRanges = 1, .pDescriptorRanges = &range}, + .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL + }; + // @see Dx12Renderer::do_present -> `command_list_->SetGraphicsRootXxx` + const D3D12_ROOT_PARAMETER params[]{param_0, param_1}; + + // Bi-linear sampling is required by default + constexpr D3D12_STATIC_SAMPLER_DESC static_sampler_desc + { + .Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR, + .AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + .AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + .AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + .MipLODBias = .0f, + .MaxAnisotropy = 0, + .ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS, + .BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK, + .MinLOD = .0f, + .MaxLOD = .0f, + .ShaderRegister = 0, + .RegisterSpace = 0, + .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL + }; + + const D3D12_ROOT_SIGNATURE_DESC root_signature_desc + { + .NumParameters = static_cast(std::ranges::size(params)), + .pParameters = params, + .NumStaticSamplers = 1, + .pStaticSamplers = &static_sampler_desc, + .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS + }; + + auto d3d12_dll = GetModuleHandleW(L"d3d12.dll"); + if (d3d12_dll == nullptr) + { + d3d12_dll = LoadLibraryW(L"d3d12.dll"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(d3d12_dll != nullptr); + } + + const auto serialize_root_signature_function = + reinterpret_cast(GetProcAddress(d3d12_dll, "D3D12SerializeRootSignature")); // NOLINT(clang-diagnostic-cast-function-type-strict) + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(serialize_root_signature_function != nullptr); + + ComPtr blob = nullptr; + if (not check_hr_error(serialize_root_signature_function(&root_signature_desc, D3D_ROOT_SIGNATURE_VERSION_1, blob.GetAddressOf(), nullptr))) + { + return false; + } + if (not check_hr_error(device_->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(root_signature_.GetAddressOf())))) + { + return false; + } + + return true; + } + + auto RendererD3D12::create_pipeline_state() noexcept -> bool + { + // Create the vertex shader + auto vertex_shader_blob = []() -> ComPtr + { + constexpr static char shader[]{ + "cbuffer vertexBuffer : register(b0)" + "{" + " float4x4 ProjectionMatrix;" + "};" + "struct VS_INPUT" + "{" + " float2 pos : POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "PS_INPUT main(VS_INPUT input)" + "{" + " PS_INPUT output;" + " output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));" + " output.col = input.col;" + " output.uv = input.uv;" + " return output;" + "}" + }; + + ComPtr blob; + if (not check_hr_error(D3DCompile(shader, std::ranges::size(shader), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, blob.GetAddressOf(), nullptr))) + { + return nullptr; + } + + return blob; + }(); + + // Create the pixel shader + auto pixel_shader_blob = []() -> ComPtr + { + constexpr static char shader[]{ + "struct PS_INPUT" + "{" + " float4 pos : SV_POSITION;" + " float4 col : COLOR0;" + " float2 uv : TEXCOORD0;" + "};" + "sampler sampler0;" + "Texture2D texture0;" + "float4 main(PS_INPUT input) : SV_Target" + "{" + " float4 out_col = texture0.Sample(sampler0, input.uv);" + " return input.col * out_col;" + "}" + }; + + ComPtr blob; + if (not check_hr_error(D3DCompile(shader, std::ranges::size(shader), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, blob.GetAddressOf(), nullptr))) + { + return nullptr; + } + + return blob; + }(); + + if (vertex_shader_blob == nullptr or pixel_shader_blob == nullptr) + { + return false; + } + + // Create the blending setup + constexpr D3D12_RENDER_TARGET_BLEND_DESC render_target_blend_desc + { + .BlendEnable = true, + .LogicOpEnable = false, + .SrcBlend = D3D12_BLEND_SRC_ALPHA, + .DestBlend = D3D12_BLEND_INV_SRC_ALPHA, + .BlendOp = D3D12_BLEND_OP_ADD, + .SrcBlendAlpha = D3D12_BLEND_ONE, + .DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA, + .BlendOpAlpha = D3D12_BLEND_OP_ADD, + .LogicOp = D3D12_LOGIC_OP_CLEAR, + .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL + }; + constexpr D3D12_BLEND_DESC blend_desc + { + .AlphaToCoverageEnable = FALSE, + .IndependentBlendEnable = FALSE, + .RenderTarget = {render_target_blend_desc} + }; + + // Create the rasterizer state + constexpr D3D12_RASTERIZER_DESC rasterizer_desc + { + .FillMode = D3D12_FILL_MODE_SOLID, + .CullMode = D3D12_CULL_MODE_NONE, + .FrontCounterClockwise = FALSE, + .DepthBias = D3D12_DEFAULT_DEPTH_BIAS, + .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP, + .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, + .DepthClipEnable = TRUE, + .MultisampleEnable = FALSE, + .AntialiasedLineEnable = FALSE, + .ForcedSampleCount = 0, + .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF + }; + + // Create depth-stencil State + constexpr D3D12_DEPTH_STENCIL_DESC depth_stencil_desc + { + .DepthEnable = FALSE, + .DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL, + .DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS, + .StencilEnable = FALSE, + .StencilReadMask = 0, + .StencilWriteMask = 0, + .FrontFace = + { + .StencilFailOp = D3D12_STENCIL_OP_KEEP, + .StencilDepthFailOp = D3D12_STENCIL_OP_KEEP, + .StencilPassOp = D3D12_STENCIL_OP_KEEP, + .StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS + }, + .BackFace = + { + .StencilFailOp = D3D12_STENCIL_OP_KEEP, + .StencilDepthFailOp = D3D12_STENCIL_OP_KEEP, + .StencilPassOp = D3D12_STENCIL_OP_KEEP, + .StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS + } + }; + + // Create the input layout + constexpr D3D12_INPUT_ELEMENT_DESC input_element_desc[] + { + { + .SemanticName = "POSITION", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, position)), + .InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "TEXCOORD", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R32G32_FLOAT, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, uv)), + .InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + { + .SemanticName = "COLOR", + .SemanticIndex = 0, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .InputSlot = 0, + .AlignedByteOffset = static_cast(offsetof(vertex_type, color)), + .InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, + .InstanceDataStepRate = 0 + }, + }; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeline_state_desc + { + .pRootSignature = root_signature_.Get(), + .VS = {.pShaderBytecode = vertex_shader_blob->GetBufferPointer(), .BytecodeLength = vertex_shader_blob->GetBufferSize()}, + .PS = {.pShaderBytecode = pixel_shader_blob->GetBufferPointer(), .BytecodeLength = pixel_shader_blob->GetBufferSize()}, + .DS = {.pShaderBytecode = nullptr, .BytecodeLength = 0}, + .HS = {.pShaderBytecode = nullptr, .BytecodeLength = 0}, + .GS = {.pShaderBytecode = nullptr, .BytecodeLength = 0}, + .StreamOutput = {.pSODeclaration = nullptr, .NumEntries = 0, .pBufferStrides = nullptr, .NumStrides = 0, .RasterizedStream = 0}, + .BlendState = blend_desc, + .SampleMask = UINT_MAX, + .RasterizerState = rasterizer_desc, + .DepthStencilState = depth_stencil_desc, + .InputLayout = {.pInputElementDescs = input_element_desc, .NumElements = static_cast(std::ranges::size(input_element_desc))}, + .IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED, + .PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, + .NumRenderTargets = 1, + .RTVFormats = {DXGI_FORMAT_R8G8B8A8_UNORM}, + .DSVFormat = {}, + .SampleDesc = {.Count = 1, .Quality = 0}, + .NodeMask = 1, + .CachedPSO = {.pCachedBlob = nullptr, .CachedBlobSizeInBytes = 0}, + .Flags = D3D12_PIPELINE_STATE_FLAG_NONE + }; + + return check_hr_error(device_->CreateGraphicsPipelineState(&pipeline_state_desc, IID_PPV_ARGS(pipeline_state_.GetAddressOf()))); + } + + auto RendererD3D12::create_srv_descriptor_heap(const UINT num) noexcept -> bool + { + const D3D12_DESCRIPTOR_HEAP_DESC desc{ + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + .NumDescriptors = num, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + .NodeMask = 0 + }; + return check_hr_error(device_->CreateDescriptorHeap(&desc, IID_PPV_ARGS(srv_descriptor_heap_.ReleaseAndGetAddressOf()))); + } + + auto RendererD3D12::upload_texture( + const std::size_t index, + const TextureDescriptor::data_view_type data, + const TextureDescriptor::size_type size, + const bool record_resource + ) noexcept -> texture_id_type + { + constexpr D3D12_HEAP_PROPERTIES heap_properties{ + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + + const D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Alignment = 0, + .Width = static_cast(size.width), + .Height = static_cast(size.height), + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + ComPtr texture_2d; + if (not check_hr_error( + device_->CreateCommittedResource( + &heap_properties, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(texture_2d.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + const auto upload_pitch = (static_cast(size.width * 4) + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + const auto upload_size = static_cast(size.height) * upload_pitch; + + constexpr D3D12_HEAP_PROPERTIES upload_heap_properties{ + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + + const D3D12_RESOURCE_DESC upload_resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = static_cast(upload_size), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + ComPtr upload_buffer; + check_hr_error( + device_->CreateCommittedResource( + &upload_heap_properties, + D3D12_HEAP_FLAG_NONE, + &upload_resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(upload_buffer.GetAddressOf()) + ) + ); + + void* mapped_data = nullptr; + const D3D12_RANGE range{.Begin = 0, .End = upload_size}; + if (not check_hr_error(upload_buffer->Map(0, &range, &mapped_data))) + { + return invalid_texture_id; + } + for (UINT i = 0; i < static_cast(size.height); ++i) + { + auto* dest = static_cast(mapped_data) + static_cast(upload_pitch * i); + auto* source = reinterpret_cast(data.data()) + static_cast(size.width * i * 4); + const auto length = size.width * 4; + std::memcpy(dest, source, length); + } + upload_buffer->Unmap(0, &range); + + const D3D12_TEXTURE_COPY_LOCATION source_location{ + .pResource = upload_buffer.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + .PlacedFootprint = + { + .Offset = 0, + .Footprint = + { + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .Width = static_cast(size.width), + .Height = static_cast(size.height), + .Depth = 1, + .RowPitch = upload_pitch + } + } + }; + + const D3D12_TEXTURE_COPY_LOCATION dest_location{ + .pResource = texture_2d.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 0 + }; + + const D3D12_RESOURCE_BARRIER barrier{ + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = texture_2d.Get(), + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST, + .StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + } + }; + + ComPtr command_allocator; + if (not check_hr_error( + device_->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(command_allocator.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + ComPtr command_list; + if (not check_hr_error( + device_->CreateCommandList( + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + command_allocator.Get(), + nullptr, + IID_PPV_ARGS(command_list.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + command_list->CopyTextureRegion(&dest_location, 0, 0, 0, &source_location, nullptr); + command_list->ResourceBarrier(1, &barrier); + if (not check_hr_error(command_list->Close())) + { + return invalid_texture_id; + } + + constexpr D3D12_COMMAND_QUEUE_DESC command_queue_desc{ + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = 0, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 1 + }; + + ComPtr command_queue; + if (not check_hr_error( + device_->CreateCommandQueue( + &command_queue_desc, + IID_PPV_ARGS(command_queue.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + ID3D12CommandList* command_lists[]{command_list.Get()}; + command_queue->ExecuteCommandLists(1, command_lists); + + ComPtr fence; + if (not check_hr_error( + device_->CreateFence( + 0, + D3D12_FENCE_FLAG_NONE, + IID_PPV_ARGS(fence.GetAddressOf()) + ) + )) + { + return invalid_texture_id; + } + + constexpr UINT64 fence_value = 1; + if (not check_hr_error(command_queue->Signal(fence.Get(), fence_value))) + { + return invalid_texture_id; + } + if (fence->GetCompletedValue() < fence_value) + { + HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (not check_hr_error(fence->SetEventOnCompletion(fence_value, event))) + { + return invalid_texture_id; + } + WaitForSingleObject(event, INFINITE); + CloseHandle(event); + } + + // Create texture view + const D3D12_SHADER_RESOURCE_VIEW_DESC resource_view_desc{ + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D, + .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + .Texture2D = {.MostDetailedMip = 0, .MipLevels = resource_desc.MipLevels, .PlaneSlice = 0, .ResourceMinLODClamp = .0f} + }; + + const auto increment_size = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + auto cpu_handle = srv_descriptor_heap_->GetCPUDescriptorHandleForHeapStart(); + auto gpu_handle = srv_descriptor_heap_->GetGPUDescriptorHandleForHeapStart(); + + cpu_handle.ptr += index * increment_size; + gpu_handle.ptr += index * increment_size; + + device_->CreateShaderResourceView(texture_2d.Get(), &resource_view_desc, cpu_handle); + + if (record_resource) + { + textures_[index] = {.resource = texture_2d, .cpu = cpu_handle, .gpu = gpu_handle}; + } + else + { + // ComPtr + texture_2d = nullptr; + } + + return gpu_handle_to_id(index); + } + + RendererD3D12::RendererD3D12() noexcept + : device_{nullptr}, + command_list_{nullptr}, + root_signature_{nullptr}, + pipeline_state_{nullptr}, + srv_descriptor_heap_{nullptr}, + srv_max_size_{8}, + // note: overflow(max + 1 => 0) + frame_resource_index_{(std::numeric_limits::max)()}, + frame_resource_{} {} + + RendererD3D12::RendererD3D12(ID3D12Device* device, ID3D12GraphicsCommandList* command_list) noexcept + : RendererD3D12{} + { + bind_device(device); + bind_command_list(command_list); + } + + RendererD3D12::RendererD3D12(ComPtr device, ComPtr command_list) noexcept + : RendererD3D12{} + { + bind_device(std::move(device)); + bind_command_list(std::move(command_list)); + } + + auto RendererD3D12::bind_device(ID3D12Device* device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = device; + } + + auto RendererD3D12::bind_device(ComPtr device) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(device != nullptr); + device_ = std::move(device); + } + + auto RendererD3D12::bind_command_list(ID3D12GraphicsCommandList* command_list) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list != nullptr); + command_list_ = command_list; + } + + auto RendererD3D12::bind_command_list(ComPtr command_list) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list != nullptr); + command_list_ = std::move(command_list); + } + + auto RendererD3D12::do_construct() noexcept -> bool + { + if (not create_root_signature()) + { + return false; + } + if (not create_pipeline_state()) + { + return false; + } + if (not create_srv_descriptor_heap(srv_max_size_)) + { + return false; + } + textures_.resize(srv_max_size_); + + return true; + } + + auto RendererD3D12::do_destruct() noexcept -> void + { + // ComPtr + textures_.clear(); + + std::ranges::for_each( + std::ranges::subrange{frame_resource_, num_frames_in_flight}, + [](render_buffer_type& render_buffer) noexcept -> void + { + // ComPtr + render_buffer.vertex = nullptr; + render_buffer.index = nullptr; + } + ); + + // command_list_->ClearState(pipeline_state_.Get()); + // std::ignore = command_list_->Close(); + + srv_descriptor_heap_ = nullptr; + pipeline_state_ = nullptr; + root_signature_ = nullptr; + + command_list_ = nullptr; + device_ = nullptr; + } + + auto RendererD3D12::do_ready() const noexcept -> bool + { + if (device_ == nullptr or command_list_ == nullptr) + { + return false; + } + + if (root_signature_ == nullptr or pipeline_state_ == nullptr or srv_descriptor_heap_ == nullptr) + { + return false; + } + + return true; + } + + auto RendererD3D12::do_texture_create(const TextureDescriptor::data_view_type data, const TextureDescriptor::size_type size) noexcept -> texture_id_type + { + auto it = std::ranges::find(textures_, nullptr, &texture_type::resource); + if (it == textures_.end()) + { + // full, expand heap + const auto old_size = srv_max_size_; + const auto new_size = static_cast(static_cast(srv_max_size_) * 1.5f); + + srv_max_size_ = new_size; + textures_.resize(srv_max_size_); + it = textures_.begin() + old_size; + + const auto old_heap = srv_descriptor_heap_; + + const auto create_srv_descriptor_heap_result = create_srv_descriptor_heap(srv_max_size_); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(create_srv_descriptor_heap_result); + + const auto source = old_heap->GetCPUDescriptorHandleForHeapStart(); + const auto dest = srv_descriptor_heap_->GetCPUDescriptorHandleForHeapStart(); + + device_->CopyDescriptorsSimple( + old_size, + dest, + source, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV + ); + + const auto increment_size = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + const auto new_cpu_begin = srv_descriptor_heap_->GetCPUDescriptorHandleForHeapStart().ptr; + const auto new_gpu_begin = srv_descriptor_heap_->GetGPUDescriptorHandleForHeapStart().ptr; + + for (std::size_t i = 0; i < old_size; ++i) + { + if (auto& [resource, cpu, gpu] = textures_[i]; resource != nullptr) + { + cpu.ptr = new_cpu_begin + i * increment_size; + gpu.ptr = new_gpu_begin + i * increment_size; + } + } + } + + const auto index = std::ranges::distance(textures_.begin(), it); + + return upload_texture(index, data, size); + } + + auto RendererD3D12::do_texture_update(const TextureDescriptor& texture) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.id != invalid_texture_id, "Create texture first!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty, "No need to update texture!"); + + const auto index = id_to_gpu_handle(texture.id); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < srv_max_size_); + auto& texture_gpu = textures_[index]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_gpu.resource != nullptr); + + // todo + do_texture_destroy(texture.id); + std::ignore = upload_texture(index, {texture.data.get(), static_cast(texture.size.width) * texture.size.height}, texture.size); + } + + auto RendererD3D12::do_texture_destroy(const texture_id_type texture_id) noexcept -> void + { + const auto index = id_to_gpu_handle(texture_id); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < srv_max_size_); + + auto& texture = textures_[index]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.resource != nullptr); + // ComPtr + texture.resource = nullptr; + } + + auto RendererD3D12::do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void + { + const auto [total_vertex_count, total_index_count] = [&]() noexcept + { + struct sum + { + UINT vertex; + UINT index; + }; + + return std::ranges::fold_left( + render_data_list, + sum{.vertex = 0, .index = 0}, + [](const sum s, const RenderData& render_data) noexcept -> sum + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + return {.vertex = s.vertex + static_cast(vertex_list.size()), .index = s.index + static_cast(index_list.size())}; + } + ); + }(); + + frame_resource_index_ += 1; + const auto this_frame_index = frame_resource_index_ % num_frames_in_flight; + auto& this_frame = frame_resource_[this_frame_index]; + auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = this_frame; + + constexpr D3D12_HEAP_PROPERTIES heap_properties{ + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + // Create and grow vertex/index buffers if needed + if (this_frame_vertex_buffer == nullptr or this_frame_vertex_count < total_vertex_count) + { + // todo: grow factor + this_frame_vertex_count = total_vertex_count + 5000; + + const D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = this_frame_vertex_count * sizeof(vertex_type), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + check_hr_error( + device_->CreateCommittedResource( + &heap_properties, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(this_frame_vertex_buffer.ReleaseAndGetAddressOf()) + ) + ); + } + if (this_frame_index_buffer == nullptr or this_frame_index_count < total_index_count) + { + // todo: grow factor + this_frame_index_count = total_index_count + 10000; + + const D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = this_frame_index_count * sizeof(index_type), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + check_hr_error( + device_->CreateCommittedResource( + &heap_properties, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(this_frame_index_buffer.ReleaseAndGetAddressOf()) + ) + ); + } + + // Upload vertex/index data into a single contiguous GPU buffer + { + void* mapped_vertex_resource; + void* mapped_index_resource; + constexpr D3D12_RANGE vertex_range{.Begin = 0, .End = 0}; + constexpr D3D12_RANGE index_range{.Begin = 0, .End = 0}; + check_hr_error(this_frame_vertex_buffer->Map(0, &vertex_range, &mapped_vertex_resource)); + check_hr_error(this_frame_index_buffer->Map(0, &index_range, &mapped_index_resource)); + + auto* mapped_vertex = static_cast(mapped_vertex_resource); + auto* mapped_index = static_cast(mapped_index_resource); + + UINT vertex_offset = 0; + UINT index_offset = 0; + + std::ranges::for_each( + render_data_list, + [&](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + // std::ranges::transform( + // vertex_list, + // mapped_vertex + vertex_offset, + // [](const vertex_type& vertex) noexcept -> vertex_type + // { + // // return { + // // .position = {vertex.position.x, vertex.position.y}, + // // .uv = {vertex.uv.x, vertex.uv.y}, + // // .color = vertex.color.to(primitive::color_format) + // // }; + // return std::bit_cast(vertex); + // } + // ); + std::ranges::copy(vertex_list, mapped_vertex + vertex_offset); + // std::ranges::transform( + // index_list, + // mapped_index + index_offset, + // [vertex_offset](const index_type index) noexcept -> index_type + // { + // return static_cast(index + vertex_offset); + // } + // ); + std::ranges::copy(index_list, mapped_index + index_offset); + + vertex_offset += static_cast(vertex_list.size()); + index_offset += static_cast(index_list.size()); + } + ); + + this_frame_vertex_buffer->Unmap(0, &vertex_range); + this_frame_index_buffer->Unmap(0, &index_range); + } + + // Setup orthographic projection matrix into our constant buffer + projection_matrix_type projection_matrix; + { + constexpr auto left = 0.f; + const auto right = display_size.width; + constexpr auto top = 0.f; + const auto bottom = display_size.height; + + const float mvp[4][4]{ + {2.0f / (right - left), 0.0f, 0.0f, 0.0f}, + {0.0f, 2.0f / (top - bottom), 0.0f, 0.0f}, + {0.0f, 0.0f, 0.5f, 0.0f}, + {(right + left) / (left - right), (top + bottom) / (bottom - top), 0.5f, 1.0f}, + }; + static_assert(sizeof(mvp) == sizeof(projection_matrix_type)); + std::memcpy(projection_matrix, mvp, sizeof(projection_matrix_type)); + } + + // Setup viewport + { + const D3D12_VIEWPORT viewport{ + .TopLeftX = .0f, + .TopLeftY = .0f, + .Width = static_cast(display_size.width), + .Height = static_cast(display_size.height), + .MinDepth = .0f, + .MaxDepth = 1.f + }; + command_list_->RSSetViewports(1, &viewport); + } + + // Bind shader/vertex buffers, root signature and pipeline state + { + ID3D12DescriptorHeap* descriptor_heaps[]{srv_descriptor_heap_.Get()}; + command_list_->SetDescriptorHeaps(1, descriptor_heaps); + + command_list_->SetGraphicsRootSignature(root_signature_.Get()); + command_list_->SetGraphicsRoot32BitConstants(0, sizeof(projection_matrix_type) / sizeof(float), &projection_matrix, 0); + + command_list_->SetPipelineState(pipeline_state_.Get()); + + const D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view{ + .BufferLocation = this_frame_vertex_buffer->GetGPUVirtualAddress(), + .SizeInBytes = this_frame_vertex_count * static_cast(sizeof(vertex_type)), + .StrideInBytes = sizeof(vertex_type) + }; + command_list_->IASetVertexBuffers(0, 1, &vertex_buffer_view); + + const D3D12_INDEX_BUFFER_VIEW index_buffer_view{ + .BufferLocation = this_frame_index_buffer->GetGPUVirtualAddress(), + .SizeInBytes = this_frame_index_count * static_cast(sizeof(index_type)), + // ReSharper disable once CppUnreachableCode + .Format = sizeof(index_type) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT + }; + command_list_->IASetIndexBuffer(&index_buffer_view); + command_list_->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + } + + // Setup blend factor + constexpr float blend_factor[4]{.0f, .0f, .0f, .0f}; + command_list_->OMSetBlendFactor(blend_factor); + + UINT total_index_offset = 0; + std::ranges::for_each( + render_data_list, + [this, &total_index_offset](const RenderData& render_data) noexcept -> void + { + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); + + for (const auto& command_list = render_data.command_list.get(); + const auto& [clip_rect, texture_id, index_offset, element_count]: command_list) + { + const auto [point, extent] = clip_rect; + const D3D12_RECT rect + { + static_cast(point.x), + static_cast(point.y), + static_cast(point.x + extent.width), + static_cast(point.y + extent.height) + }; + command_list_->RSSetScissorRects(1, &rect); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_id != invalid_texture_id); + const auto index = id_to_gpu_handle(texture_id); + const auto& texture = textures_[index]; + command_list_->SetGraphicsRootDescriptorTable(1, texture.gpu); + + const auto this_index_offset = static_cast(total_index_offset + index_offset); + command_list_->DrawIndexedInstanced(static_cast(element_count), 1, this_index_offset, 0, 0); + } + + total_index_offset += static_cast(index_list.size()); + } + ); + } +} diff --git a/src/gfx/extension/renderer_d3d12.hpp b/src/gfx/extension/renderer_d3d12.hpp new file mode 100644 index 0000000..eaa5d93 --- /dev/null +++ b/src/gfx/extension/renderer_d3d12.hpp @@ -0,0 +1,87 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include +#include + +namespace gal::prometheus::gfx +{ + using Microsoft::WRL::ComPtr; + + class RendererD3D12 final : public Renderer + { + public: + struct texture_type + { + ComPtr resource; + D3D12_CPU_DESCRIPTOR_HANDLE cpu; + D3D12_GPU_DESCRIPTOR_HANDLE gpu; + }; + + using textures_type = std::vector; + + private: + struct render_buffer_type + { + ComPtr index; + UINT index_count; + ComPtr vertex; + UINT vertex_count; + }; + + ComPtr device_; + ComPtr command_list_; + + ComPtr root_signature_; + ComPtr pipeline_state_; + ComPtr srv_descriptor_heap_; + // 8 descriptors are enough, and if not, then 1.5 times more + UINT srv_max_size_; + + textures_type textures_; + + constexpr static UINT num_frames_in_flight = 3; + UINT frame_resource_index_; + render_buffer_type frame_resource_[num_frames_in_flight]; + + [[nodiscard]] auto create_root_signature() noexcept -> bool; + [[nodiscard]] auto create_pipeline_state() noexcept -> bool; + [[nodiscard]] auto create_srv_descriptor_heap(UINT num) noexcept -> bool; + + [[nodiscard]] auto upload_texture( + std::size_t index, + TextureDescriptor::data_view_type data, + TextureDescriptor::size_type size, + bool record_resource = true + ) noexcept -> texture_id_type; + + public: + RendererD3D12() noexcept; + RendererD3D12(ID3D12Device* device, ID3D12GraphicsCommandList* command_list) noexcept; + RendererD3D12(ComPtr device, ComPtr command_list) noexcept; + + auto bind_device(ID3D12Device* device) noexcept -> void; + auto bind_device(ComPtr device) noexcept -> void; + auto bind_command_list(ID3D12GraphicsCommandList* command_list) noexcept -> void; + auto bind_command_list(ComPtr command_list) noexcept -> void; + + private: + auto do_construct() noexcept -> bool override; + auto do_destruct() noexcept -> void override; + [[nodiscard]] auto do_ready() const noexcept -> bool override; + + auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type override; + auto do_texture_update(const TextureDescriptor& texture) noexcept -> void override; + auto do_texture_destroy(texture_id_type texture_id) noexcept -> void override; + + auto do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void override; + }; +} diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index 27860b6..7f5e974 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -5,16 +5,991 @@ #pragma once -#include +#include +#include +#include +#include +#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include -#include -#include -#include +namespace gal::prometheus::gfx +{ + // ========================================================= + // CONTEXT + // ========================================================= -#include + class Context; + + // ========================================================= + // PRIMITIVE + // ========================================================= + + using point_type = primitive::basic_point_2d; + using uv_type = primitive::basic_point_2d; + using color_type = primitive::basic_color; + using vertex_type = primitive::basic_vertex; + using index_type = std::uint16_t; + + using extent_type = primitive::basic_extent_2d; + using rect_type = primitive::basic_rect_2d; + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; + + // ========================================================= + // TEXTURE + // ========================================================= + + // D3D11: ID3D11ShaderResourceView + // D3D12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + constant offset + using texture_id_type = std::uintptr_t; + constexpr texture_id_type invalid_texture_id{0}; + + class TextureDescriptor final + { + public: + // 32-bits (RGBA) + using element_type = std::uint32_t; + // size.width * size.height + using data_type = std::unique_ptr; + using data_view_type = std::span; + + using size_type = primitive::basic_extent_2d; + + // ============================== + // CPU side + // ============================== + + data_type data; + size_type size; + + static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); + std::uint64_t dirty : 1; + + // ============================== + // GPU side + // ============================== + + std::uint64_t id : 63; + }; + + // ========================================================= + // GLYPH / FONT + // ========================================================= + + // index + using font_id_type = std::uint32_t; + constexpr font_id_type invalid_font_id{std::numeric_limits::max()}; + + enum class GlyphFlag : std::uint8_t + { + NONE = 0, + BOLD = 1 << 0, + ITALIC = 1 << 1, + }; + + class GlyphParser + { + public: + class [[nodiscard]] FontDescriptor final + { + public: + using element_type = std::uint8_t; + using data_type = std::unique_ptr; + using data_view_type = std::span; + + using size_type = std::uint32_t; + + std::string_view identifier; + font_id_type id; + + [[nodiscard]] constexpr static auto error() noexcept -> FontDescriptor + { + return {.identifier = {}, .id = invalid_font_id}; + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return id != invalid_font_id; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + class GlyphCode final + { + public: + std::uint32_t codepoint; + std::uint32_t size; + GlyphFlag flag; + + constexpr GlyphCode(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept + : codepoint{codepoint}, + size{size}, + flag{flag} {} + }; + + class [[nodiscard]] GlyphDescriptor final + { + public: + // The offset of the bitmap may be negative (signed) + using rect_type = primitive::basic_rect_2d; + + // Bitmap infos of this glyph + rect_type rect; + float advance_x; + bool visible; + bool colored; + + // FIXME-OPT: + // We could have pre-specified an area to write glyph data to, + // but that would have meant exposing the texture implementation details to the outside world, + // and its implementation will be coupled with the GlyphParser, so we will leave the choice to the GlyphParser, + // which can find a way to reduce the number of dynamic allocations in the internal. + // note: + // We always assume that the data length of @c data is not less than @c rect.size.width * @c rect.size.height + TextureDescriptor::data_type data; + + [[nodiscard]] constexpr static auto error() noexcept -> GlyphDescriptor + { + return {.rect = {-0xbad, -0xbad, 0xc0ffee, 0xc0ffee}, .advance_x = -1, .visible = false, .colored = false, .data = nullptr}; + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return data != nullptr; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return operator bool(); + } + }; + + GlyphParser(const GlyphParser&) noexcept = delete; + GlyphParser(GlyphParser&&) noexcept = default; + auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; + auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; + + virtual ~GlyphParser() noexcept; + + GlyphParser() noexcept = default; + + /** + * @brief Load font data from @c path, return id of font + * @param path Font path + * @return id of the font, or invalid_font_id if failed to load + */ + [[nodiscard]] virtual auto load(const std::filesystem::path& path) noexcept -> FontDescriptor = 0; + + /** + * @brief Determines whether the target font contains the glyphs of the specified codepoint + * @param id The id returned by loading the font from the previous load + * @param codepoint The codepoint of the glyph to be checked + * @return Exists or not + */ + [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; + + /** + * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer + * @param id The id returned by loading the font from the previous load + * @param code {codepoint, size, flag} + * @return The glyph information of the specified size (and style) of the target codepoint + */ + [[nodiscard]] virtual auto parse(font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor = 0; + }; + + // ========================================================= + // RENDERER LIST + // ========================================================= + + enum class RenderListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + + DEFAULT = ANTI_ALIASED_LINE | ANTI_ALIASED_LINE_USE_TEXTURE | ANTI_ALIASED_FILL, + }; + + enum class RenderRectFlag : std::uint8_t + { + NONE = 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + }; + + enum class RenderArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + }; + + class RenderListSharedData + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; + + constexpr static std::size_t baked_line_uv_count = 64; + using baked_line_uvs_type = std::array; + + circle_segment_counts_type circle_segment_counts; + + vertex_sample_points_type vertex_sample_points; + + baked_line_uvs_type baked_line_uvs; + point_type white_pixel_uv; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + // -------------------------------------------------- + + RenderListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; + + class RenderData final + { + public: + using size_type = std::uint32_t; + + struct [[nodiscard]] command_type + { + rect_type scissor; + texture_id_type texture; + + // ======================= + + // set by RenderList::index_list.size() + // start offset in @c RenderList::index_list + size_type index_offset; + // set by RenderList::draw_xxx + // number of indices (multiple of 3) to be rendered as triangles + size_type element_count; + }; + + using vertex_list_type = std::vector; + using index_list_type = std::vector; + using command_list_type = std::vector; + + std::reference_wrapper vertex_list; + std::reference_wrapper index_list; + std::reference_wrapper command_list; + }; + + using render_data_list_type = std::vector; + + class RenderList + { + friend class Context; + friend class Renderer; + + public: + class RenderListContext; + + class Painter final + { + public: + using path_list_type = std::vector; + + private: + memory::RefWrapper render_list_; + path_list_type path_list_; + + auto draw_polygon_line(color_type color, float thickness, bool close) noexcept -> void; + auto draw_polygon_line_aa(color_type color, float thickness, bool close) noexcept -> void; + auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; + auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; + + public: + Painter(const Painter&) noexcept = delete; + Painter(Painter&&) noexcept = default; + auto operator=(const Painter&) noexcept -> Painter& = delete; + auto operator=(Painter&&) noexcept -> Painter& = default; + + ~Painter() noexcept; + + explicit Painter(RenderList& render_list, std::size_t reserve_point) noexcept; + + auto clear() noexcept -> Painter&; + auto reserve_extra(std::size_t size) noexcept -> Painter&; + auto reserve(std::size_t size) noexcept -> Painter&; + + auto pin(const point_type& point) noexcept -> Painter&; + + auto line(const point_type& from, const point_type& to) noexcept -> Painter&; + auto triangle(const point_type& a, const point_type& b, const point_type& c) noexcept -> Painter&; + auto quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; + auto rect(const rect_type& rect, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto rect(const rect_type::point_type& left_top, const rect_type::extent_type& extent, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto rect(const rect_type::point_type& left_top, const rect_type::point_type& right_bottom, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto circle_n(const circle_type& circle, std::uint32_t segments) noexcept -> Painter&; + auto circle_n(const circle_type::point_type& center, circle_type::radius_value_type radius, std::uint32_t segments) noexcept -> Painter&; + auto circle(const circle_type& circle) noexcept -> Painter&; + auto circle(const circle_type::point_type& center, circle_type::radius_value_type radius) noexcept -> Painter&; + auto ellipse_n(const ellipse_type& ellipse, std::uint32_t segments) noexcept -> Painter&; + auto ellipse_n(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation, std::uint32_t segments) noexcept -> Painter&; + auto ellipse(const ellipse_type& ellipse) noexcept -> Painter&; + auto ellipse(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation) noexcept -> Painter&; + auto arc_fast(const circle_type& circle, int sample_point_from, int sample_point_to) noexcept -> Painter&; + auto arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> Painter&; + auto arc_n(const circle_type& circle, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; + auto arc(const circle_type& circle, float degree_from, float degree_to) noexcept -> Painter&; + auto arc_n(const ellipse_type& ellipse, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; + auto bezier_cubic_n(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, std::uint32_t segments) noexcept -> Painter&; + auto bezier_cubic(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; + auto bezier_quadratic_n(const point_type& p1, const point_type& p2, const point_type& p3, std::uint32_t segments) noexcept -> Painter&; + auto bezier_quadratic(const point_type& p1, const point_type& p2, const point_type& p3) noexcept -> Painter&; + + /** + * @brief Draws the fill shape according to the specified path points + * @param color Shape color + */ + auto stroke(color_type color) noexcept -> void; + + /** + * @brief Plot the corresponding lines/shapes according to the specified path points + * @param color Line/shape color + * @param thickness Line thickness + * @param close Whether the graph is closed, in other words, whether the first point should be connected to the last point + */ + auto stroke(color_type color, float thickness, bool close) noexcept -> void; + }; + + private: + memory::UniquePointer context_; + + explicit RenderList(Context& context, RenderListFlag flag) noexcept; + + public: + RenderList(const RenderList&) noexcept = delete; + RenderList(RenderList&&) noexcept; //= default; + auto operator=(const RenderList&) noexcept -> RenderList& = delete; + auto operator=(RenderList&&) noexcept -> RenderList&; // = default; + + ~RenderList() noexcept; + + auto reset() noexcept -> void; + + auto set_flag(RenderListFlag flag) noexcept -> void; + + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE + + auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; + + auto pop_scissor() noexcept -> void; + + auto push_texture(texture_id_type texture) noexcept -> void; + + auto pop_texture() noexcept -> void; + + // ---------------------------------------------------------------------------- + // PAINTER + + [[nodiscard]] auto painter(const Painter::path_list_type::size_type reserve_point = 0) noexcept -> Painter + { + return Painter{*this, reserve_point}; + } + + // ---------------------------------------------------------------------------- + // PRIMITIVE + + auto line( + const point_type& from, + const point_type& to, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color + ) noexcept -> void; + + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color + ) noexcept -> void; + + auto rect( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto circle_n( + const circle_type& circle, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle_n_filled( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle( + const circle_type& circle, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto circle( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto circle_filled( + const circle_type& circle, + color_type color + ) noexcept -> void; + + auto circle_filled( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse( + const ellipse_type& ellipse, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type& ellipse, + color_type color + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color + ) noexcept -> void; + + auto bezier_cubic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // TEXT + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + float wrap_width = 99999999.f + ) noexcept -> void; + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + GlyphFlag flag, + float wrap_width = 99999999.f + ) noexcept -> void; + + // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + float wrap_width = 99999999.f + ) noexcept -> extent_type; + + // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + GlyphFlag flag, + float wrap_width = 99999999.f + ) noexcept -> extent_type; + + // ---------------------------------------------------------------------------- + // IMAGE + + // p1________ p2 + // | | + // | | + // p4|_______| p3 + auto image( + texture_id_type texture_id, + const rect_type::point_type& display_p1, + const rect_type::point_type& display_p2, + const rect_type::point_type& display_p3, + const rect_type::point_type& display_p4, + const uv_type& uv_p1 = {0, 0}, + const uv_type& uv_p2 = {1, 0}, + const uv_type& uv_p3 = {1, 1}, + const uv_type& uv_p4 = {0, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::extent_type& display_size, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::point_type& display_right_bottom, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::NONE, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::extent_type& display_size, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::point_type& display_right_bottom, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + }; + + // ========================================================= + // RENDERER + // ========================================================= + + class Renderer + { + friend Context; + + public: + Renderer(const Renderer&) noexcept = delete; + Renderer(Renderer&&) noexcept = default; + auto operator=(const Renderer&) noexcept -> Renderer& = delete; + auto operator=(Renderer&&) noexcept -> Renderer& = default; + + virtual ~Renderer() noexcept; + + protected: + Renderer() noexcept; + + public: + /** + * @brief Constructs the renderer, which assumes that all the parameters needed for the renderer have been set + */ + [[nodiscard]] auto construct() noexcept -> bool; + + /** + * @brief Destructs the renderer + */ + auto destruct() noexcept -> void; + + /** + * @brief Whether the current renderer is ready (@c construct was called and succeeded) + */ + [[nodiscard]] auto ready() const noexcept -> bool; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + auto new_frame(Context& context) noexcept -> void; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + auto present(Context& context, const extent_type& display_size) noexcept -> void; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + auto end_frame(Context& context) noexcept -> void; + + private: + virtual auto do_construct() noexcept -> bool = 0; + virtual auto do_destruct() noexcept -> void = 0; + [[nodiscard]] virtual auto do_ready() const noexcept -> bool = 0; + + [[nodiscard]] virtual auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type = 0; + virtual auto do_texture_update(const TextureDescriptor& texture) noexcept -> void = 0; + virtual auto do_texture_destroy(texture_id_type texture_id) noexcept -> void = 0; + + virtual auto do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void = 0; + }; + + // ========================================================= + // CONTEXT + // ========================================================= + + [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context*; + auto destroy_context(Context& context) noexcept -> void; + auto destroy_context(Context* context) noexcept -> void; + + // ========================================================= + // GLYPH PARSER + // ========================================================= + + /** + * @brief Setting the glyph parser of context + * @return Previous glyph parser, or nullptr if newly set + */ + auto set_glyph_parser(Context& context, std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; + + // ========================================================= + // RENDERER + // ========================================================= + + /** + * @brief Setting the renderer of context + * @return Previous renderer, or nullptr if newly set + */ + auto set_renderer(Context& context, std::shared_ptr renderer) noexcept -> std::shared_ptr; + + // ========================================================= + // FONT + // ========================================================= + + /** + * @brief Load font from the specified path, assuming the path is a valid font file + * @note *Must* ensure that at least one font is added before calling @c Renderer::new_frame + */ + auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void; + + // ========================================================= + // RENDER LIST + // ========================================================= + + auto new_render_list(Context& context, RenderListFlag flag = RenderListFlag::DEFAULT) noexcept -> RenderList&; +} // namespace gal::prometheus::gfx + +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; +} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx_new/internal/context.cpp b/src/gfx/internal/context.cpp similarity index 72% rename from src/gfx_new/internal/context.cpp rename to src/gfx/internal/context.cpp index ed39f57..f6f7aa3 100644 --- a/src/gfx_new/internal/context.cpp +++ b/src/gfx/internal/context.cpp @@ -3,13 +3,12 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include - -#include "render_list.hpp" +#include +#include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { // ========================================================= // TEXTURE @@ -28,6 +27,65 @@ namespace gal::prometheus::gfx_new texture_atlas_list_.emplace_back(root_texture_atlas_size); } + auto TextureContext::initialize(RenderListSharedData& shared_data) noexcept -> void + { + // ======================================== + // BAKE LINES (AA) + // ======================================== + { + constexpr std::uint32_t white_color = 0xff'ff'ff'ff; + constexpr auto aa_width = static_cast(RenderListSharedData::baked_line_uv_count); + constexpr auto aa_height = static_cast(RenderListSharedData::baked_line_uv_count); + constexpr auto aa_size = Texture::size_type{aa_width, aa_height}; + + const auto texture_atlas_id = root_id(); + auto& texture = this->select(texture_atlas_id); + + // baked line rect area: + // white pixel + // ◿ + const auto borrowed_texture = texture.select(aa_size); + borrowed_texture.fill(0); + + const auto aa_point = borrowed_texture.position(); + const auto aa_uv_scale = texture.uv(); + + // white pixel + { + // LINE 0, 2 pixels + borrowed_texture.fill(0, 2, white_color); + // LINE 1, 2 pixels + borrowed_texture.fill(1, 2, white_color); + + const auto uv_x = static_cast(static_cast(aa_point.x) + 1.0f) * aa_uv_scale.width; + const auto uv_y = static_cast(static_cast(aa_point.y) + 1.0f) * aa_uv_scale.height; + + shared_data.white_pixel_uv = {uv_x, uv_y}; + } + + // ◿ + for (Texture::size_type::value_type y = 1; y < aa_height; ++y) + { + const auto line_width = y; + const auto offset = aa_width - line_width; + + borrowed_texture.fill(y, offset, line_width, white_color); + + const auto p_x = aa_point.x + offset; + const auto p_y = aa_point.y + y; + const auto width = line_width; + constexpr auto height = .5f; + + const auto uv_x = static_cast(p_x) * aa_uv_scale.width; + const auto uv_y = static_cast(p_y) * aa_uv_scale.height; + const auto uv_width = static_cast(width) * aa_uv_scale.width; + const auto uv_height = static_cast(height) * aa_uv_scale.height; + + shared_data.baked_line_uvs[y] = {uv_x, uv_y, uv_width, uv_height}; + } + } + } + auto TextureContext::root() noexcept -> Texture& { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); @@ -88,19 +146,19 @@ namespace gal::prometheus::gfx_new auto TextureContext::upload(const Context& context) noexcept -> void { - auto renderer = context.get_renderer(); + auto renderer_accessor = context.renderer_accessor(); std::ranges::for_each( texture_atlas_list_, - [&renderer](auto& texture) mutable noexcept -> void + [&renderer_accessor](auto& texture) mutable noexcept -> void { if (not texture.uploaded()) { - renderer.upload(texture); + renderer_accessor.upload(texture); } else { - renderer.update_if_dirty(texture); + renderer_accessor.update_if_dirty(texture); } } ); @@ -110,9 +168,9 @@ namespace gal::prometheus::gfx_new // FONT // ========================================================= - auto FontContext::set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> void + auto FontContext::set_glyph_parser(GlyphParser& glyph_parser) noexcept -> void { - glyph_parser_ = std::move(glyph_parser); + glyph_parser_ = std::addressof(glyph_parser); } auto FontContext::set_fallback_glyph() noexcept -> void @@ -346,59 +404,83 @@ namespace gal::prometheus::gfx_new } } - Context::Context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept + Context::TextureAccessor::TextureAccessor(TextureContext& texture_context) noexcept + : texture_context_{texture_context} {} + + auto Context::TextureAccessor::texture_context() const noexcept -> const TextureContext& + { + static_assert(std::is_const_v::type>); + + return texture_context_; + } + + Context::FontAccessor::FontAccessor(FontContext& font_context) noexcept + : font_context_{font_context} {} + + auto Context::FontAccessor::font_context() noexcept -> FontContext& + { + return font_context_; + } + + auto Context::FontAccessor::font_context() const noexcept -> const FontContext& + { + return font_context_; + } + + Context::RenderListAccessor::RenderListAccessor(RenderListSharedData& render_list_shared_data) noexcept + : render_list_shared_data_{render_list_shared_data} {} + + auto Context::RenderListAccessor::shared_data() const noexcept -> const RenderListSharedData& + { + return render_list_shared_data_; + } + + Context::Context() noexcept : glyph_parser_{nullptr}, renderer_{nullptr}, texture_context_{}, font_context_{} { - set_glyph_parser(std::move(glyph_parser)); - set_renderer(std::move(renderer)); - } - - auto Context::get_glyph_parser() const noexcept -> std::shared_ptr - { - return glyph_parser_; + texture_context_.initialize(render_list_shared_data_); } auto Context::set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> std::shared_ptr { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr, "GlyphParser must not be null!"); + auto old = std::exchange(glyph_parser_, glyph_parser); - font_context_.set_glyph_parser(glyph_parser_); + font_context_.set_glyph_parser(*glyph_parser_); return old; } - // auto Context::get_renderer() noexcept -> std::shared_ptr - // { - // return renderer_; - // } - - auto Context::get_renderer() const noexcept -> RendererAccessor + auto Context::set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer_ != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer != nullptr, "Renderer must not be null!"); - return RendererAccessor{*renderer_}; + return std::exchange(renderer_, renderer); } - auto Context::set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr + auto Context::renderer_accessor() const noexcept -> RendererAccessor { - return std::exchange(renderer_, renderer); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer_ != nullptr); + + return RendererAccessor{*renderer_}; } - auto Context::get_texture_context() const noexcept -> const TextureContext& + auto Context::texture_accessor() noexcept -> TextureAccessor { - return texture_context_; + return TextureAccessor{texture_context_}; } - auto Context::get_font_context() noexcept -> FontContext& + auto Context::font_accessor() noexcept -> FontAccessor { - return font_context_; + return FontAccessor{font_context_}; } - auto Context::get_render_list_shared_data() const noexcept -> const RenderListSharedData& + auto Context::render_list_accessor() noexcept -> RenderListAccessor { - return render_list_shared_data_; + return RenderListAccessor{render_list_shared_data_}; } auto Context::new_render_list(const RenderListFlag flag) noexcept -> RenderList& @@ -428,7 +510,10 @@ namespace gal::prometheus::gfx_new [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context* { - auto* context = new Context{std::move(glyph_parser), std::move(renderer)}; + auto* context = new Context{}; + context->set_glyph_parser(std::move(glyph_parser)); + context->set_renderer(std::move(renderer)); + return context; } @@ -455,7 +540,9 @@ namespace gal::prometheus::gfx_new auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void { - context.get_font_context().add_font(path); + auto font_accessor = context.font_accessor(); + + font_accessor.font_context().add_font(path); } auto new_render_list(Context& context, const RenderListFlag flag) noexcept -> RenderList& @@ -497,7 +584,7 @@ namespace gal::prometheus::gfx_new } } - auto Renderer::present(Context& context) noexcept -> void + auto Renderer::present(Context& context, const extent_type& display_size) noexcept -> void { // glyphs { @@ -505,7 +592,7 @@ namespace gal::prometheus::gfx_new context.font_context_.upload_all_glyph(context.texture_context_); } - do_present(context.render_data()); + do_present(context.render_data(), display_size); } auto Renderer::end_frame(Context& context) noexcept -> void diff --git a/src/gfx_new/internal/context.hpp b/src/gfx/internal/context.hpp similarity index 77% rename from src/gfx_new/internal/context.hpp rename to src/gfx/internal/context.hpp index 2ed8670..fbe3f6d 100644 --- a/src/gfx_new/internal/context.hpp +++ b/src/gfx/internal/context.hpp @@ -5,13 +5,13 @@ #pragma once -#include -#include -#include +#include +#include +#include #include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { // ========================================================= // TEXTURE @@ -37,6 +37,8 @@ namespace gal::prometheus::gfx_new TextureContext() noexcept; + auto initialize(RenderListSharedData& shared_data) noexcept -> void; + /** * @brief Get root (default) texture */ @@ -110,7 +112,7 @@ namespace gal::prometheus::gfx_new glyph_upload_queue_list_type glyph_upload_queue_list_; - std::shared_ptr glyph_parser_; + GlyphParser* glyph_parser_; const GlyphInfo* fallback_glyph_; public: @@ -123,7 +125,7 @@ namespace gal::prometheus::gfx_new FontContext() noexcept = default; - auto set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> void; + auto set_glyph_parser(GlyphParser& glyph_parser) noexcept -> void; auto set_fallback_glyph() noexcept -> void; @@ -174,6 +176,9 @@ namespace gal::prometheus::gfx_new public: class RendererAccessor final { + // TextureContext::upload + friend TextureContext; + public: using renderer_type = memory::RefWrapper; @@ -183,6 +188,7 @@ namespace gal::prometheus::gfx_new public: explicit RendererAccessor(Renderer& renderer) noexcept; + private: /** * @brief Upload texture atlas data to GPU and get GPU resource handle */ @@ -201,6 +207,62 @@ namespace gal::prometheus::gfx_new auto update_if_dirty(Texture& texture) noexcept -> void; }; + class TextureAccessor final + { + // RenderList::RenderListContext::default_texture + friend RenderList::RenderListContext; + // RenderList::text (select texture atlas for text rendering) + friend RenderList; + + public: + + private: + memory::RefWrapper texture_context_; + + public: + explicit TextureAccessor(TextureContext& texture_context) noexcept; + + private: + [[nodiscard]] auto texture_context() const noexcept -> const TextureContext&; + }; + + class FontAccessor final + { + // RenderList::text + // RenderList::text_size + friend RenderList; + friend auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void; + + public: + + private: + memory::RefWrapper font_context_; + + public: + explicit FontAccessor(FontContext& font_context) noexcept; + + private: + [[nodiscard]] auto font_context() noexcept -> FontContext&; + [[nodiscard]] auto font_context() const noexcept -> const FontContext&; + }; + + class RenderListAccessor final + { + // RenderList::RenderListContext::shared_data + friend RenderList::RenderListContext; + + public: + + private: + memory::RefWrapper render_list_shared_data_; + + public: + explicit RenderListAccessor(RenderListSharedData& render_list_shared_data) noexcept; + + private: + [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData&; + }; + private: std::shared_ptr glyph_parser_; std::shared_ptr renderer_; @@ -212,25 +274,19 @@ namespace gal::prometheus::gfx_new std::vector render_lists_; public: - Context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept; - - [[nodiscard]] auto get_glyph_parser() const noexcept -> std::shared_ptr; + Context() noexcept; auto set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; - - // [[nodiscard]] auto get_renderer() noexcept -> std::shared_ptr; - [[nodiscard]] auto get_renderer() const noexcept -> RendererAccessor; - auto set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr; - [[nodiscard]] auto get_texture_context() const noexcept -> const TextureContext&; - - [[nodiscard]] auto get_font_context() noexcept -> FontContext&; - - [[nodiscard]] auto get_render_list_shared_data() const noexcept -> const RenderListSharedData&; + // todo: set RenderListSharedData - auto new_render_list(RenderListFlag flag) noexcept -> RenderList&; + [[nodiscard]] auto renderer_accessor() const noexcept -> RendererAccessor; + [[nodiscard]] auto texture_accessor() noexcept -> TextureAccessor; + [[nodiscard]] auto font_accessor() noexcept -> FontAccessor; + [[nodiscard]] auto render_list_accessor() noexcept -> RenderListAccessor; + [[nodiscard]] auto new_render_list(RenderListFlag flag) noexcept -> RenderList&; [[nodiscard]] auto render_data() const noexcept -> render_data_list_type; }; } diff --git a/src/gfx_new/internal/font.cpp b/src/gfx/internal/font.cpp similarity index 94% rename from src/gfx_new/internal/font.cpp rename to src/gfx/internal/font.cpp index 9aa84c6..7426980 100644 --- a/src/gfx_new/internal/font.cpp +++ b/src/gfx/internal/font.cpp @@ -3,13 +3,13 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include +#include -#include -#include +#include +#include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { auto GlyphUploadQueue::push(GlyphInfo& info, GlyphParser::GlyphDescriptor&& descriptor) noexcept -> void { diff --git a/src/gfx_new/internal/font.hpp b/src/gfx/internal/font.hpp similarity index 98% rename from src/gfx_new/internal/font.hpp rename to src/gfx/internal/font.hpp index d831404..36ab643 100644 --- a/src/gfx_new/internal/font.hpp +++ b/src/gfx/internal/font.hpp @@ -8,12 +8,12 @@ #include #include -#include +#include #include #include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { // index using texture_atlas_id_type = std::uint32_t; diff --git a/src/gfx_new/internal/render_list.cpp b/src/gfx/internal/render_list.cpp similarity index 97% rename from src/gfx_new/internal/render_list.cpp rename to src/gfx/internal/render_list.cpp index 17dda63..891dd72 100644 --- a/src/gfx_new/internal/render_list.cpp +++ b/src/gfx/internal/render_list.cpp @@ -3,9 +3,9 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include +#include -#include +#include #include #include @@ -15,7 +15,7 @@ namespace { using namespace gal::prometheus; - using namespace gfx_new; + using namespace gfx; // @see https://stackoverflow.com/a/2244088/15194693 // Number of segments (N) is calculated using equation: @@ -391,7 +391,7 @@ namespace // }; } -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { RenderListSharedData::RenderListSharedData() noexcept : circle_segment_counts{}, @@ -531,12 +531,16 @@ namespace gal::prometheus::gfx_new auto RenderList::RenderListContext::shared_data() const noexcept -> const RenderListSharedData& { - return context.get().get_render_list_shared_data(); + const auto render_list_accessor = context.get().render_list_accessor(); + + return render_list_accessor.shared_data(); } auto RenderList::RenderListContext::default_texture() const noexcept -> texture_id_type { - return context.get().get_texture_context().root().id(); + const auto texture_accessor = context.get().texture_accessor(); + + return texture_accessor.texture_context().root().id(); } auto RenderList::RenderListContext::reset() noexcept -> void @@ -1655,8 +1659,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::rect( - const point_type& left_top, - const point_type& right_bottom, + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, const color_type color, const float rounding, const RenderRectFlag flag, @@ -1700,8 +1704,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::rect_filled( - const point_type& left_top, - const point_type& right_bottom, + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, const color_type color, const float rounding, const RenderRectFlag flag @@ -1757,8 +1761,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::rect_filled( - const point_type& left_top, - const point_type& right_bottom, + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, const color_type color_left_top, const color_type color_right_top, const color_type color_left_bottom, @@ -1784,8 +1788,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::circle_n( - const point_type& center, - const float radius, + const circle_type::point_type& center, + const circle_type::radius_value_type radius, const color_type color, const std::uint32_t segments, const float thickness @@ -1809,8 +1813,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::circle_n_filled( - const point_type& center, - const float radius, + const circle_type::point_type& center, + const circle_type::radius_value_type radius, const color_type color, const std::uint32_t segments ) noexcept -> void @@ -1833,8 +1837,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::circle( - const point_type& center, - const float radius, + const circle_type::point_type& center, + const circle_type::radius_value_type radius, const color_type color, const float thickness ) noexcept -> void @@ -1856,8 +1860,8 @@ namespace gal::prometheus::gfx_new } auto RenderList::circle_filled( - const point_type& center, - const float radius, + const circle_type::point_type& center, + const circle_type::radius_value_type radius, const color_type color ) noexcept -> void { @@ -1880,9 +1884,9 @@ namespace gal::prometheus::gfx_new } void RenderList::ellipse_n( - const point_type& center, - const extent_type& radius, - const float rotation, + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + const ellipse_type::rotation_value_type rotation, const color_type color, const std::uint32_t segments, const float thickness @@ -1906,9 +1910,9 @@ namespace gal::prometheus::gfx_new } auto RenderList::ellipse_n_filled( - const point_type& center, - const extent_type& radius, - const float rotation, + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + const ellipse_type::rotation_value_type rotation, const color_type color, const std::uint32_t segments ) noexcept -> void @@ -1931,9 +1935,9 @@ namespace gal::prometheus::gfx_new } auto RenderList::ellipse( - const point_type& center, - const extent_type& radius, - const float rotation, + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + const ellipse_type::rotation_value_type rotation, const color_type color, const float thickness ) noexcept -> void @@ -1955,9 +1959,9 @@ namespace gal::prometheus::gfx_new } auto RenderList::ellipse_filled( - const point_type& center, - const extent_type& radius, - const float rotation, + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + const ellipse_type::rotation_value_type rotation, const color_type color ) noexcept -> void { @@ -2071,11 +2075,15 @@ namespace gal::prometheus::gfx_new auto& render_list_context = *context_; auto& context = render_list_context.context.get(); - auto& font_context = context.get_font_context(); - const auto& texture_context = context.get_texture_context(); + auto font_accessor = context.font_accessor(); + auto& font_context = font_accessor.font_context(); + const auto texture_accessor = context.texture_accessor(); + const auto& texture_context = texture_accessor.texture_context(); const auto utf32_text = chars::convert(utf8_text); - const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + // todo: Don't let the fallback character replace line breaks! + // const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + const auto glyphs = font_context.glyph_of(utf32_text, font_size, flag); if (std::ranges::any_of( glyphs, @@ -2194,7 +2202,8 @@ namespace gal::prometheus::gfx_new { const auto& render_list_context = *context_; auto& context = render_list_context.context.get(); - auto& font_context = context.get_font_context(); + auto font_accessor = context.font_accessor(); + auto& font_context = font_accessor.font_context(); const auto utf32_text = chars::convert(utf8_text); const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); @@ -2250,10 +2259,10 @@ namespace gal::prometheus::gfx_new auto RenderList::image( const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, + const rect_type::point_type& display_p1, + const rect_type::point_type& display_p2, + const rect_type::point_type& display_p3, + const rect_type::point_type& display_p4, const uv_type& uv_p1, const uv_type& uv_p2, const uv_type& uv_p3, @@ -2321,8 +2330,20 @@ namespace gal::prometheus::gfx_new auto RenderList::image( const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, + const rect_type::point_type& display_left_top, + const rect_type::extent_type& display_size, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + this->image(texture_id, {display_left_top, display_size}, {uv_left_top, uv_right_bottom}, color); + } + + auto RenderList::image( + const texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::point_type& display_right_bottom, const uv_type& uv_left_top, const uv_type& uv_right_bottom, const color_type color @@ -2362,18 +2383,7 @@ namespace gal::prometheus::gfx_new if (rounding < .5f or (RenderRectFlag::ROUND_CORNER_MASK & flag) == RenderRectFlag::ROUND_CORNER_NONE) { - this->image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); + this->image(texture_id, display_rect, uv_rect, color); } else { @@ -2439,8 +2449,22 @@ namespace gal::prometheus::gfx_new auto RenderList::image_rounded( const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, + const rect_type::point_type& display_left_top, + const rect_type::extent_type& display_size, + const float rounding, + const RenderRectFlag flag, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type color + ) noexcept -> void + { + this->image_rounded(texture_id, {display_left_top, display_size}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + } + + auto RenderList::image_rounded( + const texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::point_type& display_right_bottom, const float rounding, const RenderRectFlag flag, const uv_type& uv_left_top, diff --git a/src/gfx_new/internal/render_list.hpp b/src/gfx/internal/render_list.hpp similarity index 97% rename from src/gfx_new/internal/render_list.hpp rename to src/gfx/internal/render_list.hpp index db57504..989af51 100644 --- a/src/gfx_new/internal/render_list.hpp +++ b/src/gfx/internal/render_list.hpp @@ -5,9 +5,9 @@ #pragma once -#include +#include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { class RenderList::RenderListContext final { diff --git a/src/gfx_new/internal/texture.cpp b/src/gfx/internal/texture.cpp similarity index 98% rename from src/gfx_new/internal/texture.cpp rename to src/gfx/internal/texture.cpp index 7c77cfc..5137f91 100644 --- a/src/gfx_new/internal/texture.cpp +++ b/src/gfx/internal/texture.cpp @@ -3,12 +3,12 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include +#include // #define STB_RECT_PACK_IMPLEMENTATION // #include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { Texture::Texture(const size_type size) noexcept : rp_context_{}, diff --git a/src/gfx_new/internal/texture.hpp b/src/gfx/internal/texture.hpp similarity index 98% rename from src/gfx_new/internal/texture.hpp rename to src/gfx/internal/texture.hpp index 34d87ef..7a5325d 100644 --- a/src/gfx_new/internal/texture.hpp +++ b/src/gfx/internal/texture.hpp @@ -7,11 +7,11 @@ #include -#include +#include #include -namespace gal::prometheus::gfx_new +namespace gal::prometheus::gfx { class BorrowedTexture; diff --git a/src/gfx/context.cpp b/src/gfx/old/context.cpp similarity index 100% rename from src/gfx/context.cpp rename to src/gfx/old/context.cpp diff --git a/src/gfx/context.hpp b/src/gfx/old/context.hpp similarity index 98% rename from src/gfx/context.hpp rename to src/gfx/old/context.hpp index 1ce5e35..a30543c 100644 --- a/src/gfx/context.hpp +++ b/src/gfx/old/context.hpp @@ -215,7 +215,14 @@ namespace gal::prometheus::gfx ~RenderContext() noexcept; RenderContext() noexcept; + + // ==================================================================== + // RenderContext + // ==================================================================== + /** + * @brief Initialize RenderContext, called only once + */ auto initialize() noexcept -> void; auto begin_frame(Renderer& renderer) noexcept -> void; diff --git a/src/gfx/font.cpp b/src/gfx/old/font.cpp similarity index 100% rename from src/gfx/font.cpp rename to src/gfx/old/font.cpp diff --git a/src/gfx/font.hpp b/src/gfx/old/font.hpp similarity index 100% rename from src/gfx/font.hpp rename to src/gfx/old/font.hpp diff --git a/src/gfx/old/gfx.hpp b/src/gfx/old/gfx.hpp new file mode 100644 index 0000000..27860b6 --- /dev/null +++ b/src/gfx/old/gfx.hpp @@ -0,0 +1,20 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include diff --git a/src/gfx/glyph.cpp b/src/gfx/old/glyph.cpp similarity index 100% rename from src/gfx/glyph.cpp rename to src/gfx/old/glyph.cpp diff --git a/src/gfx/glyph.hpp b/src/gfx/old/glyph.hpp similarity index 100% rename from src/gfx/glyph.hpp rename to src/gfx/old/glyph.hpp diff --git a/src/gfx/glyph_parser_freetype.cpp b/src/gfx/old/glyph_parser_freetype.cpp similarity index 99% rename from src/gfx/glyph_parser_freetype.cpp rename to src/gfx/old/glyph_parser_freetype.cpp index 1a4d56a..ee30e5a 100644 --- a/src/gfx/glyph_parser_freetype.cpp +++ b/src/gfx/old/glyph_parser_freetype.cpp @@ -195,7 +195,7 @@ namespace gal::prometheus::gfx return invalid_result; } - const auto& bitmap = info.face->glyph->bitmap; + const auto& bitmap = face->glyph->bitmap; const point_type point{static_cast(slot->bitmap_left), static_cast(slot->bitmap_top)}; const extent_type size{static_cast(bitmap.width), static_cast(bitmap.rows)}; diff --git a/src/gfx/glyph_parser_freetype.hpp b/src/gfx/old/glyph_parser_freetype.hpp similarity index 100% rename from src/gfx/glyph_parser_freetype.hpp rename to src/gfx/old/glyph_parser_freetype.hpp diff --git a/src/gfx/render_list.cpp b/src/gfx/old/render_list.cpp similarity index 100% rename from src/gfx/render_list.cpp rename to src/gfx/old/render_list.cpp diff --git a/src/gfx/render_list.hpp b/src/gfx/old/render_list.hpp similarity index 100% rename from src/gfx/render_list.hpp rename to src/gfx/old/render_list.hpp diff --git a/src/gfx/renderer.cpp b/src/gfx/old/renderer.cpp similarity index 100% rename from src/gfx/renderer.cpp rename to src/gfx/old/renderer.cpp diff --git a/src/gfx/renderer.hpp b/src/gfx/old/renderer.hpp similarity index 100% rename from src/gfx/renderer.hpp rename to src/gfx/old/renderer.hpp diff --git a/src/gfx/renderer_dx11.cpp b/src/gfx/old/renderer_dx11.cpp similarity index 99% rename from src/gfx/renderer_dx11.cpp rename to src/gfx/old/renderer_dx11.cpp index 59b8bec..776f331 100644 --- a/src/gfx/renderer_dx11.cpp +++ b/src/gfx/old/renderer_dx11.cpp @@ -19,6 +19,7 @@ #include #include #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE #include #include @@ -699,10 +700,10 @@ namespace gal::prometheus::gfx std::ranges::for_each( all_render_data, - [&](const RenderData& draw_data) noexcept -> void + [&](const RenderData& render_data) noexcept -> void { - const auto vertex_list = draw_data.vertex_list.get(); - const auto index_list = draw_data.index_list.get(); + const auto vertex_list = render_data.vertex_list.get(); + const auto index_list = render_data.index_list.get(); // std::ranges::transform( // vertex_list, diff --git a/src/gfx/renderer_dx11.hpp b/src/gfx/old/renderer_dx11.hpp similarity index 100% rename from src/gfx/renderer_dx11.hpp rename to src/gfx/old/renderer_dx11.hpp diff --git a/src/gfx/renderer_dx12.cpp b/src/gfx/old/renderer_dx12.cpp similarity index 100% rename from src/gfx/renderer_dx12.cpp rename to src/gfx/old/renderer_dx12.cpp diff --git a/src/gfx/renderer_dx12.hpp b/src/gfx/old/renderer_dx12.hpp similarity index 100% rename from src/gfx/renderer_dx12.hpp rename to src/gfx/old/renderer_dx12.hpp diff --git a/src/gfx/texture.cpp b/src/gfx/old/texture.cpp similarity index 100% rename from src/gfx/texture.cpp rename to src/gfx/old/texture.cpp diff --git a/src/gfx/texture.hpp b/src/gfx/old/texture.hpp similarity index 100% rename from src/gfx/texture.hpp rename to src/gfx/old/texture.hpp diff --git a/src/gfx/type.hpp b/src/gfx/old/type.hpp similarity index 100% rename from src/gfx/type.hpp rename to src/gfx/old/type.hpp diff --git a/src/gfx_new/gfx.hpp b/src/gfx_new/gfx.hpp deleted file mode 100644 index 584487c..0000000 --- a/src/gfx_new/gfx.hpp +++ /dev/null @@ -1,975 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace gal::prometheus::gfx_new -{ - // ========================================================= - // CONTEXT - // ========================================================= - - class Context; - - // ========================================================= - // PRIMITIVE - // ========================================================= - - using point_type = primitive::basic_point_2d; - using uv_type = primitive::basic_point_2d; - using color_type = primitive::basic_color; - using vertex_type = primitive::basic_vertex; - using index_type = std::uint16_t; - - using extent_type = primitive::basic_extent_2d; - using rect_type = primitive::basic_rect_2d; - using circle_type = primitive::basic_circle_2d; - using ellipse_type = primitive::basic_ellipse_2d; - - // ========================================================= - // TEXTURE - // ========================================================= - - // D3D11: ID3D11ShaderResourceView - // D3D12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + constant offset - using texture_id_type = std::uintptr_t; - constexpr texture_id_type invalid_texture_id{0}; - - class TextureDescriptor final - { - public: - // 32-bits (RGBA) - using element_type = std::uint32_t; - // size.width * size.height - using data_type = std::unique_ptr; - using data_view_type = std::span; - - using size_type = primitive::basic_extent_2d; - - // ============================== - // CPU side - // ============================== - - data_type data; - size_type size; - - static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); - std::uint64_t dirty : 1; - - // ============================== - // GPU side - // ============================== - - std::uint64_t id : 63; - }; - - // ========================================================= - // GLYPH / FONT - // ========================================================= - - // index - using font_id_type = std::uint32_t; - constexpr font_id_type invalid_font_id{std::numeric_limits::max()}; - - enum class GlyphFlag : std::uint8_t - { - NONE = 0, - BOLD = 1 << 0, - ITALIC = 1 << 1, - }; - - class GlyphParser - { - public: - class [[nodiscard]] FontDescriptor final - { - public: - using element_type = std::uint8_t; - using data_type = std::unique_ptr; - using data_view_type = std::span; - - using size_type = std::uint32_t; - - std::string_view identifier; - font_id_type id; - - [[nodiscard]] constexpr static auto error() noexcept -> FontDescriptor - { - return {.identifier = {}, .id = invalid_font_id}; - } - - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return id != invalid_font_id; - } - - [[nodiscard]] constexpr auto valid() const noexcept -> bool - { - return operator bool(); - } - }; - - class GlyphCode final - { - public: - std::uint32_t codepoint; - std::uint32_t size; - GlyphFlag flag; - - constexpr GlyphCode(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept - : codepoint{codepoint}, - size{size}, - flag{flag} {} - }; - - class [[nodiscard]] GlyphDescriptor final - { - public: - // The offset of the bitmap may be negative (signed) - using rect_type = primitive::basic_rect_2d; - - // Bitmap infos of this glyph - rect_type rect; - float advance_x; - bool visible; - bool colored; - - // FIXME-OPT: - // We could have pre-specified an area to write glyph data to, - // but that would have meant exposing the texture implementation details to the outside world, - // and its implementation will be coupled with the GlyphParser, so we will leave the choice to the GlyphParser, - // which can find a way to reduce the number of dynamic allocations in the internal. - // note: - // We always assume that the data length of @c data is not less than @c rect.size.width * @c rect.size.height - TextureDescriptor::data_type data; - - [[nodiscard]] constexpr static auto error() noexcept -> GlyphDescriptor - { - return {.rect = {-0xbad, -0xbad, 0xc0ffee, 0xc0ffee}, .advance_x = -1, .visible = false, .colored = false, .data = nullptr}; - } - - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return data != nullptr; - } - - [[nodiscard]] constexpr auto valid() const noexcept -> bool - { - return operator bool(); - } - }; - - GlyphParser(const GlyphParser&) noexcept = delete; - GlyphParser(GlyphParser&&) noexcept = default; - auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; - auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; - - virtual ~GlyphParser() noexcept; - - GlyphParser() noexcept = default; - - /** - * @brief Load font data from @c path, return id of font - * @param path Font path - * @return id of the font, or invalid_font_id if failed to load - */ - [[nodiscard]] virtual auto load(const std::filesystem::path& path) noexcept -> FontDescriptor = 0; - - /** - * @brief Determines whether the target font contains the glyphs of the specified codepoint - * @param id The id returned by loading the font from the previous load - * @param codepoint The codepoint of the glyph to be checked - * @return Exists or not - */ - [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; - - /** - * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer - * @param id The id returned by loading the font from the previous load - * @param code {codepoint, size, flag} - * @return The glyph information of the specified size (and style) of the target codepoint - */ - [[nodiscard]] virtual auto parse(font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor = 0; - }; - - // ========================================================= - // RENDERER LIST - // ========================================================= - - enum class RenderListFlag : std::uint8_t - { - NONE = 0, - ANTI_ALIASED_LINE = 1 << 0, - ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, - ANTI_ALIASED_FILL = 1 << 2, - - DEFAULT = ANTI_ALIASED_LINE | ANTI_ALIASED_LINE_USE_TEXTURE | ANTI_ALIASED_FILL, - }; - - enum class RenderRectFlag : std::uint8_t - { - NONE = 0, - // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_LEFT_TOP = 1 << 1, - // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_RIGHT_TOP = 1 << 2, - // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_LEFT_BOTTOM = 1 << 3, - // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, - // disable rounding on all corners (when rounding > 0.0f) - ROUND_CORNER_NONE = 1 << 5, - - ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, - ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, - ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - - ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, - ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, - }; - - enum class RenderArcFlag : std::uint8_t - { - // [0~3) - Q1 = 1 << 0, - // [3~6) - Q2 = 1 << 1, - // [6~9) - Q3 = 1 << 2, - // [9~12) - Q4 = 1 << 3, - - RIGHT_TOP = Q1, - LEFT_TOP = Q2, - LEFT_BOTTOM = Q3, - RIGHT_BOTTOM = Q4, - TOP = Q1 | Q2, - BOTTOM = Q3 | Q4, - LEFT = Q2 | Q3, - RIGHT = Q1 | Q4, - ALL = Q1 | Q2 | Q3 | Q4, - - // [3, 0) - Q1_CLOCK_WISH = 1 << 4, - // [6, 3) - Q2_CLOCK_WISH = 1 << 5, - // [9, 6) - Q3_CLOCK_WISH = 1 << 6, - // [12, 9) - Q4_CLOCK_WISH = 1 << 7, - - RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, - LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, - LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, - RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, - TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, - BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, - LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, - RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, - ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, - }; - - class RenderListSharedData - { - public: - using circle_segment_count_type = std::uint8_t; - constexpr static std::size_t circle_segment_counts_count = 64; - using circle_segment_counts_type = std::array; - - constexpr static std::uint32_t circle_segments_min = 4; - constexpr static std::uint32_t circle_segments_max = 512; - - constexpr static std::size_t vertex_sample_points_count = 48; - using vertex_sample_points_type = std::array; - - constexpr static std::size_t baked_line_uv_count = 64; - using baked_line_uvs_type = std::array; - - circle_segment_counts_type circle_segment_counts; - - vertex_sample_points_type vertex_sample_points; - - baked_line_uvs_type baked_line_uvs; - point_type white_pixel_uv; - - // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. - // Decrease for higher quality but more geometry. - float circle_segment_max_error; - // Cutoff radius after which arc drawing will fall back to slower `path_arc` - float arc_fast_radius_cutoff; - // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. - // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float curve_tessellation_tolerance; - - // -------------------------------------------------- - - RenderListSharedData() noexcept; - - // -------------------------------------------------- - - [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; - - [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; - - // -------------------------------------------------- - - auto set_circle_tessellation_max_error(float max_error) noexcept -> void; - - auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; - }; - - class RenderData final - { - public: - using size_type = std::uint32_t; - - struct [[nodiscard]] command_type - { - rect_type scissor; - texture_id_type texture; - - // ======================= - - // set by RenderList::index_list.size() - // start offset in @c RenderList::index_list - size_type index_offset; - // set by RenderList::draw_xxx - // number of indices (multiple of 3) to be rendered as triangles - size_type element_count; - }; - - using vertex_list_type = std::vector; - using index_list_type = std::vector; - using command_list_type = std::vector; - - std::reference_wrapper vertex_list; - std::reference_wrapper index_list; - std::reference_wrapper command_list; - }; - - using render_data_list_type = std::vector; - - class RenderList - { - friend class Context; - friend class Renderer; - - public: - class RenderListContext; - - class Painter final - { - public: - using path_list_type = std::vector; - - private: - memory::RefWrapper render_list_; - path_list_type path_list_; - - auto draw_polygon_line(color_type color, float thickness, bool close) noexcept -> void; - auto draw_polygon_line_aa(color_type color, float thickness, bool close) noexcept -> void; - auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; - auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; - - public: - Painter(const Painter&) noexcept = delete; - Painter(Painter&&) noexcept = default; - auto operator=(const Painter&) noexcept -> Painter& = delete; - auto operator=(Painter&&) noexcept -> Painter& = default; - - ~Painter() noexcept; - - explicit Painter(RenderList& render_list, std::size_t reserve_point) noexcept; - - auto clear() noexcept -> Painter&; - auto reserve_extra(std::size_t size) noexcept -> Painter&; - auto reserve(std::size_t size) noexcept -> Painter&; - - auto pin(const point_type& point) noexcept -> Painter&; - - auto line(const point_type& from, const point_type& to) noexcept -> Painter&; - auto triangle(const point_type& a, const point_type& b, const point_type& c) noexcept -> Painter&; - auto quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; - auto rect(const rect_type& rect, float rounding, RenderRectFlag flag) noexcept -> Painter&; - auto rect(const rect_type::point_type& left_top, const rect_type::extent_type& extent, float rounding, RenderRectFlag flag) noexcept -> Painter&; - auto rect(const rect_type::point_type& left_top, const rect_type::point_type& right_bottom, float rounding, RenderRectFlag flag) noexcept -> Painter&; - auto circle_n(const circle_type& circle, std::uint32_t segments) noexcept -> Painter&; - auto circle_n(const circle_type::point_type& center, circle_type::radius_value_type radius, std::uint32_t segments) noexcept -> Painter&; - auto circle(const circle_type& circle) noexcept -> Painter&; - auto circle(const circle_type::point_type& center, circle_type::radius_value_type radius) noexcept -> Painter&; - auto ellipse_n(const ellipse_type& ellipse, std::uint32_t segments) noexcept -> Painter&; - auto ellipse_n(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation, std::uint32_t segments) noexcept -> Painter&; - auto ellipse(const ellipse_type& ellipse) noexcept -> Painter&; - auto ellipse(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation) noexcept -> Painter&; - auto arc_fast(const circle_type& circle, int sample_point_from, int sample_point_to) noexcept -> Painter&; - auto arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> Painter&; - auto arc_n(const circle_type& circle, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; - auto arc(const circle_type& circle, float degree_from, float degree_to) noexcept -> Painter&; - auto arc_n(const ellipse_type& ellipse, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; - auto bezier_cubic_n(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, std::uint32_t segments) noexcept -> Painter&; - auto bezier_cubic(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; - auto bezier_quadratic_n(const point_type& p1, const point_type& p2, const point_type& p3, std::uint32_t segments) noexcept -> Painter&; - auto bezier_quadratic(const point_type& p1, const point_type& p2, const point_type& p3) noexcept -> Painter&; - - /** - * @brief Draws the fill shape according to the specified path points - * @param color Shape color - */ - auto stroke(color_type color) noexcept -> void; - - /** - * @brief Plot the corresponding lines/shapes according to the specified path points - * @param color Line/shape color - * @param thickness Line thickness - * @param close Whether the graph is closed, in other words, whether the first point should be connected to the last point - */ - auto stroke(color_type color, float thickness, bool close) noexcept -> void; - }; - - private: - memory::UniquePointer context_; - - explicit RenderList(Context& context, RenderListFlag flag) noexcept; - - public: - RenderList(const RenderList&) noexcept = delete; - RenderList(RenderList&&) noexcept; //= default; - auto operator=(const RenderList&) noexcept -> RenderList& = delete; - auto operator=(RenderList&&) noexcept -> RenderList&; // = default; - - ~RenderList() noexcept; - - auto reset() noexcept -> void; - - auto set_flag(RenderListFlag flag) noexcept -> void; - - // ---------------------------------------------------------------------------- - // SCISSOR & TEXTURE - - auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; - - auto pop_scissor() noexcept -> void; - - auto push_texture(texture_id_type texture) noexcept -> void; - - auto pop_texture() noexcept -> void; - - // ---------------------------------------------------------------------------- - // PAINTER - - [[nodiscard]] auto painter(const Painter::path_list_type::size_type reserve_point = 0) noexcept -> Painter - { - return Painter{*this, reserve_point}; - } - - // ---------------------------------------------------------------------------- - // PRIMITIVE - - auto line( - const point_type& from, - const point_type& to, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto triangle( - const point_type& a, - const point_type& b, - const point_type& c, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto triangle_filled( - const point_type& a, - const point_type& b, - const point_type& c, - color_type color - ) noexcept -> void; - - auto quadrilateral( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto quadrilateral_filled( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color - ) noexcept -> void; - - auto rect( - const rect_type& rect, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, - float thickness = 1.f - ) noexcept -> void; - - auto rect( - const point_type& left_top, - const rect_type::extent_type& extent, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, - float thickness = 1.f - ) noexcept -> void; - - auto rect( - const rect_type::point_type& left_top, - const rect_type::point_type& right_bottom, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, - float thickness = 1.f - ) noexcept -> void; - - auto rect_filled( - const rect_type& rect, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const rect_type::extent_type& extent, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const point_type& right_bottom, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL - ) noexcept -> void; - - auto rect_filled( - const rect_type& rect, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const rect_type::extent_type& extent, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const point_type& right_bottom, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - - auto circle_n( - const circle_type& circle, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto circle_n( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto circle_n_filled( - const circle_type& circle, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto circle_n_filled( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto circle( - const circle_type& circle, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto circle( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto circle_filled( - const circle_type& circle, - color_type color - ) noexcept -> void; - - auto circle_filled( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color - ) noexcept -> void; - - auto ellipse_n( - const ellipse_type& ellipse, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n_filled( - const ellipse_type& ellipse, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto ellipse_n_filled( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto ellipse( - const ellipse_type& ellipse, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_filled( - const ellipse_type& ellipse, - color_type color - ) noexcept -> void; - - auto ellipse_filled( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color - ) noexcept -> void; - - auto bezier_cubic_n( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_cubic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_quadratic_n( - const point_type& p1, - const point_type& p2, - const point_type& p3, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_quadratic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - // ---------------------------------------------------------------------------- - // TEXT - - auto text( - std::string_view utf8_text, - std::uint32_t font_size, - const point_type& point, - color_type color, - float wrap_width = 99999999.f - ) noexcept -> void; - - auto text( - std::string_view utf8_text, - std::uint32_t font_size, - const point_type& point, - color_type color, - GlyphFlag flag, - float wrap_width = 99999999.f - ) noexcept -> void; - - // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text - auto text_size( - std::string_view utf8_text, - std::uint32_t font_size, - float wrap_width = 99999999.f - ) noexcept -> extent_type; - - // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text - auto text_size( - std::string_view utf8_text, - std::uint32_t font_size, - GlyphFlag flag, - float wrap_width = 99999999.f - ) noexcept -> extent_type; - - // ---------------------------------------------------------------------------- - // IMAGE - - // p1________ p2 - // | | - // | | - // p4|_______| p3 - auto image( - texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1 = {0, 0}, - const uv_type& uv_p2 = {1, 0}, - const uv_type& uv_p3 = {1, 1}, - const uv_type& uv_p4 = {0, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect = {0, 0, 1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const rect_type& display_rect, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::NONE, - const rect_type& uv_rect = {0, 0, 1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::NONE, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - }; - - // ========================================================= - // RENDERER - // ========================================================= - - class Renderer - { - friend Context; - - public: - Renderer(const Renderer&) noexcept = delete; - Renderer(Renderer&&) noexcept = default; - auto operator=(const Renderer&) noexcept -> Renderer& = delete; - auto operator=(Renderer&&) noexcept -> Renderer& = default; - - virtual ~Renderer() noexcept; - - protected: - Renderer() noexcept; - - public: - /** - * @brief Constructs the renderer, which assumes that all the parameters needed for the renderer have been set - */ - [[nodiscard]] auto construct() noexcept -> bool; - - /** - * @brief Destructs the renderer - */ - auto destruct() noexcept -> void; - - /** - * @brief Whether the current renderer is ready (@c construct was called and succeeded) - */ - [[nodiscard]] auto ready() const noexcept -> bool; - - /** - * @brief - * @note @c new_frame -> @c present -> @c end_frame - */ - auto new_frame(Context& context) noexcept -> void; - - /** - * @brief - * @note @c new_frame -> @c present -> @c end_frame - */ - auto present(Context& context) noexcept -> void; - - /** - * @brief - * @note @c new_frame -> @c present -> @c end_frame - */ - auto end_frame(Context& context) noexcept -> void; - - private: - virtual auto do_construct() noexcept -> bool = 0; - virtual auto do_destruct() noexcept -> void = 0; - [[nodiscard]] virtual auto do_ready() const noexcept -> bool = 0; - - [[nodiscard]] virtual auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type = 0; - virtual auto do_texture_update(const TextureDescriptor& texture) noexcept -> void = 0; - virtual auto do_texture_destroy(texture_id_type texture_id) noexcept -> void = 0; - - virtual auto do_present(const render_data_list_type& render_data_list) noexcept -> void = 0; - }; - - // ========================================================= - // CONTEXT - // ========================================================= - - [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context*; - auto destroy_context(Context& context) noexcept -> void; - auto destroy_context(Context* context) noexcept -> void; - - // ========================================================= - // GLYPH PARSER - // ========================================================= - - /** - * @brief Setting the glyph parser of context - * @return Previous glyph parser, or nullptr if newly set - */ - auto set_glyph_parser(Context& context, std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; - - // ========================================================= - // RENDERER - // ========================================================= - - /** - * @brief Setting the renderer of context - * @return Previous renderer, or nullptr if newly set - */ - auto set_renderer(Context& context, std::shared_ptr renderer) noexcept -> std::shared_ptr; - - // ========================================================= - // FONT - // ========================================================= - - /** - * @brief Load font from the specified path, assuming the path is a valid font file - * @note *Must* ensure that at least one font is added before calling @c Renderer::new_frame - */ - auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void; - - // ========================================================= - // RENDER LIST - // ========================================================= - - auto new_render_list(Context& context, RenderListFlag flag = RenderListFlag::DEFAULT) noexcept -> RenderList&; -} // namespace gal::prometheus::gfx_new - -namespace gal::prometheus::meta::user_defined -{ - template<> - struct enum_is_flag : std::true_type {}; - - template<> - struct enum_is_flag : std::true_type {}; - - template<> - struct enum_is_flag : std::true_type {}; - - template<> - struct enum_is_flag : std::true_type {}; -} // namespace gal::prometheus::meta::user_defined From f35d2c20b43e4a47a91aaa279c882d88f33ba04c Mon Sep 17 00:00:00 2001 From: Life4gal Date: Tue, 27 May 2025 07:20:38 +0800 Subject: [PATCH 50/54] =?UTF-8?q?`feat`:=20Add=20gfx.rect=5Fpack.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gfx/internal/rect_pack.cpp | 407 +++++++++++++++++++++++++++++++++ src/gfx/internal/rect_pack.hpp | 94 ++++++++ 2 files changed, 501 insertions(+) create mode 100644 src/gfx/internal/rect_pack.cpp create mode 100644 src/gfx/internal/rect_pack.hpp diff --git a/src/gfx/internal/rect_pack.cpp b/src/gfx/internal/rect_pack.cpp new file mode 100644 index 0000000..d4e6aba --- /dev/null +++ b/src/gfx/internal/rect_pack.cpp @@ -0,0 +1,407 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace gfx; + + // find minimum y position if it starts at x1 + [[nodiscard]] auto skyline_find_min_y( + const rect_pack_node* first, + const Context::point_type::value_type x0, + const Context::extent_type::value_type width, + std::uint64_t& waster_area + ) noexcept -> Context::point_type::value_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(first->point.x <= x0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(first->next->point.x > x0); + + const auto x1 = x0 + width; + const auto* current = first; + + Context::point_type::value_type min_y = 0; + Context::extent_type::value_type visited_width = 0; + + while (current->point.x < x1) + { + if (current->point.y > min_y) + { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visited + waster_area += static_cast(visited_width) * (current->point.y - min_y); + min_y = current->point.y; + + // the first time through, visited_width might be reduced + visited_width += current->next->point.x - std::ranges::max(x0, current->point.x); + } + else + { + // add waste area + const auto under_width = std::ranges::min(current->next->point.x - current->point.x, width - visited_width); + + waster_area += static_cast(under_width) * (min_y - current->point.y); + visited_width += under_width; + } + + current = current->next; + } + + return min_y; + } + + struct find_result + { + Context::point_type point; + rect_pack_node** prev_link; + }; + + auto skyline_find_best_pos(const Context& context, Context::extent_type size) noexcept -> find_result + { + // align to multiple of context->align + const auto align = context.align(); + size.width = ((size.width + align - 1) / align) * align; + + // if it can't possibly fit, bail immediately + if (size.width > context.size().width or size.height > context.size().height) + { + return {.point = {0, 0}, .prev_link = nullptr}; + } + + const auto& context_size = context.size(); + const auto heuristic = context.heuristic(); + + auto best_waste = std::numeric_limits::max(); + auto best_y = std::numeric_limits::max(); + + rect_pack_node** best = nullptr; + auto* current = context.active_head(); + auto** prev = ¤t; + + while (current->point.x + size.width <= context_size.width) + { + std::uint64_t waste = 0; + const auto min_y = skyline_find_min_y(current, current->point.x, size.width, waste); + + if (heuristic == Heuristic::SKYLINE_BOTTOM_LEFT) + { + if (min_y < best_y) + { + best_y = min_y; + best = prev; + } + } + else if (heuristic == Heuristic::SKYLINE_BEST_FIT) + { + // best-fit + if (min_y + size.height <= context_size.height) + { + // can only use it if it first vertically + if (min_y < best_y or (min_y == best_y and waste < best_waste)) + { + best_y = min_y; + best_waste = waste; + best = prev; + } + } + } + else + { + GAL_PROMETHEUS_COMPILER_UNREACHABLE(); + } + + prev = ¤t->next; + current = current->next; + } + + auto best_x = best == nullptr ? 0 : (*best)->point.x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (heuristic == Heuristic::SKYLINE_BEST_FIT) + { + const auto* tail = context.active_head(); + + current = context.active_head(); + prev = ¤t; + + // find first node that's admissible + while (tail->point.x < size.width) + { + tail = tail->next; + } + + while (tail) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tail->point.x >= size.width); + const auto x = tail->point.x - size.width; + + // find the left position that matches this + while (current->next->point.x <= x) + { + prev = ¤t->next; + current = current->next; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current->next->point.x > x and current->point.x <= x); + + std::uint64_t waste = 0; + if (const auto min_y = skyline_find_min_y(current, x, size.width, waste); + min_y + size.height <= context_size.height) + { + if (min_y <= best_y) + { + if (min_y < best_y or waste < best_waste or (waste == best_waste and x < best_x)) + { + best_x = x; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(min_y <= best_y); + best_y = min_y; + best_waste = waste; + best = prev; + } + } + } + + tail = tail->next; + } + } + + return {.point = {best_x, best_y}, .prev_link = best}; + } + + auto skyline_pack_rectangle(Context& context, const Context::extent_type size) noexcept -> find_result + { + const auto context_size = context.size(); + + // find best position according to heuristic + auto result = skyline_find_best_pos(context, size); + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (result.prev_link == nullptr or result.point.y + size.height > context_size.height or context.free_head() == nullptr) + { + return {.point = result.point, .prev_link = nullptr}; + } + + // on success, create new node + auto* head = context.free_head(); + head->point = {result.point.x, result.point.y + size.height}; + + context.free_head(head->next); + + // insert the new node into the right starting point, + // and let 'current' point to the remaining nodes needing to be stitched back in + auto* current = *result.prev_link; + if (current->point.x < result.point.x) + { + // preserve the existing one, so start testing with the next one + auto* next = current->next; + current->next = head; + current = next; + } + else + { + *result.prev_link = head; + } + + // from here, traverse current and free the nodes, until we get to one + // that shouldn't be freed + while (current->next and current->next->point.x <= result.point.x + size.width) + { + auto* next = current->next; + // move the current node to the free list + current->next = context.free_head(); + context.free_head(current); + current = next; + } + + // stitch the list back in + head->next = current; + + current->point.x = std::ranges::max(current->point.x, result.point.x + size.width); + +#if GAL_PROMETHEUS_COMPILER_DEBUG + current = context.active_head(); + while (current->point.x < context_size.width) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current->point.x < current->next->point.x); + current = current->next; + } + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current->next == nullptr); + + std::uint32_t count = 0; + for (current = context.active_head(); current; current = current->next, count += 1) {} + for (current = context.free_head(); current; current = current->next, count += 1) {} + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count = context.nodes_count() + 2); +#endif + + return result; + } +} + +namespace gal::prometheus::gfx +{ + Context::Context(const extent_type& size, const std::span nodes) noexcept + : size_{size}, + heuristic_{Heuristic::DEFAULT}, + align_{0}, + nodes_count_{static_cast(nodes.size())}, + active_head_{extra_}, + free_head_{nodes.data()} + { + for (auto [index, node]: nodes | std::views::take(nodes.size() - 1) | std::views::enumerate) + { + node.next = &nodes[index + 1]; + } + nodes[nodes.size() - 1].next = nullptr; + + set_fast_fail(true); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + extra_[0] = {.point = {0, 0}, .next = &extra_[1]}; + extra_[1] = {.point = {size.width, std::numeric_limits::max()}, .next = nullptr}; + } + + auto Context::set_heuristic(const Heuristic heuristic) noexcept -> void + { + heuristic_ = heuristic; + } + + auto Context::set_fast_fail(const bool fast_fail) noexcept -> void + { + if (fast_fail) + { + align_ = (size_.width + nodes_count_ - 1) / nodes_count_; + } + else + { + align_ = 1; + } + } + + auto Context::pack(std::span rects) noexcept -> bool + { + constexpr auto invalid_point = point_type{std::numeric_limits::max(), std::numeric_limits::max()}; + + // we use the 'was_packed' field internally to allow sorting/un-sorting + for (auto [index, rect]: rects | std::views::enumerate) + { + rect.was_packed = static_cast(index); + } + + // sort according to heuristic + std::ranges::sort( + rects, + [](const rect_pack_rect& r1, const rect_pack_rect& r2) noexcept -> bool + { + return r1.size.height < r2.size.height or r1.size.width < r2.size.width; + } + ); + + std::ranges::for_each( + rects, + [this](rect_pack_rect& rect) noexcept -> void + { + if (rect.size.width == 0 or rect.size.height == 0) + { + // empty rect needs no space + rect.point = {0, 0}; + } + else + { + if (const auto [point, prev_link] = skyline_pack_rectangle(*this, rect.size); prev_link) + { + rect.point = point; + } + else + { + rect.point = invalid_point; + } + } + } + ); + + // un-sort + std::ranges::sort( + rects, + [](const rect_pack_rect& r1, const rect_pack_rect& r2) noexcept -> bool + { + return r1.was_packed < r2.was_packed; + } + ); + + // set was_packed flags + std::ranges::for_each( + rects, + [](rect_pack_rect& rect) noexcept -> void + { + rect.was_packed = rect.point != invalid_point ? 1 : 0; + } + ); + + return not std::ranges::contains(rects, std::uint32_t{0}, &rect_pack_rect::was_packed); + } + + auto Context::size() const noexcept -> const extent_type& + { + return size_; + } + + auto Context::heuristic() const noexcept -> Heuristic + { + return heuristic_; + } + + auto Context::align() const noexcept -> std::uint32_t + { + return align_; + } + + auto Context::nodes_count() const noexcept -> std::uint32_t + { + return nodes_count_; + } + + auto Context::active_head() const noexcept -> rect_pack_node* + { + return active_head_; + } + + auto Context::free_head() const noexcept -> rect_pack_node* + { + return free_head_; + } + + auto Context::free_head(rect_pack_node* node) noexcept -> void + { + free_head_ = node; + } +} diff --git a/src/gfx/internal/rect_pack.hpp b/src/gfx/internal/rect_pack.hpp new file mode 100644 index 0000000..445bbd1 --- /dev/null +++ b/src/gfx/internal/rect_pack.hpp @@ -0,0 +1,94 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +// Base on: stb_rect_pack.h - v1.01 - public domain - rectangle packing Sean Barrett 2014 + +#pragma once + +#include + +#include +#include + +namespace gal::prometheus::gfx +{ + struct rect_pack_rect final + { + using point_type = primitive::basic_point_2d; + using extent_type = primitive::basic_extent_2d; + + // INPUT + extent_type size; + + // OUTPUT + point_type point; + + // non-zero if valid packing + std::uint32_t was_packed; + // reserved for your use + std::uint32_t id; + }; + + struct rect_pack_node final + { + using point_type = rect_pack_rect::point_type; + + point_type point; + rect_pack_node* next; + }; + + enum class Heuristic : std::uint8_t + { + SKYLINE_BOTTOM_LEFT = 0, + SKYLINE_BEST_FIT = 1, + + DEFAULT = SKYLINE_BOTTOM_LEFT, + }; + + class Context final + { + public: + using point_type = rect_pack_rect::point_type; + using extent_type = rect_pack_rect::extent_type; + + private: + extent_type size_; + + Heuristic heuristic_; + std::uint32_t align_; + std::uint32_t nodes_count_; + + rect_pack_node* active_head_; + rect_pack_node* free_head_; + // we allocate two extra nodes so optimal user-node-count is 'size.width' not 'size.width+2' + rect_pack_node extra_[2]; + + public: + Context(const extent_type& size, std::span nodes) noexcept; + + auto set_heuristic(Heuristic heuristic) noexcept -> void; + + auto set_fast_fail(bool fast_fail) noexcept -> void; + + auto pack(std::span rects) noexcept -> bool; + + // ======================================= + // INTERNAL + // ======================================= + + [[nodiscard]] auto size() const noexcept -> const extent_type&; + + [[nodiscard]] auto heuristic() const noexcept -> Heuristic; + + [[nodiscard]] auto align() const noexcept -> std::uint32_t; + + [[nodiscard]] auto nodes_count() const noexcept -> std::uint32_t; + + [[nodiscard]] auto active_head() const noexcept -> rect_pack_node*; + + [[nodiscard]] auto free_head() const noexcept -> rect_pack_node*; + auto free_head(rect_pack_node* node) noexcept -> void; + }; +} From 46abcd60239b11d812e4ee59bc37dabcfe1d3394 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Tue, 27 May 2025 14:51:41 +0800 Subject: [PATCH 51/54] =?UTF-8?q?`feat`:=20Add=20gfx.rect=5Fpack.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gfx/internal/rect_pack.cpp | 283 +++++++++++++-------------------- src/gfx/internal/rect_pack.hpp | 114 +++++++------ 2 files changed, 175 insertions(+), 222 deletions(-) diff --git a/src/gfx/internal/rect_pack.cpp b/src/gfx/internal/rect_pack.cpp index d4e6aba..02a0749 100644 --- a/src/gfx/internal/rect_pack.cpp +++ b/src/gfx/internal/rect_pack.cpp @@ -10,37 +10,43 @@ #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -namespace +namespace gal::prometheus::gfx { - using namespace gal::prometheus; - using namespace gfx; - - // find minimum y position if it starts at x1 - [[nodiscard]] auto skyline_find_min_y( - const rect_pack_node* first, - const Context::point_type::value_type x0, - const Context::extent_type::value_type width, - std::uint64_t& waster_area - ) noexcept -> Context::point_type::value_type + auto RectPackContext::rect_type::packed() const noexcept -> bool + { + return point != invalid_point; + } + + auto RectPackContext::align_of(const PackPrefer pack_prefer) const noexcept -> std::uint32_t { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(first->point.x <= x0); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(first->next->point.x > x0); + if (pack_prefer == PackPrefer::FAST_FAIL) + { + return static_cast(static_cast(size_.width) + nodes_.size() - 1 / nodes_.size()); + } + + return 1; + } + + auto RectPackContext::skyline_find_min_y(const rect_pack_node* head, const point_type::value_type x0, const extent_type::value_type width) noexcept -> find_y_result + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(head->point.x <= x0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(head->next->point.x > x0); const auto x1 = x0 + width; - const auto* current = first; + const auto* current = head; - Context::point_type::value_type min_y = 0; - Context::extent_type::value_type visited_width = 0; + find_y_result result{.y = 0, .waste = 0}; + extent_type::value_type visited_width = 0; while (current->point.x < x1) { - if (current->point.y > min_y) + if (current->point.y > result.y) { // raise min_y higher. // we've accounted for all waste up to min_y, // but we'll now add more waste for everything we've visited - waster_area += static_cast(visited_width) * (current->point.y - min_y); - min_y = current->point.y; + result.waste += static_cast(visited_width) * (current->point.y - result.y); + result.y = current->point.y; // the first time through, visited_width might be reduced visited_width += current->next->point.x - std::ranges::max(x0, current->point.x); @@ -50,78 +56,72 @@ namespace // add waste area const auto under_width = std::ranges::min(current->next->point.x - current->point.x, width - visited_width); - waster_area += static_cast(under_width) * (min_y - current->point.y); + result.waste += static_cast(under_width) * (result.y - current->point.y); visited_width += under_width; } current = current->next; } - return min_y; + return result; } - struct find_result + auto RectPackContext::skyline_find_best_pos(extent_type size, const PackPrefer pack_prefer, const Heuristic heuristic) noexcept -> find_result { - Context::point_type point; - rect_pack_node** prev_link; - }; + const auto& context_size = size_; - auto skyline_find_best_pos(const Context& context, Context::extent_type size) noexcept -> find_result - { - // align to multiple of context->align - const auto align = context.align(); + // align to multiple of 'align' + const auto align = align_of(pack_prefer); size.width = ((size.width + align - 1) / align) * align; // if it can't possibly fit, bail immediately - if (size.width > context.size().width or size.height > context.size().height) + if (size.width > context_size.width or size.height > context_size.height) { return {.point = {0, 0}, .prev_link = nullptr}; } - const auto& context_size = context.size(); - const auto heuristic = context.heuristic(); - auto best_waste = std::numeric_limits::max(); - auto best_y = std::numeric_limits::max(); + auto best_y = std::numeric_limits::max(); rect_pack_node** best = nullptr; - auto* current = context.active_head(); - auto** prev = ¤t; - - while (current->point.x + size.width <= context_size.width) { - std::uint64_t waste = 0; - const auto min_y = skyline_find_min_y(current, current->point.x, size.width, waste); + auto* current = active_head_; + auto** prev = ¤t; - if (heuristic == Heuristic::SKYLINE_BOTTOM_LEFT) - { - if (min_y < best_y) - { - best_y = min_y; - best = prev; - } - } - else if (heuristic == Heuristic::SKYLINE_BEST_FIT) + while (current->point.x + size.width <= context_size.width) { - // best-fit - if (min_y + size.height <= context_size.height) + const auto [min_y, waste] = skyline_find_min_y(current, current->point.x, size.width); + + if (heuristic == Heuristic::SKYLINE_BOTTOM_LEFT) { - // can only use it if it first vertically - if (min_y < best_y or (min_y == best_y and waste < best_waste)) + if (min_y < best_y) { best_y = min_y; - best_waste = waste; best = prev; } } - } - else - { - GAL_PROMETHEUS_COMPILER_UNREACHABLE(); - } + else if (heuristic == Heuristic::SKYLINE_BEST_FIT) + { + // best-fit + if (min_y + size.height <= context_size.height) + { + // can only use it if it first vertically + if (min_y < best_y or (min_y == best_y and waste < best_waste)) + { + best_y = min_y; + best_waste = waste; + best = prev; + } + } + } + else + { + GAL_PROMETHEUS_COMPILER_UNREACHABLE(); + } - prev = ¤t->next; - current = current->next; + prev = ¤t->next; + current = current->next; + } } auto best_x = best == nullptr ? 0 : (*best)->point.x; @@ -145,10 +145,9 @@ namespace if (heuristic == Heuristic::SKYLINE_BEST_FIT) { - const auto* tail = context.active_head(); - - current = context.active_head(); - prev = ¤t; + const auto* tail = active_head_; + auto* current = active_head_; + auto** prev = ¤t; // find first node that's admissible while (tail->point.x < size.width) @@ -170,8 +169,7 @@ namespace GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current->next->point.x > x and current->point.x <= x); - std::uint64_t waste = 0; - if (const auto min_y = skyline_find_min_y(current, x, size.width, waste); + if (const auto [min_y, waste] = skyline_find_min_y(current, x, size.width); min_y + size.height <= context_size.height) { if (min_y <= best_y) @@ -194,27 +192,26 @@ namespace return {.point = {best_x, best_y}, .prev_link = best}; } - auto skyline_pack_rectangle(Context& context, const Context::extent_type size) noexcept -> find_result + auto RectPackContext::skyline_pack_rectangle(extent_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result { - const auto context_size = context.size(); + const auto context_size = size_; // find best position according to heuristic - auto result = skyline_find_best_pos(context, size); + auto result = skyline_find_best_pos(size, pack_prefer, heuristic); // bail if: // 1. it failed // 2. the best node doesn't fit (we don't always check this) // 3. we're out of memory - if (result.prev_link == nullptr or result.point.y + size.height > context_size.height or context.free_head() == nullptr) + if (result.prev_link == nullptr or result.point.y + size.height > context_size.height or free_head_ == nullptr) { return {.point = result.point, .prev_link = nullptr}; } // on success, create new node - auto* head = context.free_head(); + auto* head = free_head_; head->point = {result.point.x, result.point.y + size.height}; - - context.free_head(head->next); + free_head_ = head->next; // insert the new node into the right starting point, // and let 'current' point to the remaining nodes needing to be stitched back in @@ -237,8 +234,8 @@ namespace { auto* next = current->next; // move the current node to the free list - current->next = context.free_head(); - context.free_head(current); + current->next = free_head_; + free_head_ = current; current = next; } @@ -248,7 +245,7 @@ namespace current->point.x = std::ranges::max(current->point.x, result.point.x + size.width); #if GAL_PROMETHEUS_COMPILER_DEBUG - current = context.active_head(); + current = active_head_; while (current->point.x < context_size.width) { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current->point.x < current->next->point.x); @@ -256,79 +253,61 @@ namespace } GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current->next == nullptr); - std::uint32_t count = 0; - for (current = context.active_head(); current; current = current->next, count += 1) {} - for (current = context.free_head(); current; current = current->next, count += 1) {} + std::size_t count = 0; + for (current = active_head_; current; current = current->next, count += 1) {} + for (current = free_head_; current; current = current->next, count += 1) {} - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count = context.nodes_count() + 2); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count == nodes_.size() + 2); #endif return result; } -} -namespace gal::prometheus::gfx -{ - Context::Context(const extent_type& size, const std::span nodes) noexcept + RectPackContext::RectPackContext(const extent_type& size) noexcept : size_{size}, - heuristic_{Heuristic::DEFAULT}, - align_{0}, - nodes_count_{static_cast(nodes.size())}, - active_head_{extra_}, - free_head_{nodes.data()} + nodes_{size.width}, + free_head_{nodes_.data()} { - for (auto [index, node]: nodes | std::views::take(nodes.size() - 1) | std::views::enumerate) + for (auto [index, node]: nodes_ | std::views::take(nodes_.size() - 1) | std::views::enumerate) { - node.next = &nodes[index + 1]; + node.next = &nodes_[index + 1]; } - nodes[nodes.size() - 1].next = nullptr; - - set_fast_fail(true); + nodes_.back().next = nullptr; // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) - extra_[0] = {.point = {0, 0}, .next = &extra_[1]}; - extra_[1] = {.point = {size.width, std::numeric_limits::max()}, .next = nullptr}; - } - - auto Context::set_heuristic(const Heuristic heuristic) noexcept -> void - { - heuristic_ = heuristic; + active_head_[0] = {.point = {0, 0}, .next = &active_head_[1]}; + active_head_[1] = {.point = {size_.width, std::numeric_limits::max()}, .next = nullptr}; } - auto Context::set_fast_fail(const bool fast_fail) noexcept -> void + auto RectPackContext::pack( + std::span in_out_rects, + const PackPrefer pack_prefer, + const Heuristic heuristic + ) noexcept -> bool { - if (fast_fail) + // we use the 'internal_status_' field internally to allow sorting/un-sorting + for (auto [index, rect]: in_out_rects | std::views::enumerate) { - align_ = (size_.width + nodes_count_ - 1) / nodes_count_; - } - else - { - align_ = 1; - } - } - - auto Context::pack(std::span rects) noexcept -> bool - { - constexpr auto invalid_point = point_type{std::numeric_limits::max(), std::numeric_limits::max()}; - - // we use the 'was_packed' field internally to allow sorting/un-sorting - for (auto [index, rect]: rects | std::views::enumerate) - { - rect.was_packed = static_cast(index); + rect.internal_status_ = static_cast(index); } // sort according to heuristic std::ranges::sort( - rects, - [](const rect_pack_rect& r1, const rect_pack_rect& r2) noexcept -> bool + in_out_rects, + [](const rect_type& r1, const rect_type& r2) noexcept -> bool { - return r1.size.height < r2.size.height or r1.size.width < r2.size.width; + if (r1.size.height != r2.size.height) + { + return r1.size.height > r2.size.height; + } + + return r1.size.width > r2.size.width; } ); std::ranges::for_each( - rects, - [this](rect_pack_rect& rect) noexcept -> void + in_out_rects, + [this, pack_prefer, heuristic](rect_type& rect) noexcept -> void { if (rect.size.width == 0 or rect.size.height == 0) { @@ -337,7 +316,7 @@ namespace gal::prometheus::gfx } else { - if (const auto [point, prev_link] = skyline_pack_rectangle(*this, rect.size); prev_link) + if (const auto [point, prev_link] = skyline_pack_rectangle(rect.size, pack_prefer, heuristic); prev_link) { rect.point = point; } @@ -351,57 +330,13 @@ namespace gal::prometheus::gfx // un-sort std::ranges::sort( - rects, - [](const rect_pack_rect& r1, const rect_pack_rect& r2) noexcept -> bool - { - return r1.was_packed < r2.was_packed; - } - ); - - // set was_packed flags - std::ranges::for_each( - rects, - [](rect_pack_rect& rect) noexcept -> void + in_out_rects, + [](const rect_type& r1, const rect_type& r2) noexcept -> bool { - rect.was_packed = rect.point != invalid_point ? 1 : 0; + return r1.internal_status_ < r2.internal_status_; } ); - return not std::ranges::contains(rects, std::uint32_t{0}, &rect_pack_rect::was_packed); - } - - auto Context::size() const noexcept -> const extent_type& - { - return size_; - } - - auto Context::heuristic() const noexcept -> Heuristic - { - return heuristic_; - } - - auto Context::align() const noexcept -> std::uint32_t - { - return align_; - } - - auto Context::nodes_count() const noexcept -> std::uint32_t - { - return nodes_count_; - } - - auto Context::active_head() const noexcept -> rect_pack_node* - { - return active_head_; - } - - auto Context::free_head() const noexcept -> rect_pack_node* - { - return free_head_; - } - - auto Context::free_head(rect_pack_node* node) noexcept -> void - { - free_head_ = node; + return std::ranges::all_of(in_out_rects, &rect_type::packed); } } diff --git a/src/gfx/internal/rect_pack.hpp b/src/gfx/internal/rect_pack.hpp index 445bbd1..66f91e2 100644 --- a/src/gfx/internal/rect_pack.hpp +++ b/src/gfx/internal/rect_pack.hpp @@ -8,35 +8,19 @@ #pragma once #include +#include #include #include namespace gal::prometheus::gfx { - struct rect_pack_rect final + enum class PackPrefer : std::uint8_t { - using point_type = primitive::basic_point_2d; - using extent_type = primitive::basic_extent_2d; - - // INPUT - extent_type size; + FAST_FAIL = 0, + SKIP = 1, - // OUTPUT - point_type point; - - // non-zero if valid packing - std::uint32_t was_packed; - // reserved for your use - std::uint32_t id; - }; - - struct rect_pack_node final - { - using point_type = rect_pack_rect::point_type; - - point_type point; - rect_pack_node* next; + DEFAULT = FAST_FAIL, }; enum class Heuristic : std::uint8_t @@ -47,48 +31,82 @@ namespace gal::prometheus::gfx DEFAULT = SKYLINE_BOTTOM_LEFT, }; - class Context final + class RectPackContext final { public: - using point_type = rect_pack_rect::point_type; - using extent_type = rect_pack_rect::extent_type; + using point_type = primitive::basic_point_2d; + using extent_type = primitive::basic_extent_2d; - private: - extent_type size_; + constexpr static auto invalid_point = point_type{std::numeric_limits::max(), std::numeric_limits::max()}; - Heuristic heuristic_; - std::uint32_t align_; - std::uint32_t nodes_count_; + struct rect_type final + { + friend RectPackContext; - rect_pack_node* active_head_; - rect_pack_node* free_head_; - // we allocate two extra nodes so optimal user-node-count is 'size.width' not 'size.width+2' - rect_pack_node extra_[2]; + // INPUT + extent_type size; - public: - Context(const extent_type& size, std::span nodes) noexcept; + // OUTPUT + point_type point; - auto set_heuristic(Heuristic heuristic) noexcept -> void; + // reserved for your use + std::uint32_t id; - auto set_fast_fail(bool fast_fail) noexcept -> void; + private: + std::uint32_t internal_status_; - auto pack(std::span rects) noexcept -> bool; + public: + constexpr explicit rect_type(const extent_type size, const std::uint32_t id = std::numeric_limits::max()) noexcept + : size{size}, + point{0, 0}, + id{id}, + internal_status_{0} {} - // ======================================= - // INTERNAL - // ======================================= + [[nodiscard]] auto packed() const noexcept -> bool; + }; - [[nodiscard]] auto size() const noexcept -> const extent_type&; + private: + struct rect_pack_node final + { + point_type point; + rect_pack_node* next; + }; + + struct find_y_result + { + point_type::value_type y; + extent_type::value_type waste; + }; + + struct find_result + { + point_type point; + rect_pack_node** prev_link; + }; - [[nodiscard]] auto heuristic() const noexcept -> Heuristic; + extent_type size_; - [[nodiscard]] auto align() const noexcept -> std::uint32_t; + std::vector nodes_; + rect_pack_node* free_head_; - [[nodiscard]] auto nodes_count() const noexcept -> std::uint32_t; + rect_pack_node active_head_[2]; - [[nodiscard]] auto active_head() const noexcept -> rect_pack_node*; + [[nodiscard]] auto align_of(PackPrefer pack_prefer) const noexcept -> std::uint32_t; + + // find minimum y position if it starts at x1 + [[nodiscard]] static auto skyline_find_min_y(const rect_pack_node* head, point_type::value_type x0, extent_type::value_type width) noexcept -> find_y_result; + + [[nodiscard]] auto skyline_find_best_pos(extent_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result; + + [[nodiscard]] auto skyline_pack_rectangle(extent_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result; + + public: + explicit RectPackContext(const extent_type& size) noexcept; - [[nodiscard]] auto free_head() const noexcept -> rect_pack_node*; - auto free_head(rect_pack_node* node) noexcept -> void; + auto pack( + std::span in_out_rects, + PackPrefer pack_prefer = PackPrefer::DEFAULT, + Heuristic heuristic = Heuristic::DEFAULT + ) noexcept -> bool; }; } From 390df1ba33ed8104a9108ecdab18782722ff8303 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Tue, 27 May 2025 17:44:16 +0800 Subject: [PATCH 52/54] =?UTF-8?q?`feat`:=20Add=20gfx.rect=5Fpack.=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gfx/internal/rect_pack.cpp | 69 ++++++++++++++++------------------ src/gfx/internal/rect_pack.hpp | 26 +++++-------- 2 files changed, 42 insertions(+), 53 deletions(-) diff --git a/src/gfx/internal/rect_pack.cpp b/src/gfx/internal/rect_pack.cpp index 02a0749..f54f4a5 100644 --- a/src/gfx/internal/rect_pack.cpp +++ b/src/gfx/internal/rect_pack.cpp @@ -12,16 +12,16 @@ namespace gal::prometheus::gfx { - auto RectPackContext::rect_type::packed() const noexcept -> bool + auto RectPackContext::nodes_count() const noexcept -> std::size_t { - return point != invalid_point; + return nodes_.size() - 2; } auto RectPackContext::align_of(const PackPrefer pack_prefer) const noexcept -> std::uint32_t { if (pack_prefer == PackPrefer::FAST_FAIL) { - return static_cast(static_cast(size_.width) + nodes_.size() - 1 / nodes_.size()); + return static_cast((static_cast(size_.width) + nodes_count() - 1) / nodes_count()); } return 1; @@ -86,7 +86,7 @@ namespace gal::prometheus::gfx rect_pack_node** best = nullptr; { auto* current = active_head_; - auto** prev = ¤t; + auto** prev = &active_head_; while (current->point.x + size.width <= context_size.width) { @@ -192,7 +192,7 @@ namespace gal::prometheus::gfx return {.point = {best_x, best_y}, .prev_link = best}; } - auto RectPackContext::skyline_pack_rectangle(extent_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result + auto RectPackContext::skyline_pack_rectangle(const extent_type size, const PackPrefer pack_prefer, const Heuristic heuristic) noexcept -> find_result { const auto context_size = size_; @@ -257,7 +257,7 @@ namespace gal::prometheus::gfx for (current = active_head_; current; current = current->next, count += 1) {} for (current = free_head_; current; current = current->next, count += 1) {} - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count == nodes_.size() + 2); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count == nodes_count() + 2); #endif return result; @@ -265,14 +265,15 @@ namespace gal::prometheus::gfx RectPackContext::RectPackContext(const extent_type& size) noexcept : size_{size}, - nodes_{size.width}, - free_head_{nodes_.data()} + nodes_{size.width + 2}, + free_head_{nodes_.data()}, + active_head_{nodes_.data() + nodes_count()} { - for (auto [index, node]: nodes_ | std::views::take(nodes_.size() - 1) | std::views::enumerate) + for (auto [index, node]: nodes_ | std::views::take(nodes_count() - 1) | std::views::enumerate) { node.next = &nodes_[index + 1]; } - nodes_.back().next = nullptr; + nodes_[nodes_count() - 1].next = nullptr; // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) active_head_[0] = {.point = {0, 0}, .next = &active_head_[1]}; @@ -285,58 +286,54 @@ namespace gal::prometheus::gfx const Heuristic heuristic ) noexcept -> bool { - // we use the 'internal_status_' field internally to allow sorting/un-sorting - for (auto [index, rect]: in_out_rects | std::views::enumerate) - { - rect.internal_status_ = static_cast(index); - } + std::vector sorted_rects{}; + sorted_rects.reserve(in_out_rects.size()); + std::ranges::transform( + in_out_rects, + std::back_inserter(sorted_rects), + [](rect_type& rect) noexcept -> rect_type* + { + return std::addressof(rect); + } + ); // sort according to heuristic std::ranges::sort( - in_out_rects, - [](const rect_type& r1, const rect_type& r2) noexcept -> bool + sorted_rects, + [](const rect_type* r1, const rect_type* r2) noexcept -> bool { - if (r1.size.height != r2.size.height) + if (r1->size.height != r2->size.height) { - return r1.size.height > r2.size.height; + return r1->size.height > r2->size.height; } - return r1.size.width > r2.size.width; + return r1->size.width > r2->size.width; } ); std::ranges::for_each( - in_out_rects, - [this, pack_prefer, heuristic](rect_type& rect) noexcept -> void + sorted_rects, + [this, pack_prefer, heuristic](rect_type* rect) noexcept -> void { - if (rect.size.width == 0 or rect.size.height == 0) + if (rect->size.width == 0 or rect->size.height == 0) { // empty rect needs no space - rect.point = {0, 0}; + rect->point = {0, 0}; } else { - if (const auto [point, prev_link] = skyline_pack_rectangle(rect.size, pack_prefer, heuristic); prev_link) + if (const auto [point, prev_link] = skyline_pack_rectangle(rect->size, pack_prefer, heuristic); prev_link) { - rect.point = point; + rect->point = point; } else { - rect.point = invalid_point; + rect->point = invalid_point; } } } ); - // un-sort - std::ranges::sort( - in_out_rects, - [](const rect_type& r1, const rect_type& r2) noexcept -> bool - { - return r1.internal_status_ < r2.internal_status_; - } - ); - return std::ranges::all_of(in_out_rects, &rect_type::packed); } } diff --git a/src/gfx/internal/rect_pack.hpp b/src/gfx/internal/rect_pack.hpp index 66f91e2..63dc19e 100644 --- a/src/gfx/internal/rect_pack.hpp +++ b/src/gfx/internal/rect_pack.hpp @@ -41,28 +41,16 @@ namespace gal::prometheus::gfx struct rect_type final { - friend RectPackContext; - // INPUT extent_type size; // OUTPUT point_type point; - // reserved for your use - std::uint32_t id; - - private: - std::uint32_t internal_status_; - - public: - constexpr explicit rect_type(const extent_type size, const std::uint32_t id = std::numeric_limits::max()) noexcept - : size{size}, - point{0, 0}, - id{id}, - internal_status_{0} {} - - [[nodiscard]] auto packed() const noexcept -> bool; + [[nodiscard]] constexpr auto packed() const noexcept -> bool + { + return point != invalid_point; + } }; private: @@ -86,10 +74,14 @@ namespace gal::prometheus::gfx extent_type size_; + // size_.width + 2 std::vector nodes_; + // size_.width rect_pack_node* free_head_; + // 2 + rect_pack_node* active_head_; - rect_pack_node active_head_[2]; + [[nodiscard]] auto nodes_count() const noexcept -> std::size_t; [[nodiscard]] auto align_of(PackPrefer pack_prefer) const noexcept -> std::uint32_t; From c07122e96ee4e02a8c74d5b195ec5bda17fed5d7 Mon Sep 17 00:00:00 2001 From: Life4gal Date: Fri, 30 May 2025 00:49:44 +0800 Subject: [PATCH 53/54] =?UTF-8?q?`feat`:=20GFX=20basic=20Framework.?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/library.cmake | 60 +- src/gfx/context.hpp | 51 + src/gfx/extension/glyph_parser_freetype.cpp | 250 +-- src/gfx/extension/glyph_parser_freetype.hpp | 10 +- src/gfx/extension/renderer_d3d11.cpp | 146 +- src/gfx/extension/renderer_d3d11.hpp | 22 +- src/gfx/extension/renderer_d3d12.cpp | 316 +++- src/gfx/extension/renderer_d3d12.hpp | 27 +- src/gfx/gfx.hpp | 994 +--------- src/gfx/glyph.cpp | 11 + src/gfx/glyph.hpp | 111 ++ src/gfx/internal/context.cpp | 600 +----- src/gfx/internal/context.hpp | 283 +-- src/gfx/internal/font.cpp | 99 - src/gfx/internal/glyph.cpp | 103 + src/gfx/internal/{font.hpp => glyph.hpp} | 84 +- src/gfx/internal/rect_pack.cpp | 10 +- src/gfx/internal/rect_pack.hpp | 16 +- src/gfx/internal/render_list.hpp | 64 - src/gfx/internal/texture.cpp | 280 +-- src/gfx/internal/texture.hpp | 148 +- src/gfx/{internal => }/render_list.cpp | 1891 ++++--------------- src/gfx/render_list.hpp | 693 +++++++ src/gfx/renderer.cpp | 13 + src/gfx/renderer.hpp | 41 + src/gfx/texture.cpp | 186 ++ src/gfx/texture.hpp | 132 ++ src/gfx/type.hpp | 41 + 28 files changed, 2618 insertions(+), 4064 deletions(-) create mode 100644 src/gfx/context.hpp create mode 100644 src/gfx/glyph.cpp create mode 100644 src/gfx/glyph.hpp delete mode 100644 src/gfx/internal/font.cpp create mode 100644 src/gfx/internal/glyph.cpp rename src/gfx/internal/{font.hpp => glyph.hpp} (55%) delete mode 100644 src/gfx/internal/render_list.hpp rename src/gfx/{internal => }/render_list.cpp (52%) create mode 100644 src/gfx/render_list.hpp create mode 100644 src/gfx/renderer.cpp create mode 100644 src/gfx/renderer.hpp create mode 100644 src/gfx/texture.cpp create mode 100644 src/gfx/texture.hpp create mode 100644 src/gfx/type.hpp diff --git a/scripts/library.cmake b/scripts/library.cmake index 2f4f761..e94fe97 100644 --- a/scripts/library.cmake +++ b/scripts/library.cmake @@ -368,38 +368,24 @@ set( # ========================= # GFX # ========================= - - ${PROJECT_SOURCE_DIR}/src/gfx/gfx.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/type.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/texture.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/render_list.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/context.hpp + + ${PROJECT_SOURCE_DIR}/src/gfx/internal/rect_pack.hpp ${PROJECT_SOURCE_DIR}/src/gfx/internal/texture.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/internal/font.hpp - ${PROJECT_SOURCE_DIR}/src/gfx/internal/render_list.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/glyph.hpp ${PROJECT_SOURCE_DIR}/src/gfx/internal/context.hpp ${PROJECT_SOURCE_DIR}/src/gfx/extension/glyph_parser_freetype.hpp ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d11.hpp ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d12.hpp - # ========================= - # GFX-OLD - # ========================= - -# ${PROJECT_SOURCE_DIR}/src/gfx/old/type.hpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph.hpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/texture.hpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/font.hpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph_parser_freetype.hpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/render_list.hpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer.hpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx11.hpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx12.hpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/context.hpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/gfx.hpp + ${PROJECT_SOURCE_DIR}/src/gfx/gfx.hpp # ========================= # GUI @@ -455,33 +441,21 @@ set( # ========================= # GFX # ========================= + + ${PROJECT_SOURCE_DIR}/src/gfx/texture.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/glyph.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/render_list.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/renderer.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/rect_pack.cpp ${PROJECT_SOURCE_DIR}/src/gfx/internal/texture.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/internal/font.cpp - ${PROJECT_SOURCE_DIR}/src/gfx/internal/render_list.cpp + ${PROJECT_SOURCE_DIR}/src/gfx/internal/glyph.cpp ${PROJECT_SOURCE_DIR}/src/gfx/internal/context.cpp ${PROJECT_SOURCE_DIR}/src/gfx/extension/glyph_parser_freetype.cpp ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d11.cpp ${PROJECT_SOURCE_DIR}/src/gfx/extension/renderer_d3d12.cpp - # ========================= - # GFX-OLD - # ========================= - -# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph.cpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/texture.cpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/font.cpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/glyph_parser_freetype.cpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/render_list.cpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer.cpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx11.cpp -# ${PROJECT_SOURCE_DIR}/src/gfx/old/renderer_dx12.cpp -# -# ${PROJECT_SOURCE_DIR}/src/gfx/old/context.cpp - # ========================= # GUI # ========================= diff --git a/src/gfx/context.hpp b/src/gfx/context.hpp new file mode 100644 index 0000000..028bf21 --- /dev/null +++ b/src/gfx/context.hpp @@ -0,0 +1,51 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::gfx +{ + class GlyphParser; + class Renderer; + + class Context final + { + friend RenderList; + + public: + class ContextPrivate; + + RenderListSharedData render_list_shared_data; + std::vector render_lists; + + GlyphParser* glyph_parser; + Renderer* renderer; + + private: + memory::UniquePointer private_; + + public: + Context(const Context&) noexcept = delete; + Context(Context&&) noexcept; // = default; + auto operator=(const Context&) noexcept -> Context& = delete; + auto operator=(Context&&) noexcept -> Context&; // = default; + + ~Context() noexcept; // = default; + + Context() noexcept; // = default; + + auto initialize() noexcept -> void; + + auto new_render_list() noexcept -> RenderList&; + + auto new_frame() noexcept -> void; + + auto present(const extent_type& display_size) noexcept -> void; + + auto end_frame() noexcept -> void; + }; +} diff --git a/src/gfx/extension/glyph_parser_freetype.cpp b/src/gfx/extension/glyph_parser_freetype.cpp index 9b7895b..eb65bc6 100644 --- a/src/gfx/extension/glyph_parser_freetype.cpp +++ b/src/gfx/extension/glyph_parser_freetype.cpp @@ -5,6 +5,10 @@ #include +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + #include #include @@ -16,7 +20,7 @@ namespace } } // namespace -namespace gal::prometheus::gfx +namespace gal::prometheus::gfx::extension { class GlyphParserFreeType::Library final { @@ -27,7 +31,7 @@ namespace gal::prometheus::gfx class GlyphParserFreeType::FontInfo final { public: - std::filesystem::path path; + std::string path; FT_Face face{nullptr}; float ascender{0}; @@ -80,35 +84,42 @@ namespace gal::prometheus::gfx } } - auto GlyphParserFreeType::load(const std::filesystem::path& path) noexcept -> FontDescriptor + auto GlyphParserFreeType::load(const std::string_view path) noexcept -> FontLoadResult { - if (const auto it = std::ranges::find(infos_, path, &FontInfo::path); it != infos_.end()) + if (std::ranges::contains(infos_, path, &FontInfo::path)) { - const auto& info = it.operator*(); - const auto index = std::ranges::distance(infos_.begin(), it); + return FontLoadResult::FILE_ALREADY_LOADED; + } - return {.identifier = info.face->family_name, .id = static_cast(index)}; + std::error_code ec; + const std::filesystem::path fs_path{path}; + if (not exists(fs_path, ec)) + { + return FontLoadResult::FILE_NOT_FOUND; } - const auto path_string = path.string(); + if (not is_regular_file(fs_path, ec)) + { + return FontLoadResult::INVALID_FONT_FORMAT; + } FT_Face face = nullptr; - if (const auto error = FT_New_Face(library_->library, path_string.data(), 0, &face); error != FT_Err_Ok) + if (const auto error = FT_New_Face(library_->library, path.data(), 0, &face); error != FT_Err_Ok) { - return FontDescriptor::error(); + return FontLoadResult::INVALID_FONT_FORMAT; } if (const auto error = FT_Select_Charmap(face, FT_ENCODING_UNICODE); error != FT_Err_Ok) { FT_Done_Face(face); - return FontDescriptor::error(); + return FontLoadResult::INVALID_FONT_FORMAT; } - const auto id = infos_.size(); auto& info = infos_.emplace_back(); + info.path = path; info.face = face; - return {.identifier = face->family_name, .id = static_cast(id)}; + return FontLoadResult::SUCCESS; } // auto GlyphParserFreeType::load(const std::span data) noexcept -> FontDescriptor @@ -136,137 +147,142 @@ namespace gal::prometheus::gfx // return {.identifier = face->family_name, .id = static_cast(id)}; // } - auto GlyphParserFreeType::has_glyph(const font_id_type id, const std::uint32_t codepoint) const noexcept -> bool + auto GlyphParserFreeType::has_glyph(const std::uint32_t codepoint) const noexcept -> bool { - if (id >= infos_.size()) - { - return false; - } - - const auto& info = infos_[id]; - if (const auto char_index = FT_Get_Char_Index(info.face, codepoint); char_index == 0) - { - return false; - } - - return true; + return std::ranges::any_of( + infos_, + [codepoint](const auto& info) noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(info.face != nullptr); + return ::FT_Get_Char_Index(info.face, codepoint) != 0; + } + ); } - auto GlyphParserFreeType::parse(const font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor + auto GlyphParserFreeType::parse(const GlyphCode& code) noexcept -> GlyphDescriptor { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id < infos_.size()); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(code.codepoint != 0); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(code.size != 0); - auto& info = infos_[id]; - const auto& face = info.face; - - const auto char_index = FT_Get_Char_Index(face, code.codepoint); - if (char_index == 0) + for (auto& info: infos_) { - return GlyphDescriptor::error(); - } + if (const auto char_index = FT_Get_Char_Index(info.face, code.codepoint); char_index != 0) + { + const auto& face = info.face; - info.set_pixel_height(code.size); + info.set_pixel_height(code.size); - if (const auto error = FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT); error != FT_Err_Ok) - { - return GlyphDescriptor::error(); - } + if (const auto error = FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT); error != FT_Err_Ok) + { + // todo + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return {.rect = {}, .advance_x = 0, .visible = false, .colored = false, .data = nullptr}; + } - const auto& slot = face->glyph; + const auto& slot = face->glyph; - if (std::to_underlying(code.flag) & GlyphFlag::BOLD) - { - FT_GlyphSlot_Embolden(slot); - } - if (std::to_underlying(code.flag) & GlyphFlag::ITALIC) - { - FT_GlyphSlot_Oblique(slot); - } - - if (const auto error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); error != FT_Err_Ok) - { - return GlyphDescriptor::error(); - } + if (std::to_underlying(code.flag) & GlyphFlag::BOLD) + { + FT_GlyphSlot_Embolden(slot); + } + if (std::to_underlying(code.flag) & GlyphFlag::ITALIC) + { + FT_GlyphSlot_Oblique(slot); + } - const auto& bitmap = face->glyph->bitmap; + if (const auto error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); error != FT_Err_Ok) + { + // todo + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return {.rect = {}, .advance_x = 0, .visible = false, .colored = false, .data = nullptr}; + } - const GlyphDescriptor::rect_type::point_type point{slot->bitmap_left, slot->bitmap_top}; - const GlyphDescriptor::rect_type::extent_type size{bitmap.width, bitmap.rows}; - const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; + const auto& bitmap = slot->bitmap; - GlyphDescriptor result{ - .rect = {point, size}, - .advance_x = ft_size_to_float(slot->advance.x), - .visible = size.width > 0 and size.height > 0, - .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, - .data = std::make_unique_for_overwrite(data_length) - }; + const GlyphDescriptor::rect_type::point_type point{slot->bitmap_left, slot->bitmap_top}; + const GlyphDescriptor::rect_type::extent_type size{bitmap.width, bitmap.rows}; + const auto visible = size.width > 0 and size.height > 0; - { - const auto* source = bitmap.buffer; - auto* dest = result.data.get(); + GlyphDescriptor result{ + .rect = {point, size}, + .advance_x = ft_size_to_float(slot->advance.x), + .visible = visible, + .colored = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA, + .data = nullptr + }; - if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) - { - for (std::uint32_t y = 0; y < bitmap.rows; ++y) + if (visible) { - for (std::uint32_t x = 0; x < bitmap.width; ++x) - { - const auto a = source[x]; - const auto color = - // A - a << 24 | - // B - std::uint32_t{0xff} << 16 | - // G - std::uint32_t{0xff} << 8 | - // R - std::uint32_t{0xff}; - dest[x] = color; - } + const std::size_t data_length = static_cast(bitmap.width) * bitmap.rows; + result.data = std::make_unique_for_overwrite(data_length); - source += bitmap.pitch; - dest += bitmap.width; - } - } - else if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) - { - for (std::uint32_t y = 0; y < bitmap.rows; ++y) - { - const std::uint8_t* p = source; - std::uint8_t bits = 0; + const auto* source = bitmap.buffer; + auto* dest = result.data.get(); - for (std::uint32_t x = 0; x < bitmap.width; ++x) + if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { - if ((x & 7) == 0) + for (std::uint32_t y = 0; y < bitmap.rows; ++y) { - bits = *p; - p += 1; + for (std::uint32_t x = 0; x < bitmap.width; ++x) + { + const auto a = source[x]; + const auto color = + // A + a << 24 | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + dest[x] = color; + } + + source += bitmap.pitch; + dest += bitmap.width; + } + } + else if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + for (std::uint32_t y = 0; y < bitmap.rows; ++y) + { + const std::uint8_t* p = source; + std::uint8_t bits = 0; + + for (std::uint32_t x = 0; x < bitmap.width; ++x) + { + if ((x & 7) == 0) + { + bits = *p; + p += 1; + } + + const auto a = (bits & 0x80) ? std::uint32_t{0xff} : std::uint32_t{0}; + const auto color = + // A + a << 24 | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + dest[x] = color; + + bits <<= 1; + } + + source += bitmap.pitch; + dest += bitmap.width; } - - const auto a = (bits & 0x80) ? std::uint32_t{0xff} : std::uint32_t{0}; - const auto color = - // A - a << 24 | - // B - std::uint32_t{0xff} << 16 | - // G - std::uint32_t{0xff} << 8 | - // R - std::uint32_t{0xff}; - dest[x] = color; - - bits <<= 1; } - - source += bitmap.pitch; - dest += bitmap.width; } + + return result; } } - return result; + // todo + return {.rect = {}, .advance_x = 0, .visible = false, .colored = false, .data = nullptr}; } } diff --git a/src/gfx/extension/glyph_parser_freetype.hpp b/src/gfx/extension/glyph_parser_freetype.hpp index 7f083d1..69bd7e7 100644 --- a/src/gfx/extension/glyph_parser_freetype.hpp +++ b/src/gfx/extension/glyph_parser_freetype.hpp @@ -7,11 +7,11 @@ #include -#include +#include #include -namespace gal::prometheus::gfx +namespace gal::prometheus::gfx::extension { class GlyphParserFreeType final : public GlyphParser { @@ -35,10 +35,10 @@ namespace gal::prometheus::gfx GlyphParserFreeType() noexcept; - auto load(const std::filesystem::path& path) noexcept -> FontDescriptor override; + auto load(std::string_view path) noexcept -> FontLoadResult override; - [[nodiscard]] auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool override; + [[nodiscard]] auto has_glyph(std::uint32_t codepoint) const noexcept -> bool override; - auto parse(font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor override; + auto parse(const GlyphCode& code) noexcept -> GlyphDescriptor override; }; } diff --git a/src/gfx/extension/renderer_d3d11.cpp b/src/gfx/extension/renderer_d3d11.cpp index 972e918..a1bbc35 100644 --- a/src/gfx/extension/renderer_d3d11.cpp +++ b/src/gfx/extension/renderer_d3d11.cpp @@ -14,7 +14,6 @@ #include -#include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE #include @@ -67,7 +66,7 @@ namespace } } -namespace gal::prometheus::gfx +namespace gal::prometheus::gfx::extension { auto RendererD3D11::create_blend_state() noexcept -> bool { @@ -358,8 +357,8 @@ namespace gal::prometheus::gfx } auto RendererD3D11::upload_texture( - const TextureDescriptor::data_view_type data, - const TextureDescriptor::size_type size, + const Texture::data_type& data, + const Texture::size_type size, const D3D11_USAGE usage, const std::uint32_t bind_flags, const std::uint32_t cpu_access_flags, @@ -383,8 +382,8 @@ namespace gal::prometheus::gfx const D3D11_SUBRESOURCE_DATA subresource_data { - .pSysMem = data.data(), - .SysMemPitch = static_cast(size.width * 4), + .pSysMem = data.get(), + .SysMemPitch = static_cast(size.width * sizeof(Texture::element_type)), .SysMemSlicePitch = 0 }; @@ -486,7 +485,7 @@ namespace gal::prometheus::gfx device_immediate_context_ = std::move(device_immediate_context); } - auto RendererD3D11::do_construct() noexcept -> bool + auto RendererD3D11::construct() noexcept -> bool { if (not create_blend_state()) { @@ -512,7 +511,7 @@ namespace gal::prometheus::gfx return true; } - auto RendererD3D11::do_destruct() noexcept -> void + auto RendererD3D11::destruct() noexcept -> void { // ComPtr blend_state_ = nullptr; @@ -546,70 +545,103 @@ namespace gal::prometheus::gfx device_ = nullptr; } - auto RendererD3D11::do_ready() const noexcept -> bool + auto RendererD3D11::create_texture(const Texture::data_type& data, const Texture::size_type size) noexcept -> texture_id_type { - if (device_ == nullptr or device_immediate_context_ == nullptr) - { - return false; - } + return upload_texture(data, size, D3D11_USAGE_DYNAMIC, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, 0); + } - if (blend_state_ == nullptr or rasterizer_state_ == nullptr or depth_stencil_state_ == nullptr) - { - return false; - } + auto RendererD3D11::update_texture(const texture_id_type id, std::span update_viewer) noexcept -> void + { + auto* srv = id_to_gpu_handle(id); + const auto it = textures_.find(srv); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != textures_.end(), "Invalid texture id"); - if (vertex_shader_ == nullptr or vertex_input_layout_ == nullptr or vertex_projection_matrix_ == nullptr) - { - return false; - } + auto* texture_2d = it->second; - if (pixel_shader_ == nullptr or pixel_font_sampler_ == nullptr) + // todo + if (update_viewer.size() > 16) { - return false; - } + D3D11_MAPPED_SUBRESOURCE mapped_resource{}; + if (const auto result = device_immediate_context_->Map(texture_2d, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource); result != S_OK) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + } + else + { + std::ranges::for_each( + update_viewer, + [ + this, + dest_begin = static_cast(mapped_resource.pData), + pitch = static_cast(mapped_resource.RowPitch / sizeof(Texture::element_type)) + ](const TextureViewer& viewer) noexcept -> void + { + const auto point = viewer.position(); + const auto size = viewer.size(); - return true; - } + const auto y_offset = static_cast(point.y) * pitch; + const auto x_offset = point.x; + auto* dest = dest_begin + y_offset + x_offset; - auto RendererD3D11::do_texture_create(const TextureDescriptor::data_view_type data, const TextureDescriptor::size_type size) noexcept -> texture_id_type - { - return upload_texture(data, size, D3D11_USAGE_DYNAMIC, D3D11_BIND_SHADER_RESOURCE, D3D11_CPU_ACCESS_WRITE, 0); - } + for (Texture::size_type::value_type y = 0; y < size.height; ++y) + { + const auto this_line_source = viewer.line(y); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_line_source.size() == size.width, "TextureViewer line size mismatch"); - auto RendererD3D11::do_texture_update(const TextureDescriptor& texture) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.id != invalid_texture_id, "Create texture first!"); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty != 0, "No need to update texture!"); + auto* this_line_dest = dest + static_cast(y) * pitch; - auto* srv = id_to_gpu_handle(texture.id); - const auto it = textures_.find(srv); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != textures_.end(), "Invalid texture id"); + std::ranges::copy(this_line_source, this_line_dest); + } + } + ); - auto* texture_2d = it->second; - D3D11_MAPPED_SUBRESOURCE mapped_resource{}; - if (const auto result = device_immediate_context_->Map( - texture_2d, - 0, - D3D11_MAP_WRITE_DISCARD, - 0, - &mapped_resource - ); result != S_OK) - { - // todo: error handling - GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); - return; + device_immediate_context_->Unmap(texture_2d, 0); + return; + } } - const auto* source = texture.data.get(); - const auto source_length = static_cast(texture.size.width) * texture.size.height; - std::ranges::copy(source, source + source_length, static_cast(mapped_resource.pData)); + std::ranges::for_each( + update_viewer, + [this, texture_2d](const TextureViewer& viewer) noexcept -> void + { + const auto point = viewer.position(); + const auto size = viewer.size(); + + const auto data = std::make_unique_for_overwrite(static_cast(size.width) * size.height); + for (Texture::size_type::value_type y = 0; y < size.height; ++y) + { + const auto this_line = viewer.line(y); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_line.size() == size.width, "TextureViewer line size mismatch"); + + std::ranges::copy(this_line, data.get() + static_cast(size.width) * y); + } - device_immediate_context_->Unmap(texture_2d, 0); + const D3D11_BOX box + { + .left = point.x, + .top = point.y, + .front = 0, + .right = point.x + size.width, + .bottom = point.y + size.height, + .back = 1 + }; + + device_immediate_context_->UpdateSubresource( + texture_2d, + 0, + &box, + data.get(), + static_cast(size.width * sizeof(Texture::element_type)), + 0 + ); + } + ); } - auto RendererD3D11::do_texture_destroy(const texture_id_type texture_id) noexcept -> void + auto RendererD3D11::destroy_texture(const texture_id_type id) noexcept -> void { - auto* srv = id_to_gpu_handle(texture_id); + auto* srv = id_to_gpu_handle(id); if (const auto it = textures_.find(srv); it != textures_.end()) { @@ -621,7 +653,7 @@ namespace gal::prometheus::gfx } } - auto RendererD3D11::do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void + auto RendererD3D11::present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void { auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = render_buffer_; diff --git a/src/gfx/extension/renderer_d3d11.hpp b/src/gfx/extension/renderer_d3d11.hpp index 5d081d7..0aabfd8 100644 --- a/src/gfx/extension/renderer_d3d11.hpp +++ b/src/gfx/extension/renderer_d3d11.hpp @@ -7,12 +7,12 @@ #include -#include +#include #include #include -namespace gal::prometheus::gfx +namespace gal::prometheus::gfx::extension { using Microsoft::WRL::ComPtr; @@ -56,8 +56,8 @@ namespace gal::prometheus::gfx [[nodiscard]] auto create_pixel_shader() noexcept -> bool; [[nodiscard]] auto upload_texture( - TextureDescriptor::data_view_type data, - TextureDescriptor::size_type size, + const Texture::data_type& data, + Texture::size_type size, D3D11_USAGE usage, std::uint32_t bind_flags, std::uint32_t cpu_access_flags, @@ -75,15 +75,13 @@ namespace gal::prometheus::gfx auto bind_device_context(ID3D11DeviceContext* device_immediate_context) noexcept -> void; auto bind_device_context(ComPtr device_immediate_context) noexcept -> void; - private: - auto do_construct() noexcept -> bool override; - auto do_destruct() noexcept -> void override; - [[nodiscard]] auto do_ready() const noexcept -> bool override; + auto construct() noexcept -> bool; + auto destruct() noexcept -> void; - auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type override; - auto do_texture_update(const TextureDescriptor& texture) noexcept -> void override; - auto do_texture_destroy(texture_id_type texture_id) noexcept -> void override; + auto create_texture(const Texture::data_type& data, Texture::size_type size) noexcept -> texture_id_type override; + auto update_texture(texture_id_type id, std::span update_viewer) noexcept -> void override; + auto destroy_texture(texture_id_type id) noexcept -> void override; - auto do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void override; + auto present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void override; }; } diff --git a/src/gfx/extension/renderer_d3d12.cpp b/src/gfx/extension/renderer_d3d12.cpp index e9cb431..a845030 100644 --- a/src/gfx/extension/renderer_d3d12.cpp +++ b/src/gfx/extension/renderer_d3d12.cpp @@ -14,7 +14,6 @@ #include -#include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE #include @@ -69,7 +68,7 @@ namespace } } -namespace gal::prometheus::gfx +namespace gal::prometheus::gfx::extension { auto RendererD3D12::create_root_signature() noexcept -> bool { @@ -359,8 +358,8 @@ namespace gal::prometheus::gfx auto RendererD3D12::upload_texture( const std::size_t index, - const TextureDescriptor::data_view_type data, - const TextureDescriptor::size_type size, + const Texture::data_type& data, + const Texture::size_type size, const bool record_resource ) noexcept -> texture_id_type { @@ -445,7 +444,7 @@ namespace gal::prometheus::gfx for (UINT i = 0; i < static_cast(size.height); ++i) { auto* dest = static_cast(mapped_data) + static_cast(upload_pitch * i); - auto* source = reinterpret_cast(data.data()) + static_cast(size.width * i * 4); + auto* source = reinterpret_cast(data.get()) + static_cast(size.width * i * 4); const auto length = size.width * 4; std::memcpy(dest, source, length); } @@ -647,7 +646,7 @@ namespace gal::prometheus::gfx command_list_ = std::move(command_list); } - auto RendererD3D12::do_construct() noexcept -> bool + auto RendererD3D12::construct() noexcept -> bool { if (not create_root_signature()) { @@ -666,10 +665,25 @@ namespace gal::prometheus::gfx return true; } - auto RendererD3D12::do_destruct() noexcept -> void + auto RendererD3D12::destruct() noexcept -> void { // ComPtr + // command_list_->ClearState(pipeline_state_.Get()); + // std::ignore = command_list_->Close(); + + pipeline_state_ = nullptr; + root_signature_ = nullptr; + + std::ranges::for_each( + textures_, + [](auto& texture) noexcept -> void + { + // ComPtr + texture.resource = nullptr; + } + ); textures_.clear(); + srv_descriptor_heap_ = nullptr; std::ranges::for_each( std::ranges::subrange{frame_resource_, num_frames_in_flight}, @@ -681,33 +695,11 @@ namespace gal::prometheus::gfx } ); - // command_list_->ClearState(pipeline_state_.Get()); - // std::ignore = command_list_->Close(); - - srv_descriptor_heap_ = nullptr; - pipeline_state_ = nullptr; - root_signature_ = nullptr; - command_list_ = nullptr; device_ = nullptr; } - auto RendererD3D12::do_ready() const noexcept -> bool - { - if (device_ == nullptr or command_list_ == nullptr) - { - return false; - } - - if (root_signature_ == nullptr or pipeline_state_ == nullptr or srv_descriptor_heap_ == nullptr) - { - return false; - } - - return true; - } - - auto RendererD3D12::do_texture_create(const TextureDescriptor::data_view_type data, const TextureDescriptor::size_type size) noexcept -> texture_id_type + auto RendererD3D12::create_texture(const Texture::data_type& data, const Texture::size_type size) noexcept -> texture_id_type { auto it = std::ranges::find(textures_, nullptr, &texture_type::resource); if (it == textures_.end()) @@ -754,24 +746,262 @@ namespace gal::prometheus::gfx return upload_texture(index, data, size); } - auto RendererD3D12::do_texture_update(const TextureDescriptor& texture) noexcept -> void + // todo + auto RendererD3D12::update_texture(texture_id_type id, std::span update_viewer) noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.id != invalid_texture_id, "Create texture first!"); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.dirty, "No need to update texture!"); - - const auto index = id_to_gpu_handle(texture.id); + const auto index = id_to_gpu_handle(id); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < srv_max_size_); - auto& texture_gpu = textures_[index]; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_gpu.resource != nullptr); - // todo - do_texture_destroy(texture.id); - std::ignore = upload_texture(index, {texture.data.get(), static_cast(texture.size.width) * texture.size.height}, texture.size); + auto& texture = textures_[index]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture.resource != nullptr); + + const auto texture_desc = texture.resource->GetDesc(); + const auto texture_size = Texture::size_type{static_cast(texture_desc.Width), texture_desc.Height}; + + const auto upload_pitch = (static_cast(texture_size.width * 4) + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + const auto upload_size = static_cast(texture_size.height) * upload_pitch; + + constexpr D3D12_HEAP_PROPERTIES upload_heap_properties + { + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0, + .VisibleNodeMask = 0 + }; + + const D3D12_RESOURCE_DESC upload_resource_desc + { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = static_cast(upload_size), + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + ComPtr upload_buffer; + check_hr_error( + device_->CreateCommittedResource( + &upload_heap_properties, + D3D12_HEAP_FLAG_NONE, + &upload_resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(upload_buffer.GetAddressOf()) + ) + ); + + void* mapped_data = nullptr; + D3D12_RANGE range{.Begin = 0, .End = upload_size}; + if (not check_hr_error(upload_buffer->Map(0, &range, &mapped_data))) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + } + else + { + std::ranges::for_each( + update_viewer, + [ + this, + dest_begin = static_cast(mapped_data), + pitch = static_cast(upload_pitch / sizeof(Texture::element_type)) + ](const TextureViewer& viewer) noexcept -> void + { + const auto point = viewer.position(); + const auto size = viewer.size(); + + const auto y_offset = static_cast(point.y) * pitch; + const auto x_offset = point.x; + auto* dest = dest_begin + y_offset + x_offset; + + for (Texture::size_type::value_type y = 0; y < size.height; ++y) + { + const auto this_line_source = viewer.line(y); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_line_source.size() == size.width, "TextureViewer line size mismatch"); + + auto* this_line_dest = dest + static_cast(y) * pitch; + + std::ranges::copy(this_line_source, this_line_dest); + } + } + ); + + upload_buffer->Unmap(0, &range); + } + + D3D12_RESOURCE_BARRIER barrier_before + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = { + .pResource = texture.resource.Get(), + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + .StateAfter = D3D12_RESOURCE_STATE_COPY_DEST + } + }; + + ComPtr command_allocator; + if (not check_hr_error( + device_->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(command_allocator.GetAddressOf()) + ) + )) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + ComPtr command_list; + if (not check_hr_error( + device_->CreateCommandList( + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + command_allocator.Get(), + nullptr, + IID_PPV_ARGS(command_list.GetAddressOf()) + ) + )) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + command_list->ResourceBarrier(1, &barrier_before); + + for (const auto& viewer: update_viewer) + { + const auto point = viewer.position(); + const auto size = viewer.size(); + + D3D12_TEXTURE_COPY_LOCATION src_location + { + .pResource = upload_buffer.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + .PlacedFootprint = { + .Offset = static_cast(upload_pitch * point.y + point.x * 4), + .Footprint = { + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .Width = static_cast(size.width), + .Height = static_cast(size.height), + .Depth = 1, + .RowPitch = upload_pitch + } + } + }; + + D3D12_TEXTURE_COPY_LOCATION dst_location + { + .pResource = texture.resource.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 0 + }; + + D3D12_BOX src_box + { + .left = 0, + .top = 0, + .front = 0, + .right = static_cast(size.width), + .bottom = static_cast(size.height), + .back = 1 + }; + + command_list->CopyTextureRegion(&dst_location, point.x, point.y, 0, &src_location, &src_box); + } + + D3D12_RESOURCE_BARRIER barrier_after + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = + { + .pResource = texture.resource.Get(), + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST, + .StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + } + }; + command_list->ResourceBarrier(1, &barrier_after); + + if (not check_hr_error(command_list->Close())) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + constexpr D3D12_COMMAND_QUEUE_DESC command_queue_desc + { + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = 0, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 1 + }; + + ComPtr command_queue; + if (not check_hr_error( + device_->CreateCommandQueue( + &command_queue_desc, + IID_PPV_ARGS(command_queue.GetAddressOf()) + ) + )) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + ID3D12CommandList* command_lists[]{command_list.Get()}; + command_queue->ExecuteCommandLists(1, command_lists); + + ComPtr fence; + if (not check_hr_error( + device_->CreateFence( + 0, + D3D12_FENCE_FLAG_NONE, + IID_PPV_ARGS(fence.GetAddressOf()) + ) + )) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + + constexpr UINT64 fence_value = 1; + if (not check_hr_error(command_queue->Signal(fence.Get(), fence_value))) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + if (fence->GetCompletedValue() < fence_value) + { + HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (not check_hr_error(fence->SetEventOnCompletion(fence_value, event))) + { + // todo: error handling + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + return; + } + WaitForSingleObject(event, INFINITE); + CloseHandle(event); + } } - auto RendererD3D12::do_texture_destroy(const texture_id_type texture_id) noexcept -> void + auto RendererD3D12::destroy_texture(const texture_id_type id) noexcept -> void { - const auto index = id_to_gpu_handle(texture_id); + const auto index = id_to_gpu_handle(id); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < srv_max_size_); auto& texture = textures_[index]; @@ -780,7 +1010,7 @@ namespace gal::prometheus::gfx texture.resource = nullptr; } - auto RendererD3D12::do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void + auto RendererD3D12::present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void { const auto [total_vertex_count, total_index_count] = [&]() noexcept { diff --git a/src/gfx/extension/renderer_d3d12.hpp b/src/gfx/extension/renderer_d3d12.hpp index eaa5d93..ee46f76 100644 --- a/src/gfx/extension/renderer_d3d12.hpp +++ b/src/gfx/extension/renderer_d3d12.hpp @@ -5,14 +5,12 @@ #pragma once -#include - -#include +#include #include #include -namespace gal::prometheus::gfx +namespace gal::prometheus::gfx::extension { using Microsoft::WRL::ComPtr; @@ -56,12 +54,7 @@ namespace gal::prometheus::gfx [[nodiscard]] auto create_pipeline_state() noexcept -> bool; [[nodiscard]] auto create_srv_descriptor_heap(UINT num) noexcept -> bool; - [[nodiscard]] auto upload_texture( - std::size_t index, - TextureDescriptor::data_view_type data, - TextureDescriptor::size_type size, - bool record_resource = true - ) noexcept -> texture_id_type; + [[nodiscard]] auto upload_texture(std::size_t index, const Texture::data_type& data, Texture::size_type size, bool record_resource = true) noexcept -> texture_id_type; public: RendererD3D12() noexcept; @@ -73,15 +66,13 @@ namespace gal::prometheus::gfx auto bind_command_list(ID3D12GraphicsCommandList* command_list) noexcept -> void; auto bind_command_list(ComPtr command_list) noexcept -> void; - private: - auto do_construct() noexcept -> bool override; - auto do_destruct() noexcept -> void override; - [[nodiscard]] auto do_ready() const noexcept -> bool override; + auto construct() noexcept -> bool; + auto destruct() noexcept -> void; - auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type override; - auto do_texture_update(const TextureDescriptor& texture) noexcept -> void override; - auto do_texture_destroy(texture_id_type texture_id) noexcept -> void override; + auto create_texture(const Texture::data_type& data, Texture::size_type size) noexcept -> texture_id_type override; + auto update_texture(texture_id_type id, std::span update_viewer) noexcept -> void override; + auto destroy_texture(texture_id_type id) noexcept -> void override; - auto do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void override; + auto present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void override; }; } diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index 7f5e974..3c2bce7 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -5,991 +5,9 @@ #pragma once -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace gal::prometheus::gfx -{ - // ========================================================= - // CONTEXT - // ========================================================= - - class Context; - - // ========================================================= - // PRIMITIVE - // ========================================================= - - using point_type = primitive::basic_point_2d; - using uv_type = primitive::basic_point_2d; - using color_type = primitive::basic_color; - using vertex_type = primitive::basic_vertex; - using index_type = std::uint16_t; - - using extent_type = primitive::basic_extent_2d; - using rect_type = primitive::basic_rect_2d; - using circle_type = primitive::basic_circle_2d; - using ellipse_type = primitive::basic_ellipse_2d; - - // ========================================================= - // TEXTURE - // ========================================================= - - // D3D11: ID3D11ShaderResourceView - // D3D12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + constant offset - using texture_id_type = std::uintptr_t; - constexpr texture_id_type invalid_texture_id{0}; - - class TextureDescriptor final - { - public: - // 32-bits (RGBA) - using element_type = std::uint32_t; - // size.width * size.height - using data_type = std::unique_ptr; - using data_view_type = std::span; - - using size_type = primitive::basic_extent_2d; - - // ============================== - // CPU side - // ============================== - - data_type data; - size_type size; - - static_assert(sizeof(texture_id_type) == sizeof(std::uint64_t)); - std::uint64_t dirty : 1; - - // ============================== - // GPU side - // ============================== - - std::uint64_t id : 63; - }; - - // ========================================================= - // GLYPH / FONT - // ========================================================= - - // index - using font_id_type = std::uint32_t; - constexpr font_id_type invalid_font_id{std::numeric_limits::max()}; - - enum class GlyphFlag : std::uint8_t - { - NONE = 0, - BOLD = 1 << 0, - ITALIC = 1 << 1, - }; - - class GlyphParser - { - public: - class [[nodiscard]] FontDescriptor final - { - public: - using element_type = std::uint8_t; - using data_type = std::unique_ptr; - using data_view_type = std::span; - - using size_type = std::uint32_t; - - std::string_view identifier; - font_id_type id; - - [[nodiscard]] constexpr static auto error() noexcept -> FontDescriptor - { - return {.identifier = {}, .id = invalid_font_id}; - } - - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return id != invalid_font_id; - } - - [[nodiscard]] constexpr auto valid() const noexcept -> bool - { - return operator bool(); - } - }; - - class GlyphCode final - { - public: - std::uint32_t codepoint; - std::uint32_t size; - GlyphFlag flag; - - constexpr GlyphCode(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept - : codepoint{codepoint}, - size{size}, - flag{flag} {} - }; - - class [[nodiscard]] GlyphDescriptor final - { - public: - // The offset of the bitmap may be negative (signed) - using rect_type = primitive::basic_rect_2d; - - // Bitmap infos of this glyph - rect_type rect; - float advance_x; - bool visible; - bool colored; - - // FIXME-OPT: - // We could have pre-specified an area to write glyph data to, - // but that would have meant exposing the texture implementation details to the outside world, - // and its implementation will be coupled with the GlyphParser, so we will leave the choice to the GlyphParser, - // which can find a way to reduce the number of dynamic allocations in the internal. - // note: - // We always assume that the data length of @c data is not less than @c rect.size.width * @c rect.size.height - TextureDescriptor::data_type data; - - [[nodiscard]] constexpr static auto error() noexcept -> GlyphDescriptor - { - return {.rect = {-0xbad, -0xbad, 0xc0ffee, 0xc0ffee}, .advance_x = -1, .visible = false, .colored = false, .data = nullptr}; - } - - [[nodiscard]] constexpr explicit operator bool() const noexcept - { - return data != nullptr; - } - - [[nodiscard]] constexpr auto valid() const noexcept -> bool - { - return operator bool(); - } - }; - - GlyphParser(const GlyphParser&) noexcept = delete; - GlyphParser(GlyphParser&&) noexcept = default; - auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; - auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; - - virtual ~GlyphParser() noexcept; - - GlyphParser() noexcept = default; - - /** - * @brief Load font data from @c path, return id of font - * @param path Font path - * @return id of the font, or invalid_font_id if failed to load - */ - [[nodiscard]] virtual auto load(const std::filesystem::path& path) noexcept -> FontDescriptor = 0; - - /** - * @brief Determines whether the target font contains the glyphs of the specified codepoint - * @param id The id returned by loading the font from the previous load - * @param codepoint The codepoint of the glyph to be checked - * @return Exists or not - */ - [[nodiscard]] virtual auto has_glyph(font_id_type id, std::uint32_t codepoint) const noexcept -> bool = 0; - - /** - * @brief Get the glyph information of the specified size (and style) of the target codepoint, if it does not exist then data is a null pointer - * @param id The id returned by loading the font from the previous load - * @param code {codepoint, size, flag} - * @return The glyph information of the specified size (and style) of the target codepoint - */ - [[nodiscard]] virtual auto parse(font_id_type id, const GlyphCode& code) noexcept -> GlyphDescriptor = 0; - }; - - // ========================================================= - // RENDERER LIST - // ========================================================= - - enum class RenderListFlag : std::uint8_t - { - NONE = 0, - ANTI_ALIASED_LINE = 1 << 0, - ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, - ANTI_ALIASED_FILL = 1 << 2, - - DEFAULT = ANTI_ALIASED_LINE | ANTI_ALIASED_LINE_USE_TEXTURE | ANTI_ALIASED_FILL, - }; - - enum class RenderRectFlag : std::uint8_t - { - NONE = 0, - // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_LEFT_TOP = 1 << 1, - // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_RIGHT_TOP = 1 << 2, - // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_LEFT_BOTTOM = 1 << 3, - // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see RenderList::path_rect - // @see RenderList::rect - // @see RenderList::rect_filled - ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, - // disable rounding on all corners (when rounding > 0.0f) - ROUND_CORNER_NONE = 1 << 5, - - ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, - ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, - ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - - ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, - ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, - }; - - enum class RenderArcFlag : std::uint8_t - { - // [0~3) - Q1 = 1 << 0, - // [3~6) - Q2 = 1 << 1, - // [6~9) - Q3 = 1 << 2, - // [9~12) - Q4 = 1 << 3, - - RIGHT_TOP = Q1, - LEFT_TOP = Q2, - LEFT_BOTTOM = Q3, - RIGHT_BOTTOM = Q4, - TOP = Q1 | Q2, - BOTTOM = Q3 | Q4, - LEFT = Q2 | Q3, - RIGHT = Q1 | Q4, - ALL = Q1 | Q2 | Q3 | Q4, - - // [3, 0) - Q1_CLOCK_WISH = 1 << 4, - // [6, 3) - Q2_CLOCK_WISH = 1 << 5, - // [9, 6) - Q3_CLOCK_WISH = 1 << 6, - // [12, 9) - Q4_CLOCK_WISH = 1 << 7, - - RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, - LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, - LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, - RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, - TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, - BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, - LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, - RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, - ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, - }; - - class RenderListSharedData - { - public: - using circle_segment_count_type = std::uint8_t; - constexpr static std::size_t circle_segment_counts_count = 64; - using circle_segment_counts_type = std::array; - - constexpr static std::uint32_t circle_segments_min = 4; - constexpr static std::uint32_t circle_segments_max = 512; - - constexpr static std::size_t vertex_sample_points_count = 48; - using vertex_sample_points_type = std::array; - - constexpr static std::size_t baked_line_uv_count = 64; - using baked_line_uvs_type = std::array; - - circle_segment_counts_type circle_segment_counts; - - vertex_sample_points_type vertex_sample_points; - - baked_line_uvs_type baked_line_uvs; - point_type white_pixel_uv; - - // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. - // Decrease for higher quality but more geometry. - float circle_segment_max_error; - // Cutoff radius after which arc drawing will fall back to slower `path_arc` - float arc_fast_radius_cutoff; - // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. - // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float curve_tessellation_tolerance; - - // -------------------------------------------------- - - RenderListSharedData() noexcept; - - // -------------------------------------------------- - - [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; - - [[nodiscard]] auto vertex_sample_point(std::size_t index) const noexcept -> const point_type&; - - // -------------------------------------------------- - - auto set_circle_tessellation_max_error(float max_error) noexcept -> void; - - auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; - }; - - class RenderData final - { - public: - using size_type = std::uint32_t; - - struct [[nodiscard]] command_type - { - rect_type scissor; - texture_id_type texture; - - // ======================= - - // set by RenderList::index_list.size() - // start offset in @c RenderList::index_list - size_type index_offset; - // set by RenderList::draw_xxx - // number of indices (multiple of 3) to be rendered as triangles - size_type element_count; - }; - - using vertex_list_type = std::vector; - using index_list_type = std::vector; - using command_list_type = std::vector; - - std::reference_wrapper vertex_list; - std::reference_wrapper index_list; - std::reference_wrapper command_list; - }; - - using render_data_list_type = std::vector; - - class RenderList - { - friend class Context; - friend class Renderer; - - public: - class RenderListContext; - - class Painter final - { - public: - using path_list_type = std::vector; - - private: - memory::RefWrapper render_list_; - path_list_type path_list_; - - auto draw_polygon_line(color_type color, float thickness, bool close) noexcept -> void; - auto draw_polygon_line_aa(color_type color, float thickness, bool close) noexcept -> void; - auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; - auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; - - public: - Painter(const Painter&) noexcept = delete; - Painter(Painter&&) noexcept = default; - auto operator=(const Painter&) noexcept -> Painter& = delete; - auto operator=(Painter&&) noexcept -> Painter& = default; - - ~Painter() noexcept; - - explicit Painter(RenderList& render_list, std::size_t reserve_point) noexcept; - - auto clear() noexcept -> Painter&; - auto reserve_extra(std::size_t size) noexcept -> Painter&; - auto reserve(std::size_t size) noexcept -> Painter&; - - auto pin(const point_type& point) noexcept -> Painter&; - - auto line(const point_type& from, const point_type& to) noexcept -> Painter&; - auto triangle(const point_type& a, const point_type& b, const point_type& c) noexcept -> Painter&; - auto quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; - auto rect(const rect_type& rect, float rounding, RenderRectFlag flag) noexcept -> Painter&; - auto rect(const rect_type::point_type& left_top, const rect_type::extent_type& extent, float rounding, RenderRectFlag flag) noexcept -> Painter&; - auto rect(const rect_type::point_type& left_top, const rect_type::point_type& right_bottom, float rounding, RenderRectFlag flag) noexcept -> Painter&; - auto circle_n(const circle_type& circle, std::uint32_t segments) noexcept -> Painter&; - auto circle_n(const circle_type::point_type& center, circle_type::radius_value_type radius, std::uint32_t segments) noexcept -> Painter&; - auto circle(const circle_type& circle) noexcept -> Painter&; - auto circle(const circle_type::point_type& center, circle_type::radius_value_type radius) noexcept -> Painter&; - auto ellipse_n(const ellipse_type& ellipse, std::uint32_t segments) noexcept -> Painter&; - auto ellipse_n(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation, std::uint32_t segments) noexcept -> Painter&; - auto ellipse(const ellipse_type& ellipse) noexcept -> Painter&; - auto ellipse(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation) noexcept -> Painter&; - auto arc_fast(const circle_type& circle, int sample_point_from, int sample_point_to) noexcept -> Painter&; - auto arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> Painter&; - auto arc_n(const circle_type& circle, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; - auto arc(const circle_type& circle, float degree_from, float degree_to) noexcept -> Painter&; - auto arc_n(const ellipse_type& ellipse, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; - auto bezier_cubic_n(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, std::uint32_t segments) noexcept -> Painter&; - auto bezier_cubic(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; - auto bezier_quadratic_n(const point_type& p1, const point_type& p2, const point_type& p3, std::uint32_t segments) noexcept -> Painter&; - auto bezier_quadratic(const point_type& p1, const point_type& p2, const point_type& p3) noexcept -> Painter&; - - /** - * @brief Draws the fill shape according to the specified path points - * @param color Shape color - */ - auto stroke(color_type color) noexcept -> void; - - /** - * @brief Plot the corresponding lines/shapes according to the specified path points - * @param color Line/shape color - * @param thickness Line thickness - * @param close Whether the graph is closed, in other words, whether the first point should be connected to the last point - */ - auto stroke(color_type color, float thickness, bool close) noexcept -> void; - }; - - private: - memory::UniquePointer context_; - - explicit RenderList(Context& context, RenderListFlag flag) noexcept; - - public: - RenderList(const RenderList&) noexcept = delete; - RenderList(RenderList&&) noexcept; //= default; - auto operator=(const RenderList&) noexcept -> RenderList& = delete; - auto operator=(RenderList&&) noexcept -> RenderList&; // = default; - - ~RenderList() noexcept; - - auto reset() noexcept -> void; - - auto set_flag(RenderListFlag flag) noexcept -> void; - - // ---------------------------------------------------------------------------- - // SCISSOR & TEXTURE - - auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; - - auto pop_scissor() noexcept -> void; - - auto push_texture(texture_id_type texture) noexcept -> void; - - auto pop_texture() noexcept -> void; - - // ---------------------------------------------------------------------------- - // PAINTER - - [[nodiscard]] auto painter(const Painter::path_list_type::size_type reserve_point = 0) noexcept -> Painter - { - return Painter{*this, reserve_point}; - } - - // ---------------------------------------------------------------------------- - // PRIMITIVE - - auto line( - const point_type& from, - const point_type& to, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto triangle( - const point_type& a, - const point_type& b, - const point_type& c, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto triangle_filled( - const point_type& a, - const point_type& b, - const point_type& c, - color_type color - ) noexcept -> void; - - auto quadrilateral( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto quadrilateral_filled( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color - ) noexcept -> void; - - auto rect( - const rect_type& rect, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, - float thickness = 1.f - ) noexcept -> void; - - auto rect( - const point_type& left_top, - const rect_type::extent_type& extent, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, - float thickness = 1.f - ) noexcept -> void; - - auto rect( - const rect_type::point_type& left_top, - const rect_type::point_type& right_bottom, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, - float thickness = 1.f - ) noexcept -> void; - - auto rect_filled( - const rect_type& rect, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const rect_type::extent_type& extent, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL - ) noexcept -> void; - - auto rect_filled( - const rect_type::point_type& left_top, - const rect_type::point_type& right_bottom, - color_type color, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL - ) noexcept -> void; - - auto rect_filled( - const rect_type& rect, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - - auto rect_filled( - const point_type& left_top, - const rect_type::extent_type& extent, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - - auto rect_filled( - const rect_type::point_type& left_top, - const rect_type::point_type& right_bottom, - color_type color_left_top, - color_type color_right_top, - color_type color_left_bottom, - color_type color_right_bottom - ) noexcept -> void; - - auto circle_n( - const circle_type& circle, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto circle_n( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto circle_n_filled( - const circle_type& circle, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto circle_n_filled( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto circle( - const circle_type& circle, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto circle( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto circle_filled( - const circle_type& circle, - color_type color - ) noexcept -> void; - - auto circle_filled( - const circle_type::point_type& center, - circle_type::radius_value_type radius, - color_type color - ) noexcept -> void; - - auto ellipse_n( - const ellipse_type& ellipse, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_n_filled( - const ellipse_type& ellipse, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto ellipse_n_filled( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color, - std::uint32_t segments - ) noexcept -> void; - - auto ellipse( - const ellipse_type& ellipse, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto ellipse_filled( - const ellipse_type& ellipse, - color_type color - ) noexcept -> void; - - auto ellipse_filled( - const ellipse_type::point_type& center, - const ellipse_type::radius_type& radius, - ellipse_type::rotation_value_type rotation, - color_type color - ) noexcept -> void; - - auto bezier_cubic_n( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_cubic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_quadratic_n( - const point_type& p1, - const point_type& p2, - const point_type& p3, - color_type color, - std::uint32_t segments, - float thickness = 1.f - ) noexcept -> void; - - auto bezier_quadratic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - color_type color, - float thickness = 1.f - ) noexcept -> void; - - // ---------------------------------------------------------------------------- - // TEXT - - auto text( - std::string_view utf8_text, - std::uint32_t font_size, - const point_type& point, - color_type color, - float wrap_width = 99999999.f - ) noexcept -> void; - - auto text( - std::string_view utf8_text, - std::uint32_t font_size, - const point_type& point, - color_type color, - GlyphFlag flag, - float wrap_width = 99999999.f - ) noexcept -> void; - - // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text - auto text_size( - std::string_view utf8_text, - std::uint32_t font_size, - float wrap_width = 99999999.f - ) noexcept -> extent_type; - - // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text - auto text_size( - std::string_view utf8_text, - std::uint32_t font_size, - GlyphFlag flag, - float wrap_width = 99999999.f - ) noexcept -> extent_type; - - // ---------------------------------------------------------------------------- - // IMAGE - - // p1________ p2 - // | | - // | | - // p4|_______| p3 - auto image( - texture_id_type texture_id, - const rect_type::point_type& display_p1, - const rect_type::point_type& display_p2, - const rect_type::point_type& display_p3, - const rect_type::point_type& display_p4, - const uv_type& uv_p1 = {0, 0}, - const uv_type& uv_p2 = {1, 0}, - const uv_type& uv_p3 = {1, 1}, - const uv_type& uv_p4 = {0, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect = {0, 0, 1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const rect_type::point_type& display_left_top, - const rect_type::extent_type& display_size, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image( - texture_id_type texture_id, - const rect_type::point_type& display_left_top, - const rect_type::point_type& display_right_bottom, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const rect_type& display_rect, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::NONE, - const rect_type& uv_rect = {0, 0, 1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const rect_type::point_type& display_left_top, - const rect_type::extent_type& display_size, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::NONE, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - - auto image_rounded( - texture_id_type texture_id, - const rect_type::point_type& display_left_top, - const rect_type::point_type& display_right_bottom, - float rounding = .0f, - RenderRectFlag flag = RenderRectFlag::NONE, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - color_type color = primitive::colors::white - ) noexcept -> void; - }; - - // ========================================================= - // RENDERER - // ========================================================= - - class Renderer - { - friend Context; - - public: - Renderer(const Renderer&) noexcept = delete; - Renderer(Renderer&&) noexcept = default; - auto operator=(const Renderer&) noexcept -> Renderer& = delete; - auto operator=(Renderer&&) noexcept -> Renderer& = default; - - virtual ~Renderer() noexcept; - - protected: - Renderer() noexcept; - - public: - /** - * @brief Constructs the renderer, which assumes that all the parameters needed for the renderer have been set - */ - [[nodiscard]] auto construct() noexcept -> bool; - - /** - * @brief Destructs the renderer - */ - auto destruct() noexcept -> void; - - /** - * @brief Whether the current renderer is ready (@c construct was called and succeeded) - */ - [[nodiscard]] auto ready() const noexcept -> bool; - - /** - * @brief - * @note @c new_frame -> @c present -> @c end_frame - */ - auto new_frame(Context& context) noexcept -> void; - - /** - * @brief - * @note @c new_frame -> @c present -> @c end_frame - */ - auto present(Context& context, const extent_type& display_size) noexcept -> void; - - /** - * @brief - * @note @c new_frame -> @c present -> @c end_frame - */ - auto end_frame(Context& context) noexcept -> void; - - private: - virtual auto do_construct() noexcept -> bool = 0; - virtual auto do_destruct() noexcept -> void = 0; - [[nodiscard]] virtual auto do_ready() const noexcept -> bool = 0; - - [[nodiscard]] virtual auto do_texture_create(TextureDescriptor::data_view_type data, TextureDescriptor::size_type size) noexcept -> texture_id_type = 0; - virtual auto do_texture_update(const TextureDescriptor& texture) noexcept -> void = 0; - virtual auto do_texture_destroy(texture_id_type texture_id) noexcept -> void = 0; - - virtual auto do_present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void = 0; - }; - - // ========================================================= - // CONTEXT - // ========================================================= - - [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context*; - auto destroy_context(Context& context) noexcept -> void; - auto destroy_context(Context* context) noexcept -> void; - - // ========================================================= - // GLYPH PARSER - // ========================================================= - - /** - * @brief Setting the glyph parser of context - * @return Previous glyph parser, or nullptr if newly set - */ - auto set_glyph_parser(Context& context, std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; - - // ========================================================= - // RENDERER - // ========================================================= - - /** - * @brief Setting the renderer of context - * @return Previous renderer, or nullptr if newly set - */ - auto set_renderer(Context& context, std::shared_ptr renderer) noexcept -> std::shared_ptr; - - // ========================================================= - // FONT - // ========================================================= - - /** - * @brief Load font from the specified path, assuming the path is a valid font file - * @note *Must* ensure that at least one font is added before calling @c Renderer::new_frame - */ - auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void; - - // ========================================================= - // RENDER LIST - // ========================================================= - - auto new_render_list(Context& context, RenderListFlag flag = RenderListFlag::DEFAULT) noexcept -> RenderList&; -} // namespace gal::prometheus::gfx - -namespace gal::prometheus::meta::user_defined -{ - template<> - struct enum_is_flag : std::true_type {}; - - template<> - struct enum_is_flag : std::true_type {}; - - template<> - struct enum_is_flag : std::true_type {}; - - template<> - struct enum_is_flag : std::true_type {}; -} // namespace gal::prometheus::meta::user_defined +#include +#include +#include +#include +#include +#include diff --git a/src/gfx/glyph.cpp b/src/gfx/glyph.cpp new file mode 100644 index 0000000..31d93d3 --- /dev/null +++ b/src/gfx/glyph.cpp @@ -0,0 +1,11 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace gal::prometheus::gfx +{ + GlyphParser::~GlyphParser() noexcept = default; +} diff --git a/src/gfx/glyph.hpp b/src/gfx/glyph.hpp new file mode 100644 index 0000000..f2956c3 --- /dev/null +++ b/src/gfx/glyph.hpp @@ -0,0 +1,111 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include + +namespace gal::prometheus::gfx +{ + enum class FontLoadResult : std::uint8_t + { + SUCCESS = 0, + + FILE_ALREADY_LOADED = 1 << 0, + FILE_NOT_FOUND = 1 << 1, + INVALID_FONT_FORMAT = 1 << 2, + OUT_OF_MEMORY = 1 << 3, + + UNKNOWN_ERROR = 1 << 7, + }; + + enum class GlyphFlag : std::uint8_t + { + NONE = 0, + BOLD = 1 << 0, + ITALIC = 1 << 1, + }; + + class GlyphCode final + { + public: + std::uint32_t codepoint; + std::uint32_t size; + GlyphFlag flag; + + constexpr GlyphCode(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept + : codepoint{codepoint}, + size{size}, + flag{flag} {} + }; + + class GlyphDescriptor final + { + public: + // The offset of the bitmap may be negative (signed) + using rect_type = primitive::basic_rect_2d; + // Texture::data_type data; + using data_type = std::unique_ptr; + + // Bitmap infos of this glyph + rect_type rect; + float advance_x; + + bool visible; + bool colored; + + // note: + // - visible: We always assume that the data length of @c data is not less than @c rect.size.width * @c rect.size.height + // - invisible: nullptr (1.currently loaded font does not contain the specified codepoint, 2.it only affects the layout and does not need to be rendered, e.g. line breaks) + data_type data; + }; + + class GlyphParser + { + public: + GlyphParser(const GlyphParser&) noexcept = delete; + GlyphParser(GlyphParser&&) noexcept = default; + auto operator=(const GlyphParser&) noexcept -> GlyphParser& = delete; + auto operator=(GlyphParser&&) noexcept -> GlyphParser& = default; + + virtual ~GlyphParser() noexcept; + + protected: + GlyphParser() noexcept = default; + + public: + /** + * @brief Load font data from @c path, return id of font + * @param path Font path + * @return id of the font, or invalid_font_id if failed to load + */ + [[nodiscard]] virtual auto load(std::string_view path) noexcept -> FontLoadResult = 0; + + /** + * @brief Determines whether the target font contains the glyphs of the specified codepoint + * @param codepoint The codepoint of the glyph to be checked + * @return Exists or not + */ + [[nodiscard]] virtual auto has_glyph(std::uint32_t codepoint) const noexcept -> bool = 0; + + /** + * @brief Get the glyph information of the specified size (and style) of the target codepoint + * @param code {codepoint, size, flag} + * @return The glyph information of the specified size (and style) of the target codepoint + */ + [[nodiscard]] virtual auto parse(const GlyphCode& code) noexcept -> GlyphDescriptor = 0; + }; +} + +// ReSharper disable once CppRedundantNamespaceDefinition +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type {}; +} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/internal/context.cpp b/src/gfx/internal/context.cpp index f6f7aa3..2b95a7c 100644 --- a/src/gfx/internal/context.cpp +++ b/src/gfx/internal/context.cpp @@ -5,598 +5,88 @@ #include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +#include namespace gal::prometheus::gfx { - // ========================================================= - // TEXTURE - // ========================================================= + Context::Context(Context&&) noexcept = default; - auto TextureContext::root_id() const noexcept -> texture_atlas_id_type - { - std::ignore = this; - return 0; - } - - TextureContext::TextureContext() noexcept - { - // root atlas - constexpr Texture::size_type root_texture_atlas_size{2048, 2048}; - texture_atlas_list_.emplace_back(root_texture_atlas_size); - } - - auto TextureContext::initialize(RenderListSharedData& shared_data) noexcept -> void - { - // ======================================== - // BAKE LINES (AA) - // ======================================== - { - constexpr std::uint32_t white_color = 0xff'ff'ff'ff; - constexpr auto aa_width = static_cast(RenderListSharedData::baked_line_uv_count); - constexpr auto aa_height = static_cast(RenderListSharedData::baked_line_uv_count); - constexpr auto aa_size = Texture::size_type{aa_width, aa_height}; - - const auto texture_atlas_id = root_id(); - auto& texture = this->select(texture_atlas_id); - - // baked line rect area: - // white pixel - // ◿ - const auto borrowed_texture = texture.select(aa_size); - borrowed_texture.fill(0); - - const auto aa_point = borrowed_texture.position(); - const auto aa_uv_scale = texture.uv(); - - // white pixel - { - // LINE 0, 2 pixels - borrowed_texture.fill(0, 2, white_color); - // LINE 1, 2 pixels - borrowed_texture.fill(1, 2, white_color); - - const auto uv_x = static_cast(static_cast(aa_point.x) + 1.0f) * aa_uv_scale.width; - const auto uv_y = static_cast(static_cast(aa_point.y) + 1.0f) * aa_uv_scale.height; - - shared_data.white_pixel_uv = {uv_x, uv_y}; - } - - // ◿ - for (Texture::size_type::value_type y = 1; y < aa_height; ++y) - { - const auto line_width = y; - const auto offset = aa_width - line_width; - - borrowed_texture.fill(y, offset, line_width, white_color); - - const auto p_x = aa_point.x + offset; - const auto p_y = aa_point.y + y; - const auto width = line_width; - constexpr auto height = .5f; - - const auto uv_x = static_cast(p_x) * aa_uv_scale.width; - const auto uv_y = static_cast(p_y) * aa_uv_scale.height; - const auto uv_width = static_cast(width) * aa_uv_scale.width; - const auto uv_height = static_cast(height) * aa_uv_scale.height; - - shared_data.baked_line_uvs[y] = {uv_x, uv_y, uv_width, uv_height}; - } - } - } - - auto TextureContext::root() noexcept -> Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); - - return texture_atlas_list_[root_id()]; - } - - auto TextureContext::root() const noexcept -> const Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture_atlas_list_.empty()); - - return texture_atlas_list_[root_id()]; - } - - auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) noexcept -> Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); - - return texture_atlas_list_[texture_atlas_id]; - } - - auto TextureContext::select(const texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < texture_atlas_list_.size()); - - return texture_atlas_list_[texture_atlas_id]; - } - - auto TextureContext::write( - const texture_atlas_id_type texture_atlas_id, - const Texture::data_view_type data, - const Texture::size_type size - ) noexcept -> primitive::basic_rect_2d - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() >= static_cast(size.width) * size.height); - - auto& texture = this->select(texture_atlas_id); - const auto texture_uv = texture.uv(); - - const auto borrowed_texture = texture.select(size); - borrowed_texture.fill(data); - - const auto position = borrowed_texture.position(); - return {position.to() * texture_uv, size.to() * texture_uv}; - } - - auto TextureContext::write( - const Texture::data_view_type data, - const Texture::size_type size - ) noexcept -> random_write_result_type - { - // todo - const auto id = root_id(); - const auto uv = this->write(id, data, size); - - return {.texture_atlas_id = id, .uv = uv}; - } - - auto TextureContext::upload(const Context& context) noexcept -> void - { - auto renderer_accessor = context.renderer_accessor(); - - std::ranges::for_each( - texture_atlas_list_, - [&renderer_accessor](auto& texture) mutable noexcept -> void - { - if (not texture.uploaded()) - { - renderer_accessor.upload(texture); - } - else - { - renderer_accessor.update_if_dirty(texture); - } - } - ); - } - - // ========================================================= - // FONT - // ========================================================= - - auto FontContext::set_glyph_parser(GlyphParser& glyph_parser) noexcept -> void - { - glyph_parser_ = std::addressof(glyph_parser); - } - - auto FontContext::set_fallback_glyph() noexcept -> void - { - if (fallback_glyph_ == nullptr) - { - { - constexpr GlyphKey key{u'\xFFFD', 16u, GlyphFlag::NONE}; - fallback_glyph_ = glyph_of(key); - } - - if (fallback_glyph_ == nullptr) - { - constexpr GlyphKey key{u'?', 16u, GlyphFlag::NONE}; - fallback_glyph_ = glyph_of(key); - } - - if (fallback_glyph_ == nullptr) - { - constexpr GlyphKey key{u' ', 16u, GlyphFlag::NONE}; - fallback_glyph_ = glyph_of(key); - } - } - } - - auto FontContext::add_font(const std::filesystem::path& path) noexcept -> void - { - font_load_queue_.push(path); - } - - auto FontContext::load_all_font() noexcept -> void - { - const auto loader = [this](Font&& font) noexcept -> void - { - font_list_.emplace_back(std::move(font)); - }; - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser_ != nullptr, "Use Renderer::set_glyph_parser to set a valid glyph parser first!"); - font_load_queue_.upload(*glyph_parser_, loader); - } - - auto FontContext::glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo* - { - for (const auto& font: font_list_) - { - if (const auto* info = font.get_glyph(key); info != nullptr) - { - return info; - } - } - - return nullptr; - } - - auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* - { - return glyph_of({codepoint, size, flag}); - } - - auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo* - { - for (const auto& font: font_list_) - { - if (const auto* info = font.get_glyph(key); info != nullptr) - { - return info; - } - } - - return fallback_glyph_; - } - - auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo* - { - return this->glyph_of_or_fallback({codepoint, size, flag}); - } - - auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo* - { - if (const auto* info = std::as_const(*this).glyph_of(key); info != nullptr) - { - return info; - } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser_ != nullptr); - for (auto& font: font_list_) - { - if (auto result = glyph_parser_->parse(font.descriptor.id, key); result.valid()) - { - auto& queue = glyph_upload_queue_list_[std::addressof(font)]; - auto& inserted_info = font.set_glyph(key, result); - - queue.push(inserted_info, std::move(result)); - return std::addressof(inserted_info); - } - } - - return nullptr; - } - - auto FontContext::glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* - { - return this->glyph_of({codepoint, size, flag}); - } - - auto FontContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo* - { - if (const auto* info = glyph_of(key); info != nullptr) - { - return info; - } - - return fallback_glyph_; - } - - auto FontContext::glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo* - { - return this->glyph_of_or_fallback({codepoint, size, flag}); - } - - auto FontContext::glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector - { - std::vector infos; - infos.reserve(text.size()); - - std::ranges::for_each( - text, - [&infos, this, size, flag](const auto codepoint) noexcept -> void - { - const auto* info = this->glyph_of_or_fallback(codepoint, size, flag); - infos.emplace_back(info); - } - ); - - return infos; - } - - auto FontContext::upload_all_glyph(TextureContext& context) noexcept -> void - { - std::ranges::for_each( - glyph_upload_queue_list_ | std::views::values, - [&context](auto& queue) noexcept -> void - { - queue.upload(context); - } - ); - glyph_upload_queue_list_.clear(); - } - - // ========================================================= - // CONTEXT - // ========================================================= - - Context::RendererAccessor::RendererAccessor(Renderer& renderer) noexcept - : renderer_{renderer} {} - - auto Context::RendererAccessor::upload(Texture& texture) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not texture.uploaded()); + auto Context::operator=(Context&&) noexcept -> Context& = default; - auto& renderer = renderer_.get(); - - texture.texture_.id = renderer.do_texture_create(texture.data(), texture.size()); - texture.texture_.dirty = false; - } - - auto Context::RendererAccessor::update(Texture& texture) noexcept -> void - { - auto& renderer = renderer_.get(); - - renderer.do_texture_update(texture.texture_); - texture.texture_.dirty = false; - } - - auto Context::RendererAccessor::update_if_dirty(Texture& texture) noexcept -> void - { - if (texture.dirty()) - { - update(texture); - } - } - - Context::TextureAccessor::TextureAccessor(TextureContext& texture_context) noexcept - : texture_context_{texture_context} {} - - auto Context::TextureAccessor::texture_context() const noexcept -> const TextureContext& - { - static_assert(std::is_const_v::type>); - - return texture_context_; - } - - Context::FontAccessor::FontAccessor(FontContext& font_context) noexcept - : font_context_{font_context} {} - - auto Context::FontAccessor::font_context() noexcept -> FontContext& - { - return font_context_; - } - - auto Context::FontAccessor::font_context() const noexcept -> const FontContext& - { - return font_context_; - } - - Context::RenderListAccessor::RenderListAccessor(RenderListSharedData& render_list_shared_data) noexcept - : render_list_shared_data_{render_list_shared_data} {} - - auto Context::RenderListAccessor::shared_data() const noexcept -> const RenderListSharedData& - { - return render_list_shared_data_; - } + Context::~Context() noexcept = default; Context::Context() noexcept - : glyph_parser_{nullptr}, - renderer_{nullptr}, - texture_context_{}, - font_context_{} + : glyph_parser{nullptr}, + renderer{nullptr}, + private_{memory::make_unique()} { - texture_context_.initialize(render_list_shared_data_); + // ReSharper disable once CppUseStructuredBinding + auto& context_private = *private_; + auto& glyph_context = context_private.glyph_context; + + glyph_context.glyph_parser = &glyph_parser; } - auto Context::set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> std::shared_ptr + auto Context::initialize() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr, "GlyphParser must not be null!"); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer != nullptr); - auto old = std::exchange(glyph_parser_, glyph_parser); - font_context_.set_glyph_parser(*glyph_parser_); + // ReSharper disable once CppUseStructuredBinding + auto& context_private = *private_; + auto& texture_context = context_private.texture_context; - return old; + texture_context.initialize(render_list_shared_data); } - auto Context::set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr + auto Context::new_render_list() noexcept -> RenderList& { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer != nullptr, "Renderer must not be null!"); + RenderList render_list{*this}; - return std::exchange(renderer_, renderer); + return render_lists.emplace_back(std::move(render_list)); } - auto Context::renderer_accessor() const noexcept -> RendererAccessor + auto Context::new_frame() noexcept -> void { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer_ != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer != nullptr); - return RendererAccessor{*renderer_}; - } + // ReSharper disable once CppUseStructuredBinding + auto& context_private = *private_; + auto& texture_context = context_private.texture_context; - auto Context::texture_accessor() noexcept -> TextureAccessor - { - return TextureAccessor{texture_context_}; + texture_context.update_all_atlas(*renderer); } - auto Context::font_accessor() noexcept -> FontAccessor + auto Context::present(const extent_type& display_size) noexcept -> void { - return FontAccessor{font_context_}; - } + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer != nullptr); - auto Context::render_list_accessor() noexcept -> RenderListAccessor - { - return RenderListAccessor{render_list_shared_data_}; - } + // ReSharper disable once CppUseStructuredBinding + auto& context_private = *private_; + auto& texture_context = context_private.texture_context; + auto& glyph_context = context_private.glyph_context; - auto Context::new_render_list(const RenderListFlag flag) noexcept -> RenderList& - { - RenderList render_list{*this, flag}; - return render_lists_.emplace_back(std::move(render_list)); - } + glyph_context.upload_all_glyph(texture_context); - auto Context::render_data() const noexcept -> render_data_list_type - { render_data_list_type all_render_data{}; - all_render_data.reserve(render_lists_.size()); + all_render_data.reserve(render_lists.size()); std::ranges::transform( - render_lists_, + render_lists, std::back_inserter(all_render_data), - [](const RenderList& render_list) noexcept -> RenderData - { - auto& context = *render_list.context_; - - return {.vertex_list = context.vertex_list, .index_list = context.index_list, .command_list = context.command_list}; - } + &RenderList::data ); - return all_render_data; - } - - [[nodiscard]] auto create_context(std::shared_ptr glyph_parser, std::shared_ptr renderer) noexcept -> Context* - { - auto* context = new Context{}; - context->set_glyph_parser(std::move(glyph_parser)); - context->set_renderer(std::move(renderer)); - - return context; - } - - auto destroy_context(Context& context) noexcept -> void - { - delete std::addressof(context); - } - - auto destroy_context(Context* context) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(context != nullptr); - destroy_context(*context); - } - - auto set_glyph_parser(Context& context, std::shared_ptr glyph_parser) noexcept -> std::shared_ptr - { - return context.set_glyph_parser(std::move(glyph_parser)); - } - - auto set_renderer(Context& context, std::shared_ptr renderer) noexcept -> std::shared_ptr - { - return context.set_renderer(std::move(renderer)); + renderer->present(all_render_data, display_size); } - auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void + auto Context::end_frame() noexcept -> void { - auto font_accessor = context.font_accessor(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(renderer != nullptr); - font_accessor.font_context().add_font(path); - } - - auto new_render_list(Context& context, const RenderListFlag flag) noexcept -> RenderList& - { - return context.new_render_list(flag); - } - - GlyphParser::~GlyphParser() noexcept = default; - - Renderer::~Renderer() noexcept = default; - - Renderer::Renderer() noexcept = default; - - auto Renderer::construct() noexcept -> bool - { - return do_construct(); - } - - auto Renderer::destruct() noexcept -> void - { - return do_destruct(); - } - - auto Renderer::ready() const noexcept -> bool - { - return do_ready(); - } - - auto Renderer::new_frame(Context& context) noexcept -> void - { - // font - { - context.font_context_.load_all_font(); - context.font_context_.set_fallback_glyph(); - } - // texture - { - context.texture_context_.upload(context); - } - } - - auto Renderer::present(Context& context, const extent_type& display_size) noexcept -> void - { - // glyphs - { - // note: newly added glyph information is not available until the next frame - context.font_context_.upload_all_glyph(context.texture_context_); - } - - do_present(context.render_data(), display_size); - } - - auto Renderer::end_frame(Context& context) noexcept -> void - { - context.render_lists_.clear(); + render_lists.clear(); } } diff --git a/src/gfx/internal/context.hpp b/src/gfx/internal/context.hpp index fbe3f6d..e9e10aa 100644 --- a/src/gfx/internal/context.hpp +++ b/src/gfx/internal/context.hpp @@ -5,288 +5,17 @@ #pragma once -#include -#include -#include +#include -#include +#include +#include namespace gal::prometheus::gfx { - // ========================================================= - // TEXTURE - // ========================================================= - - class TextureContext final - { - public: - using texture_atlas_list_type = std::vector; - - private: - texture_atlas_list_type texture_atlas_list_; - - [[nodiscard]] auto root_id() const noexcept -> texture_atlas_id_type; - - public: - TextureContext(const TextureContext&) noexcept = delete; - TextureContext(TextureContext&&) noexcept = default; - auto operator=(const TextureContext&) noexcept -> TextureContext& = delete; - auto operator=(TextureContext&&) noexcept -> TextureContext& = default; - - ~TextureContext() noexcept = default; - - TextureContext() noexcept; - - auto initialize(RenderListSharedData& shared_data) noexcept -> void; - - /** - * @brief Get root (default) texture - */ - [[nodiscard]] auto root() noexcept -> Texture&; - - /** - * @brief Get root (default) texture - */ - [[nodiscard]] auto root() const noexcept -> const Texture&; - - /** - * @brief Get texture of id - */ - [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) noexcept -> Texture&; - - /** - * @brief Get texture of id - */ - [[nodiscard]] auto select(texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture&; - - /** - * @brief Writes the given glyph data to the specified texture - * @param texture_atlas_id id of the specified texture - * @param data Glyph data to write - * @param size Size of data (rectangle area) - * @return The uv coordinate of the position where the data is written - */ - auto write( - texture_atlas_id_type texture_atlas_id, - Texture::data_view_type data, - Texture::size_type size - ) noexcept -> primitive::basic_rect_2d; - - struct random_write_result_type - { - texture_atlas_id_type texture_atlas_id; - primitive::basic_rect_2d uv; - }; - - /** - * @brief Writes the given glyph data to any holdable texture - * @param data Glyph data to write - * @param size Size of data (rectangle area) - * @return The id of the written texture atlas and the uv coordinate of the position where the data is written - */ - auto write( - Texture::data_view_type data, - Texture::size_type size - ) noexcept -> random_write_result_type; - - /** - * @brief Upload all texture atlas (if it didn't upload or needs to be re-uploaded) - * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) - */ - auto upload(const Context& context) noexcept -> void; - }; - - // ========================================================= - // FONT - // ========================================================= - - class FontContext final + class Context::ContextPrivate final { public: - using font_list_type = std::vector; - using glyph_upload_queue_list_type = std::unordered_map; - - private: - font_list_type font_list_; - FontLoadQueue font_load_queue_; - - glyph_upload_queue_list_type glyph_upload_queue_list_; - - GlyphParser* glyph_parser_; - const GlyphInfo* fallback_glyph_; - - public: - FontContext(const FontContext&) noexcept = delete; - FontContext(FontContext&&) noexcept = default; - auto operator=(const FontContext&) noexcept -> FontContext& = delete; - auto operator=(FontContext&&) noexcept -> FontContext& = default; - - ~FontContext() noexcept = default; - - FontContext() noexcept = default; - - auto set_glyph_parser(GlyphParser& glyph_parser) noexcept -> void; - - auto set_fallback_glyph() noexcept -> void; - - /** - * @brief Load font from the specified path, assuming the path is a valid font file - * @param path Font path - */ - auto add_font(const std::filesystem::path& path) noexcept -> void; - - /** - * @brief Load the fonts previously added by @c add_font - * @note This function is usually called at initialization time (or at every frame if needed) to load all the required fonts - */ - auto load_all_font() noexcept -> void; - - [[nodiscard]] auto glyph_of(const GlyphKey& key) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; - - [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) const noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) const noexcept -> std::vector; - - [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; - - [[nodiscard]] auto glyph_of_or_fallback(const GlyphKey& key) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo*; - [[nodiscard]] auto glyph_of_or_fallback(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector; - - /** - * @brief Upload all used glyphs to the texture (if it is not already uploaded) - * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture - */ - auto upload_all_glyph(TextureContext& context) noexcept -> void; - }; - - // ========================================================= - // CONTEXT - // ========================================================= - - class Context final - { - // xxx_context - friend Renderer; - - public: - class RendererAccessor final - { - // TextureContext::upload - friend TextureContext; - - public: - using renderer_type = memory::RefWrapper; - - private: - renderer_type renderer_; - - public: - explicit RendererAccessor(Renderer& renderer) noexcept; - - private: - /** - * @brief Upload texture atlas data to GPU and get GPU resource handle - */ - auto upload(Texture& texture) noexcept -> void; - - /** - * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) - * @note Does not check for the need to update - */ - auto update(Texture& texture) noexcept -> void; - - /** - * @brief Update new texture atlas data to GPU (overwrite previous texture atlas data) - * @note If no update is needed (not dirty) then do nothing - */ - auto update_if_dirty(Texture& texture) noexcept -> void; - }; - - class TextureAccessor final - { - // RenderList::RenderListContext::default_texture - friend RenderList::RenderListContext; - // RenderList::text (select texture atlas for text rendering) - friend RenderList; - - public: - - private: - memory::RefWrapper texture_context_; - - public: - explicit TextureAccessor(TextureContext& texture_context) noexcept; - - private: - [[nodiscard]] auto texture_context() const noexcept -> const TextureContext&; - }; - - class FontAccessor final - { - // RenderList::text - // RenderList::text_size - friend RenderList; - friend auto add_font(Context& context, const std::filesystem::path& path) noexcept -> void; - - public: - - private: - memory::RefWrapper font_context_; - - public: - explicit FontAccessor(FontContext& font_context) noexcept; - - private: - [[nodiscard]] auto font_context() noexcept -> FontContext&; - [[nodiscard]] auto font_context() const noexcept -> const FontContext&; - }; - - class RenderListAccessor final - { - // RenderList::RenderListContext::shared_data - friend RenderList::RenderListContext; - - public: - - private: - memory::RefWrapper render_list_shared_data_; - - public: - explicit RenderListAccessor(RenderListSharedData& render_list_shared_data) noexcept; - - private: - [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData&; - }; - - private: - std::shared_ptr glyph_parser_; - std::shared_ptr renderer_; - - TextureContext texture_context_; - FontContext font_context_; - - RenderListSharedData render_list_shared_data_; - std::vector render_lists_; - - public: - Context() noexcept; - - auto set_glyph_parser(std::shared_ptr glyph_parser) noexcept -> std::shared_ptr; - auto set_renderer(std::shared_ptr renderer) noexcept -> std::shared_ptr; - - // todo: set RenderListSharedData - - [[nodiscard]] auto renderer_accessor() const noexcept -> RendererAccessor; - [[nodiscard]] auto texture_accessor() noexcept -> TextureAccessor; - [[nodiscard]] auto font_accessor() noexcept -> FontAccessor; - [[nodiscard]] auto render_list_accessor() noexcept -> RenderListAccessor; - - [[nodiscard]] auto new_render_list(RenderListFlag flag) noexcept -> RenderList&; - [[nodiscard]] auto render_data() const noexcept -> render_data_list_type; + TextureContext texture_context; + GlyphContext glyph_context; }; } diff --git a/src/gfx/internal/font.cpp b/src/gfx/internal/font.cpp deleted file mode 100644 index 7426980..0000000 --- a/src/gfx/internal/font.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#include - -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -namespace gal::prometheus::gfx -{ - auto GlyphUploadQueue::push(GlyphInfo& info, GlyphParser::GlyphDescriptor&& descriptor) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(descriptor.valid()); - - list_.emplace_back(memory::ref(info), std::move(descriptor)); - } - - auto GlyphUploadQueue::upload(TextureContext& context) noexcept -> void - { - std::ranges::for_each( - list_, - [&context](element_type& element) noexcept -> void - { - auto& info = element.info.get(); - const auto& descriptor = element.descriptor; - - const auto size = descriptor.rect.size(); - const Texture::data_view_type data{descriptor.data.get(), static_cast(size.width) * size.height}; - - // FIXME-OPT: - // Do we need to specify a texture for the upload? - const auto [texture_atlas_id, uv] = context.write(data, size); - info.texture_atlas_id = texture_atlas_id; - info.uv = uv; - } - ); - list_.clear(); - } - - auto Font::get_glyph(const GlyphKey& key) const noexcept -> const GlyphInfo* - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(descriptor.id != invalid_font_id); - - if (const auto it = cached_glyphs.find(key); it != cached_glyphs.end()) - { - return std::addressof(it->second); - } - - return nullptr; - } - - auto Font::set_glyph(const GlyphKey& key, const GlyphParser::GlyphDescriptor& glyph_descriptor) noexcept -> GlyphInfo& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(descriptor.id != invalid_font_id); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_descriptor.valid()); - - GlyphInfo info{}; - info.rect = glyph_descriptor.rect; - info.advance_x = glyph_descriptor.advance_x; - info.visible = glyph_descriptor.visible; - info.colored = glyph_descriptor.colored; - - return cached_glyphs.insert_or_assign(key, info).first->second; - } - - FontLoadQueue::FontLoadQueue() noexcept = default; - - auto FontLoadQueue::push(const std::filesystem::path& path) noexcept -> void - { - list_.emplace_back(path); - } - - auto FontLoadQueue::upload(GlyphParser& parser, const functional::function_reference_wrapper font_dest) noexcept -> void - { - if (not list_.empty()) - { - std::ranges::for_each( - list_, - [&](const auto& path) noexcept -> void - { - if (const auto result = parser.load(path); result.valid()) - { - Font font{.descriptor = {.identifier = result.identifier, .id = result.id}, .cached_glyphs = {}}; - font_dest(std::move(font)); - } - else - { - // todo: error handling - GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); - } - } - ); - list_.clear(); - } - } -} diff --git a/src/gfx/internal/glyph.cpp b/src/gfx/internal/glyph.cpp new file mode 100644 index 0000000..85120b2 --- /dev/null +++ b/src/gfx/internal/glyph.cpp @@ -0,0 +1,103 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::gfx +{ + auto GlyphUploadQueue::push(GlyphInfo& info, Texture::data_type&& data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data != nullptr); + + list_.emplace_back(memory::ref(info), std::move(data)); + } + + auto GlyphUploadQueue::upload(TextureContext& context) noexcept -> void + { + std::ranges::for_each( + list_, + [&context](element_type& element) noexcept -> void + { + auto& info = element.info.get(); + + const auto size = info.rect.size(); + + auto [writer, texture_atlas_id] = context.write(size); + const auto& texture = context.select_texture(texture_atlas_id); + const auto uv_scale = texture.uv_scale; + + const Texture::data_view_type data_view{element.data.get(), static_cast(size.width) * size.height}; + writer.fill(data_view); + + info.texture_atlas_id = texture_atlas_id; + info.uv.point = writer.position().to() * uv_scale; + info.uv.extent = size.to() * uv_scale; + } + ); + list_.clear(); + } + + auto GlyphContext::glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo& + { + if (const auto& it = cached_glyphs_.find(key); it != cached_glyphs_.end()) + { + return it->second; + } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(glyph_parser != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(*glyph_parser != nullptr); + + auto [rect, advance_x, visible, colored, data] = (*glyph_parser)->parse(key); + + GlyphInfo info{}; + info.rect = rect; + info.advance_x = advance_x; + info.visible = visible; + info.colored = colored; + + auto [it, inserted] = cached_glyphs_.emplace(key, info); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(inserted); + + auto& inserted_info = it->second; + + if (visible) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data != nullptr); + + upload_queue_.push(inserted_info, std::move(data)); + } + + return inserted_info; + } + + auto GlyphContext::glyph_of(const std::uint32_t codepoint, const std::uint32_t size, const GlyphFlag flag) noexcept -> const GlyphInfo& + { + return this->glyph_of({codepoint, size, flag}); + } + + auto GlyphContext::glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector> + { + std::vector> infos; + infos.reserve(text.size()); + + std::ranges::for_each( + text, + [&infos, this, size, flag](const auto codepoint) noexcept -> void + { + const auto& info = this->glyph_of(codepoint, size, flag); + infos.emplace_back(std::cref(info)); + } + ); + + return infos; + } + + auto GlyphContext::upload_all_glyph(TextureContext& context) noexcept -> void + { + upload_queue_.upload(context); + } +} diff --git a/src/gfx/internal/font.hpp b/src/gfx/internal/glyph.hpp similarity index 55% rename from src/gfx/internal/font.hpp rename to src/gfx/internal/glyph.hpp index 36ab643..603c3bb 100644 --- a/src/gfx/internal/font.hpp +++ b/src/gfx/internal/glyph.hpp @@ -6,20 +6,17 @@ #pragma once #include -#include -#include +#include + +#include #include #include namespace gal::prometheus::gfx { - // index - using texture_atlas_id_type = std::uint32_t; - constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; - - class TextureContext; + static_assert(std::is_same_v); /** * @brief This class is essentially the same as @c GlyphParser::GlyphCode, but takes up less memory space. @@ -41,7 +38,7 @@ namespace gal::prometheus::gfx return codepoint == other.codepoint and size == other.size and flag == other.flag; } - [[nodiscard]] constexpr explicit(false) operator GlyphParser::GlyphCode() const noexcept + [[nodiscard]] constexpr explicit(false) operator GlyphCode() const noexcept { return {codepoint, size, static_cast(flag)}; } @@ -63,7 +60,7 @@ namespace gal::prometheus::gfx class GlyphInfo final { public: - using rect_type = GlyphParser::GlyphDescriptor::rect_type; + using rect_type = GlyphDescriptor::rect_type; using uv_type = primitive::basic_rect_2d; // ============= @@ -73,11 +70,12 @@ namespace gal::prometheus::gfx // Bitmap infos of this glyph rect_type rect; float advance_x; + bool visible; bool colored; // ============= - // Data filled when writing texture + // Data filled when writing texture (if and only if the glyph is visible) // ============= // The id of the texture atlas where the glyph is located @@ -96,7 +94,8 @@ namespace gal::prometheus::gfx struct element_type { memory::RefWrapper info; - GlyphParser::GlyphDescriptor descriptor; + + Texture::data_type data; }; using list_type = std::vector; @@ -114,64 +113,41 @@ namespace gal::prometheus::gfx GlyphUploadQueue() noexcept = default; - auto push(GlyphInfo& info, GlyphParser::GlyphDescriptor&& descriptor) noexcept -> void; + auto push(GlyphInfo& info, Texture::data_type&& data) noexcept -> void; auto upload(TextureContext& context) noexcept -> void; }; - class Font final - { - public: - GlyphParser::FontDescriptor descriptor; - std::unordered_map cached_glyphs; - - /** - * @brief Get the glyph information of the specified codepoint, if it can't be found, then return a null pointer - * @param key {codepoint, size, flag} - * @return The glyph information of the specified codepoint, or a null pointer if it can't be found - */ - [[nodiscard]] auto get_glyph(const GlyphKey& key) const noexcept -> const GlyphInfo*; - - /** - * @brief Set the glyph information of the specified codepoint, override if it already exists - * @param key {codepoint, size, flag} - * @param glyph_descriptor The parse result of the specified codepoint - * @return GlyphInfo after insertion - */ - [[nodiscard]] auto set_glyph(const GlyphKey& key, const GlyphParser::GlyphDescriptor& glyph_descriptor) noexcept -> GlyphInfo&; - }; - - /** - * @brief Queue of font data to be loaded to the context - */ - class FontLoadQueue final + class GlyphContext final { - using descriptor = GlyphParser::FontDescriptor; - public: - using element_type = descriptor::element_type; - using data_type = descriptor::data_type; - using data_view_type = descriptor::data_view_type; + using cached_glyphs_type = std::unordered_map; - using size_type = descriptor::size_type; + GlyphParser** glyph_parser; private: - using list_type = std::vector; + cached_glyphs_type cached_glyphs_; - list_type list_; + GlyphUploadQueue upload_queue_; public: - FontLoadQueue(const FontLoadQueue&) noexcept = delete; - FontLoadQueue(FontLoadQueue&&) noexcept = default; - auto operator=(const FontLoadQueue&) noexcept -> FontLoadQueue& = delete; - auto operator=(FontLoadQueue&&) noexcept -> FontLoadQueue& = default; + GlyphContext(const GlyphContext&) noexcept = delete; + GlyphContext(GlyphContext&&) noexcept = default; + auto operator=(const GlyphContext&) noexcept -> GlyphContext& = delete; + auto operator=(GlyphContext&&) noexcept -> GlyphContext& = default; - ~FontLoadQueue() noexcept = default; + ~GlyphContext() noexcept = default; - FontLoadQueue() noexcept; + GlyphContext() noexcept = default; - auto push(const std::filesystem::path& path) noexcept -> void; + [[nodiscard]] auto glyph_of(const GlyphKey& key) noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of(std::uint32_t codepoint, std::uint32_t size, GlyphFlag flag) noexcept -> const GlyphInfo&; + [[nodiscard]] auto glyph_of(std::u32string_view text, std::uint32_t size, GlyphFlag flag) noexcept -> std::vector>; - auto upload(GlyphParser& parser, functional::function_reference_wrapper font_dest) noexcept -> void; + /** + * @brief Upload all used glyphs to the texture (if it is not already uploaded) + * @note This function is usually called every frame (unless all the needed glyphs have been uploaded to the texture, but it can still be called) to upload all new (previously unused) glyphs to the texture + */ + auto upload_all_glyph(TextureContext& context) noexcept -> void; }; } diff --git a/src/gfx/internal/rect_pack.cpp b/src/gfx/internal/rect_pack.cpp index f54f4a5..5796976 100644 --- a/src/gfx/internal/rect_pack.cpp +++ b/src/gfx/internal/rect_pack.cpp @@ -27,7 +27,7 @@ namespace gal::prometheus::gfx return 1; } - auto RectPackContext::skyline_find_min_y(const rect_pack_node* head, const point_type::value_type x0, const extent_type::value_type width) noexcept -> find_y_result + auto RectPackContext::skyline_find_min_y(const rect_pack_node* head, const point_type::value_type x0, const size_type::value_type width) noexcept -> find_y_result { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(head->point.x <= x0); GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(head->next->point.x > x0); @@ -36,7 +36,7 @@ namespace gal::prometheus::gfx const auto* current = head; find_y_result result{.y = 0, .waste = 0}; - extent_type::value_type visited_width = 0; + size_type::value_type visited_width = 0; while (current->point.x < x1) { @@ -66,7 +66,7 @@ namespace gal::prometheus::gfx return result; } - auto RectPackContext::skyline_find_best_pos(extent_type size, const PackPrefer pack_prefer, const Heuristic heuristic) noexcept -> find_result + auto RectPackContext::skyline_find_best_pos(size_type size, const PackPrefer pack_prefer, const Heuristic heuristic) noexcept -> find_result { const auto& context_size = size_; @@ -192,7 +192,7 @@ namespace gal::prometheus::gfx return {.point = {best_x, best_y}, .prev_link = best}; } - auto RectPackContext::skyline_pack_rectangle(const extent_type size, const PackPrefer pack_prefer, const Heuristic heuristic) noexcept -> find_result + auto RectPackContext::skyline_pack_rectangle(const size_type size, const PackPrefer pack_prefer, const Heuristic heuristic) noexcept -> find_result { const auto context_size = size_; @@ -263,7 +263,7 @@ namespace gal::prometheus::gfx return result; } - RectPackContext::RectPackContext(const extent_type& size) noexcept + RectPackContext::RectPackContext(const size_type& size) noexcept : size_{size}, nodes_{size.width + 2}, free_head_{nodes_.data()}, diff --git a/src/gfx/internal/rect_pack.hpp b/src/gfx/internal/rect_pack.hpp index 63dc19e..e575208 100644 --- a/src/gfx/internal/rect_pack.hpp +++ b/src/gfx/internal/rect_pack.hpp @@ -35,14 +35,14 @@ namespace gal::prometheus::gfx { public: using point_type = primitive::basic_point_2d; - using extent_type = primitive::basic_extent_2d; + using size_type = primitive::basic_extent_2d; constexpr static auto invalid_point = point_type{std::numeric_limits::max(), std::numeric_limits::max()}; struct rect_type final { // INPUT - extent_type size; + size_type size; // OUTPUT point_type point; @@ -63,7 +63,7 @@ namespace gal::prometheus::gfx struct find_y_result { point_type::value_type y; - extent_type::value_type waste; + size_type::value_type waste; }; struct find_result @@ -72,7 +72,7 @@ namespace gal::prometheus::gfx rect_pack_node** prev_link; }; - extent_type size_; + size_type size_; // size_.width + 2 std::vector nodes_; @@ -86,14 +86,14 @@ namespace gal::prometheus::gfx [[nodiscard]] auto align_of(PackPrefer pack_prefer) const noexcept -> std::uint32_t; // find minimum y position if it starts at x1 - [[nodiscard]] static auto skyline_find_min_y(const rect_pack_node* head, point_type::value_type x0, extent_type::value_type width) noexcept -> find_y_result; + [[nodiscard]] static auto skyline_find_min_y(const rect_pack_node* head, point_type::value_type x0, size_type::value_type width) noexcept -> find_y_result; - [[nodiscard]] auto skyline_find_best_pos(extent_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result; + [[nodiscard]] auto skyline_find_best_pos(size_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result; - [[nodiscard]] auto skyline_pack_rectangle(extent_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result; + [[nodiscard]] auto skyline_pack_rectangle(size_type size, PackPrefer pack_prefer, Heuristic heuristic) noexcept -> find_result; public: - explicit RectPackContext(const extent_type& size) noexcept; + explicit RectPackContext(const size_type& size) noexcept; auto pack( std::span in_out_rects, diff --git a/src/gfx/internal/render_list.hpp b/src/gfx/internal/render_list.hpp deleted file mode 100644 index 989af51..0000000 --- a/src/gfx/internal/render_list.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2025 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#include - -namespace gal::prometheus::gfx -{ - class RenderList::RenderListContext final - { - public: - using size_type = RenderData::size_type; - - using command_type = RenderData::command_type; - - using vertex_list_type = RenderData::vertex_list_type; - using index_list_type = RenderData::index_list_type; - using command_list_type = RenderData::command_list_type; - - mutable memory::RefWrapper context; - - RenderListFlag render_list_flag; - - // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) - // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 - // command_list: - // 0: .scissor = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) - // 1: .scissor = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) - // 2: .scissor = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) - command_list_type command_list; - vertex_list_type vertex_list; - index_list_type index_list; - - rect_type this_command_scissor; - texture_id_type this_command_texture; - - private: - auto push_command() noexcept -> void; - - auto on_scissor_changed() noexcept -> void; - auto on_texture_changed() noexcept -> void; - - public: - [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData&; - - [[nodiscard]] auto default_texture() const noexcept -> texture_id_type; - - auto reset() noexcept -> void; - - // ---------------------------------------------------------------------------- - // SCISSOR & TEXTURE - - auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> rect_type&; - - auto pop_scissor() noexcept -> void; - - auto push_texture(texture_id_type texture) noexcept -> void; - - auto pop_texture() noexcept -> void; - }; -} diff --git a/src/gfx/internal/texture.cpp b/src/gfx/internal/texture.cpp index 5137f91..7ec53ac 100644 --- a/src/gfx/internal/texture.cpp +++ b/src/gfx/internal/texture.cpp @@ -4,208 +4,216 @@ // found in the top-level directory of this distribution. #include +#include +#include -// #define STB_RECT_PACK_IMPLEMENTATION -// #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -namespace gal::prometheus::gfx +namespace { - Texture::Texture(const size_type size) noexcept - : rp_context_{}, - texture_{ - .data = std::make_unique_for_overwrite(static_cast(size.width) * size.height), - .size = size, - .dirty = false, - .id = invalid_texture_id, - }, - uv_{1.f / static_cast(size.width), 1.f / static_cast(size.height)} - { - rp_nodes_.resize(size.width); - - stbrp_init_target( - &rp_context_, - static_cast(size.width), - static_cast(size.height), - rp_nodes_.data(), - static_cast(rp_nodes_.size()) - ); - } + using namespace gal::prometheus; + using namespace gfx; - auto Texture::data() const noexcept -> data_view_type + [[nodiscard]] auto make_texture(const Texture::size_type size) noexcept -> Texture { - return {texture_.data.get(), area_size()}; - } + auto data = std::make_unique_for_overwrite(static_cast(size.width) * size.height); - auto Texture::area_size() const noexcept -> std::size_t - { - return static_cast(texture_.size.width) * texture_.size.height; + return { + .data = std::move(data), + .size = size, + .uv_scale = {1.f / static_cast(size.width), 1.f / static_cast(size.height)}, + .id = invalid_texture_id + }; } +} - auto Texture::size() const noexcept -> size_type +namespace gal::prometheus::gfx +{ + auto TextureContext::root_id() const noexcept -> texture_atlas_id_type { - return texture_.size; + std::ignore = this; + return 0; } - auto Texture::uv() const noexcept -> uv_type + auto TextureContext::active_id() const noexcept -> texture_atlas_id_type { - return uv_; + return static_cast(atlas_list_.size() - 1); } - auto Texture::dirty() const noexcept -> bool + auto TextureContext::select_atlas(const texture_atlas_id_type texture_atlas_id) noexcept -> atlas_type& { - return texture_.dirty; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < atlas_list_.size()); + + return atlas_list_[texture_atlas_id]; } - auto Texture::uploaded() const noexcept -> bool + auto TextureContext::select_atlas(const texture_atlas_id_type texture_atlas_id) const noexcept -> const atlas_type& { - return texture_.id != invalid_texture_id; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture_atlas_id < atlas_list_.size()); + + return atlas_list_[texture_atlas_id]; } - auto Texture::id() const noexcept -> texture_id_type + auto TextureContext::new_atlas(const size_type size) noexcept -> atlas_type& { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uploaded()); - - return texture_.id; + atlas_type atlas + { + .texture = make_texture(size), + .pending_update_data = {}, + .rp_context = gfx::RectPackContext{size}, + }; + return atlas_list_.emplace_back(std::move(atlas)); } - auto Texture::select(const size_type size) noexcept -> BorrowedTexture + auto TextureContext::write(const texture_atlas_id_type texture_atlas_id, const size_type size) noexcept -> TextureWriter { - stbrp_rect rect{.id = -1, .w = static_cast(size.width), .h = static_cast(size.height), .x = 0, .y = 0, .was_packed = 0}; + auto& [texture, pending_data_list, rp_context] = select_atlas(texture_atlas_id); - if (stbrp_pack_rects(&rp_context_, &rect, 1)) + gfx::RectPackContext::rect_type new_rect{.size = size, .point = {}}; + if (rp_context.pack({&new_rect, 1})) { - const point_type point{static_cast(rect.x), static_cast(rect.y)}; + const auto point = new_rect.point; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(point != gfx::RectPackContext::invalid_point); - auto* address = texture_.data.get() + (point.y * size.width + point.x); - const auto mapping = BorrowedTexture::borrow_data_type::mapping_type{ + auto* address = texture.data.get() + (point.y * size.width + point.x); + const auto mapping = TextureWriter::borrow_data_type::mapping_type{ std::dextents{size.height, size.width}, - std::array{texture_.size.width, 1}, + std::array{texture.size.width, 1}, }; - const auto data = BorrowedTexture::borrow_data_type{address, mapping}; + const auto data = TextureWriter::borrow_data_type{address, mapping}; + + TextureViewer viewer{point, data}; + pending_data_list.emplace_back(viewer); - texture_.dirty = true; return {point, data}; } - return {BorrowedTexture::invalid_point, {}}; + return {TextureWriter::invalid_point, {}}; } - BorrowedTexture::BorrowedTexture(const point_type point, const borrow_data_type& data) noexcept - : point_{point}, - data_{data} {} - - auto BorrowedTexture::valid() const noexcept -> bool + TextureContext::TextureContext() noexcept { - return point_ != invalid_point; - } + atlas_list_.reserve(2); - auto BorrowedTexture::position() const noexcept -> point_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + // root atlas + constexpr size_type root_texture_atlas_size{128, 128}; + new_atlas(root_texture_atlas_size); - return point_; + // first + constexpr size_type first_texture_atlas_size{2048, 2048}; + new_atlas(first_texture_atlas_size); } - auto BorrowedTexture::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void + auto TextureContext::initialize(RenderListSharedData& shared_data) noexcept -> void { - const auto width = data_.extent(1); - const auto height = data_.extent(0); + const auto atlas_id = root_id(); + const auto& texture = select_texture(atlas_id); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n <= width); - - for (size_type::value_type x = offset; x < offset + n; ++x) + // ======================================== + // BAKE LINES (AA) + // ======================================== { - data_[y, x] = element; + constexpr std::uint32_t white_color = 0xff'ff'ff'ff; + constexpr auto aa_width = static_cast(RenderListSharedData::baked_line_uv_count); + constexpr auto aa_height = static_cast(RenderListSharedData::baked_line_uv_count); + constexpr auto aa_size = size_type{aa_width, aa_height}; + + // baked line rect area: + // white pixel + ◿ + const auto texture_write = write(atlas_id, aa_size); + texture_write.fill(0); + + const auto aa_point = texture_write.position(); + const auto aa_uv_scale = texture.uv_scale; + + // white pixel + { + // LINE 0, 2 pixels + texture_write.fill(0, 2, white_color); + // LINE 1, 2 pixels + texture_write.fill(1, 2, white_color); + + const auto uv_x = static_cast(static_cast(aa_point.x) + 1.0f) * aa_uv_scale.width; + const auto uv_y = static_cast(static_cast(aa_point.y) + 1.0f) * aa_uv_scale.height; + + shared_data.white_pixel_uv = {uv_x, uv_y}; + } + + // ◿ + for (size_type::value_type y = 1; y < aa_height; ++y) + { + const auto line_width = y; + const auto offset = aa_width - line_width; + + texture_write.fill(y, offset, line_width, white_color); + + const auto p_x = aa_point.x + offset; + const auto p_y = aa_point.y + y; + const auto width = line_width; + constexpr auto height = .5f; + + const auto uv_x = static_cast(p_x) * aa_uv_scale.width; + const auto uv_y = static_cast(p_y) * aa_uv_scale.height; + const auto uv_width = static_cast(width) * aa_uv_scale.width; + const auto uv_height = static_cast(height) * aa_uv_scale.height; + + shared_data.baked_line_uvs[y] = {uv_x, uv_y, uv_width, uv_height}; + } } - } - auto BorrowedTexture::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void - { - const auto width = data_.extent(1); - const auto height = data_.extent(0); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + data.size() <= width); - - for (size_type::value_type x = 0; x < data.size(); ++x) + // ======================================== + // + // ======================================== { - data_[y, x + offset] = data[x]; + std::ignore = texture; } } - auto BorrowedTexture::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void + auto TextureContext::update_all_atlas(Renderer& renderer) noexcept -> void { - const auto width = data_.extent(1); - const auto height = data_.extent(0); + std::ranges::for_each( + atlas_list_, + [&renderer](auto& atlas) noexcept -> void + { + if (atlas.texture.id == invalid_texture_id) + { + atlas.texture.id = renderer.create_texture(atlas.texture.data, atlas.texture.size); + } + else if (not atlas.pending_update_data.empty()) + { + renderer.update_texture(atlas.texture.id, atlas.pending_update_data); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(n <= width); - - fill(y, 0, n, element); + atlas.pending_update_data.clear(); + } + } + ); } - auto BorrowedTexture::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void + auto TextureContext::root_texture() noexcept -> Texture& { - const auto width = data_.extent(1); - const auto height = data_.extent(0); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() <= width); - - fill(y, 0, data); + return select_texture(root_id()); } - auto BorrowedTexture::fill(const size_type::value_type y, const element_type element) const noexcept -> void + auto TextureContext::root_texture() const noexcept -> const Texture& { - const auto width = data_.extent(1); - const auto height = data_.extent(0); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - - fill(y, width, element); + return select_texture(root_id()); } - auto BorrowedTexture::fill(const element_type element) const noexcept -> void + auto TextureContext::select_texture(const texture_atlas_id_type texture_atlas_id) noexcept -> Texture& { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - - const auto height = data_.extent(0); - for (size_type::value_type y = 0; y < height; ++y) - { - fill(y, element); - } + return select_atlas(texture_atlas_id).texture; } - auto BorrowedTexture::fill(const data_view_type data) const noexcept -> void + auto TextureContext::select_texture(const texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture& { - const auto width = data_.extent(1); - const auto height = data_.extent(0); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - - for (size_type::value_type y = 0; y < height; ++y) - { - const data_view_type sub{data.begin() + static_cast(y) * width, width}; - - fill(y, sub); - } + return select_atlas(texture_atlas_id).texture; } - auto BorrowedTexture::operator[](const size_type::value_type x, const size_type::value_type y) const noexcept -> borrow_data_type::reference + auto TextureContext::write(const size_type size) noexcept -> write_result { - const auto width = data_.extent(1); - const auto height = data_.extent(0); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < width); + const auto id = active_id(); + const auto writer = write(id, size); - return data_[y, x]; + return {.writer = writer, .texture_atlas_id = id}; } } diff --git a/src/gfx/internal/texture.hpp b/src/gfx/internal/texture.hpp index 7a5325d..8e39a6e 100644 --- a/src/gfx/internal/texture.hpp +++ b/src/gfx/internal/texture.hpp @@ -5,124 +5,106 @@ #pragma once -#include +#include -#include +#include -#include +#include namespace gal::prometheus::gfx { - class BorrowedTexture; + // index + using texture_atlas_id_type = std::uint32_t; + constexpr texture_atlas_id_type invalid_texture_atlas_id{std::numeric_limits::max()}; - class Texture final - { - // Texture::id and Texture::dirty - friend Context; + class RenderListSharedData; + class Renderer; + class TextureContext final + { public: - using element_type = TextureDescriptor::element_type; - using data_type = TextureDescriptor::data_type; - using data_view_type = TextureDescriptor::data_view_type; + using element_type = Texture::element_type; + using data_type = Texture::data_type; + using data_view_type = Texture::data_view_type; + + using point_type = Texture::point_type; + using size_type = Texture::size_type; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + using uv_scale_type = Texture::uv_scale_type; + + struct atlas_type + { + Texture texture; + std::vector pending_update_data; - using size_type = TextureDescriptor::size_type; + gfx::RectPackContext rp_context; + }; - using point_type = primitive::basic_point_2d; - using uv_type = primitive::basic_extent_2d; + using atlas_list_type = std::vector; private: - stbrp_context rp_context_; - std::vector rp_nodes_; + atlas_list_type atlas_list_; - TextureDescriptor texture_; - // It's much more cost-effective to keep a member variable than to compute it every time - uv_type uv_; + [[nodiscard]] auto root_id() const noexcept -> texture_atlas_id_type; + + [[nodiscard]] auto active_id() const noexcept -> texture_atlas_id_type; + + [[nodiscard]] auto select_atlas(texture_atlas_id_type texture_atlas_id) noexcept -> atlas_type&; + + [[nodiscard]] auto select_atlas(texture_atlas_id_type texture_atlas_id) const noexcept -> const atlas_type&; + + auto new_atlas(size_type size) noexcept -> atlas_type&; + + [[nodiscard]] auto write(texture_atlas_id_type texture_atlas_id, size_type size) noexcept -> TextureWriter; public: - explicit Texture(size_type size) noexcept; + TextureContext(const TextureContext&) noexcept = delete; + TextureContext(TextureContext&&) noexcept = default; + auto operator=(const TextureContext&) noexcept -> TextureContext& = delete; + auto operator=(TextureContext&&) noexcept -> TextureContext& = default; - /** - * @brief Texture atlas data (for upload) - */ - [[nodiscard]] auto data() const noexcept -> data_view_type; + ~TextureContext() noexcept = default; - /** - * @brief Texture atlas area size - */ - [[nodiscard]] auto area_size() const noexcept -> std::size_t; + TextureContext() noexcept; - /** - * @brief Texture atlas size - */ - [[nodiscard]] auto size() const noexcept -> size_type; + auto initialize(RenderListSharedData& shared_data) noexcept -> void; /** - * @brief Texture atlas uv scale (1.0f / size.width, 1.0f / size.height) + * @brief Upload all texture atlas if it didn't upload, or update them if it dirty + * @note This function is usually called every frame to upload the texture to the GPU, or to update the texture (if new glyph data is written) */ - [[nodiscard]] auto uv() const noexcept -> uv_type; + auto update_all_atlas(Renderer& renderer) noexcept -> void; /** - * @brief Does this texture atlas need to be re-uploaded to the GPU + * @brief Get root (default) texture */ - [[nodiscard]] auto dirty() const noexcept -> bool; + [[nodiscard]] auto root_texture() noexcept -> Texture&; /** - * @brief Texture atlas uploaded (to GPU) + * @brief Get root (default) texture */ - [[nodiscard]] auto uploaded() const noexcept -> bool; + [[nodiscard]] auto root_texture() const noexcept -> const Texture&; /** - * @brief Texture atlas id (usually a GPU resource handle) + * @brief Get texture of id */ - [[nodiscard]] auto id() const noexcept -> texture_id_type; + [[nodiscard]] auto select_texture(texture_atlas_id_type texture_atlas_id) noexcept -> Texture&; /** - * @brief Find a region that can hold a (piece of) texture of @c size - * @param size Texture size + * @brief Get texture of id */ - [[nodiscard]] auto select(size_type size) noexcept -> BorrowedTexture; - }; - - class BorrowedTexture final - { - friend Texture; - - public: - using element_type = Texture::element_type; - using data_type = Texture::data_type; - using data_view_type = Texture::data_view_type; - - using size_type = Texture::size_type; - - using point_type = Texture::point_type; - using uv_type = Texture::uv_type; - - constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; - - private: - using borrow_data_type = std::mdspan, std::layout_stride>; - - point_type point_; - borrow_data_type data_; - - BorrowedTexture(point_type point, const borrow_data_type& data) noexcept; - - public: - [[nodiscard]] auto valid() const noexcept -> bool; - - [[nodiscard]] auto position() const noexcept -> point_type; - - auto fill(size_type::value_type y, size_type::value_type offset, size_type::value_type n, element_type element) const noexcept -> void; - auto fill(size_type::value_type y, size_type::value_type offset, data_view_type data) const noexcept -> void; - - auto fill(size_type::value_type y, size_type::value_type n, element_type element) const noexcept -> void; - auto fill(size_type::value_type y, data_view_type data) const noexcept -> void; + [[nodiscard]] auto select_texture(texture_atlas_id_type texture_atlas_id) const noexcept -> const Texture&; - auto fill(size_type::value_type y, element_type element) const noexcept -> void; + struct write_result + { + TextureWriter writer; - auto fill(element_type element) const noexcept -> void; - auto fill(data_view_type data) const noexcept -> void; + texture_atlas_id_type texture_atlas_id; + }; - auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> borrow_data_type::reference; + [[nodiscard]] auto write(size_type size) noexcept -> write_result; }; } diff --git a/src/gfx/internal/render_list.cpp b/src/gfx/render_list.cpp similarity index 52% rename from src/gfx/internal/render_list.cpp rename to src/gfx/render_list.cpp index 891dd72..1d2e5dc 100644 --- a/src/gfx/internal/render_list.cpp +++ b/src/gfx/render_list.cpp @@ -3,7 +3,7 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#include +#include #include @@ -45,11 +45,11 @@ namespace } template - [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> RenderListSharedData::vertex_sample_points_type + [[nodiscard]] constexpr auto arc_sample_points_calc() noexcept -> RenderListSharedData::arc_sample_points_type { - return [](std::index_sequence) noexcept -> RenderListSharedData::vertex_sample_points_type + return [](std::index_sequence) noexcept -> RenderListSharedData::arc_sample_points_type { - constexpr auto make_point = []() noexcept -> point_type + const auto make_point = []() noexcept -> point_type { const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; return {math::cos(a), -math::sin(a)}; @@ -61,8 +61,8 @@ namespace [[nodiscard]] constexpr auto range_of_arc(const RenderArcFlag flag) noexcept -> std::pair { - static_assert(RenderListSharedData::vertex_sample_points_count % 12 == 0); - constexpr auto factor = static_cast(RenderListSharedData::vertex_sample_points_count / 12); + static_assert(RenderListSharedData::arc_sample_points_count % 12 == 0); + constexpr auto factor = static_cast(RenderListSharedData::arc_sample_points_count / 12); switch (flag) { @@ -237,8 +237,8 @@ namespace vertex_list_{vertex_list}, index_list_{index_list} {} - explicit RenderDataAppender(RenderList::RenderListContext& context) noexcept - : RenderDataAppender{context.command_list.back(), context.vertex_list, context.index_list} {} + explicit RenderDataAppender(RenderList::RenderListContext& context) noexcept; + // : RenderDataAppender{context.command_list.back(), context.vertex_list, context.index_list} {} [[nodiscard]] auto vertex_count() const noexcept -> size_type { @@ -282,123 +282,17 @@ namespace list.push_back(c); } }; - - // class RenderListDrawer final - // { - // public: - // memory::RefWrapper self; - // - // using size_type = RenderData::size_type; - // - // using path_list_type = std::vector; - // - // path_list_type path_list; - // - // [[nodiscard]] auto make_appender() noexcept -> RenderDataAppender; - // - // // ============================================================= - // // DRAW - // // ============================================================= - // - // auto draw_polygon_line(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; - // auto draw_polygon_line_aa(color_type color, RenderFlag render_flag, float thickness) noexcept -> void; - // auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; - // auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; - // auto draw_rect_filled( - // const rect_type& rect, - // const uv_type& uv, - // color_type color_left_top, - // color_type color_right_top, - // color_type color_left_bottom, - // color_type color_right_bottom - // ) noexcept -> void; - // auto draw_rect_filled( - // const rect_type& rect, - // color_type color_left_top, - // color_type color_right_top, - // color_type color_left_bottom, - // color_type color_right_bottom - // ) noexcept -> void; - // auto draw_text( - // std::string_view utf8_text, - // std::uint32_t font_size, - // const point_type& p, - // color_type color, - // float wrap_width, - // GlyphFlag flag - // ) noexcept -> void; - // auto draw_text_size( - // std::string_view utf8_text, - // std::uint32_t font_size, - // float wrap_width, - // GlyphFlag flag - // ) noexcept -> extent_type; - // auto draw_image( - // texture_id_type texture_id, - // const point_type& display_p1, - // const point_type& display_p2, - // const point_type& display_p3, - // const point_type& display_p4, - // const uv_type& uv_p1, - // const uv_type& uv_p2, - // const uv_type& uv_p3, - // const uv_type& uv_p4, - // color_type color - // ) noexcept -> void; - // auto draw_image_rounded( - // texture_id_type texture_id, - // const rect_type& display_rect, - // const rect_type& uv_rect, - // color_type color, - // float rounding, - // RenderFlag flag - // ) noexcept -> void; - // - // // ============================================================= - // // PATH - // // ============================================================= - // - // auto path_clear() noexcept -> void; - // auto path_reserve(std::size_t size) noexcept -> void; - // auto path_reserve_extra(std::size_t size) noexcept -> void; - // auto path_pin(const point_type& point) noexcept -> void; - // auto path_stroke(color_type color, RenderFlag flag, float thickness) noexcept -> void; - // auto path_stroke(color_type color) noexcept -> void; - // auto path_arc_fast(const circle_type& circle, int from, int to) noexcept -> void; - // auto path_arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> void; - // auto path_arc_n(const circle_type& circle, float from, float to, std::uint32_t segments) noexcept -> void; - // auto path_arc(const circle_type& circle, float from, float to) noexcept -> void; - // auto path_arc_elliptical_n(const ellipse_type& ellipse, float from, float to, std::uint32_t segments) noexcept -> void; - // auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void; - // auto path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void; - // auto path_bezier_cubic_curve_casteljau( - // const point_type& p1, - // const point_type& p2, - // const point_type& p3, - // const point_type& p4, - // float tessellation_tolerance, - // std::size_t level - // ) noexcept -> void; - // auto path_bezier_quadratic_curve_casteljau( - // const point_type& p1, - // const point_type& p2, - // const point_type& p3, - // float tessellation_tolerance, - // std::size_t level - // ) noexcept -> void; - // auto path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void; - // auto path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void; - // }; } namespace gal::prometheus::gfx { RenderListSharedData::RenderListSharedData() noexcept : circle_segment_counts{}, - vertex_sample_points{vertex_sample_points_calc()}, circle_segment_max_error{0}, + arc_fast_sample_points{arc_sample_points_calc()}, arc_fast_radius_cutoff{0}, - curve_tessellation_tolerance{1.25f} + curve_tessellation_tolerance{1.25f}, + render_list_initial_flag{RenderListFlag::DEFAULT} { set_circle_tessellation_max_error(.3f); } @@ -413,11 +307,11 @@ namespace gal::prometheus::gfx return static_cast(circle_segments_calc(radius, circle_segment_max_error)); } - auto RenderListSharedData::vertex_sample_point(const std::size_t index) const noexcept -> const point_type& + auto RenderListSharedData::arc_sample_point(const std::size_t index) const noexcept -> const point_type& { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points.size()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < arc_fast_sample_points.size()); - return vertex_sample_points[index]; + return arc_fast_sample_points[index]; } auto RenderListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void @@ -429,14 +323,15 @@ namespace gal::prometheus::gfx return; } - for (auto [index, count]: circle_segment_counts | std::views::enumerate) + circle_segment_counts[0] = arc_sample_points_count; + for (auto [index, count]: circle_segment_counts | std::views::drop(1) | std::views::enumerate) { const auto radius = static_cast(index); count = static_cast(circle_segments_calc(radius, max_error)); } circle_segment_max_error = max_error; - arc_fast_radius_cutoff = circle_segments_calc_radius(vertex_sample_points_count, max_error); + arc_fast_radius_cutoff = circle_segments_calc_radius(arc_sample_points_count, max_error); } auto RenderListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void @@ -446,164 +341,251 @@ namespace gal::prometheus::gfx curve_tessellation_tolerance = tolerance; } - auto RenderList::RenderListContext::push_command() noexcept -> void + class RenderList::RenderListContext final { - // Fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this_command_texture != invalid_texture_id); + public: + using size_type = RenderData::size_type; - command_list.emplace_back( - command_type{ - .scissor = this_command_scissor, - .texture = this_command_texture, + using command_type = RenderData::command_type; + + using vertex_list_type = RenderData::vertex_list_type; + using index_list_type = RenderData::index_list_type; + using command_list_type = RenderData::command_list_type; + + mutable memory::RefWrapper context; + + RenderListFlag render_list_flag; + + // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) + // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 + // command_list: + // 0: .scissor = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) + // 1: .scissor = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) + // 2: .scissor = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) + command_list_type command_list; + vertex_list_type vertex_list; + index_list_type index_list; + + struct command_header_type + { + rect_type scissor; + texture_id_type texture; + }; + + command_header_type command_header; + + std::vector scissor_stack; + std::vector texture_stack; + + private: + auto push_command() noexcept -> void + { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_header.scissor.valid()); + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_header.texture != invalid_texture_id); + + command_type new_command + { + .scissor = command_header.scissor, + .texture = command_header.texture, .index_offset = static_cast(index_list.size()), // set by draw_xxx .element_count = 0 - } - ); - } + }; - auto RenderList::RenderListContext::on_scissor_changed() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + command_list.emplace_back(new_command); + } - const auto command_count = command_list.size(); - const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); + auto pop_command() noexcept -> void + { + command_list.pop_back(); + } - if (current_element_count != 0) + auto on_scissor_changed() noexcept -> void { - if (current_scissor != this_command_scissor) + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + + // if current command is used with different settings we need to add a new command + // ReSharper disable once CppTooWideScopeInitStatement + auto& current_command = command_list.back(); + if (current_command.element_count != 0 and current_command.scissor != command_header.scissor) { push_command(); - return; } + + // try to merge with previous command if it matches, else use current command + if (current_command.element_count == 0 and command_list.size() > 1) + { + // ReSharper disable once CppUseStructuredBinding + if (const auto& previous_command = command_list[command_list.size() - 2]; + command_header.scissor == previous_command.scissor and // + command_header.texture == previous_command.texture and // + // sequential + current_command.index_offset == previous_command.index_offset + previous_command.element_count + ) + { + pop_command(); + return; + } + } + + current_command.scissor = command_header.scissor; } - // try to merge with previous command if it matches, else use current command - if (command_count > 1) + auto on_texture_changed() noexcept -> void { - if (const auto& [previous_scissor, previous_texture, previous_index_offset, previous_element_count] = command_list[command_count - 2]; - current_element_count == 0 and (this_command_scissor == previous_scissor and this_command_texture == previous_texture) and - // sequential - current_index_offset == previous_index_offset + previous_element_count) + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + + // if current command is used with different settings we need to add a new command + // ReSharper disable once CppTooWideScopeInitStatement + auto& current_command = command_list.back(); + if (current_command.element_count != 0 and current_command.texture != command_header.texture) { - command_list.pop_back(); + push_command(); return; } - } - command_list.back().scissor = this_command_scissor; - } + // try to merge with previous command if it matches, else use current command + if (current_command.element_count == 0 and command_list.size() > 1) + { + // ReSharper disable once CppUseStructuredBinding + if (const auto& previous_command = command_list[command_list.size() - 2]; + command_header.scissor == previous_command.scissor and // + command_header.texture == previous_command.texture and // + // sequential + current_command.index_offset == previous_command.index_offset + previous_command.element_count + ) + { + pop_command(); + return; + } + } - auto RenderList::RenderListContext::on_texture_changed() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + current_command.texture = command_header.texture; + } - const auto command_count = command_list.size(); - const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); + public: + explicit RenderListContext(Context& context) noexcept + : context{context}, + render_list_flag{shared_data().render_list_initial_flag}, + command_header{.scissor = {}, .texture = invalid_texture_id} {} - if (current_element_count != 0) + [[nodiscard]] auto shared_data() const noexcept -> const RenderListSharedData& { - if (current_texture != this_command_texture) - { - push_command(); + const auto& c = context.get(); - return; - } + return c.render_list_shared_data; } - // try to merge with previous command if it matches, else use current command - if (command_count > 1) + [[nodiscard]] auto default_texture() const noexcept -> texture_id_type { - if (const auto& [previous_scissor, previous_texture, previous_index_offset, previous_element_count] = command_list[command_count - 2]; - current_element_count == 0 and (this_command_scissor == previous_scissor and this_command_texture == previous_texture) and - // sequential - current_index_offset == previous_index_offset + previous_element_count) - { - command_list.pop_back(); - return; - } + const auto& c = context.get(); + const auto& pc = c.private_; + const auto& texture_context = pc->texture_context; + + return texture_context.root_texture().id; } - command_list.back().texture = this_command_texture; - } + auto reset() noexcept -> void + { + command_list.clear(); + vertex_list.clear(); + index_list.clear(); - auto RenderList::RenderListContext::shared_data() const noexcept -> const RenderListSharedData& - { - const auto render_list_accessor = context.get().render_list_accessor(); + command_header.scissor = {}; + // the first texture is always the (default) font texture + command_header.texture = invalid_texture_id; - return render_list_accessor.shared_data(); - } + // we always have a command ready in the buffer + push_command(); - auto RenderList::RenderListContext::default_texture() const noexcept -> texture_id_type - { - const auto texture_accessor = context.get().texture_accessor(); + // todo + push_texture(default_texture()); + push_scissor(shared_data().fullscreen_scissor, false); + } - return texture_accessor.texture_context().root().id(); - } + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE - auto RenderList::RenderListContext::reset() noexcept -> void - { - command_list.clear(); - vertex_list.clear(); - index_list.clear(); - - // we don't know the size of the clip rect, so we need the user to set it - this_command_scissor = {}; - // the first texture is always the (default) font texture - this_command_texture = default_texture(); + auto push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> const rect_type& + { + // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); - // we always have a command ready in the buffer - command_list.emplace_back( - command_type{ - .scissor = this_command_scissor, - .texture = this_command_texture, - .index_offset = static_cast(index_list.size()), - // set by subsequent draw_xxx - .element_count = 0, + if (intersect_with_current_scissor) + { + command_header.scissor = rect.combine_min(command_header.scissor); } - ); - } - - auto RenderList::RenderListContext::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& - { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + else + { + command_header.scissor = rect; + } + command_header.scissor = intersect_with_current_scissor ? rect.combine_min(command_header.scissor) : rect; - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + // If the rectangle is not valid (the right-bottom corner is to the left/top of the left-top), + // or if the rectangle is not valid after the intersection calculation (the two rectangles do not intersect), + // make it valid + const auto size = command_header.scissor.size(); + if (size.width < 0) + { + command_header.scissor.extent.width = 0; + } + if (size.height < 0) + { + command_header.scissor.extent.height = 0; + } - const auto& [current_scissor, current_texture, current_index_offset, current_element_count] = command_list.back(); + scissor_stack.push_back(command_header.scissor); + on_scissor_changed(); - this_command_scissor = intersect_with_current_scissor ? rect.combine_min(current_scissor) : rect; + return scissor_stack.back(); + } - on_scissor_changed(); - return command_list.back().scissor; - } + auto push_scissor_fullscreen() noexcept -> void + { + push_scissor(shared_data().fullscreen_scissor, false); + } - auto RenderList::RenderListContext::pop_scissor() noexcept -> void - { - // at least one command - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); - this_command_scissor = command_list[command_list.size() - 2].scissor; + auto pop_scissor() noexcept -> void + { + scissor_stack.pop_back(); + if (scissor_stack.empty()) + { + // if we have no scissor, use the fullscreen scissor + command_header.scissor = shared_data().fullscreen_scissor; + } + else + { + command_header.scissor = scissor_stack.back(); + } - on_scissor_changed(); - } + on_scissor_changed(); + } - auto RenderList::RenderListContext::push_texture(const texture_id_type texture) noexcept -> void - { - this_command_texture = texture; + auto push_texture(const texture_id_type texture) noexcept -> void + { + texture_stack.push_back(texture); + command_header.texture = texture; - on_texture_changed(); - } + on_texture_changed(); + } - auto RenderList::RenderListContext::pop_texture() noexcept -> void - { - // at least one command - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); - this_command_texture = command_list[command_list.size() - 2].texture; + auto pop_texture() noexcept -> void + { + texture_stack.pop_back(); + if (texture_stack.empty()) + { + // if we have no texture, use the default texture + command_header.texture = default_texture(); + } + else + { + command_header.texture = texture_stack.back(); + } - on_texture_changed(); - } + on_texture_changed(); + } + }; auto RenderList::Painter::draw_polygon_line(const color_type color, const float thickness, const bool close) noexcept -> void { @@ -1108,7 +1090,7 @@ namespace gal::prometheus::gfx auto RenderList::Painter::circle(const circle_type& circle) noexcept -> Painter& { - return arc_fast(circle, 0, RenderListSharedData::vertex_sample_points_count - 1); + return arc_fast(circle, 0, RenderListSharedData::arc_sample_points_count - 1); } auto RenderList::Painter::circle(const circle_type::point_type& center, const circle_type::radius_value_type radius) noexcept -> Painter& @@ -1160,9 +1142,9 @@ namespace gal::prometheus::gfx const auto& shared_data = render_list_context.shared_data(); // Calculate arc auto segment step size - auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); + auto step = RenderListSharedData::arc_sample_points_count / shared_data.circle_auto_segment_count(radius); // Make sure we never do steps larger than one quarter of the circle - step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); + step = std::clamp(step, static_cast(1), RenderListSharedData::arc_sample_points_count / 4); const auto sample_range = math::abs(sample_point_to - sample_point_from); const auto next_step = step; @@ -1188,12 +1170,12 @@ namespace gal::prometheus::gfx } auto sample_index = sample_point_from; - if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::arc_sample_points_count)) { - sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); + sample_index = sample_index % static_cast(RenderListSharedData::arc_sample_points_count); if (sample_index < 0) { - sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + sample_index += static_cast(RenderListSharedData::arc_sample_points_count); } } @@ -1201,13 +1183,13 @@ namespace gal::prometheus::gfx { for (int i = sample_point_from; i <= sample_point_to; i += static_cast(step), sample_index += static_cast(step), step = next_step) { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) + // a_step is clamped to arc_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (std::cmp_greater_equal(sample_index, RenderListSharedData::arc_sample_points_count)) { - sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); + sample_index -= static_cast(RenderListSharedData::arc_sample_points_count); } - const auto& sample_point = shared_data.vertex_sample_point(sample_index); + const auto& sample_point = shared_data.arc_sample_point(sample_index); pin({center + sample_point * radius}); } @@ -1216,13 +1198,13 @@ namespace gal::prometheus::gfx { for (int i = sample_point_from; i >= sample_point_to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + // a_step is clamped to arc_sample_points_count, so we have guaranteed that it will not wrap over range twice or more if (sample_index < 0) { - sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); + sample_index += static_cast(RenderListSharedData::arc_sample_points_count); } - const auto& sample_point = shared_data.vertex_sample_point(sample_index); + const auto& sample_point = shared_data.arc_sample_point(sample_index); pin({center + sample_point * radius}); } @@ -1230,13 +1212,13 @@ namespace gal::prometheus::gfx if (extra_max_sample) { - auto normalized_max_sample_index = sample_point_to % static_cast(RenderListSharedData::vertex_sample_points_count); + auto normalized_max_sample_index = sample_point_to % static_cast(RenderListSharedData::arc_sample_points_count); if (normalized_max_sample_index < 0) { - normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; + normalized_max_sample_index += RenderListSharedData::arc_sample_points_count; } - const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); + const auto& sample_point = shared_data.arc_sample_point(normalized_max_sample_index); pin({center + sample_point * radius}); } @@ -1292,15 +1274,15 @@ namespace gal::prometheus::gfx // We are going to use precomputed values for mid-samples. // Determine first and last sample in lookup table that belong to the arc - const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * degree_from / (std::numbers::pi_v * 2); - const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * degree_to / (std::numbers::pi_v * 2); + const auto sample_from_f = RenderListSharedData::arc_sample_points_count * degree_from / (std::numbers::pi_v * 2); + const auto sample_to_f = RenderListSharedData::arc_sample_points_count * degree_to / (std::numbers::pi_v * 2); const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); - const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; - const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; + const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::arc_sample_points_count; + const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::arc_sample_points_count; const auto emit_start = math::abs(segment_from_angle - degree_from) >= 1e-5f; const auto emit_end = math::abs(degree_to - segment_to_angle) >= 1e-5f; @@ -1512,8 +1494,8 @@ namespace gal::prometheus::gfx clear(); } - RenderList::RenderList(Context& context, RenderListFlag flag) noexcept - : context_{memory::make_unique(context, flag)} {} + RenderList::RenderList(Context& context) noexcept + : context_{memory::make_unique(context)} {} RenderList::RenderList(RenderList&&) noexcept = default; @@ -1531,7 +1513,12 @@ namespace gal::prometheus::gfx context_->render_list_flag = flag; } - auto RenderList::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> rect_type& + auto RenderList::data() const noexcept -> RenderData + { + return {.vertex_list = context_->vertex_list, .index_list = context_->index_list, .command_list = context_->command_list}; + } + + auto RenderList::push_scissor(const rect_type& rect, const bool intersect_with_current_scissor) noexcept -> const rect_type& { return context_->push_scissor(rect, intersect_with_current_scissor); } @@ -2011,7 +1998,6 @@ namespace gal::prometheus::gfx const std::uint32_t segments, const float thickness ) noexcept -> void - // clang-format on { if (color.alpha == 0) { @@ -2028,7 +2014,6 @@ namespace gal::prometheus::gfx const color_type color, const float thickness ) noexcept -> void - // clang-format on { if (color.alpha == 0) { @@ -2057,7 +2042,6 @@ namespace gal::prometheus::gfx const GlyphFlag flag, const float wrap_width ) noexcept -> void - // clang-format on { if (color.alpha == 0) { @@ -2070,92 +2054,91 @@ namespace gal::prometheus::gfx // The texture used for glyphs may not be the default texture, then we need to switch the texture, // but at this time, the glyph information may not be written to the texture atlas (e.g. the first frame), then we can't know the ID of the texture atlas - // note: - // Line break ('\n') have no glyph information (nullptr), meaning we need to skip it - auto& render_list_context = *context_; auto& context = render_list_context.context.get(); - auto font_accessor = context.font_accessor(); - auto& font_context = font_accessor.font_context(); - const auto texture_accessor = context.texture_accessor(); - const auto& texture_context = texture_accessor.texture_context(); + // ReSharper disable once CppUseStructuredBinding + auto& context_private = *context.private_; - const auto utf32_text = chars::convert(utf8_text); - // todo: Don't let the fallback character replace line breaks! - // const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); - const auto glyphs = font_context.glyph_of(utf32_text, font_size, flag); + const auto& texture_context = context_private.texture_context; + auto& glyph_context = context_private.glyph_context; - if (std::ranges::any_of( - glyphs, - [](const auto* glyph) noexcept -> bool - { - return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; - } - )) - { - // skip this frame? - return; - } + const auto utf32_text = chars::convert(utf8_text); + const auto glyphs = glyph_context.glyph_of(utf32_text, font_size, flag); - const auto visible_glyph_count = std::ranges::count_if( - glyphs, - [](const auto* glyph) noexcept -> bool - { - if (glyph == nullptr) + if (const auto has_glyph_not_uploaded = std::ranges::any_of( + glyphs, + [](const auto& glyph) noexcept -> bool { - return false; - } + if (const auto& g = glyph.get(); g.visible) + { + return g.texture_atlas_id == invalid_texture_atlas_id; + } - if (not glyph->visible) - { return false; } + ); + has_glyph_not_uploaded) + { + // skip this frame? + return; + } - return true; - } - ); - - // two triangle without path - const auto vertex_count = std::size_t{4} * visible_glyph_count; - const auto index_count = std::size_t{6} * visible_glyph_count; - RenderDataAppender appender{render_list_context}; - appender.reserve(vertex_count, index_count); + // const auto visible_glyph_count = std::ranges::count_if( + // glyphs, + // [](const auto& glyph) noexcept -> bool + // { + // const auto& g = glyph.get(); + // + // return g.visible; + // } + // ); + // + // // two triangle without path + // const auto vertex_count = std::size_t{4} * visible_glyph_count; + // const auto index_count = std::size_t{6} * visible_glyph_count; + // RenderDataAppender appender{render_list_context}; + // appender.reserve(vertex_count, index_count); const auto wrap_pos_x = point.x + wrap_width; const auto line_height = static_cast(font_size); auto cursor = point + point_type{0, line_height}; - for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + auto adjust_cursor_position = [=, &cursor](const float advance_x) noexcept -> void { - if (glyph == nullptr) + if (cursor.x + advance_x > wrap_pos_x) { - if (codepoint == U'\n') - { - cursor.x = point.x; - cursor.y += line_height; - } + cursor.x = point.x; + cursor.y += line_height; } - else if (glyph->visible) + }; + + for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) + { + // ReSharper disable once CppUseStructuredBinding + if (auto& g = glyph.get(); g.visible) { - const auto& texture = texture_context.select(glyph->texture_atlas_id); + const auto& glyph_rect = g.rect; + const auto& glyph_uv = g.uv; + const auto glyph_advance_x = g.advance_x; + const auto glyph_colored = g.colored; - const auto new_texture = render_list_context.this_command_texture != texture.id(); + const auto& texture = texture_context.select_texture(g.texture_atlas_id); + const auto new_texture = render_list_context.command_header.texture != texture.id; if (new_texture) { - render_list_context.push_texture(texture.id()); + render_list_context.push_texture(texture.id); } - const auto& glyph_rect = glyph->rect; - const auto& glyph_uv = glyph->uv; - const auto glyph_advance_x = glyph->advance_x; - const auto glyph_colored = glyph->colored; + // We could have used RenderDataAppender::reserve on the outer scope to allocate space once, but we can't guarantee that we won't switch textures when rendering characters - if (cursor.x + glyph_advance_x > wrap_pos_x) - { - cursor.x = point.x; - cursor.y += line_height; - } + // two triangle without path + constexpr auto vertex_count = std::size_t{4}; + constexpr auto index_count = std::size_t{6}; + RenderDataAppender appender{render_list_context}; + appender.reserve(vertex_count, index_count); + + adjust_cursor_position(glyph_advance_x); const auto left_top = glyph_rect.left_top().to(); const rect_type char_rect @@ -2174,12 +2157,46 @@ namespace gal::prometheus::gfx appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + cursor.x += glyph_advance_x; + if (new_texture) { render_list_context.pop_texture(); } + } + else + { + if (codepoint == U'\n' or codepoint == U'\r') + { + cursor.x = point.x; + cursor.y += line_height; + } + else if (codepoint == U'\t') + { + constexpr std::size_t step = 4; + const auto& space_glyph = glyph_context.glyph_of(' ', font_size, flag); + const auto tab_width = step * space_glyph.advance_x; - cursor.x += glyph_advance_x; + if (const auto remaining_width = wrap_width - (cursor.x - point.x); + remaining_width >= tab_width) + { + cursor.x += tab_width; + } + else + { + const auto n = remaining_width / space_glyph.advance_x; + const auto n_integer = static_cast(n); + + cursor.x = point.x + static_cast(step - n_integer) * space_glyph.advance_x; + cursor.y += line_height; + } + } + else + { + adjust_cursor_position(g.advance_x); + + cursor.x += g.advance_x; + } } } } @@ -2202,19 +2219,27 @@ namespace gal::prometheus::gfx { const auto& render_list_context = *context_; auto& context = render_list_context.context.get(); - auto font_accessor = context.font_accessor(); - auto& font_context = font_accessor.font_context(); + // ReSharper disable once CppUseStructuredBinding + auto& context_private = *context.private_; + + auto& glyph_context = context_private.glyph_context; const auto utf32_text = chars::convert(utf8_text); - const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); + const auto glyphs = glyph_context.glyph_of(utf32_text, font_size, flag); - if (std::ranges::any_of( - glyphs, - [](const auto& glyph) noexcept -> bool - { - return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; - } - )) + if (const auto has_glyph_not_uploaded = std::ranges::any_of( + glyphs, + [](const auto& glyph) noexcept -> bool + { + if (const auto& g = glyph.get(); g.visible) + { + return g.texture_atlas_id == invalid_texture_atlas_id; + } + + return false; + } + ); + has_glyph_not_uploaded) { // skip this frame? return {0, 0}; @@ -2228,26 +2253,51 @@ namespace gal::prometheus::gfx for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) { - if (glyph == nullptr) + if (auto& g = glyph.get(); g.visible) { - if (codepoint == U'\n') + if (const auto glyph_advance_x = g.advance_x; + current_width + glyph_advance_x > wrap_width) { max_width = std::ranges::max(max_width, current_width); - current_width = 0; + current_width = glyph_advance_x; total_height += line_height; } + else + { + current_width += glyph_advance_x; + } } - else if (glyph->visible) + else { - if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) + if (codepoint == U'\n' or codepoint == U'\r') { max_width = std::ranges::max(max_width, current_width); - current_width = glyph_advance_x; + current_width = 0; total_height += line_height; } + else if (codepoint == U'\t') + { + constexpr std::size_t step = 4; + const auto& space_glyph = glyph_context.glyph_of(' ', font_size, flag); + const auto tab_width = step * space_glyph.advance_x; + + if (const auto remaining_width = wrap_width - current_width; remaining_width >= tab_width) + { + current_width += tab_width; + } + else + { + const auto n = remaining_width / space_glyph.advance_x; + const auto n_integer = static_cast(n); + + max_width += static_cast(n_integer) * space_glyph.advance_x; + current_width = static_cast(step - n_integer) * space_glyph.advance_x; + total_height += line_height; + } + } else { - current_width += glyph_advance_x; + current_width += g.advance_x; } } } @@ -2277,7 +2327,7 @@ namespace gal::prometheus::gfx auto& render_list_context = *context_; - const auto new_texture = render_list_context.this_command_texture != texture_id; + const auto new_texture = render_list_context.command_header.texture != texture_id; if (new_texture) { @@ -2389,7 +2439,7 @@ namespace gal::prometheus::gfx { auto& render_list_context = *context_; - const auto new_texture = render_list_context.this_command_texture != texture_id; + const auto new_texture = render_list_context.command_header.texture != texture_id; if (new_texture) { @@ -2478,1165 +2528,6 @@ namespace gal::prometheus::gfx namespace { - // auto RenderListDrawer::make_appender() noexcept -> RenderDataAppender - // { - // auto& render_list = self.get(); - // - // return {render_list.command_list.back(), render_list.vertex_list, render_list.index_list}; - // } - // - // auto RenderListDrawer::draw_polygon_line(const color_type color, const RenderFlag render_flag, const float thickness) noexcept -> void - // { - // const auto path_point_count = path_list.size(); - // const auto& path_point = path_list; - // - // if (path_point_count < 2 or color.alpha == 0) - // { - // return; - // } - // - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // auto appender = make_appender(); - // - // const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; - // const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - // - // const auto vertex_count = segments_count * 4; - // const auto index_count = segments_count * 6; - // appender.reserve(vertex_count, index_count); - // - // for (std::decay_t i = 0; i < segments_count; ++i) - // { - // const auto n = (i + 1) % path_point_count; - // - // const auto& p1 = path_point[i]; - // const auto& p2 = path_point[n]; - // - // auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); - // normalized_x *= (thickness * .5f); - // normalized_y *= (thickness * .5f); - // - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // const auto& opaque_uv = shared_data.white_pixel_uv; - // - // appender.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - // appender.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - // appender.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - // appender.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - // - // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - // } - // } - // - // auto RenderListDrawer::draw_polygon_line_aa(const color_type color, const RenderFlag render_flag, float thickness) noexcept -> void - // { - // const auto path_point_count = path_list.size(); - // const auto& path_point = path_list; - // - // if (path_point_count < 2 or color.alpha == 0) - // { - // return; - // } - // - // const auto& render_list = self.get(); - // const auto render_list_flag = render_list.render_list_flag; - // const auto& shared_data = render_list.shared_data(); - // auto appender = make_appender(); - // - // const auto& opaque_uv = shared_data.white_pixel_uv; - // const auto transparent_color = color.transparent(); - // - // const auto is_closed = (render_flag & RenderFlag::CLOSED) != RenderFlag::NONE; - // const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - // const auto is_thick_line = thickness > 1.f; - // - // thickness = std::ranges::max(thickness, 1.f); - // const auto thickness_integer = static_cast(thickness); - // const auto thickness_fractional = thickness - static_cast(thickness_integer); - // - // const auto is_use_texture = - // ((render_list_flag & RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == RenderListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and - // (thickness_integer < RenderListSharedData::baked_line_uv_count) and (thickness_fractional <= .00001f)); - // - // const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); - // const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); - // appender.reserve(vertex_cont, index_count); - // - // // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - // path_list_type temp_buffer{}; - // temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); - // auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - // auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; - // - // // Calculate normals (tangents) for each line segment - // for (std::decay_t i = 0; i < segments_count; ++i) - // { - // const auto n = (i + 1) % path_point_count; - // const auto d = path_point[n] - path_point[i]; - // - // const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); - // temp_buffer_normals[i].x = normalized_y; - // temp_buffer_normals[i].y = -normalized_x; - // } - // - // if (not is_closed) - // { - // temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; - // } - // - // // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point - // if (is_use_texture or not is_thick_line) - // { - // // [PATH 1] Texture-based lines (thick or non-thick) - // - // // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA - // const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; - // - // // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - // if (not is_closed) - // { - // temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; - // temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; - // temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; - // temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; - // } - // - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // - // // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - // auto vertex_index_for_start = current_vertex_index; - // for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - // { - // const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - // const auto vertex_index_for_end = static_cast( - // // closed - // (first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + (is_use_texture ? 2 : 3)) - // ); - // - // // Average normals - // const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - // // dm_x, dm_y are offset to the outer edge of the AA area - // auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - // dm_x *= half_draw_size; - // dm_y *= half_draw_size; - // - // // Add temporary vertexes for the outer edges - // temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; - // temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; - // - // if (is_use_texture) - // { - // // Add indices for two triangles - // - // // right - // appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); - // // left - // appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); - // } - // else - // { - // // Add indexes for four triangles - // - // // right 1 - // appender.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); - // // right 2 - // appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); - // // left 1 - // appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); - // // left 2 - // appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); - // } - // - // vertex_index_for_start = vertex_index_for_end; - // } - // - // // Add vertexes for each point on the line - // if (is_use_texture) - // { - // const auto& uv = shared_data.baked_line_uvs[thickness_integer]; - // - // const auto uv0 = uv.left_top(); - // const auto uv1 = uv.right_bottom(); - // for (std::decay_t i = 0; i < path_point_count; ++i) - // { - // // left-side outer edge - // appender.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); - // // right-side outer edge - // appender.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); - // } - // } - // else - // { - // // If we're not using a texture, we need the center vertex as well - // for (std::decay_t i = 0; i < path_point_count; ++i) - // { - // // center of line - // appender.add_vertex(path_point[i], opaque_uv, color); - // // left-side outer edge - // appender.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); - // // right-side outer edge - // appender.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); - // } - // } - // } - // else - // { - // // [PATH 2] Non-texture-based lines (non-thick) - // - // // we need to draw the solid line core and thus require four vertices per point - // const auto half_inner_thickness = (thickness - 1.f) * .5f; - // - // // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - // if (not is_closed) - // { - // const auto point_last = path_point_count - 1; - // temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); - // temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); - // temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); - // temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); - // temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - // temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - // temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - // temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - // } - // - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // - // // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - // auto vertex_index_for_start = current_vertex_index; - // for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - // { - // const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - // const auto vertex_index_for_end = static_cast((first_point_of_segment + 1) == path_point_count ? current_vertex_index : (vertex_index_for_start + 4)); - // - // // Average normals - // const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - // const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - // const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); - // const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); - // const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); - // const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); - // - // // Add temporary vertices - // temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; - // temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; - // temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; - // temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; - // - // // Add indexes - // appender.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); - // appender.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); - // appender.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); - // appender.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); - // appender.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); - // appender.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); - // - // vertex_index_for_start = vertex_index_for_end; - // } - // - // // Add vertices - // for (std::decay_t i = 0; i < path_point_count; ++i) - // { - // appender.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); - // appender.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); - // appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); - // appender.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); - // } - // } - // } - // - // auto RenderListDrawer::draw_convex_polygon_line_filled(const color_type color) noexcept -> void - // { - // const auto path_point_count = path_list.size(); - // const auto& path_point = path_list; - // - // if (path_point_count < 3 or color.alpha == 0) - // { - // return; - // } - // - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // auto appender = make_appender(); - // - // const auto vertex_count = path_point_count; - // const auto index_count = (path_point_count - 2) * 3; - // appender.reserve(vertex_count, index_count); - // - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // const auto& opaque_uv = shared_data.white_pixel_uv; - // - // std::ranges::for_each( - // path_point, - // [&](const point_type& point) noexcept -> void - // { - // appender.add_vertex(point, opaque_uv, color); - // } - // ); - // for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); - // - // appender.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); - // } - // } - // - // auto RenderListDrawer::draw_convex_polygon_line_filled_aa(const color_type color) noexcept -> void - // { - // const auto path_point_count = path_list.size(); - // const auto& path_point = path_list; - // - // if (path_point_count < 3 or color.alpha == 0) - // { - // return; - // } - // - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // auto appender = make_appender(); - // - // const auto& opaque_uv = shared_data.white_pixel_uv; - // const auto transparent_color = color.transparent(); - // - // const auto vertex_count = path_point_count * 2; - // const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; - // appender.reserve(vertex_count, index_count); - // - // const auto current_vertex_inner_index = static_cast(appender.vertex_count()); - // const auto current_vertex_outer_index = static_cast(appender.vertex_count() + 1); - // - // // Add indexes for fill - // for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); - // - // appender.add_index(current_vertex_inner_index + 0, current_vertex_inner_index + static_cast((i - 1) << 1), current_vertex_inner_index + static_cast(i << 1)); - // } - // - // path_list_type temp_buffer{}; - // temp_buffer.resize(path_point_count); - // auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - // - // for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - // { - // const auto d = path_point[n] - path_point[i]; - // - // const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); - // temp_buffer_normals[i].x = normalized_y; - // temp_buffer_normals[i].y = -normalized_x; - // } - // for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - // { - // // Average normals - // const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; - // auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); - // dm_x *= .5f; - // dm_y *= .5f; - // - // // inner - // appender.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); - // // outer - // appender.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); - // - // // Add indexes for fringes - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); - // - // appender.add_index( - // current_vertex_inner_index + static_cast(n << 1), - // current_vertex_inner_index + static_cast(i << 1), - // current_vertex_outer_index + static_cast(i << 1) - // ); - // appender.add_index( - // current_vertex_outer_index + static_cast(i << 1), - // current_vertex_outer_index + static_cast(n << 1), - // current_vertex_inner_index + static_cast(n << 1) - // ); - // } - // } - // - // auto RenderListDrawer::draw_rect_filled( - // const rect_type& rect, - // const uv_type& uv, - // const color_type color_left_top, - // const color_type color_right_top, - // const color_type color_left_bottom, - // const color_type color_right_bottom - // ) noexcept -> void - // { - // auto appender = make_appender(); - // - // // two triangle without path - // constexpr size_type vertex_count = 4; - // constexpr size_type index_count = 6; - // appender.reserve(vertex_count, index_count); - // - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // - // appender.add_vertex(rect.left_top(), uv, color_left_top); - // appender.add_vertex(rect.right_top(), uv, color_right_top); - // appender.add_vertex(rect.right_bottom(), uv, color_right_bottom); - // appender.add_vertex(rect.left_bottom(), uv, color_left_bottom); - // - // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - // } - // - // auto RenderListDrawer::draw_rect_filled( - // const rect_type& rect, - // const color_type color_left_top, - // const color_type color_right_top, - // const color_type color_left_bottom, - // const color_type color_right_bottom - // ) noexcept -> void - // { - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // const auto& opaque_uv = shared_data.white_pixel_uv; - // - // draw_rect_filled(rect, opaque_uv, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - // } - // - // auto RenderListDrawer::draw_text( - // const std::string_view utf8_text, - // const std::uint32_t font_size, - // const point_type& p, - // const color_type color, - // const float wrap_width, - // const GlyphFlag flag - // ) noexcept -> void - // { - // // todo: - // // The texture used for glyphs may not be the default texture, then we need to switch the texture, - // // but at this time, the glyph information may not be written to the texture atlas (e.g. the first frame), then we can't know the ID of the texture atlas - // - // // note: - // // Line break ('\n') have no glyph information (nullptr), meaning we need to skip it - // - // auto& render_list = self.get(); - // - // const Renderer::AccessorTexture texture_accessor{render_list.renderer.get()}; - // const auto& texture_context = texture_accessor.context(); - // - // Renderer::AccessorFont font_accessor{render_list.renderer.get()}; - // auto& font_context = font_accessor.context(); - // - // const auto utf32_text = chars::convert(utf8_text); - // const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); - // - // if (std::ranges::any_of( - // glyphs, - // [](const auto* glyph) noexcept -> bool - // { - // return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; - // } - // )) - // { - // // skip this frame? - // return; - // } - // - // const auto visible_glyph_count = std::ranges::count_if( - // glyphs, - // [](const auto* glyph) noexcept -> bool - // { - // if (glyph == nullptr) - // { - // return false; - // } - // - // if (not glyph->visible) - // { - // return false; - // } - // - // return true; - // } - // ); - // - // // two triangle without path - // const auto vertex_count = std::size_t{4} * visible_glyph_count; - // const auto index_count = std::size_t{6} * visible_glyph_count; - // auto appender = make_appender(); - // appender.reserve(vertex_count, index_count); - // - // const auto wrap_pos_x = p.x + wrap_width; - // const auto line_height = static_cast(font_size); - // auto cursor = p + point_type{0, line_height}; - // - // for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) - // { - // if (glyph == nullptr) - // { - // if (codepoint == U'\n') - // { - // cursor.x = p.x; - // cursor.y += line_height; - // } - // } - // else if (glyph->visible) - // { - // const auto& texture = texture_context.select(glyph->texture_atlas_id); - // - // const auto new_texture = render_list.this_command_texture != texture.id(); - // - // if (new_texture) - // { - // render_list.push_texture(texture.id()); - // } - // - // const auto& glyph_rect = glyph->rect; - // const auto& glyph_uv = glyph->uv; - // const auto glyph_advance_x = glyph->advance_x; - // const auto glyph_colored = glyph->colored; - // - // if (cursor.x + glyph_advance_x > wrap_pos_x) - // { - // cursor.x = p.x; - // cursor.y += line_height; - // } - // - // const auto left_top = glyph_rect.left_top().to(); - // const rect_type char_rect - // { - // cursor + point_type{left_top.x, -left_top.y}, - // glyph_rect.size().to() - // }; - // const auto color_may_colored = glyph_colored ? color.transparent() : color; - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // - // appender.add_vertex(char_rect.left_top(), glyph_uv.left_top(), color_may_colored); - // appender.add_vertex(char_rect.right_top(), glyph_uv.right_top(), color_may_colored); - // appender.add_vertex(char_rect.right_bottom(), glyph_uv.right_bottom(), color_may_colored); - // appender.add_vertex(char_rect.left_bottom(), glyph_uv.left_bottom(), color_may_colored); - // - // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - // - // if (new_texture) - // { - // render_list.pop_texture(); - // } - // - // cursor.x += glyph_advance_x; - // } - // } - // } - // - // auto RenderListDrawer::draw_text_size( - // const std::string_view utf8_text, - // const std::uint32_t font_size, - // const float wrap_width, - // const GlyphFlag flag - // ) noexcept -> extent_type - // { - // const auto& render_list = self.get(); - // - // Renderer::AccessorFont font_accessor{render_list.renderer.get()}; - // auto& font_context = font_accessor.context(); - // - // const auto utf32_text = chars::convert(utf8_text); - // const auto glyphs = font_context.glyph_of_or_fallback(utf32_text, font_size, flag); - // - // if (std::ranges::any_of( - // glyphs, - // [](const auto& glyph) noexcept -> bool - // { - // return glyph and glyph->texture_atlas_id == invalid_texture_atlas_id; - // } - // )) - // { - // // skip this frame? - // return {0, 0}; - // } - // - // const auto line_height = static_cast(font_size); - // - // float max_width = 0; - // float current_width = 0; - // float total_height = line_height; - // - // for (const auto [codepoint, glyph]: std::views::zip(utf32_text, glyphs)) - // { - // if (glyph == nullptr) - // { - // if (codepoint == U'\n') - // { - // max_width = std::ranges::max(max_width, current_width); - // current_width = 0; - // total_height += line_height; - // } - // } - // else if (glyph->visible) - // { - // if (const auto glyph_advance_x = glyph->advance_x; current_width + glyph_advance_x > wrap_width) - // { - // max_width = std::ranges::max(max_width, current_width); - // current_width = glyph_advance_x; - // total_height += line_height; - // } - // else - // { - // current_width += glyph_advance_x; - // } - // } - // } - // - // max_width = std::ranges::max(max_width, current_width); - // - // return {max_width, total_height}; - // } - // - // auto RenderListDrawer::draw_image( - // const texture_id_type texture_id, - // const point_type& display_p1, - // const point_type& display_p2, - // const point_type& display_p3, - // const point_type& display_p4, - // const uv_type& uv_p1, - // const uv_type& uv_p2, - // const uv_type& uv_p3, - // const uv_type& uv_p4, - // const color_type color - // ) noexcept -> void - // { - // auto& render_list = self.get(); - // - // const auto new_texture = render_list.this_command_texture != texture_id; - // - // if (new_texture) - // { - // render_list.push_texture(texture_id); - // } - // - // auto appender = make_appender(); - // - // // two triangle without path - // constexpr size_type vertex_count = 4; - // constexpr size_type index_count = 6; - // appender.reserve(vertex_count, index_count); - // - // const auto current_vertex_index = static_cast(appender.vertex_count()); - // - // appender.add_vertex(display_p1, uv_p1, color); - // appender.add_vertex(display_p2, uv_p2, color); - // appender.add_vertex(display_p3, uv_p3, color); - // appender.add_vertex(display_p4, uv_p4, color); - // - // appender.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); - // appender.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); - // - // if (new_texture) - // { - // render_list.pop_texture(); - // } - // } - // - // auto RenderListDrawer::draw_image_rounded( - // const texture_id_type texture_id, - // const rect_type& display_rect, - // const rect_type& uv_rect, - // const color_type color, - // float rounding, - // RenderFlag flag - // ) noexcept -> void - // { - // // @see `path_rect` - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); - // - // if (rounding >= .5f) - // { - // flag = to_fixed_rect_corner_flag(flag); - // - // const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; - // const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; - // - // rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); - // rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); - // } - // - // if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) - // { - // draw_image( - // texture_id, - // display_rect.left_top(), - // display_rect.right_top(), - // display_rect.right_bottom(), - // display_rect.left_bottom(), - // uv_rect.left_top(), - // uv_rect.right_top(), - // uv_rect.right_bottom(), - // uv_rect.left_bottom(), - // color - // ); - // } - // else - // { - // auto& render_list = self.get(); - // - // const auto new_texture = render_list.this_command_texture != texture_id; - // - // if (new_texture) - // { - // render_list.push_texture(texture_id); - // } - // - // const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; - // const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; - // const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - // const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - // - // path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); - // path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); - // path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); - // path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); - // - // const auto before_vertex_count = render_list.vertex_list.size(); - // // draw - // path_stroke(color); - // const auto after_vertex_count = render_list.vertex_list.size(); - // - // // set uv manually - // - // const auto display_size = display_rect.size(); - // const auto uv_size = uv_rect.size(); - // const auto scale = uv_size / display_size; - // - // auto it = render_list.vertex_list.begin() + static_cast(before_vertex_count); - // const auto end = render_list.vertex_list.begin() + static_cast(after_vertex_count); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == render_list.vertex_list.end()); - // - // // note: linear uv - // const auto uv_min = uv_rect.left_top(); - // // const auto uv_max = uv_rect.right_bottom(); - // while (it != end) - // { - // const auto v = uv_min + (it->position - display_rect.left_top()) * scale; - // - // it->uv = { - // // std::ranges::clamp(v.x, uv_min.x, uv_max.x), - // v.x, - // // std::ranges::clamp(v.y, uv_min.y, uv_max.y) - // v.y - // }; - // it += 1; - // } - // - // if (new_texture) - // { - // render_list.pop_texture(); - // } - // } - // } - // - // auto RenderListDrawer::path_clear() noexcept -> void - // { - // path_list.clear(); - // } - // - // auto RenderListDrawer::path_reserve(const std::size_t size) noexcept -> void - // { - // path_list.reserve(size); - // } - // - // auto RenderListDrawer::path_reserve_extra(const std::size_t size) noexcept -> void - // { - // path_reserve(path_list.size() + size); - // } - // - // auto RenderListDrawer::path_pin(const point_type& point) noexcept -> void - // { - // path_list.push_back(point); - // } - // - // auto RenderListDrawer::path_stroke(const color_type color, const RenderFlag flag, const float thickness) noexcept -> void - // { - // if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_LINE) != RenderListFlag::NONE) - // { - // draw_polygon_line_aa(color, flag, thickness); - // } - // else - // { - // draw_polygon_line(color, flag, thickness); - // } - // - // path_clear(); - // } - // - // auto RenderListDrawer::path_stroke(const color_type color) noexcept -> void - // { - // if (const auto render_list_flag = self.get().render_list_flag; (render_list_flag & RenderListFlag::ANTI_ALIASED_FILL) != RenderListFlag::NONE) - // { - // draw_convex_polygon_line_filled_aa(color); - // } - // else - // { - // draw_convex_polygon_line_filled(color); - // } - // - // path_clear(); - // } - // - // auto RenderListDrawer::path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void - // { - // const auto& [center, radius] = circle; - // - // if (radius < .5f) - // { - // path_pin(center); - // return; - // } - // - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // - // // Calculate arc auto segment step size - // auto step = RenderListSharedData::vertex_sample_points_count / shared_data.circle_auto_segment_count(radius); - // // Make sure we never do steps larger than one quarter of the circle - // step = std::clamp(step, static_cast(1), RenderListSharedData::vertex_sample_points_count / 4); - // - // const auto sample_range = math::abs(to - from); - // const auto next_step = step; - // - // auto extra_max_sample = false; - // if (step > 1) - // { - // const auto overstep = sample_range % step; - // if (overstep > 0) - // { - // extra_max_sample = true; - // - // // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, - // // distribute first step range evenly between them by reducing first step size. - // step -= (step - overstep) / 2; - // } - // - // path_reserve_extra(sample_range / step + 1 + (overstep > 0)); - // } - // else - // { - // path_reserve_extra(sample_range + 1); - // } - // - // auto sample_index = from; - // if (sample_index < 0 or std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) - // { - // sample_index = sample_index % static_cast(RenderListSharedData::vertex_sample_points_count); - // if (sample_index < 0) - // { - // sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); - // } - // } - // - // if (to >= from) - // { - // for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) - // { - // // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - // if (std::cmp_greater_equal(sample_index, RenderListSharedData::vertex_sample_points_count)) - // { - // sample_index -= static_cast(RenderListSharedData::vertex_sample_points_count); - // } - // - // const auto& sample_point = shared_data.vertex_sample_point(sample_index); - // - // path_pin({center + sample_point * radius}); - // } - // } - // else - // { - // for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) - // { - // // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - // if (sample_index < 0) - // { - // sample_index += static_cast(RenderListSharedData::vertex_sample_points_count); - // } - // - // const auto& sample_point = shared_data.vertex_sample_point(sample_index); - // - // path_pin({center + sample_point * radius}); - // } - // } - // - // if (extra_max_sample) - // { - // auto normalized_max_sample_index = to % static_cast(RenderListSharedData::vertex_sample_points_count); - // if (normalized_max_sample_index < 0) - // { - // normalized_max_sample_index += RenderListSharedData::vertex_sample_points_count; - // } - // - // const auto& sample_point = shared_data.vertex_sample_point(normalized_max_sample_index); - // - // path_pin({center + sample_point * radius}); - // } - // } - // - // auto RenderListDrawer::path_arc_fast(const circle_type& circle, const RenderArcFlag flag) noexcept -> void - // { - // const auto [from, to] = range_of_arc(flag); - // - // return path_arc_fast(circle, from, to); - // } - // - // auto RenderListDrawer::path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); - // - // const auto& [center, radius] = circle; - // - // if (radius < .5f) - // { - // path_pin(center); - // return; - // } - // - // path_reserve_extra(segments); - // for (std::uint32_t i = 0; i < segments; ++i) - // { - // const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - // path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); - // } - // } - // - // auto RenderListDrawer::path_arc(const circle_type& circle, const float from, const float to) noexcept -> void - // { - // const auto& [center, radius] = circle; - // - // if (radius < .5f) - // { - // path_pin(center); - // return; - // } - // - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // - // // Automatic segment count - // if (radius <= shared_data.arc_fast_radius_cutoff) - // { - // const auto is_reversed = to < from; - // - // // We are going to use precomputed values for mid-samples. - // // Determine first and last sample in lookup table that belong to the arc - // const auto sample_from_f = RenderListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); - // const auto sample_to_f = RenderListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); - // - // const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); - // const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); - // const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); - // - // const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; - // const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / RenderListSharedData::vertex_sample_points_count; - // - // const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; - // const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; - // - // if (emit_start) - // { - // // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - // path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); - // } - // if (sample_mid > 0) - // { - // path_arc_fast(circle, sample_from, sample_to); - // } - // if (emit_end) - // { - // // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - // path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); - // } - // } - // else - // { - // const auto arc_length = to - from; - // const auto circle_segment_count = shared_data.circle_auto_segment_count(radius); - // const auto arc_segment_count = std::ranges::max( - // static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), - // static_cast(std::numbers::pi_v * 2 / arc_length) - // ); - // path_arc_n(circle, from, to, arc_segment_count); - // } - // } - // - // auto RenderListDrawer::path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void - // { - // const auto& [center, radius, rotation] = ellipse; - // const auto cos_theta = math::cos(rotation); - // const auto sin_theta = math::sin(rotation); - // - // path_reserve_extra(segments); - // for (std::uint32_t i = 0; i < segments; ++i) - // { - // const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - // const auto offset = point_type{math::cos(a), math::sin(a)} * radius; - // const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; - // const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; - // path_pin({center + point_type{prime_x, prime_y}}); - // } - // } - // - // auto RenderListDrawer::path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void - // { - // path_pin(p1); - // path_pin(p2); - // path_pin(p3); - // path_pin(p4); - // } - // - // auto RenderListDrawer::path_rect(const rect_type& rect, float rounding, RenderFlag flag) noexcept -> void - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); - // - // if (rounding >= .5f) - // { - // flag = to_fixed_rect_corner_flag(flag); - // - // const auto v = (flag & RenderFlag::ROUND_CORNER_TOP) == RenderFlag::ROUND_CORNER_TOP or (flag & RenderFlag::ROUND_CORNER_BOTTOM) == RenderFlag::ROUND_CORNER_BOTTOM; - // const auto h = (flag & RenderFlag::ROUND_CORNER_LEFT) == RenderFlag::ROUND_CORNER_LEFT or (flag & RenderFlag::ROUND_CORNER_RIGHT) == RenderFlag::ROUND_CORNER_RIGHT; - // - // rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); - // rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); - // } - // - // if (rounding < .5f or (RenderFlag::ROUND_CORNER_MASK & flag) == RenderFlag::ROUND_CORNER_NONE) - // { - // path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); - // } - // else - // { - // const auto rounding_left_top = (flag & RenderFlag::ROUND_CORNER_LEFT_TOP) != RenderFlag::NONE ? rounding : 0; - // const auto rounding_right_top = (flag & RenderFlag::ROUND_CORNER_RIGHT_TOP) != RenderFlag::NONE ? rounding : 0; - // const auto rounding_left_bottom = (flag & RenderFlag::ROUND_CORNER_LEFT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - // const auto rounding_right_bottom = (flag & RenderFlag::ROUND_CORNER_RIGHT_BOTTOM) != RenderFlag::NONE ? rounding : 0; - // - // path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, RenderArcFlag::Q2_CLOCK_WISH); - // path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, RenderArcFlag::Q1_CLOCK_WISH); - // path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, RenderArcFlag::Q4_CLOCK_WISH); - // path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, RenderArcFlag::Q3_CLOCK_WISH); - // } - // } - // - // auto RenderListDrawer::path_bezier_cubic_curve_casteljau( - // const point_type& p1, - // const point_type& p2, - // const point_type& p3, - // const point_type& p4, - // const float tessellation_tolerance, - // const std::size_t level - // ) noexcept -> void - // { - // const auto dx = p4.x - p1.x; - // const auto dy = p4.y - p1.y; - // const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); - // const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); - // - // if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) - // { - // path_pin(p4); - // } - // else if (level < bezier_curve_casteljau_max_level) - // { - // const auto p_12 = (p1 + p2) * .5f; - // const auto p_23 = (p2 + p3) * .5f; - // const auto p_34 = (p3 + p4) * .5f; - // const auto p_123 = (p_12 + p_23) * .5f; - // const auto p_234 = (p_23 + p_34) * .5f; - // const auto p_1234 = (p_123 + p_234) * .5f; - // - // path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); - // path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); - // } - // } - // - // auto RenderListDrawer::path_bezier_quadratic_curve_casteljau( - // const point_type& p1, - // const point_type& p2, - // const point_type& p3, - // const float tessellation_tolerance, - // const std::size_t level - // ) noexcept -> void - // { - // const auto dx = p3.x - p1.x; - // const auto dy = p3.y - p1.y; - // const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; - // - // if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) - // { - // path_pin(p3); - // } - // else if (level < bezier_curve_casteljau_max_level) - // { - // const auto p_12 = (p1 + p2) * .5f; - // const auto p_23 = (p2 + p3) * .5f; - // const auto p_123 = (p_12 + p_23) * .5f; - // - // path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); - // path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); - // } - // } - // - // auto RenderListDrawer::path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void - // { - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // - // path_pin(p1); - // if (segments == 0) - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); - // - // path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // // auto-tessellated - // path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data.curve_tessellation_tolerance, 0); - // } - // else - // { - // path_reserve_extra(segments); - // const auto step = 1.f / static_cast(segments); - // for (std::uint32_t i = 1; i <= segments; ++i) - // { - // path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); - // } - // } - // } - // - // auto RenderListDrawer::path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void - // { - // const auto& render_list = self.get(); - // const auto& shared_data = render_list.shared_data(); - // - // path_pin(p1); - // if (segments == 0) - // { - // GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data.curve_tessellation_tolerance > 0); - // - // path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // // auto-tessellated - // path_bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data.curve_tessellation_tolerance, 0); - // } - // else - // { - // path_reserve_extra(segments); - // const auto step = 1.f / static_cast(segments); - // for (std::uint32_t i = 1; i <= segments; ++i) - // { - // path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); - // } - // } - // } + RenderDataAppender::RenderDataAppender(RenderList::RenderListContext& context) noexcept + : RenderDataAppender{context.command_list.back(), context.vertex_list, context.index_list} {} }; diff --git a/src/gfx/render_list.hpp b/src/gfx/render_list.hpp new file mode 100644 index 0000000..f4c40c9 --- /dev/null +++ b/src/gfx/render_list.hpp @@ -0,0 +1,693 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace gal::prometheus::gfx +{ + enum class RenderListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + + DEFAULT = ANTI_ALIASED_LINE | ANTI_ALIASED_LINE_USE_TEXTURE | ANTI_ALIASED_FILL, + }; + + enum class RenderRectFlag : std::uint8_t + { + NONE = 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see RenderList::path_rect + // @see RenderList::rect + // @see RenderList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + }; + + enum class RenderArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + }; + + class RenderListSharedData + { + public: + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t arc_sample_points_count = 48; + using arc_sample_points_type = std::array; + + constexpr static std::size_t baked_line_uv_count = 64; + using baked_line_uvs_type = std::array; + + // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) + circle_segment_counts_type circle_segment_counts; + // Maximum error (in pixels) allowed when using @c RenderList::circle and @c RenderList::circle_filled or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error; + + // Sample points on the quarter of the circle + arc_sample_points_type arc_fast_sample_points; + // Cutoff radius after which arc drawing will fall back to slower @c RenderList::Painter::arc + float arc_fast_radius_cutoff; + + // Tessellation tolerance when using @c RenderList::Painter::bezier_curve without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance; + + baked_line_uvs_type baked_line_uvs; + point_type white_pixel_uv; + + // Initial flags at the beginning of the frame (it is possible to alter flag on a per-RenderList basis afterward) + RenderListFlag render_list_initial_flag; + rect_type fullscreen_scissor; + + // -------------------------------------------------- + + RenderListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto arc_sample_point(std::size_t index) const noexcept -> const point_type&; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; + + class RenderData final + { + public: + using size_type = std::uint32_t; + + struct [[nodiscard]] command_type + { + rect_type scissor; + texture_id_type texture; + + // ======================= + + // set by RenderList::index_list.size() + // start offset in @c RenderList::index_list + size_type index_offset; + // set by RenderList::draw_xxx + // number of indices (multiple of 3) to be rendered as triangles + size_type element_count; + }; + + using vertex_list_type = std::vector; + using index_list_type = std::vector; + using command_list_type = std::vector; + + std::reference_wrapper vertex_list; + std::reference_wrapper index_list; + std::reference_wrapper command_list; + }; + + using render_data_list_type = std::vector; + + class RenderList final + { + friend class Context; + friend class Renderer; + + public: + class RenderListContext; + + class Painter final + { + public: + using path_list_type = std::vector; + + private: + memory::RefWrapper render_list_; + path_list_type path_list_; + + auto draw_polygon_line(color_type color, float thickness, bool close) noexcept -> void; + auto draw_polygon_line_aa(color_type color, float thickness, bool close) noexcept -> void; + auto draw_convex_polygon_line_filled(color_type color) noexcept -> void; + auto draw_convex_polygon_line_filled_aa(color_type color) noexcept -> void; + + public: + Painter(const Painter&) noexcept = delete; + Painter(Painter&&) noexcept = default; + auto operator=(const Painter&) noexcept -> Painter& = delete; + auto operator=(Painter&&) noexcept -> Painter& = default; + + ~Painter() noexcept; + + explicit Painter(RenderList& render_list, std::size_t reserve_point) noexcept; + + auto clear() noexcept -> Painter&; + auto reserve_extra(std::size_t size) noexcept -> Painter&; + auto reserve(std::size_t size) noexcept -> Painter&; + + auto pin(const point_type& point) noexcept -> Painter&; + + auto line(const point_type& from, const point_type& to) noexcept -> Painter&; + auto triangle(const point_type& a, const point_type& b, const point_type& c) noexcept -> Painter&; + auto quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; + auto rect(const rect_type& rect, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto rect(const rect_type::point_type& left_top, const rect_type::extent_type& extent, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto rect(const rect_type::point_type& left_top, const rect_type::point_type& right_bottom, float rounding, RenderRectFlag flag) noexcept -> Painter&; + auto circle_n(const circle_type& circle, std::uint32_t segments) noexcept -> Painter&; + auto circle_n(const circle_type::point_type& center, circle_type::radius_value_type radius, std::uint32_t segments) noexcept -> Painter&; + auto circle(const circle_type& circle) noexcept -> Painter&; + auto circle(const circle_type::point_type& center, circle_type::radius_value_type radius) noexcept -> Painter&; + auto ellipse_n(const ellipse_type& ellipse, std::uint32_t segments) noexcept -> Painter&; + auto ellipse_n(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation, std::uint32_t segments) noexcept -> Painter&; + auto ellipse(const ellipse_type& ellipse) noexcept -> Painter&; + auto ellipse(const ellipse_type::point_type& center, const ellipse_type::radius_type& radius, ellipse_type::rotation_value_type rotation) noexcept -> Painter&; + auto arc_fast(const circle_type& circle, int sample_point_from, int sample_point_to) noexcept -> Painter&; + auto arc_fast(const circle_type& circle, RenderArcFlag flag) noexcept -> Painter&; + auto arc_n(const circle_type& circle, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; + auto arc(const circle_type& circle, float degree_from, float degree_to) noexcept -> Painter&; + auto arc_n(const ellipse_type& ellipse, float degree_from, float degree_to, std::uint32_t segments) noexcept -> Painter&; + auto bezier_cubic_n(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, std::uint32_t segments) noexcept -> Painter&; + auto bezier_cubic(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> Painter&; + auto bezier_quadratic_n(const point_type& p1, const point_type& p2, const point_type& p3, std::uint32_t segments) noexcept -> Painter&; + auto bezier_quadratic(const point_type& p1, const point_type& p2, const point_type& p3) noexcept -> Painter&; + + /** + * @brief Draws the fill shape according to the specified path points + * @param color Shape color + */ + auto stroke(color_type color) noexcept -> void; + + /** + * @brief Plot the corresponding lines/shapes according to the specified path points + * @param color Line/shape color + * @param thickness Line thickness + * @param close Whether the graph is closed, in other words, whether the first point should be connected to the last point + */ + auto stroke(color_type color, float thickness, bool close) noexcept -> void; + }; + + private: + memory::UniquePointer context_; + + explicit RenderList(Context& context) noexcept; + + public: + RenderList(const RenderList&) noexcept = delete; + RenderList(RenderList&&) noexcept; //= default; + auto operator=(const RenderList&) noexcept -> RenderList& = delete; + auto operator=(RenderList&&) noexcept -> RenderList&; // = default; + + ~RenderList() noexcept; + + auto reset() noexcept -> void; + + auto set_flag(RenderListFlag flag) noexcept -> void; + + [[nodiscard]] auto data() const noexcept -> RenderData; + + // ---------------------------------------------------------------------------- + // SCISSOR & TEXTURE + + auto push_scissor(const rect_type& rect, bool intersect_with_current_scissor) noexcept -> const rect_type&; + + auto pop_scissor() noexcept -> void; + + auto push_texture(texture_id_type texture) noexcept -> void; + + auto pop_texture() noexcept -> void; + + // ---------------------------------------------------------------------------- + // PAINTER + + [[nodiscard]] auto painter(const Painter::path_list_type::size_type reserve_point = 0) noexcept -> Painter + { + return Painter{*this, reserve_point}; + } + + // ---------------------------------------------------------------------------- + // PRIMITIVE + + auto line( + const point_type& from, + const point_type& to, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + color_type color + ) noexcept -> void; + + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color + ) noexcept -> void; + + auto rect( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL, + float thickness = 1.f + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, + color_type color, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::ROUND_CORNER_ALL + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const rect_type::extent_type& extent, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const rect_type::point_type& left_top, + const rect_type::point_type& right_bottom, + color_type color_left_top, + color_type color_right_top, + color_type color_left_bottom, + color_type color_right_bottom + ) noexcept -> void; + + auto circle_n( + const circle_type& circle, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n_filled( + const circle_type& circle, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle_n_filled( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto circle( + const circle_type& circle, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto circle( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto circle_filled( + const circle_type& circle, + color_type color + ) noexcept -> void; + + auto circle_filled( + const circle_type::point_type& center, + circle_type::radius_value_type radius, + color_type color + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type& ellipse, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse( + const ellipse_type& ellipse, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type& ellipse, + color_type color + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type::point_type& center, + const ellipse_type::radius_type& radius, + ellipse_type::rotation_value_type rotation, + color_type color + ) noexcept -> void; + + auto bezier_cubic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic_n( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + color_type color, + float thickness = 1.f + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // TEXT + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + float wrap_width = 99999999.f + ) noexcept -> void; + + auto text( + std::string_view utf8_text, + std::uint32_t font_size, + const point_type& point, + color_type color, + GlyphFlag flag, + float wrap_width = 99999999.f + ) noexcept -> void; + + // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + float wrap_width = 99999999.f + ) noexcept -> extent_type; + + // FIXME: There is no suitable place to implement this function, this function must be highly consistent with the implementation when drawing text + auto text_size( + std::string_view utf8_text, + std::uint32_t font_size, + GlyphFlag flag, + float wrap_width = 99999999.f + ) noexcept -> extent_type; + + // ---------------------------------------------------------------------------- + // IMAGE + + // p1________ p2 + // | | + // | | + // p4|_______| p3 + auto image( + texture_id_type texture_id, + const rect_type::point_type& display_p1, + const rect_type::point_type& display_p2, + const rect_type::point_type& display_p3, + const rect_type::point_type& display_p4, + const uv_type& uv_p1 = {0, 0}, + const uv_type& uv_p2 = {1, 0}, + const uv_type& uv_p3 = {1, 1}, + const uv_type& uv_p4 = {0, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::extent_type& display_size, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::point_type& display_right_bottom, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::NONE, + const rect_type& uv_rect = {0, 0, 1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::extent_type& display_size, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type::point_type& display_left_top, + const rect_type::point_type& display_right_bottom, + float rounding = .0f, + RenderRectFlag flag = RenderRectFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + color_type color = primitive::colors::white + ) noexcept -> void; + }; +} + +namespace gal::prometheus::meta::user_defined +{ + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; + + template<> + struct enum_is_flag : std::true_type {}; +} // namespace gal::prometheus::meta::user_defined diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp new file mode 100644 index 0000000..4a6da28 --- /dev/null +++ b/src/gfx/renderer.cpp @@ -0,0 +1,13 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace gal::prometheus::gfx +{ + Renderer::~Renderer() noexcept = default; + + Renderer::Renderer() noexcept = default; +} diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp new file mode 100644 index 0000000..8afd339 --- /dev/null +++ b/src/gfx/renderer.hpp @@ -0,0 +1,41 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include +#include + +namespace gal::prometheus::gfx +{ + class Renderer + { + public: + Renderer(const Renderer&) noexcept = delete; + Renderer(Renderer&&) noexcept = default; + auto operator=(const Renderer&) noexcept -> Renderer& = delete; + auto operator=(Renderer&&) noexcept -> Renderer& = default; + + virtual ~Renderer() noexcept; + + protected: + Renderer() noexcept; + + public: + [[nodiscard]] virtual auto create_texture(const Texture::data_type& data, Texture::size_type size) noexcept -> texture_id_type = 0; + virtual auto update_texture(texture_id_type id, std::span update_viewer) noexcept -> void = 0; + virtual auto destroy_texture(texture_id_type id) noexcept -> void = 0; + + /** + * @brief + * @note @c new_frame -> @c present -> @c end_frame + */ + virtual auto present(const render_data_list_type& render_data_list, const extent_type& display_size) noexcept -> void = 0; + }; +} diff --git a/src/gfx/texture.cpp b/src/gfx/texture.cpp new file mode 100644 index 0000000..78ec189 --- /dev/null +++ b/src/gfx/texture.cpp @@ -0,0 +1,186 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::gfx +{ + TextureWriter::TextureWriter(const point_type point, const borrow_data_type& data) noexcept + : point_{point}, + data_{data} {} + + auto TextureWriter::valid() const noexcept -> bool + { + return point_ != invalid_point; + } + + auto TextureWriter::position() const noexcept -> point_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + return point_; + } + + auto TextureWriter::fill(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n, const element_type element) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n <= width); + + for (size_type::value_type x = offset; x < offset + n; ++x) + { + data_[y, x] = element; + } + } + + auto TextureWriter::fill(const size_type::value_type y, const size_type::value_type offset, const data_view_type data) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + data.size() <= width); + + for (size_type::value_type x = 0; x < data.size(); ++x) + { + data_[y, x + offset] = data[x]; + } + } + + auto TextureWriter::fill(const size_type::value_type y, const size_type::value_type n, const element_type element) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(n <= width); + + fill(y, 0, n, element); + } + + auto TextureWriter::fill(const size_type::value_type y, const data_view_type data) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data.size() <= width); + + fill(y, 0, data); + } + + auto TextureWriter::fill(const size_type::value_type y, const element_type element) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + + fill(y, width, element); + } + + auto TextureWriter::fill(const element_type element) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + const auto height = data_.extent(0); + for (size_type::value_type y = 0; y < height; ++y) + { + fill(y, element); + } + } + + auto TextureWriter::fill(const data_view_type data) const noexcept -> void + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + + for (size_type::value_type y = 0; y < height; ++y) + { + const data_view_type sub{data.begin() + static_cast(y) * width, width}; + + fill(y, sub); + } + } + + auto TextureWriter::operator[](const size_type::value_type x, const size_type::value_type y) const noexcept -> borrow_data_type::reference + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < width); + + return data_[y, x]; + } + + TextureViewer::TextureViewer(const point_type point, const borrow_data_type& data) noexcept + : point_{point}, + data_{data} {} + + auto TextureViewer::position() const noexcept -> point_type + { + return point_; + } + + auto TextureViewer::size() const noexcept -> size_type + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + return {width, height}; + } + + auto TextureViewer::line(const size_type::value_type y, const size_type::value_type offset, const size_type::value_type n) const noexcept -> data_view_type + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(offset + n <= width); + + // TODO: submdspan(C++ 26) + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data_.stride(1) == 1); + const auto& this_line = data_[y, 0]; + + return {&this_line, n}; + } + + auto TextureViewer::line(const size_type::value_type y, const size_type::value_type n) const noexcept -> data_view_type + { + return line(y, 0, n); + } + + auto TextureViewer::line(const size_type::value_type y) const noexcept -> data_view_type + { + const auto width = data_.extent(1); + // const auto height = data_.extent(0); + + return line(y, width); + } + + auto TextureViewer::operator[](const size_type::value_type x, const size_type::value_type y) const noexcept -> borrow_data_type::reference + { + const auto width = data_.extent(1); + const auto height = data_.extent(0); + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(y < height); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(x < width); + + return data_[y, x]; + } +} diff --git a/src/gfx/texture.hpp b/src/gfx/texture.hpp new file mode 100644 index 0000000..182b942 --- /dev/null +++ b/src/gfx/texture.hpp @@ -0,0 +1,132 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +namespace gal::prometheus::gfx +{ + class Texture final + { + public: + // 32-bits (RGBA) + using element_type = std::uint32_t; + // size.width * size.height + using data_type = std::unique_ptr; + using data_view_type = std::span; + + // RectPackContext::point_type + using point_type = primitive::basic_point_2d; + // RectPackContext::size_type + using size_type = primitive::basic_extent_2d; + + using uv_scale_type = primitive::basic_extent_2d; + + // ============================== + // CPU side + // ============================== + + data_type data; + size_type size; + + // It's much more cost-effective to keep a member variable than to compute it every time + uv_scale_type uv_scale; + + // ============================== + // GPU side + // ============================== + + texture_id_type id; + }; + + class TextureContext; + class TextureWriter; + class TextureViewer; + + class TextureWriter final + { + friend TextureContext; + friend TextureViewer; + + public: + using element_type = Texture::element_type; + using data_type = Texture::data_type; + using data_view_type = Texture::data_view_type; + + using point_type = Texture::point_type; + using size_type = Texture::size_type; + + using uv_scale_type = Texture::uv_scale_type; + + constexpr static point_type invalid_point{(std::numeric_limits::max)(), (std::numeric_limits::max)()}; + + private: + using borrow_data_type = std::mdspan, std::layout_stride>; + + point_type point_; + borrow_data_type data_; + + TextureWriter(point_type point, const borrow_data_type& data) noexcept; + + public: + [[nodiscard]] auto valid() const noexcept -> bool; + + [[nodiscard]] auto position() const noexcept -> point_type; + + auto fill(size_type::value_type y, size_type::value_type offset, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, size_type::value_type offset, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, size_type::value_type n, element_type element) const noexcept -> void; + auto fill(size_type::value_type y, data_view_type data) const noexcept -> void; + + auto fill(size_type::value_type y, element_type element) const noexcept -> void; + + auto fill(element_type element) const noexcept -> void; + auto fill(data_view_type data) const noexcept -> void; + + auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> borrow_data_type::reference; + }; + + class TextureViewer final + { + friend TextureContext; + + public: + using element_type = Texture::element_type; + using data_type = Texture::data_type; + using data_view_type = Texture::data_view_type; + + using point_type = Texture::point_type; + using size_type = Texture::size_type; + + using uv_scale_type = Texture::uv_scale_type; + + private: + using borrow_data_type = std::mdspan, std::layout_stride>; + + point_type point_; + borrow_data_type data_; + + TextureViewer(point_type point, const borrow_data_type& data) noexcept; + + public: + [[nodiscard]] auto position() const noexcept -> point_type; + + [[nodiscard]] auto size() const noexcept -> size_type; + + [[nodiscard]] auto line(size_type::value_type y, size_type::value_type offset, size_type::value_type n) const noexcept -> data_view_type; + + [[nodiscard]] auto line(size_type::value_type y, size_type::value_type n) const noexcept -> data_view_type; + + [[nodiscard]] auto line(size_type::value_type y) const noexcept -> data_view_type; + + auto operator[](size_type::value_type x, size_type::value_type y) const noexcept -> borrow_data_type::reference; + }; +} diff --git a/src/gfx/type.hpp b/src/gfx/type.hpp new file mode 100644 index 0000000..a8fbc68 --- /dev/null +++ b/src/gfx/type.hpp @@ -0,0 +1,41 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace gal::prometheus::gfx +{ + // ========================================================= + // PRIMITIVE + // ========================================================= + + using point_type = primitive::basic_point_2d; + using uv_type = primitive::basic_point_2d; + using color_type = primitive::basic_color; + using vertex_type = primitive::basic_vertex; + using index_type = std::uint16_t; + + using extent_type = primitive::basic_extent_2d; + using rect_type = primitive::basic_rect_2d; + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; + + // ========================================================= + // TEXTURE + // ========================================================= + + // D3D11: ID3D11ShaderResourceView + // D3D12: D3D12_GPU_DESCRIPTOR_HANDLE::ptr / HEAP index + using texture_id_type = std::uintptr_t; + constexpr texture_id_type invalid_texture_id{std::numeric_limits::max()}; +} From b86acd7fb2fac1a6593e213e030ce96b7438f36b Mon Sep 17 00:00:00 2001 From: Life4gal Date: Fri, 30 May 2025 00:50:33 +0800 Subject: [PATCH 54/54] =?UTF-8?q?`fix`:=20Remove=20assertions=20from=20som?= =?UTF-8?q?e=20lightweight=20modules.=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/internal/draw_list.cpp | 1 + src/gui/internal/mouse.cpp | 2 ++ src/gui/internal/window.cpp | 2 ++ src/primitive/ellipse.hpp | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/internal/draw_list.cpp b/src/gui/internal/draw_list.cpp index 424c532..e599b4c 100644 --- a/src/gui/internal/draw_list.cpp +++ b/src/gui/internal/draw_list.cpp @@ -12,6 +12,7 @@ #include #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace { diff --git a/src/gui/internal/mouse.cpp b/src/gui/internal/mouse.cpp index 874df9f..21bd9be 100644 --- a/src/gui/internal/mouse.cpp +++ b/src/gui/internal/mouse.cpp @@ -6,6 +6,8 @@ #include #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + namespace gal::prometheus::gui::internal { auto Mouse::is_down(const Context& context, MouseKey key) const noexcept -> bool diff --git a/src/gui/internal/window.cpp b/src/gui/internal/window.cpp index 2f0807e..9db8c77 100644 --- a/src/gui/internal/window.cpp +++ b/src/gui/internal/window.cpp @@ -10,6 +10,8 @@ #include #include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + namespace { using namespace gal::prometheus; diff --git a/src/primitive/ellipse.hpp b/src/primitive/ellipse.hpp index baaa698..1d78d0c 100644 --- a/src/primitive/ellipse.hpp +++ b/src/primitive/ellipse.hpp @@ -19,7 +19,7 @@ #include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE +// #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE namespace gal::prometheus {