diff --git a/runtime/Includes/Core/Enums.h b/runtime/Includes/Core/Enums.h index ef15390..4934b1a 100644 --- a/runtime/Includes/Core/Enums.h +++ b/runtime/Includes/Core/Enums.h @@ -19,9 +19,10 @@ namespace mlx enum class Event { - ResizeEventCode = 56, - FrameBeginEventCode = 57, - FatalErrorEventCode = 168, + ResizeEventCode = 1, + SwapchainResizeEventCode, + FrameBeginEventCode, + FatalErrorEventCode, EndEnum }; diff --git a/runtime/Includes/Core/Fps.h b/runtime/Includes/Core/Fps.h index 6cebc77..05364f4 100644 --- a/runtime/Includes/Core/Fps.h +++ b/runtime/Includes/Core/Fps.h @@ -1,26 +1,30 @@ #ifndef __MLX_FPS__ #define __MLX_FPS__ +#include + namespace mlx { + typedef std::chrono::steady_clock fps_clock; + class FpsManager { public: FpsManager() = default; void Init(); - bool Update(); - inline void SetMaxFPS(std::uint32_t fps) noexcept { m_max_fps = fps; m_ns = 1000000000.0 / fps; } + void WaitUntilNextFrame(); + inline void SetMaxFPS(std::uint32_t fps) noexcept { m_target_delta = fps_clock::duration(std::chrono::seconds(1)) / fps;} ~FpsManager() = default; private: - double m_ns = 1000000000.0 / 1'337'000.0; - std::int64_t m_fps_before = 0; - std::int64_t m_fps_now = 0; - std::int64_t m_timer = 0; - std::uint32_t m_max_fps = 1'337'000; - std::uint32_t m_fps_elapsed_time = 0; + fps_clock::time_point m_current_time; + fps_clock::time_point m_target_time; + fps_clock::time_point m_last_time_record; + fps_clock::duration m_delta_time = fps_clock::duration().zero(); + fps_clock::duration m_target_delta = fps_clock::duration().zero(); + fps_clock::duration m_sleep_margin = std::chrono::microseconds(200); }; } diff --git a/runtime/Sources/Core/Application.cpp b/runtime/Sources/Core/Application.cpp index ef253b3..009c800 100644 --- a/runtime/Sources/Core/Application.cpp +++ b/runtime/Sources/Core/Application.cpp @@ -8,7 +8,7 @@ namespace mlx { - Application::Application() : p_mem_manager(std::make_unique()), p_sdl_manager(std::make_unique()), m_fps(), m_in() + Application::Application() : p_mem_manager(std::make_unique()), p_sdl_manager(std::make_unique()), m_fps(), m_in() { MLX_PROFILE_FUNCTION(); std::srand(std::time(nullptr)); @@ -33,8 +33,7 @@ namespace mlx while(m_in.IsRunning()) { - if(!m_fps.Update()) - continue; + m_fps.WaitUntilNextFrame(); m_in.FetchInputs(); diff --git a/runtime/Sources/Core/Bridge.cpp b/runtime/Sources/Core/Bridge.cpp index 76ee9f6..4123d6a 100644 --- a/runtime/Sources/Core/Bridge.cpp +++ b/runtime/Sources/Core/Bridge.cpp @@ -45,10 +45,9 @@ extern "C" void mlx_set_fps_goal(mlx_context mlx, int fps) { MLX_CHECK_APPLICATION_POINTER(mlx); - if(fps < 0) - mlx::Error("You cannot set a negative FPS cap (nice try)"); - else - mlx->app->SetFPSCap(static_cast(fps)); + if(fps <= 0) + fps = -1; + mlx->app->SetFPSCap(static_cast(fps)); } void mlx_destroy_context(mlx_context mlx) @@ -313,14 +312,14 @@ extern "C" mlx::Error("Font loader: filepath is NULL"); return; } - + std::filesystem::path file(filepath); if (std::strcmp(filepath, "default") != 0 && !std::filesystem::exists(file)) { mlx::Error("TTF loader: unable to find file '%'", filepath); return; } - + if(std::strcmp(filepath, "default") != 0) { if(file.extension() != ".ttf" && file.extension() != ".tte") @@ -350,7 +349,7 @@ extern "C" mlx::Error("Font loader: filepath is NULL"); return; } - + std::filesystem::path file(filepath); if (std::strcmp(filepath, "default") != 0 && !std::filesystem::exists(file)) { diff --git a/runtime/Sources/Core/Fps.cpp b/runtime/Sources/Core/Fps.cpp index a464f67..d97879c 100644 --- a/runtime/Sources/Core/Fps.cpp +++ b/runtime/Sources/Core/Fps.cpp @@ -1,30 +1,37 @@ #include #include +#ifndef __APPLE__ +#include +#endif + namespace mlx { void FpsManager::Init() { - m_timer = static_cast(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count()); - m_fps_before = m_timer; - m_fps_now = m_timer; + m_current_time = fps_clock::now(); + m_target_time = m_current_time + m_target_delta; } - bool FpsManager::Update() + void FpsManager::WaitUntilNextFrame() { - using namespace std::chrono_literals; - m_fps_now = static_cast(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count()); - - if(std::chrono::duration{m_fps_now - m_timer} >= 1s) - m_timer += m_fps_now; - - m_fps_elapsed_time = m_fps_now - m_fps_before; - if(m_fps_elapsed_time >= m_ns) + m_current_time = fps_clock::now(); + if(m_current_time < m_target_time) { - m_fps_before += m_ns; - return true; + std::this_thread::sleep_until(m_target_time - m_sleep_margin); + m_current_time = fps_clock::now(); + while (m_current_time < m_target_time) + { + #ifndef __APPLE__ + _mm_pause(); // reduces CPU usage on x86 without yielding + #endif + m_current_time = fps_clock::now(); + } } - std::this_thread::sleep_for(std::chrono::duration(m_ns - 1)); - return false; + else if (m_target_time < m_current_time - m_target_delta * 4) + m_target_time = m_current_time; + m_target_time += m_target_delta; + m_delta_time = m_current_time - m_last_time_record; + m_last_time_record = m_current_time; } } diff --git a/runtime/Sources/Platform/Inputs.cpp b/runtime/Sources/Platform/Inputs.cpp index bf86a1c..f41c248 100644 --- a/runtime/Sources/Platform/Inputs.cpp +++ b/runtime/Sources/Platform/Inputs.cpp @@ -8,9 +8,9 @@ namespace mlx { namespace Internal { - struct ResizeEventBroadcast : public EventBase + struct SwapchainResizeEventBroadcast : public EventBase { - Event What() const override { return Event::ResizeEventCode; } + Event What() const override { return Event::SwapchainResizeEventCode; } }; } @@ -23,7 +23,7 @@ namespace mlx if(!m_events_hooks.contains(window_id) || m_events_hooks[window_id][event].empty()) return; if(event == MLX_WINDOW_EVENT && code == 8) - EventBus::SendBroadcast(Internal::ResizeEventBroadcast{}); + EventBus::SendBroadcast(Internal::SwapchainResizeEventBroadcast{}); for(const auto& hook : m_events_hooks[window_id][event]) { if(hook.fn) diff --git a/runtime/Sources/Renderer/Image.cpp b/runtime/Sources/Renderer/Image.cpp index 30c8ff8..f4766c8 100644 --- a/runtime/Sources/Renderer/Image.cpp +++ b/runtime/Sources/Renderer/Image.cpp @@ -226,25 +226,34 @@ namespace mlx void Texture::SetRegion(int x, int y, int w, int h, mlx_color* pixels) noexcept { MLX_PROFILE_FUNCTION(); - if(x < 0 || y < 0 || static_cast(x) >= m_width || static_cast(y) >= m_height) - return; - if(w < 0 || h < 0) + if(w < 0 || h < 0 || x < -w || y < -h + || x >= static_cast(m_width) || y >= static_cast(m_height)) return; if(!m_staging_buffer.has_value()) OpenCPUBuffer(); - for(std::uint32_t i = 0, moving_x = x, moving_y = y;; i++, moving_x++) + const int + start_x = std::max(x, 0), + start_y = std::max(y, 0), + start_row = start_y * m_width, + start_i = (start_y - y) * w + (start_x - x), + end_x = std::min(x + w, m_width), + end_y = std::min(y + h, m_height), + end_row = end_y * m_width, + incr = (x + w) - end_x + (start_x - x); + for(int i = start_i, dx = start_x, row = start_row;; i++, dx++) { - if(moving_x >= static_cast(x + w) || moving_x >= m_width) + if(dx >= end_x) { - moving_x = x; - moving_y++; - if(moving_y >= static_cast(y + h) || moving_y >= m_height) + i += incr; + dx = start_x; + row += m_width; + if(row >= end_row) break; } if constexpr(std::endian::native == std::endian::little) - m_staging_buffer->GetMap()[(moving_y * m_width) + moving_x] = ReverseColor(pixels[i]); + m_staging_buffer->GetMap()[row + dx] = ReverseColor(pixels[i]); else - m_staging_buffer->GetMap()[(moving_y * m_width) + moving_x] = pixels[i]; + m_staging_buffer->GetMap()[row + dx] = pixels[i]; } m_has_been_modified = true; } @@ -252,23 +261,25 @@ namespace mlx void Texture::SetLinearRegion(int x, int y, std::size_t len, mlx_color* pixels) noexcept { MLX_PROFILE_FUNCTION(); - if(x < 0 || y < 0 || static_cast(x) >= m_width || static_cast(y) >= m_height) + if(x >= static_cast(m_width) || y >= static_cast(m_height)) return; if(!m_staging_buffer.has_value()) OpenCPUBuffer(); + int + start = y * m_width + x, + dest_start = std::max(start, 0), + src_start = dest_start - start, + dest_end = std::min(start + len, m_width * m_height); if constexpr(std::endian::native == std::endian::little) { - for(std::size_t i = 0; i < len && (y * m_width) + x + i < m_width * m_height; i++) - m_staging_buffer->GetMap()[(y * m_width) + x + i] = ReverseColor(pixels[i]); + for(int i = dest_start, j = src_start; i < dest_end; i++, j++) + m_staging_buffer->GetMap()[i] = ReverseColor(pixels[j]); } else { - std::size_t len_guard; - if((y * m_width + x + len) < m_width * m_height) - len_guard = len; - else - len_guard = len - (m_width * m_height - (y * m_width + x + len)); - std::memcpy(&m_staging_buffer->GetMap()[(y * m_width) + x], pixels, len_guard); + std::memcpy( + &m_staging_buffer->GetMap()[dest_start], + &pixels[src_start], dest_end - dest_start); } m_has_been_modified = true; } @@ -289,23 +300,23 @@ namespace mlx void Texture::GetRegion(int x, int y, int w, int h, mlx_color* dst) noexcept { MLX_PROFILE_FUNCTION(); - if(x < 0 || y < 0 || static_cast(x) >= m_width || static_cast(y) >= m_height) + if(w < 0 || h < 0 || x < 0 || y < 0 || static_cast(x + w) >= m_width || static_cast(y + h) >= m_height) return; if(!m_staging_buffer.has_value()) OpenCPUBuffer(); - for(std::uint32_t i = 0, moving_x = x, moving_y = y;; i++, moving_x++) + for(std::uint32_t i = 0, dx = x, row = y * m_width;; i++, dx++) { - if(moving_x >= static_cast(x + w) || moving_x >= m_width) + if(dx >= m_width) { - moving_x = x; - moving_y++; - if(moving_y >= static_cast(y + h) || moving_y >= m_height) + dx = x; + row += m_width; + if(row >= m_height * m_width) break; } if constexpr(std::endian::native == std::endian::little) - dst[i] = ReverseColor(m_staging_buffer->GetMap()[(moving_y * m_width) + moving_x]); + dst[i] = ReverseColor(m_staging_buffer->GetMap()[row + dx]); else - dst[i] = m_staging_buffer->GetMap()[(moving_y * m_width) + moving_x]; + dst[i] = m_staging_buffer->GetMap()[row + dx]; } } @@ -320,16 +331,8 @@ namespace mlx processed_color.g = static_cast(color.g * 255.f); processed_color.b = static_cast(color.b * 255.f); processed_color.a = static_cast(color.a * 255.f); - if(processed_color.r == 0 && processed_color.g == 0 && processed_color.b == 0) - std::memset(m_staging_buffer->GetMap(), processed_color.a, m_staging_buffer->GetSize()); - else - { - for(std::size_t y = 0; y < m_height; y++) - { - for(std::size_t x = 0; x < m_width; x++) - m_staging_buffer->GetMap()[y * m_width + x] = processed_color; - } - } + for(std::size_t i = 0; i < m_width * m_height; i++) + m_staging_buffer->GetMap()[i] = processed_color; } } diff --git a/runtime/Sources/Renderer/Swapchain.cpp b/runtime/Sources/Renderer/Swapchain.cpp index 9c08607..6b2cfe3 100644 --- a/runtime/Sources/Renderer/Swapchain.cpp +++ b/runtime/Sources/Renderer/Swapchain.cpp @@ -15,7 +15,7 @@ namespace mlx Event What() const override { return Event::ResizeEventCode; } }; } - + std::string VulkanFormatName(VkFormat format) { #define STRINGIFY(x) case x: return #x @@ -161,7 +161,7 @@ namespace mlx std::function functor = [this](const EventBase& event) { - if(event.What() == Event::ResizeEventCode && !m_resize) + if(event.What() == Event::SwapchainResizeEventCode && !m_resize) m_resize = true; }; EventBus::RegisterListener({ functor, "mlx_swapchain_" + std::to_string(reinterpret_cast(this)) });