From 7af18a2aa8b1cb7051e92cd268e583780c701eaf Mon Sep 17 00:00:00 2001 From: Zephyron Date: Wed, 21 Jan 2026 18:53:40 +1000 Subject: [PATCH] fix(texture_cache): handle unmapped memory regions gracefully - Skip upload for unmapped texture regions with zeroed staging buffer - Add safety checks in UploadImageContents for unmapped GPU memory - Prevents GPU errors from invalid memory access during texture upload --- src/video_core/texture_cache/texture_cache.h | 17 +++++++++++++++++ src/video_core/texture_cache/util.cpp | 15 ++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index be4c3c948..d038ec3a7 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -4,10 +4,12 @@ #pragma once +#include #include #include #include "common/alignment.h" +#include "common/logging/log.h" #include "common/settings.h" #include "video_core/control/channel_state.h" #include "video_core/dirty_flags.h" @@ -1076,6 +1078,21 @@ void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) const std::span mapped_span = staging.mapped_span; const GPUVAddr gpu_addr = image.gpu_addr; + // FIXED: Check if GPU memory is mapped before reading to prevent device loss + // This fixes Switch Sports and other games that crash due to unmapped GPU memory reads + // Similar to Ryujinx's GetSpanMapped() approach - gracefully handle unmapped memory + if (!gpu_memory->IsFullyMappedRange(gpu_addr, image.guest_size_bytes)) { + LOG_WARNING(HW_GPU, + "Texture upload from unmapped GPU memory at 0x{:016X} (size: {} KB). " + "Zeroing staging buffer and skipping upload.", + gpu_addr, image.guest_size_bytes / 1024); + // Zero the staging buffer to ensure no garbage data is used if something reads it + std::memset(mapped_span.data(), 0, mapped_span.size_bytes()); + // Skip the upload - trying to upload with invalid copy parameters can crash + // The image will use its previous state or be initialized to a safe default + return; + } + if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(), VideoCommon::CacheType::NoTextureCache); diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index cf31dc70d..dff3458c8 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include "common/bit_util.h" #include "common/common_types.h" #include "common/div_ceil.h" +#include "common/logging/log.h" #include "common/scratch_buffer.h" #include "common/settings.h" #include "video_core/compatible_formats.h" @@ -858,7 +860,18 @@ boost::container::small_vector UnswizzleImage(Tegra::Memory if (info.type == ImageType::Linear) { ASSERT(output.size_bytes() >= guest_size_bytes); - gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes); + + // FIXED: Validate GPU memory is mapped before reading to prevent device loss + // This fixes Switch Sports crash - similar to Ryujinx's GetSpanMapped() approach + if (!gpu_memory.IsFullyMappedRange(gpu_addr, guest_size_bytes)) { + LOG_WARNING(HW_GPU, + "Linear texture read from unmapped GPU memory at 0x{:016X} (size: {} KB). " + "Zeroing texture data to prevent device loss.", + gpu_addr, guest_size_bytes / 1024); + std::memset(output.data(), 0, guest_size_bytes); + } else { + gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes); + } ASSERT((info.pitch >> bpp_log2) << bpp_log2 == info.pitch); return {{