Author SHA1 Message Date
Daemo 42f0d52bfa changed mlx_put_transformed_image_to_window to use float position 2026-08-12 19:25:09 +02:00
DaemoandGitHub 63fe3088bf Fix inconsistencies with the FPS scheduler (#241)
The current Fps manager will sometime skip the intended delay and start
the next frame immediately, I'm not 100% sure why it happens, but I
rewrote most of it to stay consistent and improved the overall precision
by compensating for OS wake delay after sleep and finishing with a busy
wait
I also changed the behavior of mlx_set_fps_goal to allow zero/negative
values and treat them as uncapped (like most graphics libraries)
2026-08-08 15:45:56 +02:00
DaemoandGitHub c9df651a14 Optimized region drawing and fixed incorrect region clipping (#240)
Made small optimizations to Texture's SetRegion, SetLinearRegion,
GetRegion and Clear. They might not be super effective depending on how
the compiler already optimized them. The optimizations mainly focus on
removing the multiplication when transforming a position to an index by
using the row index rather than the y coordinate during iteration.
Also fixed regions not drawing properly when clipping against the sides
of a window and made GetRegion reject any region that isn't fully inside
of the window (previously accepted regions that clipped outside of the
right and bottom side, possibly leading to incorrect reads on the user
end due to desynchronized region dimensions)
2026-08-07 12:31:13 +02:00
DaemoandGitHub 2d0c02ae7e Fix freeze when resizing a window (#239)
Fixed an annoying bug where the application would freeze when resizing
the window
This pull request made it so the SDL window's resizing event now
broadcasts a new event specifically asking the swapchain to recreate
itself, which will in turn broadcast the original event when done. It
also removed the Event enum specific values, as they were irrelevant.
2026-08-07 00:18:52 +02:00
ddfbe32cea Indev (#228)
Co-authored-by: kbJeff-8 <kbJeff-8@users.noreply.github.com>
2026-05-13 14:29:51 +02:00
12 changed files with 100 additions and 87 deletions
+1 -1
View File
@@ -153,7 +153,7 @@ MLX_API void mlx_set_image_region(mlx_context mlx, mlx_image image, int x, int y
* @param scale_y Scale y of the image
* @param angle Rotation angle of the image (clockwise)
*/
MLX_API void mlx_put_transformed_image_to_window(mlx_context mlx, mlx_window win, mlx_image image, int x, int y, float scale_x, float scale_y, float angle);
MLX_API void mlx_put_transformed_image_to_window(mlx_context mlx, mlx_window win, mlx_image image, float x, float y, float scale_x, float scale_y, float angle);
/**
* @brief Get direct pointers to hidden functions
+4 -3
View File
@@ -19,9 +19,10 @@ namespace mlx
enum class Event
{
ResizeEventCode = 56,
FrameBeginEventCode = 57,
FatalErrorEventCode = 168,
ResizeEventCode = 1,
SwapchainResizeEventCode,
FrameBeginEventCode,
FatalErrorEventCode,
EndEnum
};
+12 -8
View File
@@ -1,26 +1,30 @@
#ifndef __MLX_FPS__
#define __MLX_FPS__
#include <chrono>
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);
};
}
+1 -1
View File
@@ -28,7 +28,7 @@ namespace mlx
void PixelPutArray(int x, int y, mlx_color* color, std::size_t pixels_size) noexcept;
void PixelPutRegion(int x, int y, int w, int h, mlx_color* color) noexcept;
void StringPut(int x, int y, mlx_color color, std::string str);
void TexturePut(NonOwningPtr<class Texture> texture, int x, int y, float scale_x, float scale_y, float angle);
void TexturePut(NonOwningPtr<class Texture> texture, float x, float y, float scale_x, float scale_y, float angle);
inline void TryEraseSpritesInScene(NonOwningPtr<Texture> texture) noexcept;
+2 -3
View File
@@ -8,7 +8,7 @@
namespace mlx
{
Application::Application() : p_mem_manager(std::make_unique<MemManager>()), p_sdl_manager(std::make_unique<SDLManager>()), m_fps(), m_in()
Application::Application() : p_mem_manager(std::make_unique<MemManager>()), p_sdl_manager(std::make_unique<SDLManager>()), 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();
+7 -8
View File
@@ -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<std::uint32_t>(fps));
if(fps <= 0)
fps = -1;
mlx->app->SetFPSCap(static_cast<std::uint32_t>(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))
{
@@ -459,7 +458,7 @@ extern "C"
texture->SetRegion(x, y, w, h, pixels);
}
void mlx_put_transformed_image_to_window(mlx_context mlx, mlx_window win, mlx_image image, int x, int y, float scale_x, float scale_y, float angle)
void mlx_put_transformed_image_to_window(mlx_context mlx, mlx_window win, mlx_image image, float x, float y, float scale_x, float scale_y, float angle)
{
MLX_CHECK_APPLICATION_POINTER(mlx);
mlx::NonOwningPtr<mlx::GraphicsSupport> gs = mlx->app->GetGraphicsSupport(win);
+23 -16
View File
@@ -1,30 +1,37 @@
#include <PreCompiled.h>
#include <Core/Fps.h>
#ifndef __APPLE__
#include <emmintrin.h>
#endif
namespace mlx
{
void FpsManager::Init()
{
m_timer = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(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::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
if(std::chrono::duration<std::uint64_t>{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<double, std::nano>(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;
}
}
+3 -3
View File
@@ -136,7 +136,7 @@ namespace mlx
p_scene->BringToDrawLayer(text.Get(), m_draw_layer);
}
void GraphicsSupport::TexturePut(NonOwningPtr<Texture> texture, int x, int y, float scale_x, float scale_y, float angle)
void GraphicsSupport::TexturePut(NonOwningPtr<Texture> texture, float x, float y, float scale_x, float scale_y, float angle)
{
MLX_PROFILE_FUNCTION();
NonOwningPtr<Sprite> sprite = p_scene->GetSpriteFromTexturePositionScaleRotation(texture, Vec2f{ static_cast<float>(x), static_cast<float>(y) }, scale_x, scale_y, angle);
@@ -146,14 +146,14 @@ namespace mlx
{
m_draw_layer++;
m_pixelput_called = false;
}
}
Sprite& new_sprite = p_scene->CreateSprite(texture);
new_sprite.SetCenter(Vec2f{ texture->GetWidth() * 0.5f, texture->GetHeight() * 0.5f });
new_sprite.SetPosition(Vec2f{ static_cast<float>(x), static_cast<float>(y) });
new_sprite.SetScale(Vec2f{ scale_x, scale_y });
new_sprite.SetRotation(angle);
}
else if(!p_scene->IsTextureAtGivenDrawLayer(texture, m_draw_layer))
else if(!p_scene->IsTextureAtGivenDrawLayer(texture, m_draw_layer))
p_scene->BringToDrawLayer(sprite.Get(), m_draw_layer);
}
+3 -3
View File
@@ -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)
+40 -37
View File
@@ -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<std::uint32_t>(x) >= m_width || static_cast<std::uint32_t>(y) >= m_height)
return;
if(w < 0 || h < 0)
if(w < 0 || h < 0 || x < -w || y < -h
|| x >= static_cast<int>(m_width) || y >= static_cast<int>(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<int>(x, 0),
start_y = std::max<int>(y, 0),
start_row = start_y * m_width,
start_i = (start_y - y) * w + (start_x - x),
end_x = std::min<int>(x + w, m_width),
end_y = std::min<int>(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<std::uint32_t>(x + w) || moving_x >= m_width)
if(dx >= end_x)
{
moving_x = x;
moving_y++;
if(moving_y >= static_cast<std::uint32_t>(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<mlx_color*>()[(moving_y * m_width) + moving_x] = ReverseColor(pixels[i]);
m_staging_buffer->GetMap<mlx_color*>()[row + dx] = ReverseColor(pixels[i]);
else
m_staging_buffer->GetMap<mlx_color*>()[(moving_y * m_width) + moving_x] = pixels[i];
m_staging_buffer->GetMap<mlx_color*>()[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<std::uint32_t>(x) >= m_width || static_cast<std::uint32_t>(y) >= m_height)
if(x >= static_cast<int>(m_width) || y >= static_cast<int>(m_height))
return;
if(!m_staging_buffer.has_value())
OpenCPUBuffer();
int
start = y * m_width + x,
dest_start = std::max<int>(start, 0),
src_start = dest_start - start,
dest_end = std::min<int>(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<mlx_color*>()[(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<mlx_color*>()[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<mlx_color*>()[(y * m_width) + x], pixels, len_guard);
std::memcpy(
&m_staging_buffer->GetMap<mlx_color*>()[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<std::uint32_t>(x) >= m_width || static_cast<std::uint32_t>(y) >= m_height)
if(w < 0 || h < 0 || x < 0 || y < 0 || static_cast<std::uint32_t>(x + w) >= m_width || static_cast<std::uint32_t>(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<std::uint32_t>(x + w) || moving_x >= m_width)
if(dx >= m_width)
{
moving_x = x;
moving_y++;
if(moving_y >= static_cast<std::uint32_t>(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<mlx_color*>()[(moving_y * m_width) + moving_x]);
dst[i] = ReverseColor(m_staging_buffer->GetMap<mlx_color*>()[row + dx]);
else
dst[i] = m_staging_buffer->GetMap<mlx_color*>()[(moving_y * m_width) + moving_x];
dst[i] = m_staging_buffer->GetMap<mlx_color*>()[row + dx];
}
}
@@ -320,16 +331,8 @@ namespace mlx
processed_color.g = static_cast<std::uint8_t>(color.g * 255.f);
processed_color.b = static_cast<std::uint8_t>(color.b * 255.f);
processed_color.a = static_cast<std::uint8_t>(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<mlx_color*>()[y * m_width + x] = processed_color;
}
}
for(std::size_t i = 0; i < m_width * m_height; i++)
m_staging_buffer->GetMap<mlx_color*>()[i] = processed_color;
}
}
+2 -2
View File
@@ -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<void(const EventBase&)> 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<std::uintptr_t>(this)) });
+2 -2
View File
@@ -3176,7 +3176,7 @@ void kvfGPipelineBuilderEnableAdditiveBlending(KvfGraphicsPipelineBuilder* build
builder->color_blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
builder->color_blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
builder->color_blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
builder->color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
builder->color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
builder->color_blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
}
@@ -3189,7 +3189,7 @@ void kvfGPipelineBuilderEnableAlphaBlending(KvfGraphicsPipelineBuilder* builder)
builder->color_blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
builder->color_blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
builder->color_blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
builder->color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
builder->color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
builder->color_blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
}