diff --git a/code/.clang-tidy b/code/.clang-tidy index a466cd5..b9d3014 100644 --- a/code/.clang-tidy +++ b/code/.clang-tidy @@ -11,13 +11,17 @@ CheckOptions: - { key: readability-identifier-naming.VariableCase, value: lower_case } - { key: readability-identifier-naming.FunctionCase, value: lower_case } - { key: readability-identifier-naming.StructCase, value: CamelCase } + - { key: readability-identifier-naming.MemberVariableCase, value: snake_case } + - { key: readability-identifier-naming.ConstantMemberCase, value: snake_case } - { key: readability-identifier-naming.ClassCase, value: CamelCase } - { key: readability-identifier-naming.MethodCase, value: lower_case } - { key: readability-identifier-naming.PrivateMemberPrefix, value: _ } - { key: readability-identifier-naming.StaticMemberPrefix, value: _ } - - { key: readability-identifier-naming.ConstantCase, value: UPPER_CASE } - { 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 5b332fd..e37f4f9 100644 --- a/code/Demos/src/Main.cpp +++ b/code/Demos/src/Main.cpp @@ -410,15 +410,56 @@ struct IsoData { Renderer::ResourceManager resource_manager; Renderer::InstanceRenderer instance_renderer; + + std::optional font_batch_renderer; + + Cameras::StationaryPerspective camera { glm::vec3(0, 1, -1), glm::normalize(glm::vec3(0, -0.25f, 0.5f)) }; }; struct IsoLogic { void init(AppRenderer& renderer, Core::AudioManager& audio, IsoData& data) { - Media::Sound sound {}; + 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"); - auto wav_file_data = Utily::FileReader::load_entire_file("assets/background_sound.wav"); - wav_file_data.on_error(print_then_quit); - sound.init_from_wav(wav_file_data.value()).on_error(print_then_quit); + 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); @@ -428,27 +469,20 @@ struct IsoLogic { data.spinning.angle = 0; data.spinning.rotations_per_second = 1; - data.source_handle = audio.play_sound(data.sound_buffer).on_error(print_then_quit).value(); - data.start_time = std::chrono::high_resolution_clock::now(); + auto model_data = Utily::FileReader::load_entire_file("assets/teapot.obj").on_error_panic().value_move(); + auto model = Model::decode_as_static_model(model_data, ".obj").on_error_panic().value_move(); + auto image = Media::Image::create("assets/texture.png").on_error_panic().value_move(); - auto model_data = std::move( - Utily::FileReader::load_entire_file("assets/teapot.obj") - .on_error(print_then_quit) - .value()); - auto model = std::move( - Model::decode_as_static_model(model_data, ".obj") - .on_error(print_then_quit) - .value()); - - auto image_png = std::move( - Utily::FileReader::load_entire_file("assets/texture.png") - .on_error(print_then_quit) - .value()); - auto image = Media::Image {}; - image.init(image_png, true, false) - .on_error(print_then_quit); + data.font_batch_renderer.emplace(Renderer::FontBatchRenderer::create(data.resource_manager, "assets/RobotoMono.ttf") + .on_error_panic() + .value_move()); 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(); } void update(float dt, const Core::InputManager& input, Core::AudioManager& audio, AppState& state, IsoData& data) { @@ -470,12 +504,27 @@ struct IsoLogic { lp.dir = { 0, 0, 1 }; lp.vel = { 0, 0, 0 }; audio.set_listener_properties(lp); + + auto t = Components::Transform {}; + t.position = glm::vec3(0, -1, 1); + t.scale = glm::vec3(0.5f); + + auto model = t.calc_transform_mat(); + data.instance_renderer.push_instance(model); + + t.position = glm::vec3(0, -1, 2); + model = t.calc_transform_mat(); + data.instance_renderer.push_instance(model); } void draw(AppRenderer& renderer, IsoData& data) { renderer.screen_frame_buffer.bind(); renderer.screen_frame_buffer.clear(data.background_colour); renderer.screen_frame_buffer.resize(renderer.window_width, renderer.window_height); + + auto v = data.camera.view_matrix(); + auto p = data.camera.projection_matrix(renderer.window_width, renderer.window_height); + data.instance_renderer.draw_instances(data.resource_manager, v, p); } void stop(IsoData& data) { } 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 bb06595..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" @@ -21,7 +20,7 @@ namespace Core { Texture(Texture&&); [[nodiscard]] auto init() noexcept -> Utily::Result; - auto upload_image(Media::Image& image, Filter filter = Filter::smooth, bool offload_image_on_success = false) noexcept -> Utily::Result; + auto upload_image(const Media::Image& image, Filter filter = Filter::smooth) noexcept -> Utily::Result; // Once texture unit is aquired, it cannot be taken away unless unbinded() or just bind(false). [[nodiscard]] auto bind(bool locked = false) noexcept -> Utily::Result; diff --git a/code/Engine/include/Media/Font.hpp b/code/Engine/include/Media/Font.hpp deleted file mode 100644 index ce0c55d..0000000 --- a/code/Engine/include/Media/Font.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include - -#include "Media/Image.hpp" -#include "Model/Static.hpp" -#include -#include -#include -#include -#include - -namespace Media { - - namespace FontAtlasConstants { - consteval static auto gen_drawable_chars() { - constexpr char first_printable = char(32); - constexpr char last_printable = char(127); - constexpr size_t n = last_printable - first_printable; - std::array chars {}; - std::ranges::copy(std::views::iota(first_printable, last_printable), chars.begin()); - return chars; - } - constexpr static auto DRAWABLE_CHARS = gen_drawable_chars(); - - consteval static auto gen_is_char_drawable_table() -> std::array { - auto table = std::array { false }; - std::ptrdiff_t i = 0; - for (auto iter = table.begin(); iter != table.end(); ++iter, ++i) { - *iter = std::ranges::find(DRAWABLE_CHARS, static_cast(i)) != DRAWABLE_CHARS.end(); - } - return table; - } - constexpr static auto IS_CHAR_DRAWABLE = gen_is_char_drawable_table(); - } - - class Font; - - class FontAtlas - { - public: - struct UvCoord { - float min_x; - float max_x; - float min_y; - float max_y; - }; - - Media::Image image = {}; - int32_t columns { 0 }, rows { 0 }; - int32_t glyph_width { 0 }, glyph_height { 0 }; - - constexpr FontAtlas() = default; - - // Allow move operations - FontAtlas(FontAtlas&& other) = default; - auto operator=(FontAtlas&& other) noexcept -> FontAtlas&; - - // Refuse copy operations. - FontAtlas(const FontAtlas&) = delete; - FontAtlas& operator=(const FontAtlas&) = delete; - - auto init(Media::Font& font, uint32_t char_height_px) -> Utily::Result; - - auto uv_coord_of_char(char a) const -> UvCoord; - }; - - class Font - { - public: - Font() = default; - Font(const Font&) = delete; - Font(Font&& other); - - [[nodiscard]] auto init(std::vector& encoded_ttf) noexcept -> Utily::Result; - [[nodiscard]] auto gen_image_atlas(uint32_t char_height_px) -> Utily::Result; - - void stop() noexcept; - - ~Font(); - - private: - void* _font_face = nullptr; - }; - - namespace FontMeshGenerator { - auto generate_static_mesh(std::string_view str, const float char_height, const glm::vec2 bottom_left_pos, const FontAtlas& atlas) -> std::tuple, std::array>; - } -} \ No newline at end of file diff --git a/code/Engine/include/Media/FontAtlas.hpp b/code/Engine/include/Media/FontAtlas.hpp new file mode 100644 index 0000000..eaf17b9 --- /dev/null +++ b/code/Engine/include/Media/FontAtlas.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "Media/Image.hpp" +#include "Model/Static.hpp" +#include +#include +#include +#include +#include + +namespace Media { + + class FontAtlas + { + public: + /// @brief Load .ttf font from disk. Generate a font-atlas image. Can fail. + [[nodiscard]] static auto create(std::filesystem::path path, uint32_t char_height_px) noexcept -> Utily::Result; + + FontAtlas(FontAtlas&& other) + : _m(std::move(other._m)) { } + + struct UvCoord { + float min_x; + float max_x; + float min_y; + float max_y; + }; + [[nodiscard]] auto uv_for(char a) const noexcept -> FontAtlas::UvCoord; + + [[nodiscard]] auto atlas_image() const noexcept -> const Media::Image& { return _m.atlas_image; } + [[nodiscard]] auto atlas_layout() const noexcept { return _m.atlas_layout; } + [[nodiscard]] auto glyph_dimensions() const noexcept { return _m.glyph_dimensions; } + + private: + struct M { + Media::Image atlas_image; + glm::vec2 atlas_layout; + glm::vec2 glyph_dimensions; + } _m; + + explicit FontAtlas(M&& m) + : _m(std::move(m)) { } + }; +} diff --git a/code/Engine/include/Media/Image.hpp b/code/Engine/include/Media/Image.hpp index 5237cdb..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 @@ -11,53 +10,40 @@ #include 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); + enum class InternalFormat { + undefined = 0, + greyscale, + rgba + }; - Image& operator=(Image&& other) noexcept; + [[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]] auto init( - std::vector& encoded_png, - bool include_alpha_channel, - bool is_gamma_corrected) noexcept - -> 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]] inline auto format() const { return _m.format; } - void init_raw(std::vector&& raw_data, uint32_t width, uint32_t height, ColourFormat format) noexcept; + [[nodiscard]] auto opengl_format() const -> uint32_t; + [[nodiscard]] auto libspng_format() const -> uint8_t; - [[nodiscard]] auto save_to_disk(std::filesystem::path path) noexcept - -> Utily::Result; + [[nodiscard]] auto save_to_disk(std::filesystem::path path) const 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(); + Image(Image&& other); + Image(const Image&) = delete; private: - std::vector _data; - Media::ColourFormat _format = Media::ColourFormat::rgb; - uint32_t _width { 0 }, _height { 0 }; - std::optional _fence; + struct M { + std::unique_ptr data = {}; + size_t data_size_bytes = 0; + glm::uvec2 dimensions = { 0, 0 }; + InternalFormat format = InternalFormat::undefined; + } _m; + + explicit Image(M&& m) + : _m(std::move(m)) { } }; } \ No newline at end of file diff --git a/code/Engine/include/Media/Media.hpp b/code/Engine/include/Media/Media.hpp index 99f13c5..39ec241 100644 --- a/code/Engine/include/Media/Media.hpp +++ b/code/Engine/include/Media/Media.hpp @@ -1,5 +1,5 @@ #pragma once #include "Media/Image.hpp" -#include "Media/Font.hpp" +#include "Media/FontAtlas.hpp" #include "Media/Sound.hpp" diff --git a/code/Engine/include/Media/Sound.hpp b/code/Engine/include/Media/Sound.hpp index d2f0d19..a343da9 100644 --- a/code/Engine/include/Media/Sound.hpp +++ b/code/Engine/include/Media/Sound.hpp @@ -24,7 +24,7 @@ namespace Media { stereo16 = AL_FORMAT_STEREO16 }; - [[nodiscard]] auto init_from_wav(const std::vector& encoded_wav) -> Utily::Result; + [[nodiscard]] static auto create(std::filesystem::path wav_path) noexcept -> Utily::Result; inline static auto to_openal_format(const Format& format) -> FormatOpenal { if (format == Format::mono_i16) { @@ -36,14 +36,18 @@ namespace Media { } } - [[nodiscard]] inline auto raw_bytes() const noexcept -> std::span { return { _data.cbegin(), _data.cend() }; } - [[nodiscard]] inline auto frequency() const noexcept -> size_t { return _frequency; } - [[nodiscard]] inline auto openal_format() const noexcept -> FormatOpenal { return to_openal_format(_format); } + [[nodiscard]] inline auto raw_bytes() const noexcept -> std::span { return { _m.data.cbegin(), _m.data.cend() }; } + [[nodiscard]] inline auto frequency() const noexcept -> size_t { return _m.frequency; } + [[nodiscard]] inline auto openal_format() const noexcept -> FormatOpenal { return to_openal_format(_m.format); } private: - std::vector _data = {}; - size_t _data_size_bytes = 0; - size_t _frequency = 0; - Format _format = {}; + struct M { + std::vector data; + size_t frequency; + Format format; + } _m; + + explicit Sound(M&& m) + : _m(std::move(m)) { } }; } \ No newline at end of file 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/FontBatchRenderer.hpp b/code/Engine/include/Renderer/FontBatchRenderer.hpp index cce803f..9fbc723 100644 --- a/code/Engine/include/Renderer/FontBatchRenderer.hpp +++ b/code/Engine/include/Renderer/FontBatchRenderer.hpp @@ -12,12 +12,16 @@ namespace Renderer { glm::vec2 screen_dimensions; glm::vec4 font_colour; }; - void init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas); - + + static auto create(ResourceManager& resource_manager, std::filesystem::path ttf_path) noexcept -> Utily::Result; + void begin_batch(BatchConfig&& batch_config); void push_to_batch(std::string_view text, glm::vec2 bottom_left, float height_px); void end_batch(); + FontBatchRenderer(FontBatchRenderer&& other) + : _m(std::move(other._m)) { } + private: void load_text_into_vb(const std::string_view& text, glm::vec2 bottom_left, float height_px); @@ -26,17 +30,21 @@ namespace Renderer { glm::vec2 uv_coord; using VBL = Core::VertexBufferLayout; }; - - std::optional _current_batch_config = std::nullopt; - std::vector _current_batch_vertices = {}; - - glm::vec2 _glyph_dimensions = { 0, 0 }; - glm::vec2 _atlas_dimensions = { 0, 0 }; - - Renderer::ResourceHandle _s; - Renderer::ResourceHandle _t; - Renderer::ResourceHandle _vb; - Renderer::ResourceHandle _ib; - Renderer::ResourceHandle _va; + + struct M { + std::vector current_batch_vertices; + std::optional current_batch_config; + + Media::FontAtlas font_atlas; + + Renderer::ResourceHandle s; + Renderer::ResourceHandle t; + Renderer::ResourceHandle vb; + Renderer::ResourceHandle ib; + Renderer::ResourceHandle va; + } _m; + + explicit FontBatchRenderer(M&& m) + : _m(std::move(m)) { } }; } \ No newline at end of file diff --git a/code/Engine/include/Renderer/FontRenderer.hpp b/code/Engine/include/Renderer/FontRenderer.hpp deleted file mode 100644 index 9e4649c..0000000 --- a/code/Engine/include/Renderer/FontRenderer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "Core/Core.hpp" -#include "Media/Media.hpp" -#include "Renderer/ResourceManager.hpp" - -namespace Renderer { - class FontRenderer - { - private: - Renderer::ResourceHandle _s; - Renderer::ResourceHandle _t; - Renderer::ResourceHandle _vb; - Renderer::ResourceHandle _ib; - Renderer::ResourceHandle _va; - - struct FontVertex { - glm::vec2 position; - glm::vec2 uv_coord; - - using VBL = Core::VertexBufferLayout; - }; - - size_t _loaded_text_hash = 0; - - std::vector _vertices; - std::vector _indices; - - glm::vec2 _glyph_dimensions = { 0, 0 }; - glm::vec2 _atlas_dimensions = { 0, 0 }; - - void ensure_buffers_have_capacity_for(const size_t N, Core::IndexBuffer& ib); - void load_text_into_vb(const std::string_view& text, Core::VertexBuffer& vb); - bool is_init() const noexcept; - auto uv_coord_of_char(char a) const noexcept -> Media::FontAtlas::UvCoord; - - public: - void init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas); - void stop(ResourceManager& resource_manager); - void draw(ResourceManager& resource_manager, glm::vec2 screen_width, std::string_view text, float char_size_px, glm::vec2 bottom_left, glm::vec4 colour = { 1, 1, 1, 1 }); - }; -} \ No newline at end of file diff --git a/code/Engine/include/Renderer/InstanceRenderer.hpp b/code/Engine/include/Renderer/InstanceRenderer.hpp index 4067885..fffb95a 100644 --- a/code/Engine/include/Renderer/InstanceRenderer.hpp +++ b/code/Engine/include/Renderer/InstanceRenderer.hpp @@ -12,7 +12,7 @@ namespace Renderer { void stop(ResourceManager& resource_manager); void push_instance(const glm::mat4& instance_transformation); - void draw_instances(ResourceManager& resource_manager, glm::vec2 screen_dimensions); + void draw_instances(ResourceManager& resource_manager, const glm::mat4& projection, const glm::mat4& view); private: Renderer::ResourceHandle _s; Renderer::ResourceHandle _t; diff --git a/code/Engine/include/Renderer/Renderer.hpp b/code/Engine/include/Renderer/Renderer.hpp index e1eee69..94e5568 100644 --- a/code/Engine/include/Renderer/Renderer.hpp +++ b/code/Engine/include/Renderer/Renderer.hpp @@ -13,6 +13,5 @@ namespace Renderer { #include "Renderer/ResourceHandle.hpp" #include "Renderer/ResourceManager.hpp" -#include "Renderer/FontRenderer.hpp" #include "Renderer/FontBatchRenderer.hpp" #include "Renderer/InstanceRenderer.hpp" diff --git a/code/Engine/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 ec4a157..ed1b271 100644 --- a/code/Engine/src/App/AppRenderer.cpp +++ b/code/Engine/src/App/AppRenderer.cpp @@ -56,14 +56,13 @@ auto AppRenderer::add_texture(Media::Image& image) noexcept -> Utily::Result(id) }; } void AppRenderer::stop() noexcept { - Profiler::Timer("AppRenderer::stop()"); // explicitly destroy, just in case the gl context closes first. shaders.resize(0); vertex_buffers.resize(0); diff --git a/code/Engine/src/Core/AudioManager.cpp b/code/Engine/src/Core/AudioManager.cpp index c082ce7..8918040 100644 --- a/code/Engine/src/Core/AudioManager.cpp +++ b/code/Engine/src/Core/AudioManager.cpp @@ -331,14 +331,18 @@ namespace Core { return Utily::Error("Core::AudioManager::set_source_motion() failed. OpenAL has invalidated the source."); } } - const uint32_t& source_id = _sources[source_handle.index].id; - { - Profiler::Timer timer3("alSource3f(AL_POSITION)"); + + auto& source = _sources[source_handle.index]; + const uint32_t& source_id = source.id; + + if (source.pos != pos) { alSource3f(source_id, AL_POSITION, pos.x, pos.y, pos.z); + source.pos = pos; } - { - Profiler::Timer timer3("alSource3f(AL_VELOCITY)"); + + if (source.vel != vel) { alSource3f(source_id, AL_VELOCITY, vel.x, vel.y, vel.z); + source.vel = vel; } if constexpr (Config::DEBUG_LEVEL != Config::DebugInfo::none) { diff --git a/code/Engine/src/Core/OpenglContext.cpp b/code/Engine/src/Core/OpenglContext.cpp index 3424293..ac532b5 100644 --- a/code/Engine/src/Core/OpenglContext.cpp +++ b/code/Engine/src/Core/OpenglContext.cpp @@ -140,16 +140,23 @@ namespace Core { if (_window.has_value()) { return {}; } + glfwWindowHint(GLFW_SAMPLES, 4); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_DEPTH_BITS, 24); - _window = glfwCreateWindow(width, height, app_name.data(), NULL, NULL); + GLFWmonitor* primary_monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode* mode = glfwGetVideoMode(primary_monitor); + width = mode->width; + height = mode->height; + + _window = glfwCreateWindow(width, height, app_name.data(), primary_monitor, NULL); if (!_window) { return Utily::Error("GLFW3 failed to create a window"); } window_width = width; window_height = height; glfwMakeContextCurrent(*_window); + if constexpr (Config::ENABLE_VSYNC) { glfwSwapInterval(1); } @@ -173,6 +180,7 @@ namespace Core { if (_window.has_value()) { return {}; } + glfwWindowHint(GLFW_SAMPLES, 4); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_DEPTH_BITS, 24); diff --git a/code/Engine/src/Core/Texture.cpp b/code/Engine/src/Core/Texture.cpp index 3e10efc..184d553 100644 --- a/code/Engine/src/Core/Texture.cpp +++ b/code/Engine/src/Core/Texture.cpp @@ -24,7 +24,7 @@ namespace Core { , _id(std::exchange(other._id, std::nullopt)) , _texture_unit_index(std::exchange(other._texture_unit_index, std::nullopt)) { } - auto getUsableTextureUnit() noexcept + auto get_usable_texture_unit() noexcept -> Utily::Result, Utily::Error> { if (!texture_units().size()) { // TODO collect analytics about how many texture slots. @@ -63,9 +63,8 @@ namespace Core { } auto Texture::upload_image( - Media::Image& image, - Filter filter, - bool offload_image_on_success) noexcept + const Media::Image& image, + Filter filter) noexcept -> Utily::Result { Core::DebugOpRecorder::instance().push("Core::Texture", "upload_image()"); Profiler::Timer timer("Core::Texture::upload_image()", { "rendering" }); @@ -90,9 +89,9 @@ namespace Core { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); int32_t bytes_per_pixel = 4; - uint32_t gl_format = GL_RGBA8; + uint32_t gl_format = GL_RGBA; - if (image.format() == Media::ColourFormat::greyscale) { + if (image.format() == Media::Image::InternalFormat::greyscale) { bytes_per_pixel = 1; gl_format = GL_RED; } else { @@ -102,17 +101,8 @@ namespace Core { const void* img_data = reinterpret_cast(image.raw_bytes().data()); glPixelStorei(GL_UNPACK_ALIGNMENT, bytes_per_pixel); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)image.format(), image.dimensions().x, image.dimensions().y, 0, gl_format, GL_UNSIGNED_BYTE, img_data); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)image.opengl_format(), image.dimensions().x, image.dimensions().y, 0, gl_format, GL_UNSIGNED_BYTE, img_data); - if (offload_image_on_success) { - glFinish(); - image.stop(); - } - if constexpr (!Config::SKIP_IMAGE_TEXTURE_FENCING) { - auto fence = Core::Fence {}; - fence.init(); - image.add_fence(std::move(fence)); - } return {}; } @@ -133,7 +123,7 @@ namespace Core { _texture_unit_index = std::nullopt; } - auto result = getUsableTextureUnit(); + auto result = get_usable_texture_unit(); if (result.has_error()) { return result.error(); } diff --git a/code/Engine/src/Media/Font.cpp b/code/Engine/src/Media/Font.cpp deleted file mode 100644 index 1e1a613..0000000 --- a/code/Engine/src/Media/Font.cpp +++ /dev/null @@ -1,304 +0,0 @@ -#include "Media/Font.hpp" - -#include -#include -#include -#include -#include - -#include "Profiler/Profiler.hpp" - -// #include - -#include -#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; - } -}; - -static FreeType free_type {}; - -namespace Media { - auto FontAtlas::operator=(FontAtlas&& other) noexcept -> FontAtlas& { - this->image = std::move(other.image); - this->columns = other.columns; - this->rows = other.rows; - this->glyph_width = other.glyph_width; - this->glyph_height = other.glyph_height; - return *this; - } - - auto FontAtlas::init(Media::Font& font, uint32_t char_height_px) -> Utily::Result { - auto res = font.gen_image_atlas(char_height_px); - if (res.has_error()) { - return res.error(); - } - *this = std::move(res.value()); - - return {}; - } - - auto FontAtlas::uv_coord_of_char(char a) const -> FontAtlas::UvCoord { - constexpr auto drawble_chars = FontAtlasConstants::DRAWABLE_CHARS; - - assert(drawble_chars.front() <= a && a <= drawble_chars.back() && "Must be a printable character"); - - const auto i = static_cast(a - drawble_chars.front()); - - const float r = static_cast(i / columns); - const float c = static_cast(i % columns); - - return UvCoord { - .min_x = c / static_cast(columns), - .max_x = (c + 1) / static_cast(columns), - .min_y = 1 - (r + 1) / static_cast(rows), - .max_y = 1 - r / static_cast(rows), - }; - } - - auto Font::init(std::vector& encoded_ttf) noexcept -> Utily::Result { - FT_Face ff = nullptr; - if (auto error = FT_New_Memory_Face(free_type.library, encoded_ttf.data(), encoded_ttf.size(), 0, &ff); error) { - return Utily::Error { FT_Error_String(error) }; - } - _font_face = ff; - return {}; - } - - struct FreeTypeGlyph { - std::vector buffer = {}; - std::ptrdiff_t width { 0 }, height { 0 }, spanline { 0 }, left_padding { 0 }; - - constexpr FreeTypeGlyph() = default; - - constexpr FreeTypeGlyph(const FT_GlyphSlot& slot) - : width(slot->bitmap.width) - , height(slot->bitmap.rows) - , spanline(slot->bitmap_top) - , left_padding(slot->bitmap_left) { - auto buffer_data = std::span(slot->bitmap.buffer, slot->bitmap.width * slot->bitmap.rows); - buffer = std::vector { buffer_data.begin(), buffer_data.end() }; - } - }; - - struct GlyphInfo { - std::ptrdiff_t width = 0, height = 0, spanline = 0, left_padding = 0; - - GlyphInfo() = default; - - GlyphInfo(const FT_GlyphSlot& slot) - : width(slot->bitmap.width) - , height(slot->bitmap.rows + 1) - , spanline(slot->bitmap_top) - , left_padding(slot->bitmap_left) { } - - static auto take_max_values(const GlyphInfo& lhs, const GlyphInfo& rhs) -> GlyphInfo { - GlyphInfo gi; - gi.width = std::max(lhs.width, rhs.width); - gi.height = std::max(lhs.height, rhs.height); - gi.spanline = std::max(lhs.spanline, rhs.spanline); - gi.left_padding = std::max(lhs.left_padding, rhs.left_padding); - return gi; - } - }; - - auto Font::gen_image_atlas(uint32_t char_height_px) -> Utily::Result { - Profiler::Timer timer("Font::gen_image_atlas()", { "font", "loading" }); - - constexpr auto& drawable_chars = FontAtlasConstants::DRAWABLE_CHARS; - - // Validate and Scale FreeType Face. - if (_font_face == nullptr) { - return Utily::Error { "Font not initialised." }; - } - FT_Face ff = reinterpret_cast(_font_face); - if (auto error = FT_Set_Pixel_Sizes(ff, char_height_px, char_height_px); error) { - return Utily::Error { FT_Error_String(error) }; - } - - auto generate_and_cache_ft_glyph = [&](char c, FreeTypeGlyph& ftg) -> GlyphInfo { - auto glyph_index = FT_Get_Char_Index(ff, static_cast(c)); - - if (auto error = FT_Load_Glyph(ff, glyph_index, FT_LOAD_DEFAULT); error) [[unlikely]] { - throw std::runtime_error(FT_Error_String(error)); - } - if (auto error = FT_Render_Glyph(ff->glyph, FT_Render_Mode::FT_RENDER_MODE_NORMAL); error) [[unlikely]] { - throw std::runtime_error(FT_Error_String(error)); - } - ftg = FreeTypeGlyph(ff->glyph); - return GlyphInfo(ff->glyph); - }; - - auto cached_ft_bitmaps = std::array {}; - GlyphInfo atlas_info = {}; - { - Profiler::Timer timer("FT_Render_glyphs()"); - // 1. Transform -> Generates and caches the freetype bitmaps & returns its glyph layout - // 2. Reduce -> Calculate the maximum bounding glyph layout, so all characters have enough space in atlas. - atlas_info = std::transform_reduce( - drawable_chars.begin(), - drawable_chars.end(), - cached_ft_bitmaps.begin(), - GlyphInfo {}, - &GlyphInfo::take_max_values, - generate_and_cache_ft_glyph); - } - Profiler::Timer blit_timer("blit_glyphs_into_altas()"); - - const auto [atlas_glyphs_per_row, atlas_num_rows] = [](float glyph_width, float glyph_height, float num_glyphs_in_atlas) { - -#if 1 // generate most square shape by brute force. - auto [min_diff, min_x, min_y] = std::tuple { std::numeric_limits::max(), 0, num_glyphs_in_atlas }; - for (float i = 1; i < num_glyphs_in_atlas; ++i) { - auto x = i; - auto y = num_glyphs_in_atlas / i; - x = std::floor(x); - y = std::ceil(y); - auto diff = std::abs((x * glyph_width) - (y * glyph_height)); - if (diff < min_diff && num_glyphs_in_atlas <= x * y) { - min_diff = diff; - min_x = x; - min_y = y; - } - } -#else - float expected_dimensions = std::sqrtf(num_glyphs_in_atlas); - float ratio = glyph_height / glyph_width; - float min_x = std::floor(expected_dimensions * ratio); - float min_y = std::ceil(expected_dimensions / ratio); - assert(num_glyphs_in_atlas < min_x * min_y); -#endif - return std::tuple { min_x, min_y }; - }(atlas_info.width, atlas_info.height, drawable_chars.size()); - - const int atlas_img_height = atlas_info.height * atlas_num_rows; - const int atlas_img_width = atlas_info.width * atlas_glyphs_per_row; - - std::vector atlas_buffer; - - atlas_buffer.resize(atlas_img_height * atlas_img_width, (uint8_t)0); - - for (std::ptrdiff_t i = 0; i < static_cast(drawable_chars.size()); ++i) { - const auto& bitmap_ft = cached_ft_bitmaps[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.width, atlas_coords.y * atlas_info.height }; - - auto get_atlas_buffer_dest = [&](int x, int y) -> uint8_t& { - const auto px_coords = glm::ivec2(adjusted_offset.x + x, adjusted_offset.y + y); - return atlas_buffer[px_coords.y * atlas_img_width + px_coords.x + bitmap_ft.left_padding]; - }; - for (std::ptrdiff_t y = 0; y < bitmap_ft.height; ++y) { - for (std::ptrdiff_t x = 0; x < bitmap_ft.width; ++x) { - // align to relative bitmap - const std::ptrdiff_t relative_y = (y + atlas_info.spanline - bitmap_ft.spanline); - const std::ptrdiff_t ft_offset = y * bitmap_ft.width + x; - get_atlas_buffer_dest(x, relative_y) = bitmap_ft.buffer[ft_offset]; - } - } - } - - FontAtlas font_atlas; - font_atlas.columns = atlas_glyphs_per_row; - font_atlas.rows = atlas_num_rows; - font_atlas.glyph_width = static_cast(atlas_info.width); - font_atlas.glyph_height = static_cast(atlas_info.height); - font_atlas.image.init_raw(std::move(atlas_buffer), atlas_img_width, atlas_img_height, ColourFormat::greyscale); - return font_atlas; - } - - void Font::stop() noexcept { - if (_font_face != nullptr) { - FT_Done_Face(reinterpret_cast(_font_face)); - } - _font_face = nullptr; - } - - Font::~Font() { - stop(); - } - - auto FontMeshGenerator::generate_static_mesh(std::string_view str, const float char_height, const glm::vec2 bottom_left_pos, const FontAtlas& atlas) -> std::tuple, std::array> { - Profiler::Timer timer("FontMeshGenerate::generate_static_mesh()"); - - auto is_not_printable = [](char c) { - return !(FontAtlasConstants::DRAWABLE_CHARS.front() <= c && c <= FontAtlasConstants::DRAWABLE_CHARS.front()); - }; - assert(std::any_of(str.begin(), str.end(), is_not_printable)); - - constexpr int max_chars = 100; - if (str.size() > max_chars) { - throw std::length_error("Exceeded maximum char capacity"); - } - - static std::array vertices = {}; - static std::array indices = {}; - - const size_t vert_size = str.size() * 4; - const size_t indi_size = str.size() * 6; - - Model::Vertex2D* v_ptr[] = { - vertices.data() + 0, - vertices.data() + 1, - vertices.data() + 2, - vertices.data() + 3 - }; - Model::Index* i_ptr[] = { - indices.data() + 0, - indices.data() + 1, - indices.data() + 2, - indices.data() + 3, - indices.data() + 4, - indices.data() + 5, - }; - - const float char_width = static_cast(atlas.glyph_width) / static_cast(atlas.glyph_height) * char_height; - const float y_min = bottom_left_pos.y; - const float y_max = y_min + char_height; - -#if 1 - for (int i = 0; i < str.size(); ++i) { - char c = str[i]; -#else - for (auto [i, c] : str | std::views::enumerate) { -#endif - const auto uv = atlas.uv_coord_of_char(c); - - const int v_offset = i * 4; - const int i_offset = i * 6; - - const float x_min = char_width * i + bottom_left_pos.x; - const float x_max = x_min + char_width; - - std::construct_at(v_ptr[0] + v_offset, glm::vec2(x_min, y_min), glm::vec2(uv.min_x, uv.min_y)); - std::construct_at(v_ptr[1] + v_offset, glm::vec2(x_max, y_min), glm::vec2(uv.max_x, uv.min_y)); - std::construct_at(v_ptr[2] + v_offset, glm::vec2(x_max, y_max), glm::vec2(uv.max_x, uv.max_y)); - std::construct_at(v_ptr[3] + v_offset, glm::vec2(x_min, y_max), glm::vec2(uv.min_x, uv.max_y)); - - *(i_ptr[0] + i_offset) = v_offset + 0; - *(i_ptr[1] + i_offset) = v_offset + 1; - *(i_ptr[2] + i_offset) = v_offset + 2; - *(i_ptr[3] + i_offset) = v_offset + 2; - *(i_ptr[4] + i_offset) = v_offset + 3; - *(i_ptr[5] + i_offset) = v_offset + 0; - } - return std::tuple { vertices, indices }; - } -} \ No newline at end of file diff --git a/code/Engine/src/Media/FontAtlas.cpp b/code/Engine/src/Media/FontAtlas.cpp new file mode 100644 index 0000000..e371d0b --- /dev/null +++ b/code/Engine/src/Media/FontAtlas.cpp @@ -0,0 +1,202 @@ +#include "Media/FontAtlas.hpp" + +#include +#include +#include +#include +#include + +#include "Profiler/Profiler.hpp" + +// #include + +#include +#include FT_FREETYPE_H + +namespace Media { + constexpr static auto printable_chars = []() { + constexpr char first_printable = char(32); + constexpr char last_printable = char(127); + constexpr size_t n = last_printable - first_printable; + std::array chars {}; + std::ranges::copy(std::views::iota(first_printable, last_printable), chars.begin()); + return chars; + }(); + + auto FontAtlas::create(std::filesystem::path path, uint32_t char_height_px) noexcept -> Utily::Result { + Profiler::Timer timer("Media::FontAtlas::create()"); + + // 1. Load ttf file from disk. + // 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. + // 6. Blit each cached glyph bitmap onto the atlas, ensuring the same spanline. + // 7. Create Image and font atlas. + + // 1. + auto file_load_result = Utily::FileReader::load_entire_file(path); + if (file_load_result.has_error()) { + return file_load_result.error(); + } + 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) { + 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) }; + } + + // 3. + struct GlyphDimensions { + glm::uvec2 bitmap_dimensions = { 0, 0 }; + uint32_t spanline = 0; + uint32_t left_padding = 0; + }; + struct CachedGlyph { + char c; + std::vector bitmap; + GlyphDimensions dimensions; + }; + auto create_cached_glyph = [&ft_face](char c) -> CachedGlyph { + auto glyph_index = FT_Get_Char_Index(ft_face, static_cast(c)); + FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_DEFAULT); + FT_Render_Glyph(ft_face->glyph, FT_Render_Mode::FT_RENDER_MODE_NORMAL); + auto ft_bitmap = std::span { + ft_face->glyph->bitmap.buffer, + ft_face->glyph->bitmap.width * ft_face->glyph->bitmap.rows + }; + auto bitmap = std::vector(ft_bitmap.size()); + std::ranges::copy(ft_bitmap, bitmap.begin()); + + auto a = std::span { + ft_face->glyph, static_cast(ft_face->num_glyphs) + }; + + return CachedGlyph { + .c = c, + .bitmap = std::move(bitmap), + .dimensions = { + .bitmap_dimensions = { ft_face->glyph->bitmap.width, ft_face->glyph->bitmap.rows }, + .spanline = static_cast(ft_face->glyph->bitmap_top), + .left_padding = static_cast(ft_face->glyph->bitmap_left) } + }; + }; + std::array cached_glyphs; + std::transform(printable_chars.begin(), printable_chars.end(), cached_glyphs.begin(), create_cached_glyph); + + FT_Done_FreeType(free_type_library); + + // 4. + auto take_max_dimensions = [&](GlyphDimensions&& agg, const CachedGlyph& cg) { + return GlyphDimensions { + .bitmap_dimensions = { + std::max(agg.bitmap_dimensions.x, cg.dimensions.bitmap_dimensions.x), + std::max(agg.bitmap_dimensions.y, cg.dimensions.bitmap_dimensions.y), + }, + .spanline = std::max(agg.spanline, cg.dimensions.spanline), + .left_padding = std::max(agg.left_padding, cg.dimensions.left_padding), + }; + }; + auto atlas_info = std::reduce(cached_glyphs.begin(), cached_glyphs.end(), GlyphDimensions {}, take_max_dimensions); + auto take_max_height = [&](uint32_t&& agg, const CachedGlyph& cd) { + return std::max(cd.dimensions.bitmap_dimensions.y + atlas_info.spanline - cd.dimensions.spanline, agg); + }; + atlas_info.bitmap_dimensions.y = std::reduce(cached_glyphs.begin(), cached_glyphs.end(), atlas_info.bitmap_dimensions.y, take_max_height); + + // 5. + const auto [atlas_glyphs_per_row, atlas_num_rows] = [](float glyph_width, float glyph_height, float num_glyphs_in_atlas) { + auto [min_diff, min_x, min_y] = std::tuple { std::numeric_limits::max(), 0, num_glyphs_in_atlas }; + for (float i = 1; i < num_glyphs_in_atlas; ++i) { + auto x = i; + auto y = num_glyphs_in_atlas / i; + x = std::floor(x); + y = std::ceil(y); + auto diff = std::abs((x * glyph_width) - (y * glyph_height)); + if (diff < min_diff && num_glyphs_in_atlas <= x * y) { + min_diff = diff; + min_x = x; + min_y = y; + } + } + return std::tuple { min_x, min_y }; + }(atlas_info.bitmap_dimensions.x, atlas_info.bitmap_dimensions.y, printable_chars.size()); + + 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; + + auto atlas_buffer_size = static_cast(atlas_img_height * atlas_img_width); + auto atlas_buffer = std::make_unique(atlas_buffer_size); + auto atlas_buffer_begin = atlas_buffer.get(); + auto atlas_buffer_end = atlas_buffer_begin + atlas_img_height * atlas_img_width; + + std::fill(atlas_buffer_begin, atlas_buffer_end, (uint8_t)0); + + // 6. + for (std::ptrdiff_t i = 0; i < static_cast(printable_chars.size()); ++i) { + const auto& bitmap_ft = cached_glyphs[i]; + const auto atlas_coords = glm::ivec2 { i % atlas_glyphs_per_row, i / atlas_glyphs_per_row }; + const auto adjusted_offset = glm::ivec2 { atlas_coords.x * atlas_info.bitmap_dimensions.x, atlas_coords.y * atlas_info.bitmap_dimensions.y }; + + auto get_atlas_buffer_dest = [&](int x, int y) { + 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) + }; + std::ranges::copy(src, get_atlas_buffer_dest(0, relative_y)); + } + } + + // 7. + auto image_result = Media::Image::create( + std::move(atlas_buffer), + atlas_buffer_size, + glm::uvec2(atlas_img_width, atlas_img_height), + Media::Image::InternalFormat::greyscale); + + if (image_result.has_error()) { + return image_result.error(); + } + + return FontAtlas(M { + .atlas_image = std::move(image_result.value()), + .atlas_layout = { atlas_glyphs_per_row, atlas_num_rows }, + .glyph_dimensions = { + atlas_info.bitmap_dimensions.x, + atlas_info.bitmap_dimensions.y }, + }); + } + auto FontAtlas::uv_for(char a) const noexcept -> FontAtlas::UvCoord { + assert(printable_chars.front() <= a && a <= printable_chars.back() && "Must be a printable character"); + + const auto i = static_cast(a - printable_chars.front()); + const float r = static_cast(i / _m.atlas_layout.x); + const float c = static_cast(i % static_cast(_m.atlas_layout.x)); + + return { + .min_x = c / static_cast(_m.atlas_layout.x), + .max_x = (c + 1) / static_cast(_m.atlas_layout.x), + .min_y = 1 - (r + 1) / static_cast(_m.atlas_layout.y), + .max_y = 1 - r / static_cast(_m.atlas_layout.y), + }; + } +} diff --git a/code/Engine/src/Media/Image.cpp b/code/Engine/src/Media/Image.cpp index d1583de..5fccaa2 100644 --- a/code/Engine/src/Media/Image.cpp +++ b/code/Engine/src/Media/Image.cpp @@ -10,189 +10,178 @@ #include 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; + auto Image::create(std::filesystem::path path) -> Utily::Result { + Profiler::Timer timer("Media::Image::create()"); + // 1. Load the file contents into memory. + // 2. Decode the file contents via libspng. + // 3. Construct a valid Image instance. + + // 1. + if (path.extension() != ".png") { + return Utily::Error("Invalid extension for image, .png is the only supported file type."); } - - if (_data.size()) { - _data.clear(); + auto load_file_result = Utily::FileReader::load_entire_file(path); + if (load_file_result.has_error()) { + return load_file_result.error(); } + const auto& encoded_png = load_file_result.value(); - if (include_alpha_channel) { - if (is_gamma_corrected) { - _format = ColourFormat::s_rgba; - } else { - _format = ColourFormat::rgba; + // 2. + std::unique_ptr data; + size_t data_size_bytes = 0; + glm::uvec2 dimensions = { 0, 0 }; + { + Profiler::Timer timer2("libspng_decode_image()"); + spng_ctx* ctx = spng_ctx_new(0); + int spng_error = 0; + spng_error = spng_set_png_buffer(ctx, encoded_png.data(), encoded_png.size()); + if (spng_error) { + return Utily::Error(std::string(spng_strerror(spng_error))); } - } else { - if (is_gamma_corrected) { - _format = ColourFormat::s_rgb; - } else { - _format = ColourFormat::rgb; + spng_error = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &data_size_bytes); + if (spng_error) { + return Utily::Error(std::string(spng_strerror(spng_error))); } - } -#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; + data = std::make_unique_for_overwrite(data_size_bytes); + spng_error = spng_decode_image(ctx, data.get(), data_size_bytes, SPNG_FMT_RGBA8, 0); + if (spng_error) { + return Utily::Error(std::string(spng_strerror(spng_error))); } - }; - - 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)) - }; + spng_ihdr idhr; + spng_error = spng_get_ihdr(ctx, &idhr); + if (spng_error) { + return Utily::Error(std::string(spng_strerror(spng_error))); } + dimensions = { idhr.width, idhr.height }; + spng_ctx_free(ctx); } -#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); + // 3. + return Image(M { + .data = std::move(data), + .data_size_bytes = data_size_bytes, + .dimensions = dimensions, + .format = InternalFormat::rgba }); + } + auto Image::create(std::span raw_bytes, glm::uvec2 dimensions, InternalFormat format) -> Utily::Result { + // 1. Check given raw sizes. + // 2. Allocate resources. + // 3. Copy. + // 4. Construct valid Image instance. + + // 1. + size_t expected_size = dimensions.x * dimensions.y; + if (format == InternalFormat::rgba) { + expected_size *= 4; + } else if (format == InternalFormat::undefined) { + return Utily::Error("Undefined format param"); + } + if (expected_size != raw_bytes.size()) { + return Utily::Error("The raw data is not the expected size for those dimensions and format"); + } + // 2. + auto data = std::make_unique_for_overwrite(raw_bytes.size()); + auto data_size_bytes = raw_bytes.size(); + // 3. + std::uninitialized_copy(raw_bytes.begin(), raw_bytes.end(), data.get()); + // 4. + return Image(M { + .data = std::move(data), + .data_size_bytes = data_size_bytes, + .dimensions = dimensions, + .format = format }); + } - 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 {}; + auto Image::create(std::unique_ptr&& data, size_t data_size_bytes, glm::uvec2 dimensions, InternalFormat format) -> Utily::Result { + return Image(M { + .data = std::move(data), + .data_size_bytes = data_size_bytes, + .dimensions = dimensions, + .format = format }); + } + + auto Image::opengl_format() const -> uint32_t { + switch (_m.format) { + case InternalFormat::greyscale: + return GL_R8; + case InternalFormat::rgba: + return GL_RGBA8; + case InternalFormat::undefined: + [[fallthrough]]; + default: + throw std::runtime_error("Invalid internal format enum state."); + return std::numeric_limits::max(); + } } - 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; + 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(); } - _data = std::move(raw_data); - _format = format; - _width = width; - _height = height; } - auto Image::save_to_disk(std::filesystem::path path) noexcept - -> Utily::Result { + 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, _data, _width, _height, 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); } - return {}; - } - void Image::stop() noexcept { - _data.reserve(0); - _width = 0; - _height = 0; - } + 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); + } - void Image::add_fence(Core::Fence&& fence) noexcept { - _fence.emplace(std::forward(fence)); - } + size_t png_size = 0; + uint8_t* png_buf = reinterpret_cast(spng_get_png_buffer(ctx, &png_size, &spng_error)); - Image::~Image() { - if (_fence) { - _fence->wait_for_sync(); + if (!png_buf || spng_error) { + auto msg = spng_strerror(spng_error); + spng_ctx_free(ctx); + return Utily::Error(msg); } - } - 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; + 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(); } - _data = std::exchange(resized_image, std::vector {}); - _width = width; - _height = height; + spng_ctx_free(ctx); + free(png_buf); + + return {}; } } \ No newline at end of file diff --git a/code/Engine/src/Media/Sound.cpp b/code/Engine/src/Media/Sound.cpp index f4424df..a4b669c 100644 --- a/code/Engine/src/Media/Sound.cpp +++ b/code/Engine/src/Media/Sound.cpp @@ -3,57 +3,60 @@ #include "Profiler/Profiler.hpp" #include - #define DR_WAV_IMPLEMENTATION #include namespace Media { - auto Sound::init_from_wav(const std::vector& encoded_wav) -> Utily::Result { - Profiler::Timer timer("Media::Sound::init_from_wav()"); + auto Sound::create(std::filesystem::path wav_path) noexcept -> Utily::Result { + Profiler::Timer timer("Media::Sound::create()"); + // 1. Load the raw contents of the wav file. + // 2. Decode the contents use dr-wav. + // 3. Compress stereo audio into mono audio and copy into buffer. + // 4. Free resources + // 5. Create Media::Sound + + // 1. + auto load_file_result = Utily::FileReader::load_entire_file(wav_path); + if (load_file_result.has_error()) { + return load_file_result.error(); + } + const auto& encoded_wav = load_file_result.value(); + // 2. uint32_t num_channels = 0; uint32_t sample_rate = 0; uint64_t frame_count = 0; int16_t* dr_wav_ptr = drwav_open_memory_and_read_pcm_frames_s16(encoded_wav.data(), encoded_wav.size(), &num_channels, &sample_rate, &frame_count, nullptr); - - auto dr_wav_data_i16 = std::span { dr_wav_ptr, static_cast(num_channels * frame_count) }; - if (!dr_wav_ptr) { return Utily::Error("Media::Sound::init_from_wave() failed. Dr wav failed to parse the file."); } + auto dr_wav_data_i16 = std::span { dr_wav_ptr, static_cast(num_channels * frame_count) }; -#if 1 // Convert all sounds to mono format. Stereo is not used for spatial audio. + // 3. + std::vector data; if (num_channels == 2) { - _data.resize(dr_wav_data_i16.size() / 2); - auto iter = _data.begin(); + data.resize(dr_wav_data_i16.size() / 2); + auto iter = data.begin(); + // Combine to mono by taking average of the two channels. for (int i = 0; i < dr_wav_data_i16.size(); i += 2) { *iter = (dr_wav_data_i16[i] / 2) + (dr_wav_data_i16[i + 1] / 2); ++iter; } num_channels = 1; - - } else -#endif - { - _data.resize(dr_wav_data_i16.size()); - std::ranges::copy(dr_wav_data_i16, _data.begin()); + } else { + data.resize(dr_wav_data_i16.size()); + std::ranges::copy(dr_wav_data_i16, data.begin()); } + // 4. drwav_free(dr_wav_ptr, nullptr); - _frequency = sample_rate; - - if (num_channels == 1) { - _format = Format::mono_i16; - } else if (num_channels == 2) { - _format = Format::stereo_i16; - } else { - _data.clear(); - _frequency = 0; - return Utily::Error("Media::Sound::init_from_wave() failed. Too many channels in .wav data. Can only handle 2 channels."); - } - return {}; + // 5. + return Sound(M { + .data = std::move(data), + .frequency = sample_rate, + .format = Format::mono_i16, + }); } - } \ 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 diff --git a/code/Engine/src/Renderer/FontBatchRenderer.cpp b/code/Engine/src/Renderer/FontBatchRenderer.cpp index 7699fb5..b3e025c 100644 --- a/code/Engine/src/Renderer/FontBatchRenderer.cpp +++ b/code/Engine/src/Renderer/FontBatchRenderer.cpp @@ -2,14 +2,13 @@ namespace Renderer { - constexpr static std::string_view FBR_SHADER_VERT_SRC = "precision highp float;\n" "layout(location = 0) in vec2 l_pos;\n" "layout(location = 1) in vec2 l_uv;\n" - + "out vec2 uv;\n" - + "void main() {\n" " gl_Position = vec4(l_pos, 0, 1.0);\n" " uv = l_uv;\n" @@ -21,9 +20,9 @@ namespace Renderer { "uniform vec4 u_colour;\n" "in vec2 uv;\n" - + "out vec4 FragColor;\n" - + "void main() {\n" " vec2 uv_flipped = vec2(uv.x, 1.0f - uv.y);\n" " float r = texture(u_texture, uv_flipped).r;" @@ -35,21 +34,15 @@ namespace Renderer { "}"; void FontBatchRenderer::load_text_into_vb(const std::string_view& text, glm::vec2 bottom_left, float height_px) { - int v = static_cast(_current_batch_vertices.size()); - _current_batch_vertices.resize(_current_batch_vertices.size() + text.size() * 4); - - Media::FontAtlas font_atlas; - font_atlas.glyph_width = _glyph_dimensions.x; - font_atlas.glyph_height = _glyph_dimensions.y; - font_atlas.columns = _atlas_dimensions.x; - font_atlas.rows = _atlas_dimensions.y; + int v = static_cast(_m.current_batch_vertices.size()); + _m.current_batch_vertices.resize(_m.current_batch_vertices.size() + text.size() * 4); - const float glyph_ratio = _glyph_dimensions.x / _glyph_dimensions.y; + const float glyph_ratio = _m.font_atlas.glyph_dimensions().x / _m.font_atlas.glyph_dimensions().y; for (int t = 0; t < text.size(); ++t, v += 4) { - Vertex* vertices = _current_batch_vertices.data() + v; + Vertex* vertices = _m.current_batch_vertices.data() + v; - const auto uv = font_atlas.uv_coord_of_char(text[t]); + const auto uv = _m.font_atlas.uv_for(text[t]); // translate it by screen coords const float translated_min_x = glyph_ratio * t * height_px + bottom_left.x; @@ -58,10 +51,10 @@ namespace Renderer { const float translated_max_y = height_px + bottom_left.y; // scale it to screen coords [-1, 1] - const float actual_min_x = translated_min_x / _current_batch_config->screen_dimensions.x * 2 - 1; - const float actual_max_x = translated_max_x / _current_batch_config->screen_dimensions.x * 2 - 1; - const float actual_min_y = translated_min_y / _current_batch_config->screen_dimensions.y * 2 - 1; - const float actual_max_y = translated_max_y / _current_batch_config->screen_dimensions.y * 2 - 1; + const float actual_min_x = translated_min_x / _m.current_batch_config->screen_dimensions.x * 2 - 1; + const float actual_max_x = translated_max_x / _m.current_batch_config->screen_dimensions.x * 2 - 1; + const float actual_min_y = translated_min_y / _m.current_batch_config->screen_dimensions.y * 2 - 1; + const float actual_max_y = translated_max_y / _m.current_batch_config->screen_dimensions.y * 2 - 1; vertices[0] = { .position = { actual_min_x, actual_min_y }, @@ -81,60 +74,78 @@ namespace Renderer { }; } } - void FontBatchRenderer::init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas) { + + auto FontBatchRenderer::create(ResourceManager& resource_manager, std::filesystem::path ttf_path) noexcept -> Utily::Result { + + auto font_atlas_result = Media::FontAtlas::create(ttf_path, 300); + if (font_atlas_result.has_error()) { + return font_atlas_result.error(); + } + auto& font_atlas = font_atlas_result.value(); + auto [s_handle, shader] = resource_manager.create_and_init_resource(FBR_SHADER_VERT_SRC, FBR_SHADER_FRAG_SRC); auto [t_handle, texture] = resource_manager.create_and_init_resource(); auto [vb_handle, vertex_buffer] = resource_manager.create_and_init_resource(); auto [ib_handle, index_buffer] = resource_manager.create_and_init_resource(); auto [va_handle, vertex_array] = resource_manager.create_and_init_resource(Vertex::VBL {}, vertex_buffer, index_buffer); - _s = s_handle; - _t = t_handle; - _vb = vb_handle; - _ib = ib_handle; - _va = va_handle; - - texture.upload_image(font_atlas.image).on_error(Panic {}); + auto texture_upload_result = texture.upload_image(font_atlas.atlas_image()); + if (texture_upload_result.has_error()) { + return texture_upload_result.error(); + } - _glyph_dimensions = { font_atlas.glyph_width, font_atlas.glyph_height }; - _atlas_dimensions = { font_atlas.columns, font_atlas.rows }; + return FontBatchRenderer(M { + .current_batch_vertices = {}, + .current_batch_config = std::nullopt, + .font_atlas = std::move(font_atlas), + .s = s_handle, + .t = t_handle, + .vb = vb_handle, + .ib = ib_handle, + .va = va_handle, + }); } + void FontBatchRenderer::begin_batch(BatchConfig&& batch_config) { - assert(!_current_batch_config); - _current_batch_config.emplace(std::move(batch_config)); + assert(!_m.current_batch_config); + _m.current_batch_config.emplace(std::move(batch_config)); } void FontBatchRenderer::push_to_batch(std::string_view text, glm::vec2 bottom_left, float height_px) { Profiler::Timer timer("FontBatchRenderer::push_to_batch()", {}); - - assert(_current_batch_config); + assert(_m.current_batch_config); load_text_into_vb(text, bottom_left, height_px); } void FontBatchRenderer::end_batch() { + // 1. Validate a batch config has been passed in. + // 2. Get resources, bind, set uniforms, load vertices. + // 3. Ensure the index buffer has enough loaded for the vertex buffer. + // 4. Disable depth testing, draw, and then re-enable depth testing. + // 5. Clear batch's config and vertices. + + // 1. Profiler::Timer timer("FontBatchRenderer::end_batch()", {}); - assert(_current_batch_config); - if (_current_batch_vertices.size() == 0) { + assert(_m.current_batch_config); + if (_m.current_batch_vertices.size() == 0) { assert(false && "redundant batching"); return; } - // bind everything - auto [s, t, va, vb, ib] = _current_batch_config->resource_manager.get_resources(_s, _t, _va, _vb, _ib); + // 2. + auto [s, t, va, vb, ib] = _m.current_batch_config->resource_manager.get_resources(_m.s, _m.t, _m.va, _m.vb, _m.ib); const int32_t texture_slot = t.bind().value(); s.bind(); va.bind(); ib.bind(); vb.bind(); - s.set_uniform("u_texture", texture_slot).on_error(Panic{}); - s.set_uniform("u_colour", _current_batch_config->font_colour).on_error(Panic{}); - - // load vertices - vb.load_vertices(_current_batch_vertices); + s.set_uniform("u_texture", texture_slot).on_error(Panic {}); + s.set_uniform("u_colour", _m.current_batch_config->font_colour).on_error(Panic {}); + vb.load_vertices(_m.current_batch_vertices); - // ensure ib has enough capacity/indices for vb - if (ib.get_count() < _current_batch_vertices.size() / 4 * 6) { + // 3. + if (ib.get_count() < _m.current_batch_vertices.size() / 4 * 6) { std::vector indices; - indices.resize(_current_batch_vertices.size() / (size_t)4 * (size_t)6, 0); + indices.resize(_m.current_batch_vertices.size() / (size_t)4 * (size_t)6, 0); for (int v = 0, i = 0; i < indices.size(); i += 6, v += 4) { indices[i + 0] = v + 0; @@ -148,15 +159,16 @@ namespace Renderer { ib.load_indices(indices); } - // gldraw indices = vb.vertices / 4 * 6 + // 4. { Profiler::Timer draw_timer("glDrawElements()", {}); glDisable(GL_DEPTH_TEST); - glDrawElements(GL_TRIANGLES, _current_batch_vertices.size() / 4 * 6, GL_UNSIGNED_INT, (void*)0); + glDrawElements(GL_TRIANGLES, _m.current_batch_vertices.size() / 4 * 6, GL_UNSIGNED_INT, (void*)0); glEnable(GL_DEPTH_TEST); } - _current_batch_config = std::nullopt; - _current_batch_vertices.resize(0); + // 5. + _m.current_batch_config = std::nullopt; + _m.current_batch_vertices.resize(0); } } \ No newline at end of file diff --git a/code/Engine/src/Renderer/FontRenderer.cpp b/code/Engine/src/Renderer/FontRenderer.cpp deleted file mode 100644 index fed0bbe..0000000 --- a/code/Engine/src/Renderer/FontRenderer.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#pragma once -#include "Renderer/FontRenderer.hpp" - -#include "Cameras/Orthographic.hpp" -#include "Core/Core.hpp" -#include "Media/Media.hpp" - -#include -#include - -namespace Renderer { - constexpr static std::string_view SHADER_VERT_SRC = - "precision highp float;\n" - - "layout(location = 0) in vec2 l_pos;\n" - "layout(location = 1) in vec2 l_uv;\n" - - "uniform mat4 u_mvp;\n" - - "out vec2 uv;\n" - - "void main() {\n" - " gl_Position = u_mvp * vec4(l_pos, 0, 1.0);\n" - " uv = l_uv;\n" - "}"; - constexpr static std::string_view SHADER_FRAG_SRC = - "precision highp float;\n" - - "uniform sampler2D u_texture;\n" - "uniform vec4 u_colour;\n" - - "in vec2 uv;\n" - "out vec4 FragColor;\n" - - "void main() {\n" - " vec2 uv_flipped = vec2(uv.x, 1.0f - uv.y);\n" - " float r = texture(u_texture, uv_flipped).r;" - " if(r > 0.1f) {\n" - " FragColor = vec4(u_colour.rgb, r * u_colour.a);\n" - " } else {\n" - " FragColor = vec4(0,0,0,0);" - " }\n" - "}"; - - void FontRenderer::ensure_buffers_have_capacity_for(const size_t N, Core::IndexBuffer& ib) { - Profiler::Timer timer("FontRenderer::ensure_buffers_have_capacity_for()", {}); - - if (_vertices.size() >= (N * 4) || _indices.size() >= (N * 6)) { - return; - } - - _vertices.resize(N * 4); - _indices.resize(N * 6); - - const float glyph_ratio = _glyph_dimensions.x / _glyph_dimensions.y; - constexpr float min_y = 0; - constexpr float max_y = 1; - - for (int i = 0, v = 0; i < _vertices.size(); ++v, i += 4) { - auto vertices = std::span { _vertices.data() + i, 4 }; - - const float min_x = glyph_ratio * v; - const float max_x = min_x + glyph_ratio; - - vertices[0].position = { min_x, min_y }; - vertices[1].position = { max_x, min_y }; - vertices[2].position = { max_x, max_y }; - vertices[3].position = { min_x, max_y }; - } - - for (int i = 0, v = 0; i < _indices.size(); v += 4, i += 6) { - auto indices = _indices.data() + i; - indices[0] = v + 0; - indices[1] = v + 1; - indices[2] = v + 2; - indices[3] = v + 2; - indices[4] = v + 3; - indices[5] = v + 0; - } - ib.load_indices(_indices); - } - - auto FontRenderer::uv_coord_of_char(char a) const noexcept -> Media::FontAtlas::UvCoord { - constexpr auto drawble_chars = Media::FontAtlasConstants::DRAWABLE_CHARS; - - assert(drawble_chars.front() <= a && a <= drawble_chars.back() && "Must be a printable character"); - - const auto i = static_cast(a - drawble_chars.front()); - - const float r = static_cast(i / _atlas_dimensions.x); - const float c = static_cast(i % static_cast(_atlas_dimensions.x)); - - return { - .min_x = c / static_cast(_atlas_dimensions.x), - .max_x = (c + 1) / static_cast(_atlas_dimensions.x), - .min_y = 1 - (r + 1) / static_cast(_atlas_dimensions.y), - .max_y = 1 - r / static_cast(_atlas_dimensions.y), - }; - } - - void FontRenderer::load_text_into_vb(const std::string_view& text, Core::VertexBuffer& vb) { - Profiler::Timer timer("FontRenderer::load_text_into_vb()", {}); - const auto text_hash = std::hash {}(text); - const auto text_size = text.size(); - - if (text_hash == _loaded_text_hash) { - return; - } - - Media::FontAtlas fa {}; - - fa.glyph_width = _glyph_dimensions.x; - fa.glyph_height = _glyph_dimensions.y; - fa.columns = _atlas_dimensions.x; - fa.rows = _atlas_dimensions.y; - - for (int i = 0, v = 0; i < _vertices.size() && v < text.size(); ++v, i += 4) { - auto vertices = std::span { _vertices.data() + i, 4 }; - const auto uv = fa.uv_coord_of_char(text[v]); - - vertices[0].uv_coord = { uv.min_x, uv.min_y }; - vertices[1].uv_coord = { uv.max_x, uv.min_y }; - vertices[2].uv_coord = { uv.max_x, uv.max_y }; - vertices[3].uv_coord = { uv.min_x, uv.max_y }; - } - vb.load_vertices(std::span { _vertices.begin(), _vertices.begin() + (text.size() * 4) }); - } - - bool FontRenderer::is_init() const noexcept { - return _glyph_dimensions.x > 0 && _glyph_dimensions.y > 0 - && _atlas_dimensions.x > 0 && _atlas_dimensions.y > 0; - } - - void FontRenderer::init(ResourceManager& resource_manager, Media::FontAtlas& font_atlas) { - - auto [s_handle, shader] = resource_manager.create_and_init_resource(SHADER_VERT_SRC, SHADER_FRAG_SRC); - auto [t_handle, texture] = resource_manager.create_and_init_resource(); - auto [vb_handle, vertex_buffer] = resource_manager.create_and_init_resource(); - auto [ib_handle, index_buffer] = resource_manager.create_and_init_resource(); - auto [va_handle, vertex_array] = resource_manager.create_and_init_resource(FontVertex::VBL {}, vertex_buffer, index_buffer); - - _s = s_handle; - _t = t_handle; - _vb = vb_handle; - _ib = ib_handle; - _va = va_handle; - - texture.upload_image(font_atlas.image).on_error(Panic {}); - - _glyph_dimensions = { font_atlas.glyph_width, font_atlas.glyph_height }; - _atlas_dimensions = { font_atlas.columns, font_atlas.rows }; - } - - - - void FontRenderer::stop(ResourceManager& resource_manager) { - resource_manager.free_resources(_s, _t, _va, _ib, _vb); - } - - void FontRenderer::draw(ResourceManager& resource_manager, glm::vec2 screen_dimensions, std::string_view text, float char_size_px, glm::vec2 bottom_left, glm::vec4 colour) { - Profiler::Timer timer("FontRenderer::draw()", {}); - assert(is_init()); - - auto [s, t, va, vb, ib] = resource_manager.get_resources(_s, _t, _va, _vb, _ib); - - ensure_buffers_have_capacity_for(text.size(), ib); - load_text_into_vb(text, vb); - - const int32_t texture_slot = t.bind().on_error(Panic {}).value(); - s.bind(); - va.bind(); - ib.bind(); - vb.bind(); - - auto mat = glm::mat4(1.0f); - mat = glm::translate(mat, glm::vec3(bottom_left.x / screen_dimensions.x * 2 - 1, bottom_left.y / screen_dimensions.y * 2 - 1, 0)); - mat = glm::scale(mat, glm::vec3(char_size_px / screen_dimensions.x * 2, char_size_px / screen_dimensions.y * 2, 0.0f)); - - s.set_uniform("u_texture", texture_slot).on_error(Panic {}); - s.set_uniform("u_mvp", mat).on_error(Panic {}); - s.set_uniform("u_colour", colour).on_error(Panic {}); - - assert(text.size() * 6 <= ib.get_count()); - - { - Profiler::Timer draw_timer("glDrawElements()", {}); - glDisable(GL_DEPTH_TEST); - glDrawElements(GL_TRIANGLES, text.size() * 6, GL_UNSIGNED_INT, (void*)0); - glEnable(GL_DEPTH_TEST); - } - } -} \ No newline at end of file diff --git a/code/Engine/src/Renderer/InstanceRenderer.cpp b/code/Engine/src/Renderer/InstanceRenderer.cpp index 8d4ab79..972716e 100644 --- a/code/Engine/src/Renderer/InstanceRenderer.cpp +++ b/code/Engine/src/Renderer/InstanceRenderer.cpp @@ -1,32 +1,6 @@ #include "Renderer/InstanceRenderer.hpp" namespace Renderer { - - // constexpr static std::string_view FBR_SHADER_VERT_SRC = - // "precision highp float;\n" - // "layout(location = 0) in vec2 l_pos;\n" - // "layout(location = 1) in vec2 l_uv;\n" - // "out vec2 uv;\n" - // "void main() {\n" - // " gl_Position = vec4(l_pos, 0, 1.0);\n" - // " uv = l_uv;\n" - // "}"; - // constexpr static std::string_view FBR_SHADER_FRAG_SRC = - // "precision highp float;\n" - // "uniform sampler2D u_texture;\n" - // "uniform vec4 u_colour;\n" - // "in vec2 uv;\n" - // "out vec4 FragColor;\n" - // "void main() {\n" - // " vec2 uv_flipped = vec2(uv.x, 1.0f - uv.y);\n" - // " float r = texture(u_texture, uv_flipped).r;" - // " if(r > 0.1f) {\n" - // " FragColor = vec4(u_colour.rgb, r * u_colour.a);\n" - // " } else {\n" - // " FragColor = vec4(0,0,0,0);" - // " }\n" - // "}"; - constexpr static std::string_view INSTANCE_SHADER_VERT_SRC = "precision highp float;\n" "layout(location = 0) in vec3 l_pos;\n" @@ -44,7 +18,7 @@ namespace Renderer { "void main() {\n" " mat4 m = mat4(l_inst_col_0, l_inst_col_1, l_inst_col_2, l_inst_col_3);" - " mat4 mvp = u_view * u_proj * m;\n" + " mat4 mvp = u_proj * u_view * m;\n" " gl_Position = mvp * vec4(l_pos, 1);\n" " uv = l_uv;\n" "}"; @@ -58,8 +32,7 @@ namespace Renderer { " FragColor = texture(u_texture, uv);\n" "}"; - void - InstanceRenderer::init(ResourceManager & resource_manager, const Model::Static& model, Media::Image& image) { + void InstanceRenderer::init(ResourceManager& resource_manager, const Model::Static& model, Media::Image& image) { auto [ib_handle, ib] = resource_manager.create_and_init_resource(); auto [vb_mesh_handle, vb_mesh] = resource_manager.create_and_init_resource(); auto [vb_tran_handle, vb_tran] = resource_manager.create_and_init_resource(); @@ -74,13 +47,41 @@ namespace Renderer { _vb_transforms = vb_tran_handle; _ib = ib_handle; _va = va_handle; + + vb_mesh.bind(); + vb_mesh.load_vertices(model.vertices); + + ib.bind(); + ib.load_indices(model.indices); + + va.unbind(); } void InstanceRenderer::stop(ResourceManager& resource_manager) { } void InstanceRenderer::push_instance(const glm::mat4& instance_transformation) { _current_instances.emplace_back(instance_transformation); } - void InstanceRenderer::draw_instances(ResourceManager& resource_manager, glm::vec2 screen_dimensions) { + void InstanceRenderer::draw_instances(ResourceManager& resource_manager, const glm::mat4& projection, const glm::mat4& view) { + auto [s, t, ib, vbm, vbt, va] = resource_manager.get_resources(_s, _t, _ib, _vb_mesh, _vb_transforms, _va); + + auto transfrom_verts = std::span { + reinterpret_cast(_current_instances.data()), + _current_instances.size() * 16 + }; + va.bind(); + ib.bind(); + vbt.bind(); + vbt.load_vertices(transfrom_verts); + vbm.bind(); + s.bind(); + s.set_uniform("u_view", view).on_error(Panic{}); + s.set_uniform("u_proj", projection).on_error(Panic{}); + + int32_t t_id = static_cast(t.bind().on_error(Panic {}).value()); + s.set_uniform("u_texture", t_id); + glDrawElementsInstanced(GL_TRIANGLES, ib.get_count(), GL_UNSIGNED_INT, 0, _current_instances.size()); + va.unbind(); + _current_instances.clear(); } } \ No newline at end of file diff --git a/code/Test/include/Integration/BasicApps.hpp b/code/Test/include/Integration/BasicApps.hpp index a8dff3f..975fc97 100644 --- a/code/Test/include/Integration/BasicApps.hpp +++ b/code/Test/include/Integration/BasicApps.hpp @@ -158,10 +158,10 @@ struct SpinningSquareLogic { struct SpinningTeapotData { std::chrono::steady_clock::time_point start_time; entt::entity teapot; - Cameras::StationaryPerspective camera { glm::vec3(0, 1, -1), glm::normalize(glm::vec3(0, -0.25f, 0.5f)) }; - entt::registry ecs; + Cameras::StationaryPerspective camera { glm::vec3(0, 1, -1), glm::normalize(glm::vec3(0, -0.25f, 0.5f)) }; + Renderer::ResourceManager resource_manager; Renderer::ResourceHandle s_h; Renderer::ResourceHandle vb_h; @@ -207,7 +207,7 @@ struct SpinningTeapotLogic { .on_error(print_pause_quit) .value(); - auto teapot_model = std::move(Model::decode_as_static_model(teapot_source, { '.', 'o', 'b', 'j' }) + auto teapot_model = std::move(Model::decode_as_static_model(teapot_source, ".obj") .on_error(print_pause_quit) .value()); @@ -230,8 +230,8 @@ struct SpinningTeapotLogic { } void update(double dt, const Core::InputManager& input, Core::AudioManager& audio, AppState& state, SpinningTeapotData& data) { data.ecs.get(data.teapot).rotation = data.ecs.get(data.teapot) - .update(dt) - .calc_quat(); + .update(dt) + .calc_quat(); auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - data.start_time); if (duration > std::chrono::seconds(1)) { @@ -341,7 +341,7 @@ TEST(BasicApps, spinning_square) { } #endif TEST(BasicApps, spinning_teapot) { - auto_run_app("Test App: Spinning Teapot"); + auto_run_app("Test App: Spinning Teapot", 1000, 1000); } #if 0 @@ -350,7 +350,6 @@ TEST(BasicApps, font_rendering) { } #endif - #elif defined(CONFIG_TARGET_WEB) void run_font_renderer() { diff --git a/code/Test/include/Unit/UnitModelStatic.hpp b/code/Test/include/Unit/UnitModelStatic.hpp index 28a7158..7184f51 100644 --- a/code/Test/include/Unit/UnitModelStatic.hpp +++ b/code/Test/include/Unit/UnitModelStatic.hpp @@ -20,8 +20,7 @@ TEST(Unit, Model_static) { std::span { maybe_data.value().begin(), maybe_data.value().end() }, - { '.', 'o', 'b', 'j' }); + ".obj"); EXPECT_FALSE(maybe_cube.has_error()); - } diff --git a/code/build-web.bat b/code/build-web.bat index 33833bd..63d8ed9 100644 --- a/code/build-web.bat +++ b/code/build-web.bat @@ -1,7 +1,7 @@ set VCPKG_PATH=C:/apps/vcpkg/vcpkg/ set EMSDK=C:/apps/emscripten/emsdk/ set EMSCRIPTEN=C:/apps/emscripten/emsdk/upstream/emscripten/ -set BUILD_TYPE=Debug +set BUILD_TYPE=Release if not exist "build-web\" ( mkdir build-web @@ -11,6 +11,6 @@ cd build-web call emcmake cmake .. -DCMAKE_TOOLCHAIN_FILE=%VCPKG_PATH%/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=wasm32-emscripten -DEMSCRIPTEN=1 -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=%EMSCRIPTEN%cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=%BUILD_TYPE% call cmake --build . --target Engine --config %BUILD_TYPE% call cmake --build . --target Demos --config %BUILD_TYPE% -@REM call cmake --build . --target Test --config %BUILD_TYPE% +call cmake --build . --target Test --config %BUILD_TYPE% cd ..