From 8b4f1ccf9ecf53621a3a219650d29936075a5bd0 Mon Sep 17 00:00:00 2001 From: WillisMedwell Date: Wed, 13 Mar 2024 14:20:02 +1100 Subject: [PATCH] Added: Scheduler = task multithreading. --- code/.clang-tidy | 5 +- code/Demos/src/Main.cpp | 44 ++- code/Engine/CMakeLists.txt | 4 +- code/Engine/include/App/App.hpp | 3 - code/Engine/include/Core/AudioManager.hpp | 3 + code/Engine/include/Core/Core.hpp | 6 +- code/Engine/include/Core/Fence.hpp | 53 ---- code/Engine/include/Core/Scheduler.hpp | 139 +++++++++ code/Engine/include/Core/Texture.hpp | 1 - code/Engine/include/Media/Image.hpp | 77 +---- code/Engine/include/Profiler/Profiler.hpp | 6 +- .../include/Renderer/ResourceManager.hpp | 2 +- code/Engine/src/App/AppRenderer.cpp | 1 - code/Engine/src/Core/AudioManager.cpp | 14 +- code/Engine/src/Media/FontAtlas.cpp | 88 +++--- code/Engine/src/Media/Image.cpp | 263 ++++-------------- code/Engine/src/Profiler/Profiler.cpp | 37 ++- 17 files changed, 339 insertions(+), 407 deletions(-) delete mode 100644 code/Engine/include/Core/Fence.hpp create mode 100644 code/Engine/include/Core/Scheduler.hpp diff --git a/code/.clang-tidy b/code/.clang-tidy index 40782ed..b9d3014 100644 --- a/code/.clang-tidy +++ b/code/.clang-tidy @@ -18,7 +18,10 @@ CheckOptions: - { key: readability-identifier-naming.PrivateMemberPrefix, value: _ } - { key: readability-identifier-naming.StaticMemberPrefix, value: _ } - { key: readability-identifier-naming.LocalConstantCase, value: lower_case } - - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.GlobalConstantCase, value: snake_case } + - { key: readability-identifier-naming.ConstexprVariableCase, value: snake_case } + - { key: readability-identifier-naming.ConstexprMethodCase, value: snake_case } + - { key: readability-identifier-naming.ConstexprFunctionCase, value: snake_case } - { key: readability-identifier-naming.EnumConstantCase, value: lower_case } diff --git a/code/Demos/src/Main.cpp b/code/Demos/src/Main.cpp index c512210..302559c 100644 --- a/code/Demos/src/Main.cpp +++ b/code/Demos/src/Main.cpp @@ -420,6 +420,45 @@ struct IsoLogic { Media::Sound sound = Media::Sound::create("assets/background_sound.wav").on_error_panic().value_move(); + auto scheduler = std::move(Core::Scheduler::create(2).value()); + + scheduler.add_task([]() { + Media::FontAtlas::create("assets/RobotoMono.ttf", 500).on_error_panic().value().atlas_image().save_to_disk("RobotoMonoAtlas.png"); + }); + + std::mutex sound_mutex; + std::optional sound_2; + scheduler.add_task([&]() { + auto create_sound_result = Media::Sound::create("assets/background_sound.wav"); + + sound_mutex.lock(); + sound_2.emplace(create_sound_result.on_error_panic().value_move()); + sound_mutex.unlock(); + }); + + std::mutex model_mutex; + std::optional model_data_2; + scheduler.add_task([&]() { + auto model_data = Utily::FileReader::load_entire_file("assets/teapot.obj").on_error_panic().value_move(); + auto model_decode_result = Model::decode_as_static_model(model_data, ".obj"); + + model_mutex.lock(); + model_data_2.emplace(model_decode_result.on_error_panic().value_move()); + model_mutex.unlock(); + }); + + std::mutex image_mutex; + std::optional image_2; + scheduler.add_task([&]() { + auto image_result = Media::Image::create("assets/texture.png"); + + image_mutex.lock(); + image_2.emplace(image_result.on_error_panic().value_move()); + image_mutex.unlock(); + }); + + scheduler.launch_threads(); + auto res = audio.load_sound_into_buffer(sound).on_error(print_then_quit); data.sound_buffer = res.value(); @@ -436,10 +475,11 @@ struct IsoLogic { .on_error_panic() .value_move()); - Media::FontAtlas::create("assets/RobotoMono.ttf", 500).on_error_panic().value().atlas_image().save_to_disk("RobotoMonoAtlas.png"); - data.instance_renderer.init(data.resource_manager, model, image); data.source_handle = audio.play_sound(data.sound_buffer, { 5, 0, 0 }).on_error(print_then_quit).value(); + + scheduler.wait_for_threads(); + data.start_time = std::chrono::high_resolution_clock::now(); } diff --git a/code/Engine/CMakeLists.txt b/code/Engine/CMakeLists.txt index 90e926c..dfbce29 100644 --- a/code/Engine/CMakeLists.txt +++ b/code/Engine/CMakeLists.txt @@ -49,7 +49,9 @@ if(DEFINED EMSCRIPTEN) "$<$:-O2;-g3;-sDEMANGLE_SUPPORT=1;-sFORCE_FILESYSTEM=1;-sASSERTIONS=1;-sSAFE_HEAP=1;-sSTACK_OVERFLOW_CHECK=2;-sNO_DISABLE_EXCEPTION_CATCHING;-Wno-unused-command-line-argument;-fno-inline-functions;-sEXIT_RUNTIME=1>" "$<$:-Oz;-sGL_FFP_ONLY;-msimd128;-mrelaxed-simd;-msse;-msse2;-msse3;-msse4.1;-Wno-unused-command-line-argument;-sFORCE_FILESYSTEM=1>" ) - target_link_options(Engine PUBLIC -sUSE_WEBGL2=1 -sUSE_GLFW=3 -sFULL_ES3=1 -sFULL_ES2=1 -Wno-unused-command-line-argument -sALLOW_MEMORY_GROWTH) + #target_compile_options(Engine PUBLIC -sUSE_PTHREADS=1) + target_link_options(Engine PUBLIC -sUSE_WEBGL2=1 -sUSE_GLFW=3 -sFULL_ES3=1 -sFULL_ES2=1 -Wno-unused-command-line-argument -sALLOW_MEMORY_GROWTH) #-sUSE_PTHREADS=1) + else() find_package(OpenGL REQUIRED) find_package(OpenAL CONFIG REQUIRED) diff --git a/code/Engine/include/App/App.hpp b/code/Engine/include/App/App.hpp index 9cfabd9..02a9ef0 100644 --- a/code/Engine/include/App/App.hpp +++ b/code/Engine/include/App/App.hpp @@ -85,7 +85,6 @@ class App } auto stop() -> void { if (!_has_stopped) { - Profiler::Timer timer("App::stop()", { "App" }); _logic.stop(_data); _renderer.stop(); _audio.stop(); @@ -113,7 +112,6 @@ class App } _context.swap_buffers(); } - auto poll_events() -> void { Profiler::Timer timer("App::poll_events()", { "App" }); this->_context.poll_events(); @@ -121,7 +119,6 @@ class App auto is_running() -> bool { return _has_init && !_context.should_close() && !_state.should_close; } - ~App() { stop(); } diff --git a/code/Engine/include/Core/AudioManager.hpp b/code/Engine/include/Core/AudioManager.hpp index 967a06a..0d3df0b 100644 --- a/code/Engine/include/Core/AudioManager.hpp +++ b/code/Engine/include/Core/AudioManager.hpp @@ -55,6 +55,8 @@ namespace Core { }; struct Source { uint32_t id = std::numeric_limits::max(); + glm::vec3 pos = { 0, 0, 0 }; + glm::vec3 vel = { 0, 0, 0 }; std::optional attached_buffer = std::nullopt; std::optional expected_finish = std::nullopt; }; @@ -75,4 +77,5 @@ namespace Core { void stop_buffers(); void stop_sources(); }; + } \ No newline at end of file diff --git a/code/Engine/include/Core/Core.hpp b/code/Engine/include/Core/Core.hpp index 5024b1c..d878e92 100644 --- a/code/Engine/include/Core/Core.hpp +++ b/code/Engine/include/Core/Core.hpp @@ -1,7 +1,6 @@ #pragma once namespace Core { - class Fence; class IndexBuffer; class InputManager; class OpenglContext; @@ -12,9 +11,9 @@ namespace Core { class FrameBuffer; class ScreenFrameBuffer; class AudioManager; + class Scheduler; } -#include "Fence.hpp" #include "IndexBuffer.hpp" #include "Input.hpp" #include "OpenglContext.hpp" @@ -24,4 +23,5 @@ namespace Core { #include "VertexBuffer.hpp" #include "VertexBufferLayout.hpp" #include "FrameBuffer.hpp" -#include "AudioManager.hpp" \ No newline at end of file +#include "AudioManager.hpp" +#include "Scheduler.hpp" \ No newline at end of file diff --git a/code/Engine/include/Core/Fence.hpp b/code/Engine/include/Core/Fence.hpp deleted file mode 100644 index c6d8378..0000000 --- a/code/Engine/include/Core/Fence.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "../Config.hpp" -#include "Profiler/Profiler.hpp" - -#include -#include - -namespace Core { - class Fence - { - public: - Fence() = default; - Fence(const Fence&) = delete; - Fence(Fence&& other) noexcept - : _id(std::exchange(other._id, std::nullopt)) { - } - - void init() { - _id = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - - bool has_finished() { - if (!_id) { - return true; - } - - auto wait_status = glClientWaitSync(*_id, GL_SYNC_FLUSH_COMMANDS_BIT, 0); - if (wait_status == GL_ALREADY_SIGNALED || wait_status == GL_CONDITION_SATISFIED) { - glDeleteSync(*_id); - _id = std::nullopt; - return true; - } - return false; - } - void wait_for_sync() { - Profiler::Timer("Core::Fence::wait_for_sync()"); - if (_id) { - glWaitSync(*_id, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(*_id); - _id = std::nullopt; - } - } - ~Fence() { - if (_id) { - glDeleteSync(*_id); - } - } - - private: - std::optional _id; - }; -} \ No newline at end of file diff --git a/code/Engine/include/Core/Scheduler.hpp b/code/Engine/include/Core/Scheduler.hpp new file mode 100644 index 0000000..4c3d206 --- /dev/null +++ b/code/Engine/include/Core/Scheduler.hpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Config.hpp" + +namespace Core { + + class Scheduler + { + public: + using SimpleFunctionPtr = void (*)(); + using Task = std::variant>; + + static auto create(size_t num_threads = std::thread::hardware_concurrency()) + -> std::optional { + if (OptAtomicSize temp = 1; !temp.value().is_lock_free()) { + throw std::runtime_error( + "This has lock, as such it cannot be moved. Therefore the move " + "semantics workaround is invalidated."); + } + if constexpr (Config::PLATFORM == Config::TargetPlatform::web) { + num_threads = 0; + } + + return Scheduler(M { + .tasks = {}, + .threads = {}, + .num_threads = num_threads, + .current_task_buffer = null_opt_atomic(), + }); + } + + void add_task(SimpleFunctionPtr task) { + if (get_current_task().has_value()) { + throw std::runtime_error("trying to add task when already lauched"); + } + _m.tasks.emplace_back(task); + } + + void add_task(Task task) { + if (get_current_task().has_value()) { + throw std::runtime_error("trying to add task when already lauched"); + } + _m.tasks.emplace_back(task); + } + + void launch_threads() { + if (get_current_task().has_value()) { + return; + } + + get_current_task().emplace(0); + + auto execute = [this]() { + for (;;) { + auto task_id = + get_current_task()->fetch_add(1, std::memory_order_seq_cst); + + bool has_tasks_remaining = task_id < _m.tasks.size(); + + if (has_tasks_remaining) { + std::string timer_name = "Scheduler::Task(" + std::to_string(task_id) + ")"; + Profiler::Timer timer(timer_name); + auto& task = _m.tasks.at(task_id); + std::visit([](auto& task) { task(); }, task); + } else { + return; + } + } + }; + auto start_thread = [&]() { return std::thread(execute); }; + + // start the other threads + std::generate_n(std::back_inserter(_m.threads), _m.num_threads, start_thread); + } + + void wait_for_threads() { + // get the main thread going too. + [this]() { + for (;;) { + auto task_id = + get_current_task()->fetch_add(1, std::memory_order_seq_cst); + + bool has_tasks_remaining = task_id < _m.tasks.size(); + + if (has_tasks_remaining) { + auto& task = _m.tasks.at(task_id); + std::visit([](auto& task) { task(); }, task); + } else { + return; + } + } + }(); + + std::ranges::for_each(_m.threads, &std::thread::join); + + _m.threads.resize(0); + _m.tasks.resize(0); + get_current_task() = std::nullopt; + } + + Scheduler(Scheduler&&) = default; + + private: + using OptAtomicSize = std::optional>; + using OptAtomicSizeBuffer = std::array; + + constexpr static auto null_opt_atomic = []() { + OptAtomicSize empty = std::nullopt; + OptAtomicSizeBuffer buffer = + *reinterpret_cast(&empty); + return buffer; + }; + + struct M { + std::vector tasks; + std::vector threads; + size_t num_threads; + alignas(OptAtomicSize) OptAtomicSizeBuffer current_task_buffer; + } _m; + + auto get_current_task() -> OptAtomicSize& { + return *reinterpret_cast(_m.current_task_buffer.data()); + } + + explicit Scheduler(M&& m) + : _m(std::move(m)) { } + + Scheduler(const Scheduler&) = delete; + }; +} diff --git a/code/Engine/include/Core/Texture.hpp b/code/Engine/include/Core/Texture.hpp index 7aa5316..a3cd7f8 100644 --- a/code/Engine/include/Core/Texture.hpp +++ b/code/Engine/include/Core/Texture.hpp @@ -2,7 +2,6 @@ #include -#include "Core/Fence.hpp" #include "Media/Image.hpp" #include "Config.hpp" diff --git a/code/Engine/include/Media/Image.hpp b/code/Engine/include/Media/Image.hpp index 3451335..68da67d 100644 --- a/code/Engine/include/Media/Image.hpp +++ b/code/Engine/include/Media/Image.hpp @@ -1,7 +1,6 @@ #pragma once #include "../Config.hpp" -#include "Core/Fence.hpp" #include #include @@ -10,60 +9,6 @@ #include -#if 0 -namespace Media { - - enum class ColourFormat : uint32_t { - greyscale = GL_R8, - rgb = GL_RGB8, - rgba = GL_RGBA8, - s_rgb = GL_SRGB8, - s_rgba = GL_SRGB8_ALPHA8, - }; - - class Image - { - public: - Image() = default; - Image(const Image&) = delete; - Image(Image&& other); - - Image& operator=(Image&& other) noexcept; - - [[nodiscard]] auto init( - std::vector& encoded_png, - bool include_alpha_channel, - bool is_gamma_corrected) noexcept - -> Utily::Result; - - void init_raw(std::vector&& raw_data, uint32_t width, uint32_t height, ColourFormat format) noexcept; - - [[nodiscard]] auto save_to_disk(std::filesystem::path path) noexcept - -> Utily::Result; - - void stop() noexcept; - - void resize(float scale) noexcept; - void resize(uint32_t width, uint32_t height) noexcept; - - [[nodiscard]] inline auto raw_bytes() const noexcept -> std::span { - return std::span { _data.cbegin(), _data.cend() }; - } - [[nodiscard]] inline auto dimensions() const noexcept -> glm::uvec2 { return { _width, _height }; } - [[nodiscard]] inline auto format() const noexcept -> ColourFormat { return _format; } - - void add_fence(Core::Fence&& fence) noexcept; - ~Image(); - - private: - std::vector _data; - Media::ColourFormat _format = Media::ColourFormat::rgb; - uint32_t _width { 0 }, _height { 0 }; - std::optional _fence; - }; -} -#endif - namespace Media { class Image { @@ -74,28 +19,22 @@ namespace Media { rgba }; - /// @brief Load png image from disk and decode it. Can fail. - [[nodiscard]] static auto create(std::filesystem::path path) - -> Utily::Result; - - /// @brief Take decoded-raw image data and copy it. Can fail. - [[nodiscard]] static auto create(std::span raw_bytes, glm::uvec2 dimensions, InternalFormat format) - -> Utily::Result; - - /// @brief Take ownership of decoded-raw image data. Can fail. - [[nodiscard]] static auto create(std::unique_ptr&& data, size_t data_size_bytes, glm::uvec2 dimensions, InternalFormat format) - -> Utily::Result; + [[nodiscard]] static auto create(std::filesystem::path path) -> Utily::Result; + [[nodiscard]] static auto create(std::span raw_bytes, glm::uvec2 dimensions, InternalFormat format) -> Utily::Result; + [[nodiscard]] static auto create(std::unique_ptr&& data, size_t data_size_bytes, glm::uvec2 dimensions, InternalFormat format) -> Utily::Result; [[nodiscard]] inline auto raw_bytes() const noexcept { return std::span { _m.data.get(), _m.data_size_bytes }; } [[nodsicard]] inline auto dimensions() const noexcept { return _m.dimensions; } - [[nodiscard]] auto format() const { return _m.format; } + [[nodiscard]] inline auto format() const { return _m.format; } + [[nodiscard]] auto opengl_format() const -> uint32_t; + [[nodiscard]] auto libspng_format() const -> uint8_t; + + [[nodiscard]] auto save_to_disk(std::filesystem::path path) const noexcept -> Utily::Result; Image(Image&& other); Image(const Image&) = delete; - auto save_to_disk(std::filesystem::path path) const noexcept -> Utily::Result; - private: struct M { std::unique_ptr data = {}; diff --git a/code/Engine/include/Profiler/Profiler.hpp b/code/Engine/include/Profiler/Profiler.hpp index 3734c33..47172fd 100644 --- a/code/Engine/include/Profiler/Profiler.hpp +++ b/code/Engine/include/Profiler/Profiler.hpp @@ -10,7 +10,7 @@ class Profiler { private: struct Recording { - std::string_view name; + std::string name; std::chrono::time_point start_time; std::chrono::time_point end_time; std::thread::id thread_id; @@ -25,7 +25,7 @@ class Profiler class Timer { public: - std::string_view name; + std::string name; std::chrono::time_point start_time; std::chrono::time_point end_time; std::thread::id thread_id; @@ -35,7 +35,7 @@ class Profiler Timer(const Timer&) = delete; Timer(Timer&&) = delete; - Timer(std::string_view function_name, std::vector cats = {}); + Timer(std::string function_name, std::vector cats = {}); ~Timer(); }; diff --git a/code/Engine/include/Renderer/ResourceManager.hpp b/code/Engine/include/Renderer/ResourceManager.hpp index 814477c..367d6f3 100644 --- a/code/Engine/include/Renderer/ResourceManager.hpp +++ b/code/Engine/include/Renderer/ResourceManager.hpp @@ -76,7 +76,7 @@ namespace Renderer { [[nodiscard]] inline auto create_and_init_resource(Args&&... args) -> std::tuple, T&> { auto& resource_buffer = get_resource_buffer(); - ResourceHandle handle { resource_buffer.size(), this->_owner_id }; + ResourceHandle handle { static_cast(resource_buffer.size()), this->_owner_id }; resource_buffer.emplace_back(); diff --git a/code/Engine/src/App/AppRenderer.cpp b/code/Engine/src/App/AppRenderer.cpp index c127f8f..ed1b271 100644 --- a/code/Engine/src/App/AppRenderer.cpp +++ b/code/Engine/src/App/AppRenderer.cpp @@ -63,7 +63,6 @@ auto AppRenderer::add_texture(Media::Image& image) noexcept -> Utily::Result #include FT_FREETYPE_H -struct FreeType { - FT_Library library = nullptr; - - FreeType() { - Profiler::Timer timer("Font::FreeType() *library init*", { "freetype", "font", "init" }); - if (auto error = FT_Init_FreeType(&library); error) { - const auto ft_err_msg = std::string_view { FT_Error_String(error) }; - std::cerr - << "Failed to initialise FreeType library: \n" - << ft_err_msg - << std::endl; - } - } - ~FreeType() { - if (library != nullptr) { - FT_Done_FreeType(library); - } - library = nullptr; - } -}; - -thread_local FreeType free_type {}; - namespace Media { - constexpr static auto PRINTABLE_CHARS = []() { + constexpr static auto printable_chars = []() { constexpr char first_printable = char(32); constexpr char last_printable = char(127); constexpr size_t n = last_printable - first_printable; @@ -50,7 +27,7 @@ namespace Media { Profiler::Timer timer("Media::FontAtlas::create()"); // 1. Load ttf file from disk. - // 2. Initalise the font face + // 2. Initalise the freetype and fontface. // 3. Generate and cache the bitmap for each glyph. // 4. Determine the most compact atlas dimensions. // 5. Allocate raw image data. @@ -65,11 +42,19 @@ namespace Media { const auto& encoded_ttf = file_load_result.value(); // 2. + FT_Library free_type_library = nullptr; + if (auto error = FT_Init_FreeType(&free_type_library); error) { + FT_Done_FreeType(free_type_library); + return Utily::Error { FT_Error_String(error) }; + } + FT_Face ft_face = nullptr; - if (auto error = FT_New_Memory_Face(free_type.library, encoded_ttf.data(), encoded_ttf.size(), 0, &ft_face); error) { + if (auto error = FT_New_Memory_Face(free_type_library, encoded_ttf.data(), encoded_ttf.size(), 0, &ft_face); error) { + FT_Done_FreeType(free_type_library); return Utily::Error { FT_Error_String(error) }; } if (auto error = FT_Set_Pixel_Sizes(ft_face, 0, char_height_px); error) { + FT_Done_FreeType(free_type_library); return Utily::Error { FT_Error_String(error) }; } @@ -108,8 +93,10 @@ namespace Media { .left_padding = static_cast(ft_face->glyph->bitmap_left) } }; }; - std::array cached_glyphs; - std::transform(PRINTABLE_CHARS.begin(), PRINTABLE_CHARS.end(), cached_glyphs.begin(), create_cached_glyph); + std::array cached_glyphs; + std::transform(printable_chars.begin(), printable_chars.end(), cached_glyphs.begin(), create_cached_glyph); + + FT_Done_FreeType(free_type_library); // 4. auto take_max_dimensions = [&](GlyphDimensions&& agg, const CachedGlyph& cg) { @@ -144,7 +131,7 @@ namespace Media { } } return std::tuple { min_x, min_y }; - }(atlas_info.bitmap_dimensions.x, atlas_info.bitmap_dimensions.y, PRINTABLE_CHARS.size()); + }(atlas_info.bitmap_dimensions.x, atlas_info.bitmap_dimensions.y, printable_chars.size()); const int atlas_img_height = atlas_info.bitmap_dimensions.y * atlas_num_rows; const int atlas_img_width = atlas_info.bitmap_dimensions.x * atlas_glyphs_per_row; @@ -157,28 +144,25 @@ namespace Media { std::fill(atlas_buffer_begin, atlas_buffer_end, (uint8_t)0); // 6. - { - Profiler::Timer timer2("blit_glyphs_to_atlas()"); - for (std::ptrdiff_t i = 0; i < static_cast(PRINTABLE_CHARS.size()); ++i) { - const auto& bitmap_ft = cached_glyphs[i]; - const auto atlas_coords = glm::ivec2 { i % atlas_glyphs_per_row, i / atlas_glyphs_per_row }; - const auto adjusted_offset = glm::ivec2 { atlas_coords.x * atlas_info.bitmap_dimensions.x, atlas_coords.y * atlas_info.bitmap_dimensions.y }; - - auto get_atlas_buffer_dest = [&](int x, int y) { - const auto px_coords = glm::ivec2(adjusted_offset.x + x, adjusted_offset.y + y); - return atlas_buffer_begin + (px_coords.y * atlas_img_width + px_coords.x + bitmap_ft.dimensions.left_padding); + for (std::ptrdiff_t i = 0; i < static_cast(printable_chars.size()); ++i) { + const auto& bitmap_ft = cached_glyphs[i]; + const auto atlas_coords = glm::ivec2 { i % atlas_glyphs_per_row, i / atlas_glyphs_per_row }; + const auto adjusted_offset = glm::ivec2 { atlas_coords.x * atlas_info.bitmap_dimensions.x, atlas_coords.y * atlas_info.bitmap_dimensions.y }; + + auto get_atlas_buffer_dest = [&](int x, int y) { + const auto px_coords = glm::ivec2(adjusted_offset.x + x, adjusted_offset.y + y); + return atlas_buffer_begin + (px_coords.y * atlas_img_width + px_coords.x + bitmap_ft.dimensions.left_padding); + }; + for (std::ptrdiff_t y = 0; y < bitmap_ft.dimensions.bitmap_dimensions.y; ++y) { + // align to relative bitmap + const std::ptrdiff_t relative_y = y + atlas_info.spanline - bitmap_ft.dimensions.spanline; + const std::ptrdiff_t ft_offset = y * bitmap_ft.dimensions.bitmap_dimensions.x; + + auto src = std::span { + bitmap_ft.bitmap.data() + ft_offset, + static_cast(bitmap_ft.dimensions.bitmap_dimensions.x) }; - for (std::ptrdiff_t y = 0; y < bitmap_ft.dimensions.bitmap_dimensions.y; ++y) { - // align to relative bitmap - const std::ptrdiff_t relative_y = y + atlas_info.spanline - bitmap_ft.dimensions.spanline; - const std::ptrdiff_t ft_offset = y * bitmap_ft.dimensions.bitmap_dimensions.x; - - auto src = std::span { - bitmap_ft.bitmap.data() + ft_offset, - static_cast(bitmap_ft.dimensions.bitmap_dimensions.x) - }; - std::ranges::copy(src, get_atlas_buffer_dest(0, relative_y)); - } + std::ranges::copy(src, get_atlas_buffer_dest(0, relative_y)); } } @@ -202,9 +186,9 @@ namespace Media { }); } auto FontAtlas::uv_for(char a) const noexcept -> FontAtlas::UvCoord { - assert(PRINTABLE_CHARS.front() <= a && a <= PRINTABLE_CHARS.back() && "Must be a printable character"); + assert(printable_chars.front() <= a && a <= printable_chars.back() && "Must be a printable character"); - const auto i = static_cast(a - PRINTABLE_CHARS.front()); + const auto i = static_cast(a - printable_chars.front()); const float r = static_cast(i / _m.atlas_layout.x); const float c = static_cast(i % static_cast(_m.atlas_layout.x)); diff --git a/code/Engine/src/Media/Image.cpp b/code/Engine/src/Media/Image.cpp index 8f1f98f..5fccaa2 100644 --- a/code/Engine/src/Media/Image.cpp +++ b/code/Engine/src/Media/Image.cpp @@ -9,196 +9,6 @@ #define STB_IMAGE_RESIZE_IMPLEMENTATION #include -#if 0 -namespace Media { - Image::Image(Image&& other) - : _data(std::move(other._data)) - , _format(std::exchange(other._format, ColourFormat::rgb)) - , _width(std::exchange(other._width, 0)) - , _height(std::exchange(other._height, 0)) { - } - - Image& Image::operator=(Image&& other) noexcept { - if (_fence) { - _fence.value().wait_for_sync(); - } - if (other._fence) { - other._fence.value().wait_for_sync(); - } - std::swap(this->_data, other._data); - std::swap(this->_format, other._format); - std::swap(this->_width, other._width); - std::swap(this->_height, other._height); - return *this; - } - - auto Image::init( - std::vector& encoded_png, - bool include_alpha_channel, - bool is_gamma_corrected) noexcept - -> Utily::Result { - - Profiler::Timer timer("Media::Image::init()"); - - if (_fence) { - _fence->wait_for_sync(); - _fence = std::nullopt; - } - - if (_data.size()) { - _data.clear(); - } - - if (include_alpha_channel) { - if (is_gamma_corrected) { - _format = ColourFormat::s_rgba; - } else { - _format = ColourFormat::rgba; - } - } else { - if (is_gamma_corrected) { - _format = ColourFormat::s_rgb; - } else { - _format = ColourFormat::rgb; - } - } -#if 0 // Use LodePng - auto asLodeFormat = [](ColourFormat format) -> LodePNGColorType { - switch (format) { - case ColourFormat::rgb: - case ColourFormat::s_rgb: - return LodePNGColorType::LCT_RGB; - case ColourFormat::rgba: - case ColourFormat::s_rgba: - return LodePNGColorType::LCT_RGBA; - case ColourFormat::greyscale: - return LodePNGColorType::LCT_GREY; - } - }; - - Profiler::Timer lode_timer("lodepng::decode()"); - - [[maybe_unused]] auto has_error = lodepng::decode(_data, _width, _height, encoded_png, asLodeFormat(_format)); - - if constexpr (Config::DEBUG_LEVEL != Config::DebugInfo::none) { - if (has_error) { - return Utily::Error { - std::format( - "Image failed to init. " - "The lodepng failed to decode the png file. " - "The error was: \n{}", - lodepng_error_text(has_error)) - }; - } - } -#else // Use spng - Profiler::Timer lode_timer("spng::decode_image()"); - - spng_ctx* ctx = spng_ctx_new(0); - spng_set_png_buffer(ctx, encoded_png.data(), encoded_png.size()); - - size_t out_size = 0; - spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size); - _data.resize(out_size); - - spng_decode_image(ctx, _data.data(), _data.size(), SPNG_FMT_RGBA8, 0); - spng_ctx_free(ctx); - this->_format = Media::ColourFormat::rgba; -#endif - _data.shrink_to_fit(); - return {}; - } - - void Image::init_raw(std::vector&& raw_data, uint32_t width, uint32_t height, ColourFormat format) noexcept { - Profiler::Timer timer("Media::Image::init_raw()"); - if (_fence) { - Profiler::Timer fence_timer("Media::Image::fence::wait_for_sync()"); - _fence->wait_for_sync(); - _fence = std::nullopt; - } - _data = std::move(raw_data); - _format = format; - _width = width; - _height = height; - } - - auto Image::save_to_disk(std::filesystem::path path) noexcept - -> Utily::Result { - Profiler::Timer timer("Media::Image::save_to_disk()"); - - std::vector encoded; - lodepng::State state; - state.info_raw.colortype = LodePNGColorType::LCT_GREY; - { - Profiler::Timer timer("lodepng::encode()"); - if (auto error = lodepng::encode(encoded, _data, _width, _height, state); error) { - return Utily::Error { std::string("Image.save_to_disk() failed to be converted to png: ") + lodepng_error_text(error) }; - } - } - { - Profiler::Timer timer("lodepng::save_file()"); - if (auto error = lodepng::save_file(encoded, path.string()); error) { - return Utily::Error { std::string("Image.save_to_disk() failed to save: ") + lodepng_error_text(error) }; - } - } - return {}; - } - - void Image::stop() noexcept { - _data.reserve(0); - _width = 0; - _height = 0; - } - - void Image::add_fence(Core::Fence&& fence) noexcept { - _fence.emplace(std::forward(fence)); - } - - Image::~Image() { - if (_fence) { - _fence->wait_for_sync(); - } - } - - void Image::resize(float scale) noexcept { - this->resize( - static_cast(scale * static_cast(_width)), - static_cast(scale * static_cast(_height))); - } - void Image::resize(uint32_t width, uint32_t height) noexcept { - int32_t channels = [&] { - if (_format == ColourFormat::rgb || _format == ColourFormat::s_rgb) { - return 3; - } else { - return 4; - } - }(); - - auto resized_image = std::vector(width * height * channels); - - stbir_resize_uint8( - _data.data(), - _width, - _height, - 0, - resized_image.data(), - width, - height, - 0, - channels); - - if (_fence) { - _fence->wait_for_sync(); - _fence = std::nullopt; - } - - _data = std::exchange(resized_image, std::vector {}); - _width = width; - _height = height; - } -} -#endif - namespace Media { auto Image::create(std::filesystem::path path) -> Utily::Result { Profiler::Timer timer("Media::Image::create()"); @@ -304,27 +114,74 @@ namespace Media { } } + auto Image::libspng_format() const -> uint8_t { + switch (_m.format) { + case InternalFormat::greyscale: + return SPNG_COLOR_TYPE_GRAYSCALE; + case InternalFormat::rgba: + return SPNG_COLOR_TYPE_TRUECOLOR_ALPHA; + case InternalFormat::undefined: + [[fallthrough]]; + default: + throw std::runtime_error("Invalid internal format enum state."); + return std::numeric_limits::max(); + } + } + Image::Image(Image&& other) : _m(std::move(other._m)) { } auto Image::save_to_disk(std::filesystem::path path) const noexcept -> Utily::Result { Profiler::Timer timer("Media::Image::save_to_disk()"); - std::vector encoded; - lodepng::State state; - state.info_raw.colortype = LodePNGColorType::LCT_GREY; - { - Profiler::Timer timer("lodepng::encode()"); - if (auto error = lodepng::encode(encoded, _m.data.get(), _m.dimensions.x, _m.dimensions.y, state); error) { - return Utily::Error { std::string("Image.save_to_disk() failed to be converted to png: ") + lodepng_error_text(error) }; - } + spng_ctx* ctx = spng_ctx_new(SPNG_CTX_ENCODER); + if (!ctx) { + return Utily::Error("Unable to create libspng context"); } - { - Profiler::Timer timer("lodepng::save_file()"); - if (auto error = lodepng::save_file(encoded, path.string()); error) { - return Utily::Error { std::string("Image.save_to_disk() failed to save: ") + lodepng_error_text(error) }; - } + + spng_set_option(ctx, SPNG_ENCODE_TO_BUFFER, 1); + + spng_ihdr ihdr = { + .width = _m.dimensions.x, + .height = _m.dimensions.y, + .bit_depth = 8, + .color_type = libspng_format(), + .compression_method = 0, + .filter_method = 0, + .interlace_method = 0 + }; + + int spng_error = spng_set_ihdr(ctx, &ihdr); + if (spng_error) { + auto msg = spng_strerror(spng_error); + spng_ctx_free(ctx); + return Utily::Error(msg); } + + spng_error = spng_encode_image(ctx, _m.data.get(), _m.data_size_bytes, SPNG_FMT_PNG, SPNG_ENCODE_FINALIZE); + if (spng_error) { + auto msg = spng_strerror(spng_error); + spng_ctx_free(ctx); + return Utily::Error(msg); + } + + size_t png_size = 0; + uint8_t* png_buf = reinterpret_cast(spng_get_png_buffer(ctx, &png_size, &spng_error)); + + if (!png_buf || spng_error) { + auto msg = spng_strerror(spng_error); + spng_ctx_free(ctx); + return Utily::Error(msg); + } + + auto dump_file_result = Utily::FileWriter::dump_to_file(path, std::span { png_buf, png_size }); + if (dump_file_result.has_error()) { + return dump_file_result.error(); + } + + spng_ctx_free(ctx); + free(png_buf); + return {}; } } \ No newline at end of file diff --git a/code/Engine/src/Profiler/Profiler.cpp b/code/Engine/src/Profiler/Profiler.cpp index f44e7dd..a410e49 100644 --- a/code/Engine/src/Profiler/Profiler.cpp +++ b/code/Engine/src/Profiler/Profiler.cpp @@ -36,9 +36,14 @@ auto Profiler::format_as_trace_event_json() -> std::string { res += "{ \n\t\"traceEvents\": [ \n"; + std::unordered_map thread_names {}; + for (const auto& [process, recordings] : _processes_recordings) { for (const auto& recording : recordings) { - + if (std::chrono::duration_cast(recording.end_time - recording.start_time).count() < 1000) { + continue; + } + res += "\t\t{ \"args\":{}, \"name\":\""; res += recording.name; @@ -55,6 +60,7 @@ auto Profiler::format_as_trace_event_json() -> std::string { } res += "\"ph\":\"X\", "; res += "\"ts\":"; + res += std::to_string(std::chrono::duration_cast(recording.start_time - _profiler_start_time).count() / 1000.0); res += ", "; res += "\"dur\":"; @@ -62,9 +68,12 @@ auto Profiler::format_as_trace_event_json() -> std::string { res += ", "; res += "\"pid\": \""; res += process; - res += "\", \"tid\":"; - res += std::to_string(static_cast(std::hash {}(recording.thread_id))); - res += " },\n"; + res += "\", \"tid\":\""; + if (!thread_names.contains(recording.thread_id)) { + thread_names[recording.thread_id] = thread_names.size() + 1; + } + res += "Thread " + std::to_string(thread_names[recording.thread_id]); + res += "\" },\n"; } } if (_processes_recordings.size()) { @@ -113,16 +122,26 @@ Profiler::~Profiler() { Profiler::save_as_trace_event_json(TRACE_FILE_NAME); } -Profiler::Timer::Timer(std::string_view function_name, std::vector cats) - : name(function_name) - , start_time(std::chrono::high_resolution_clock::now()) - , end_time(std::chrono::high_resolution_clock::now()) +auto now = []() { + thread_local std::chrono::steady_clock::time_point last_time = std::chrono::high_resolution_clock::now(); + auto curr = std::chrono::high_resolution_clock::now(); + if (curr <= last_time) { + curr = last_time + std::chrono::nanoseconds(10); + } + last_time = curr; + return curr; +}; + +Profiler::Timer::Timer(std::string function_name, std::vector cats) + : name(std::move(function_name)) + , start_time(now()) + , end_time(now()) , thread_id(std::this_thread::get_id()) , categories(cats) { } Profiler::Timer::~Timer() { if constexpr (Config::SKIP_PROFILE) { return; } - end_time = std::chrono::high_resolution_clock::now(); + end_time = now(); Profiler::instance().submit_timer(*this); } \ No newline at end of file