From 7639c27df4a26533d5d9d25aa7caa27aa88ea7c2 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Tue, 3 Feb 2026 11:58:06 +0100 Subject: [PATCH] fixing put pixel resize issue --- example/main.c | 9 +- runtime/Includes/Graphics/PutPixelManager.h | 3 +- runtime/Includes/Renderer/Image.h | 6 ++ runtime/Sources/Graphics/PutPixelManager.cpp | 55 +++++++--- runtime/Sources/Renderer/Image.cpp | 104 ++++++++++++++++++- third_party/kvf.h | 14 +++ 6 files changed, 172 insertions(+), 19 deletions(-) diff --git a/example/main.c b/example/main.c index 6db5745..80e13bd 100644 --- a/example/main.c +++ b/example/main.c @@ -44,11 +44,16 @@ void update(void* param) mlx_set_font(mlx->mlx, "default"); mlx_string_put(mlx->mlx, mlx->win, 20, 50, (mlx_color){ .rgba = 0xFFFFFFFF }, "that's a text"); + int win_width; + int win_height; + + mlx_get_window_size(mlx->mlx, mlx->win, &win_width, &win_height); + uint32_t color = 0; - for(int j = 0; j < 400; j++) + for(int j = 0; j < win_height; j++) { mlx_pixel_put(mlx->mlx, mlx->win, j, j, (mlx_color){ .rgba = 0x0000FFFF + (color << 24) }); - mlx_pixel_put(mlx->mlx, mlx->win, 399 - j, j, (mlx_color){ .rgba = 0x0000FFFF }); + mlx_pixel_put(mlx->mlx, mlx->win, win_width - j, j, (mlx_color){ .rgba = 0x0000FFFF }); color += (color < 255); } diff --git a/runtime/Includes/Graphics/PutPixelManager.h b/runtime/Includes/Graphics/PutPixelManager.h index 8d1626c..9ff9b25 100644 --- a/runtime/Includes/Graphics/PutPixelManager.h +++ b/runtime/Includes/Graphics/PutPixelManager.h @@ -8,7 +8,7 @@ namespace mlx class PutPixelManager { public: - PutPixelManager(NonOwningPtr renderer) : p_renderer(renderer) {} + PutPixelManager(NonOwningPtr renderer); // Returns a valid pointer when a new texture has been created NonOwningPtr DrawPixel(int x, int y, std::uint64_t draw_layer, mlx_color color); @@ -20,6 +20,7 @@ namespace mlx private: NonOwningPtr GetLayer(std::uint64_t draw_layer, bool& is_newlayer); + std::unique_ptr NewTexture(); private: std::unordered_map> m_placements; diff --git a/runtime/Includes/Renderer/Image.h b/runtime/Includes/Renderer/Image.h index ba46d0e..5121f27 100644 --- a/runtime/Includes/Renderer/Image.h +++ b/runtime/Includes/Renderer/Image.h @@ -89,7 +89,13 @@ namespace mlx mlx_color GetPixel(int x, int y) noexcept; void GetRegion(int x, int y, int w, int h, mlx_color* dst) noexcept; void Clear(VkCommandBuffer cmd, Vec4f color) override; + void CopyTo(Texture& other); + void Resize(std::uint32_t width, std::uint32_t height); + void Swap(Texture& texture) noexcept; + + // If a valid cmd buffer is passed, this function takes ownership and makes it invalid after + void SyncCPUBuffer(VkCommandBuffer cmd = VK_NULL_HANDLE); void Update(VkCommandBuffer cmd); ~Texture() override { Destroy(); } diff --git a/runtime/Sources/Graphics/PutPixelManager.cpp b/runtime/Sources/Graphics/PutPixelManager.cpp index 41ba4de..082b3f9 100644 --- a/runtime/Sources/Graphics/PutPixelManager.cpp +++ b/runtime/Sources/Graphics/PutPixelManager.cpp @@ -6,6 +6,29 @@ namespace mlx { + PutPixelManager::PutPixelManager(NonOwningPtr renderer) : p_renderer(renderer) + { + MLX_PROFILE_FUNCTION(); + std::function functor = [this](const EventBase& event) + { + // Suboptimal for multi-windows applications + if(event.What() == Event::ResizeEventCode) + { + VkExtent2D extent{ .width = 0, .height = 0 }; + if(p_renderer->GetWindow()) + extent = kvfGetSwapchainImagesSize(p_renderer->GetSwapchain().Get()); + else if(p_renderer->GetRenderTarget()) + extent = VkExtent2D{ .width = p_renderer->GetRenderTarget()->GetWidth(), .height = p_renderer->GetRenderTarget()->GetHeight() }; + else + FatalError("a renderer was created without window nor render target attached (wtf!?)"); + + for(auto& texture : m_textures) + texture->Resize(extent.width, extent.height); + } + }; + EventBus::RegisterListener({ functor, "mlx_put_pixel_manager_" + std::to_string(reinterpret_cast(this)) }); + } + NonOwningPtr PutPixelManager::DrawPixel(int x, int y, std::uint64_t draw_layer, mlx_color color) { MLX_PROFILE_FUNCTION(); @@ -53,21 +76,8 @@ namespace mlx is_newlayer = true; if(m_current_texture_index >= m_textures.size()) - { - VkExtent2D extent{ .width = 0, .height = 0 }; - if(p_renderer->GetWindow()) - extent = kvfGetSwapchainImagesSize(p_renderer->GetSwapchain().Get()); - else if(p_renderer->GetRenderTarget()) - extent = VkExtent2D{ .width = p_renderer->GetRenderTarget()->GetWidth(), .height = p_renderer->GetRenderTarget()->GetHeight() }; - else - FatalError("a renderer was created without window nor render target attached (wtf)"); + m_textures.push_back(NewTexture()); - #ifdef DEBUG - m_textures.push_back(std::make_unique(CPUBuffer{}, extent.width, extent.height, VK_FORMAT_R8G8B8A8_SRGB, false, "mlx_put_pixel_layer_" + std::to_string(m_current_texture_index))); - #else - m_textures.push_back(std::make_unique(CPUBuffer{}, extent.width, extent.height, VK_FORMAT_R8G8B8A8_SRGB, false, std::string_view{})); - #endif - } try { m_placements[draw_layer] = m_textures.at(m_current_texture_index).get(); @@ -83,6 +93,23 @@ namespace mlx } } + std::unique_ptr PutPixelManager::NewTexture() + { + VkExtent2D extent{ .width = 0, .height = 0 }; + if(p_renderer->GetWindow()) + extent = kvfGetSwapchainImagesSize(p_renderer->GetSwapchain().Get()); + else if(p_renderer->GetRenderTarget()) + extent = VkExtent2D{ .width = p_renderer->GetRenderTarget()->GetWidth(), .height = p_renderer->GetRenderTarget()->GetHeight() }; + else + FatalError("a renderer was created without window nor render target attached (wtf!?)"); + + #ifdef DEBUG + return std::make_unique(CPUBuffer{}, extent.width, extent.height, VK_FORMAT_R8G8B8A8_SRGB, false, "mlx_put_pixel_layer_" + std::to_string(m_current_texture_index)); + #else + return std::make_unique(CPUBuffer{}, extent.width, extent.height, VK_FORMAT_R8G8B8A8_SRGB, false, std::string_view{}); + #endif + } + void PutPixelManager::ResetRenderData() { m_placements.clear(); diff --git a/runtime/Sources/Renderer/Image.cpp b/runtime/Sources/Renderer/Image.cpp index 10859f7..2dff418 100644 --- a/runtime/Sources/Renderer/Image.cpp +++ b/runtime/Sources/Renderer/Image.cpp @@ -24,7 +24,7 @@ namespace mlx { - mlx_color ReverseColor(mlx_color color) + MLX_FORCEINLINE mlx_color ReverseColor(mlx_color color) { mlx_color reversed_color; reversed_color.r = color.a; @@ -362,8 +362,16 @@ namespace mlx m_staging_buffer->Init(BufferType::Staging, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, {}, {}); #endif + SyncCPUBuffer(); + } + + void Texture::SyncCPUBuffer(VkCommandBuffer cmd) + { + if(!m_staging_buffer.has_value()) + return; VkImageLayout old_layout = m_layout; - VkCommandBuffer cmd = kvfCreateCommandBuffer(RenderCore::Get().GetDevice()); + if(cmd == VK_NULL_HANDLE) + cmd = kvfCreateCommandBuffer(RenderCore::Get().GetDevice()); kvfBeginCommandBuffer(cmd, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); TransitionLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, cmd); kvfCopyImageToBuffer(cmd, m_staging_buffer->Get(), m_image, m_staging_buffer->GetOffset(), VK_IMAGE_ASPECT_COLOR_BIT, { m_width, m_height, 1 }); @@ -375,6 +383,98 @@ namespace mlx kvfDestroyCommandBuffer(RenderCore::Get().GetDevice(), cmd); } + void Texture::CopyTo(Texture& other) + { + VkImageLayout old_layout = m_layout; + VkImageLayout other_old_layout = other.GetLayout(); + + VkImageSubresourceLayers subresource{}; + subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresource.mipLevel = 0; + subresource.layerCount = 1; + subresource.baseArrayLayer = 0; + + VkExtent3D extent{}; + extent.width = m_width; + extent.height = m_height; + extent.depth = 1; + + VkOffset3D offset{}; + offset.x = 0; + offset.y = 0; + offset.z = 0; + + VkImageCopy region{}; + region.srcSubresource = subresource; + region.dstSubresource = subresource; + region.extent = extent; + region.srcOffset = offset; + region.dstOffset = offset; + + VkCommandBuffer cmd = kvfCreateCommandBuffer(RenderCore::Get().GetDevice()); + kvfBeginCommandBuffer(cmd, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + + TransitionLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, cmd); + other.TransitionLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, cmd); + + kvfCopyImageToImage(cmd, m_image, m_layout, other.Get(), other.GetLayout(), 1, ®ion); + + TransitionLayout(old_layout, cmd); + other.TransitionLayout(other_old_layout, cmd); + + kvfEndCommandBuffer(cmd); + + SyncCPUBuffer(cmd); + } + + void Texture::Resize(std::uint32_t width, std::uint32_t height) + { + #ifdef DEBUG + Texture new_texture = Texture(CPUBuffer{}, width, height, m_format, m_is_multisampled, m_debug_name); + #else + Texture new_texture = Texture(CPUBuffer{}, width, height, m_format, m_is_multisampled, std::string_view{}); + #endif + + if(m_staging_buffer.has_value()) + new_texture.OpenCPUBuffer(); + + // Suboptimal operations, should bake all of them in a single command buffer + new_texture.Clear(VK_NULL_HANDLE, Vec4f{ 0.f }); + CopyTo(new_texture); + + Swap(new_texture); + } + + void Texture::Swap(Texture& texture) noexcept + { + MLX_PROFILE_FUNCTION(); + + #ifdef DEBUG + std::swap(m_debug_name, texture.m_debug_name); + #endif + std::swap(m_allocation, texture.m_allocation); + std::swap(m_image, texture.m_image); + std::swap(m_image_view, texture.m_image_view); + std::swap(m_sampler, texture.m_sampler); + std::swap(m_format, texture.m_format); + std::swap(m_tiling, texture.m_tiling); + std::swap(m_layout, texture.m_layout); + std::swap(m_type, texture.m_type); + std::swap(m_width, texture.m_width); + std::swap(m_height, texture.m_height); + std::swap(m_is_multisampled, texture.m_is_multisampled); + + if(m_staging_buffer.has_value() && texture.m_staging_buffer.has_value()) + m_staging_buffer->Swap(*texture.m_staging_buffer); + else if(m_staging_buffer.has_value()) + m_staging_buffer.reset(); + else if(texture.m_staging_buffer.has_value()) + texture.m_staging_buffer.reset(); + + m_has_been_modified = true; + texture.m_has_been_modified = true; + } + Texture* StbTextureLoad(const std::filesystem::path& file, int* w, int* h) { using namespace std::literals; diff --git a/third_party/kvf.h b/third_party/kvf.h index d21c93c..03accb4 100755 --- a/third_party/kvf.h +++ b/third_party/kvf.h @@ -152,6 +152,8 @@ void kvfDestroySemaphore(VkDevice device, VkSemaphore semaphore); VkImage kvfCreateImage(VkDevice device, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, KvfImageType type); void kvfCopyImageToBuffer(VkCommandBuffer cmd, VkBuffer dst, VkImage src, size_t buffer_offset, VkImageAspectFlagBits aspect, VkExtent3D extent); +void kvfCopyImageToImage(VkCommandBuffer cmd, VkImage src, VkImageLayout src_layout, VkImage dst, VkImageLayout dst_layout, uint32_t count, const VkImageCopy* regions); + void kvfDestroyImage(VkDevice device, VkImage image); VkImageView kvfCreateImageView(VkDevice device, VkImage image, VkFormat format, VkImageViewType type, VkImageAspectFlags aspect, int layer_count); void kvfDestroyImageView(VkDevice device, VkImageView image_view); @@ -2147,6 +2149,18 @@ void kvfCopyImageToBuffer(VkCommandBuffer cmd, VkBuffer dst, VkImage src, size_t KVF_GET_DEVICE_FUNCTION(vkCmdCopyImageToBuffer)(cmd, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst, 1, ®ion); } +void kvfCopyImageToImage(VkCommandBuffer cmd, VkImage src, VkImageLayout src_layout, VkImage dst, VkImageLayout dst_layout, uint32_t count, const VkImageCopy* regions) +{ + KVF_ASSERT(cmd != VK_NULL_HANDLE); + KVF_ASSERT(dst != VK_NULL_HANDLE); + KVF_ASSERT(src != VK_NULL_HANDLE); + #ifdef KVF_IMPL_VK_NO_PROTOTYPES + __KvfDevice* kvf_device = __kvfGetKvfDeviceFromVkCommandBuffer(cmd); + KVF_ASSERT(kvf_device != NULL && "could not find VkDevice in registered devices"); + #endif + KVF_GET_DEVICE_FUNCTION(vkCmdCopyImage)(cmd, src, src_layout, dst, dst_layout, count, regions); +} + void kvfDestroyImage(VkDevice device, VkImage image) { if(image == VK_NULL_HANDLE)