diff --git a/.github/workflows/fork-windows-check.yml b/.github/workflows/fork-windows-check.yml
index df03d8dde3..97675f7590 100644
--- a/.github/workflows/fork-windows-check.yml
+++ b/.github/workflows/fork-windows-check.yml
@@ -8,6 +8,8 @@ on:
- .github/workflows/fork-windows-check.yml
- .github/workflows/build-windows.yml
- src/CMakeLists.txt
+ - src/video_core/**
+ - src/shader_recompiler/**
- src/yuzu/**
- src/yuzu_cmd/**
- src/qt_common/**
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 1d4b4c82d0..8988780d17 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -2001,12 +2001,19 @@ void TextureCache
::UploadLayers(Image& image, std::span layers) {
std::vector copies;
for (size_t i = 0; i < layers.size(); ++i) {
const GPUVAddr layer_addr = image.gpu_addr + u64{layers[i]} * image.info.layer_stride;
- Tegra::Memory::GpuGuestMemory
- swizzle_data(*gpu_memory, layer_addr, image.info.layer_stride,
- &swizzle_data_buffer);
- auto layer_copies =
- UnswizzleImage(*gpu_memory, layer_addr, layer_info, swizzle_data,
- staging.mapped_span.subspan(i * layer_size, layer_size));
+ const std::span layer_out = staging.mapped_span.subspan(i * layer_size, layer_size);
+ auto layer_copies = [&] {
+ if (layer_info.type == ImageType::Linear) {
+ // Read straight from guest memory; no copy into swizzle_data first.
+ return UnswizzleLinearImage(*gpu_memory, layer_addr, layer_info,
+ image.info.layer_stride, layer_out);
+ }
+ Tegra::Memory::GpuGuestMemory
+ swizzle_data(*gpu_memory, layer_addr, image.info.layer_stride,
+ &swizzle_data_buffer);
+ return UnswizzleImage(*gpu_memory, layer_addr, layer_info, swizzle_data,
+ layer_out);
+ }();
for (BufferImageCopy copy : layer_copies) {
copy.buffer_offset += i * layer_size;
copy.image_subresource.base_layer = static_cast(layers[i]);
@@ -2073,6 +2080,23 @@ void TextureCache::UploadImageContents(Image& image, StagingBuffer& staging)
return;
}
+ if (image.info.type == ImageType::Linear) {
+ // A linear image is read straight from guest memory into its destination: copying it
+ // into swizzle_data first, as below, read it twice (3% of the split's back thread with
+ // Pokopia's town in view).
+ if (True(image.flags & ImageFlagBits::Converted)) {
+ unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes);
+ auto copies = UnswizzleLinearImage(*gpu_memory, gpu_addr, image.info,
+ image.guest_size_bytes, unswizzle_data_buffer);
+ ConvertImage(unswizzle_data_buffer, image.info, mapped_span, copies);
+ image.UploadMemory(staging, copies);
+ } else {
+ const auto copies = UnswizzleLinearImage(*gpu_memory, gpu_addr, image.info,
+ image.guest_size_bytes, mapped_span);
+ image.UploadMemory(staging, copies);
+ }
+ return;
+ }
Tegra::Memory::GpuGuestMemory swizzle_data(
*gpu_memory, gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
if (True(image.flags & ImageFlagBits::Converted)) {
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 09e9513a5b..b4abb3b0d4 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -850,6 +850,32 @@ bool IsValidEntry(const Tegra::MemoryManager& gpu_memory, const TICEntry& config
return gpu_memory.GpuToCpuAddress(address, guest_size_bytes).has_value();
}
+boost::container::small_vector UnswizzleLinearImage(
+ Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
+ size_t guest_size_bytes, std::span output) {
+ const u32 bpp_log2 = BytesPerBlockLog2(info.format);
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ const Extent3D size = info.size;
+ ASSERT(output.size_bytes() >= guest_size_bytes);
+ gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes);
+
+ ASSERT((info.pitch >> bpp_log2) << bpp_log2 == info.pitch);
+ return {{
+ .buffer_offset = 0,
+ .buffer_size = guest_size_bytes,
+ .buffer_row_length = info.pitch * tile_size.width >> bpp_log2,
+ .buffer_image_height = size.height,
+ .image_subresource =
+ {
+ .base_level = 0,
+ .base_layer = 0,
+ .num_layers = 1,
+ },
+ .image_offset = {0, 0, 0},
+ .image_extent = size,
+ }};
+}
+
boost::container::small_vector UnswizzleImage(Tegra::MemoryManager& gpu_memory,
GPUVAddr gpu_addr,
const ImageInfo& info,
@@ -861,24 +887,7 @@ boost::container::small_vector UnswizzleImage(Tegra::Memory
const Extent3D size = info.size;
if (info.type == ImageType::Linear) {
- ASSERT(output.size_bytes() >= guest_size_bytes);
- gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes);
-
- ASSERT((info.pitch >> bpp_log2) << bpp_log2 == info.pitch);
- return {{
- .buffer_offset = 0,
- .buffer_size = guest_size_bytes,
- .buffer_row_length = info.pitch * tile_size.width >> bpp_log2,
- .buffer_image_height = size.height,
- .image_subresource =
- {
- .base_level = 0,
- .base_layer = 0,
- .num_layers = 1,
- },
- .image_offset = {0, 0, 0},
- .image_extent = size,
- }};
+ return UnswizzleLinearImage(gpu_memory, gpu_addr, info, guest_size_bytes, output);
}
const LevelInfo level_info = MakeLevelInfo(info);
const s32 num_layers = info.resources.layers;
diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h
index d91ee74c52..fa12cd1c4d 100644
--- a/src/video_core/texture_cache/util.h
+++ b/src/video_core/texture_cache/util.h
@@ -103,6 +103,11 @@ void ExecuteUnswizzlePlan(Tegra::MemoryManager& gpu_memory, const UnswizzlePlan&
Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
std::span input, std::span output);
+/// Read a linear guest image directly into the staging destination.
+[[nodiscard]] boost::container::small_vector UnswizzleLinearImage(
+ Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
+ size_t guest_size_bytes, std::span output);
+
/// For a single-layer 2D block-linear image of 1x1-texel blocks: the host sparse blocks
/// (tile_width x tile_height texels) that the guest byte ranges (offset, size) touch, merged into
/// runs along x. Levels from tail_level on (the host's mip tail) come whole.