diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp index 9b07f8645e..f4caacfc6a 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp @@ -462,6 +462,12 @@ Texture2D* FramebufferManager::ResolveEFBColorTexture(const VkRect2D& region) // Can't resolve within a render pass. StateTracker::GetInstance()->EndRenderPass(); + // It's not valid to resolve out-of-bounds coordinates. + // Ensuring the region is within the image is the caller's responsibility. + _assert_(region.offset.x >= 0 && region.offset.y >= 0 && + (static_cast(region.offset.x) + region.extent.width) <= m_efb_width && + (static_cast(region.offset.y) + region.extent.height) <= m_efb_height); + // Resolving is considered to be a transfer operation. m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 9eafb0c22f..15fb00e77e 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -587,12 +587,11 @@ void Renderer::ResolveEFBForSwap(const TargetRectangle& scaled_rect) { // While the source rect can be out-of-range when drawing, the resolve rectangle must be within // the bounds of the texture. - TargetRectangle resolve_rect{scaled_rect}; - resolve_rect.ClampUL(0, 0, m_target_width, m_target_height); - VkRect2D region = { - {resolve_rect.left, resolve_rect.top}, - {static_cast(resolve_rect.GetWidth()), static_cast(resolve_rect.GetHeight())}}; + {scaled_rect.left, scaled_rect.top}, + {static_cast(scaled_rect.GetWidth()), static_cast(scaled_rect.GetHeight())}}; + region = Util::ClampRect2D(region, FramebufferManager::GetInstance()->GetEFBWidth(), + FramebufferManager::GetInstance()->GetEFBHeight()); FramebufferManager::GetInstance()->ResolveEFBColorTexture(region); } diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp index 23cc06abff..08c370b48f 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp @@ -96,11 +96,14 @@ void TextureCache::CopyEFB(u8* dst, const EFBCopyFormat& format, u32 native_widt FramebufferManager::GetInstance()->FlushEFBPokes(); // MSAA case where we need to resolve first. - // TODO: Do in one pass. + // An out-of-bounds source region is valid here, and fine for the draw (since it is converted + // to texture coordinates), but it's not valid to resolve an out-of-range rectangle. TargetRectangle scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top}, {static_cast(scaled_src_rect.GetWidth()), static_cast(scaled_src_rect.GetHeight())}}; + region = Util::ClampRect2D(region, FramebufferManager::GetInstance()->GetEFBWidth(), + FramebufferManager::GetInstance()->GetEFBHeight()); Texture2D* src_texture; if (is_depth_copy) src_texture = FramebufferManager::GetInstance()->ResolveEFBDepthTexture(region); @@ -465,10 +468,14 @@ void TextureCache::TCacheEntry::FromRenderTarget(bool is_depth_copy, const EFBRe VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); StateTracker::GetInstance()->EndRenderPass(); - // Transition EFB to shader resource before binding + // Transition EFB to shader resource before binding. + // An out-of-bounds source region is valid here, and fine for the draw (since it is converted + // to texture coordinates), but it's not valid to resolve an out-of-range rectangle. VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top}, {static_cast(scaled_src_rect.GetWidth()), static_cast(scaled_src_rect.GetHeight())}}; + region = Util::ClampRect2D(region, FramebufferManager::GetInstance()->GetEFBWidth(), + FramebufferManager::GetInstance()->GetEFBHeight()); Texture2D* src_texture; if (is_depth_copy) src_texture = FramebufferManager::GetInstance()->ResolveEFBDepthTexture(region); diff --git a/Source/Core/VideoBackends/Vulkan/Util.cpp b/Source/Core/VideoBackends/Vulkan/Util.cpp index 02ad129972..278a31aaee 100644 --- a/Source/Core/VideoBackends/Vulkan/Util.cpp +++ b/Source/Core/VideoBackends/Vulkan/Util.cpp @@ -97,6 +97,16 @@ u32 GetTexelSize(VkFormat format) } } +VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height) +{ + VkRect2D out; + out.offset.x = MathUtil::Clamp(rect.offset.x, 0, static_cast(width - 1)); + out.offset.y = MathUtil::Clamp(rect.offset.y, 0, static_cast(height - 1)); + out.extent.width = std::min(rect.extent.width, width - static_cast(rect.offset.x)); + out.extent.height = std::min(rect.extent.height, height - static_cast(rect.offset.y)); + return out; +} + VkBlendFactor GetAlphaBlendFactor(VkBlendFactor factor) { switch (factor) diff --git a/Source/Core/VideoBackends/Vulkan/Util.h b/Source/Core/VideoBackends/Vulkan/Util.h index f5385932bd..93e68efa9c 100644 --- a/Source/Core/VideoBackends/Vulkan/Util.h +++ b/Source/Core/VideoBackends/Vulkan/Util.h @@ -27,6 +27,9 @@ bool IsDepthFormat(VkFormat format); VkFormat GetLinearFormat(VkFormat format); u32 GetTexelSize(VkFormat format); +// Clamps a VkRect2D to the specified dimensions. +VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height); + // Map {SRC,DST}_COLOR to {SRC,DST}_ALPHA VkBlendFactor GetAlphaBlendFactor(VkBlendFactor factor);