From 636efaf2b550d3762037ad081197f0f3dc1490bb Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 1 Mar 2026 20:49:55 +0200 Subject: [PATCH 1/5] changed readbacks mode to Relaxed,Precised (#4091) --- src/common/config.h | 4 ++-- src/video_core/buffer_cache/region_manager.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/config.h b/src/common/config.h index 7a351d424..0fa241c6c 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -25,8 +25,8 @@ enum HideCursorState : int { Never, Idle, Always }; enum GpuReadbacksMode : int { Disabled, - Low, - High, + Relaxed, + Precised, }; void load(const std::filesystem::path& path, bool is_game_specific = false); diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index f9da020d1..3467b6791 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::High) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precised) { UpdateProtection(); } } From e5d7dc4090c00ffe566a3086d4033e74409e7f4a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 1 Mar 2026 21:02:21 +0200 Subject: [PATCH 2/5] the uber fix (#4092) --- src/common/config.h | 2 +- src/video_core/buffer_cache/region_manager.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/config.h b/src/common/config.h index 0fa241c6c..b341030e0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -26,7 +26,7 @@ enum HideCursorState : int { Never, Idle, Always }; enum GpuReadbacksMode : int { Disabled, Relaxed, - Precised, + Precise, }; void load(const std::filesystem::path& path, bool is_game_specific = false); diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index 3467b6791..ecf9406af 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precised) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precise) { UpdateProtection(); } } From fc949a74497a29a4eda5ad0b4b7a78ada4a22ba1 Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Mon, 2 Mar 2026 18:32:28 +0000 Subject: [PATCH 3/5] CI wget linuxdeploy retries (#4093) --- .github/linux-appimage-sdl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh index 7961f5312..d85aa6c4c 100755 --- a/.github/linux-appimage-sdl.sh +++ b/.github/linux-appimage-sdl.sh @@ -8,8 +8,8 @@ if [[ -z $GITHUB_WORKSPACE ]]; then fi # Prepare Tools for building the AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh From 1f5430e4c276dfac0286e25d4fa6470121b8910e Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:35:58 -0600 Subject: [PATCH 4/5] More targeted fix (#4096) Cyberpunk's issue seems to actually come from the incrementing in the loop. It wasn't clear while debugging, but the problem is that the pattern the game supplies causes match to fail when str_wild_it hits the end, and then tries iterating past end due to the loop condition. Our pattern matching code seems broken for the case Cyberpunk triggers, but since I'm not aware of the intricacies of how real hardware behaves, best to just revert the loop condition change and instead break the loop before the broken iteration. --- src/core/libraries/save_data/savedata.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 1edb3d40b..48b086457 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -330,9 +330,12 @@ static bool match(std::string_view str, std::string_view pattern) { auto pat_it = pattern.begin(); while (str_it != str.end() && pat_it != pattern.end()) { if (*pat_it == '%') { // 0 or more wildcard - for (auto str_wild_it = str_it; str_wild_it < str.end(); ++str_wild_it) { + for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { return true; + } else if (str_wild_it == str.end()) { + // Avoid incrementing str_wild_it past str.end(). + break; } } return false; From 14450d330f450ce68368819711d423a5f7f456f3 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 3 Mar 2026 08:52:12 +0200 Subject: [PATCH 5/5] CopyImage stencil fixes (#4095) * stencil fixes hope it fixes driveclub * revert image copy to the one that had driveclub worked * reverted texture cache change * some more fixes and reverts * added logging for overlap again --- src/video_core/texture_cache/image.cpp | 142 +++++++----------- src/video_core/texture_cache/image_info.h | 2 +- .../texture_cache/texture_cache.cpp | 8 - 3 files changed, 56 insertions(+), 96 deletions(-) diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 418641bc3..972f028d4 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -461,33 +461,36 @@ static std::pair SanitizeCopyLayers(const ImageInfo& src_info, const I void Image::CopyImage(Image& src_image) { const auto& src_info = src_image.info; + const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); - // Check format compatibility + // Format mismatch warning (safe but useful) if (src_info.pixel_format != info.pixel_format) { LOG_DEBUG(Render_Vulkan, - "Copy between different formats: src={}, dst={}. Color may be incorrect.", + "Copy between different formats: src={}, dst={}. " + "Result may be undefined.", vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); } - const u32 width = src_info.size.width; - const u32 height = src_info.size.height; + const u32 base_width = src_info.size.width; + const u32 base_height = src_info.size.height; const u32 base_depth = info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth; - auto [test_src_layers, test_dst_layers] = SanitizeCopyLayers(src_info, info, base_depth); - - ASSERT(test_src_layers == test_dst_layers || num_mips == 1 || - (ConvertImageType(src_info.type) != ConvertImageType(info.type) && - (test_src_layers == 1 || test_dst_layers == 1))); - + // Match sample count before copying SetBackingSamples(info.num_samples, false); src_image.SetBackingSamples(src_info.num_samples); - boost::container::small_vector image_copies; + boost::container::small_vector regions; + + const vk::ImageAspectFlags src_aspect = + src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil; + + const vk::ImageAspectFlags dst_aspect = aspect_mask & ~vk::ImageAspectFlagBits::eStencil; const bool src_is_2d = ConvertImageType(src_info.type) == vk::ImageType::e2D; const bool src_is_3d = ConvertImageType(src_info.type) == vk::ImageType::e3D; + const bool dst_is_2d = ConvertImageType(info.type) == vk::ImageType::e2D; const bool dst_is_3d = ConvertImageType(info.type) == vk::ImageType::e3D; @@ -495,103 +498,68 @@ void Image::CopyImage(Image& src_image) { const bool is_3d_to_2d = src_is_3d && dst_is_2d; const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d; - // Determine aspect mask - exclude stencil - vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; - - // For depth/stencil images, only copy the depth aspect (skip stencil) - if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) { - aspect = vk::ImageAspectFlagBits::eDepth; - } - for (u32 mip = 0; mip < num_mips; ++mip) { - const auto mip_w = std::max(width >> mip, 1u); - const auto mip_h = std::max(height >> mip, 1u); - const auto mip_d = std::max(base_depth >> mip, 1u); + const u32 mip_w = std::max(base_width >> mip, 1u); + const u32 mip_h = std::max(base_height >> mip, 1u); + const u32 mip_d = std::max(base_depth >> mip, 1u); auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + vk::ImageCopy region{}; + + region.srcSubresource.aspectMask = src_aspect; + region.srcSubresource.mipLevel = mip; + region.srcSubresource.baseArrayLayer = 0; + + region.dstSubresource.aspectMask = dst_aspect; + region.dstSubresource.mipLevel = mip; + region.dstSubresource.baseArrayLayer = 0; + if (is_same_type) { - u32 copy_layers = std::min(src_layers, dst_layers); - - if (src_is_3d) - src_layers = 1; - if (dst_is_3d) - dst_layers = 1; - - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = copy_layers, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = copy_layers, - }, - .extent = vk::Extent3D(mip_w, mip_h, mip_d), - }; - image_copies.push_back(copy_region); + // 2D->2D OR 3D->3D + if (src_is_3d) { + // 3D images must use layerCount=1 + region.srcSubresource.layerCount = 1; + region.dstSubresource.layerCount = 1; + region.extent = vk::Extent3D(mip_w, mip_h, mip_d); + } else { + // Array images + const u32 copy_layers = std::min(src_layers, dst_layers); + region.srcSubresource.layerCount = copy_layers; + region.dstSubresource.layerCount = copy_layers; + region.extent = vk::Extent3D(mip_w, mip_h, 1); + } } else if (is_2d_to_3d) { - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = src_layers, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .extent = vk::Extent3D(mip_w, mip_h, src_layers), - }; - image_copies.push_back(copy_region); + // 2D array -> 3D volume + region.srcSubresource.layerCount = src_layers; + region.dstSubresource.layerCount = 1; + region.extent = vk::Extent3D(mip_w, mip_h, src_layers); } else if (is_3d_to_2d) { - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = dst_layers, - }, - .extent = vk::Extent3D(mip_w, mip_h, dst_layers), - }; - image_copies.push_back(copy_region); + // 3D volume -> 2D array + region.srcSubresource.layerCount = 1; + region.dstSubresource.layerCount = dst_layers; + region.extent = vk::Extent3D(mip_w, mip_h, dst_layers); } + + regions.push_back(region); } scheduler->EndRendering(); - // Remove the pipeline stage flags - they don't belong here src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); - if (!image_copies.empty()) { - cmdbuf.copyImage(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, GetImage(), - vk::ImageLayout::eTransferDstOptimal, image_copies); + if (!regions.empty()) { + cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), + backing->state.layout, regions); } - // Remove pipeline stage flags here too - src_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, - {}); - - Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } - void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) { const auto& src_info = src_image.info; const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index 0da9c8bfb..543e144d2 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -48,7 +48,7 @@ struct ImageInfo { } Extent2D BlockDim() const { const auto dim = props.is_block ? 2 : 0; - return Extent2D{size.width >> dim, size.height >> dim}; + return Extent2D{pitch >> dim, size.height >> dim}; } s32 MipOf(const ImageInfo& info) const; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a8d846cfc..8163902cc 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -297,14 +297,6 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == cache_image.info.guest_address) { const u32 lhs_block_size = image_info.num_bits * image_info.num_samples; const u32 rhs_block_size = cache_image.info.num_bits * cache_image.info.num_samples; - - if (image_info.pitch != cache_image.info.pitch) { - if (safe_to_delete) { - FreeImage(cache_image_id); - } - return {merged_image_id, -1, -1}; - } - if (image_info.BlockDim() != cache_image.info.BlockDim() || lhs_block_size != rhs_block_size) { // Very likely this kind of overlap is caused by allocation from a pool.