From 440245f616cde99964d548648fa14ff4d304ac55 Mon Sep 17 00:00:00 2001 From: Pixeluted Date: Mon, 19 Oct 2026 04:12:25 +0200 Subject: [PATCH] gpu: bound overlapping image copies by both native backings --- .../host_gpu/renderer/image/image.cpp | 8 +++- tests/ShaderRecompilerComputeTests.cpp | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/graphics/host_gpu/renderer/image/image.cpp b/src/graphics/host_gpu/renderer/image/image.cpp index c94866edd..cf89634f8 100644 --- a/src/graphics/host_gpu/renderer/image/image.cpp +++ b/src/graphics/host_gpu/renderer/image/image.cpp @@ -353,8 +353,12 @@ void Image::CopyImage(Image& source) { std::vector copies; copies.reserve(levels); for (uint32_t level = 0; level < levels; level++) { - const auto width = std::max(source.backing.extent.width >> level, 1u); - const auto height = std::max(source.backing.extent.height >> level, 1u); + // Overlapping cache images can differ in size; copying the source extent into a smaller + // destination writes out of bounds and loses the device. + const auto width = std::min(std::max(source.backing.extent.width >> level, 1u), + std::max(backing.extent.width >> level, 1u)); + const auto height = std::min(std::max(source.backing.extent.height >> level, 1u), + std::max(backing.extent.height >> level, 1u)); const auto depth = std::max(base_depth >> level, 1u); const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth); vk::ImageCopy copy {}; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index f2a40459c..2ecdfccca 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -6709,6 +6709,51 @@ public: "Image::CopyImage did not retain pinned " "source/destination states"); + auto wide_volume_info = native_volume_info; + wide_volume_info.extent = {85, 5, 5}; + wide_volume_info.pitch = 85; + auto narrow_volume_info = wide_volume_info; + narrow_volume_info.extent.width = 20; + narrow_volume_info.pitch = 20; + Libs::Graphics::Image wide_volume(m_runtime_context, scheduler, + wide_volume_info); + Libs::Graphics::Image narrow_volume(m_runtime_context, scheduler, + narrow_volume_info); + std::vector volume_source_words(85 * 5 * 5); + for (uint32_t i = 0; i < volume_source_words.size(); ++i) + volume_source_words[i] = i + 1; + auto volume_upload = CreateHostBuffer( + name, volume_source_words.size() * sizeof(uint32_t), + vk::BufferUsageFlagBits::eTransferSrc, volume_source_words); + auto shrinking_readback = + CreateHostBuffer(name, 20 * 5 * 5 * sizeof(uint32_t), + vk::BufferUsageFlagBits::eTransferDst, {}); + vk::BufferImageCopy volume_region{}; + volume_region.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, + 1}; + volume_region.imageExtent = wide_volume_info.extent; + wide_volume.Upload(std::span{&volume_region, 1}, volume_upload.buffer, 0, + volume_upload.size); + narrow_volume.CopyImage(wide_volume); + volume_region.imageExtent = narrow_volume_info.extent; + narrow_volume.Download(std::span{&volume_region, 1}, + shrinking_readback.buffer, 0, + shrinking_readback.size); + HostReadBarrier(shrinking_readback.buffer, shrinking_readback.size, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + scheduler.Finish(); + const auto volume_copied_words = + ReadBuffer(name, shrinking_readback, 20 * 5 * 5); + for (uint32_t row = 0; row < 5 * 5; ++row) + Require(name, "shrinking volume copy contents", + std::equal(volume_copied_words.begin() + row * 20, + volume_copied_words.begin() + (row + 1) * 20, + volume_source_words.begin() + row * 85), + "85x5x5 to 20x5x5 copy lost a row or depth slice"); + DestroyBuffer(&shrinking_readback); + DestroyBuffer(&volume_upload); + ImageInfo resolve_source_info{}; resolve_source_info.pixel_format = vk::Format::eR8G8B8A8Unorm; resolve_source_info.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;