aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/engines
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 21:14:39 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 21:14:39 -0400
commit1b7c637c615d01ab23673b7297a3d6d6e8174f2a (patch)
tree8a4b89945a39bbb5e47e3e84855325ecb280aebc /src/video_core/engines
parentb40ad217ab829226841cb71f6e4c5cb05977a225 (diff)
video_core: software 2D blits touch only the rows they cover
A blit read the whole destination surface and wrote all of it back, so copying a tile into one layer of a large texture moved the full layer (64-256 MiB) both ways. Read and write back only the rows of blocks (block linear) or the lines (pitch) between the blit's first and last line. On Switch 2 the full write-back also gave every block of a sparse layer memory.
Diffstat (limited to 'src/video_core/engines')
-rw-r--r--src/video_core/engines/sw_blitter/blitter.cpp68
1 files changed, 58 insertions, 10 deletions
diff --git a/src/video_core/engines/sw_blitter/blitter.cpp b/src/video_core/engines/sw_blitter/blitter.cpp
index 8bcc2f7a76..e08204f223 100644
--- a/src/video_core/engines/sw_blitter/blitter.cpp
+++ b/src/video_core/engines/sw_blitter/blitter.cpp
@@ -5,6 +5,7 @@
#include <cmath>
#include <vector>
+#include "common/alignment.h"
#include "common/scratch_buffer.h"
#include "video_core/engines/sw_blitter/blitter.h"
#include "video_core/engines/sw_blitter/converter.h"
@@ -125,6 +126,46 @@ void ProcessPitchLinear(std::span<const u8> input, std::span<u8> output, size_t
}
}
+struct SubSurface {
+ u64 offset; ///< From the surface's address
+ size_t size; ///< Bytes
+ u32 height; ///< Lines the part has
+ u32 origin_y; ///< The blit's first line in it
+};
+
+// The part of a surface a blit covers: the rows of blocks (block linear) or the lines (pitch) from
+// its first line to its last. A tile copied into a layer of a large texture used to read and write
+// back the whole layer (64-256 MiB), which also gave every block of a Switch 2 sparse layer memory.
+SubSurface CoveredRows(const Fermi2D::Surface& surface, u32 bytes_per_pixel, s32 x0, s32 y0,
+ u32 extent_x, u32 extent_y, size_t surface_size) {
+ const SubSurface whole{0, surface_size, surface.height, static_cast<u32>(y0)};
+ if (x0 < 0 || y0 < 0 || extent_y == 0) {
+ return whole;
+ }
+ const u64 first_line = static_cast<u32>(y0);
+ if (surface.linear == Fermi2D::MemoryLayout::BlockLinear) {
+ if (surface.depth != 1) {
+ return whole;
+ }
+ const u32 block_height = surface.block_height;
+ const u32 block_depth = surface.block_depth;
+ const u32 lines_shift = GOB_SIZE_Y_SHIFT + block_height;
+ const u64 row_size =
+ u64{Common::AlignUpLog2(surface.width * bytes_per_pixel, GOB_SIZE_X_SHIFT)}
+ << (lines_shift + block_depth);
+ const u64 first_row = first_line >> lines_shift;
+ const u64 rows = ((first_line + extent_y - 1) >> lines_shift) - first_row + 1;
+ const SubSurface part{first_row * row_size, static_cast<size_t>(rows * row_size),
+ static_cast<u32>(rows << lines_shift),
+ static_cast<u32>(first_line - (first_row << lines_shift))};
+ return part.offset + part.size <= surface_size ? part : whole;
+ }
+ const u64 size = (extent_y - 1) * u64{surface.pitch} +
+ (static_cast<u64>(x0) + extent_x) * bytes_per_pixel;
+ const SubSurface part{first_line * surface.pitch, static_cast<size_t>(size), extent_y, 0};
+ return part.offset + part.size <= surface_size ? part : whole;
+}
+
} // namespace
struct SoftwareBlitEngine::BlitEngineImpl {
@@ -161,9 +202,11 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
const auto src_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const auto dst_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(dst.format));
const size_t src_size = get_surface_size(src, src_bytes_per_pixel);
+ const SubSurface src_part = CoveredRows(src, src_bytes_per_pixel, config.src_x0, config.src_y0,
+ src_extent_x, src_extent_y, src_size);
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::SafeRead> tmp_buffer(
- memory_manager, src.Address(), src_size, &impl->tmp_buffer);
+ memory_manager, src.Address() + src_part.offset, src_part.size, &impl->tmp_buffer);
const size_t src_copy_size = src_extent_x * src_extent_y * src_bytes_per_pixel;
const size_t dst_copy_size = dst_extent_x * dst_extent_y * dst_bytes_per_pixel;
@@ -202,12 +245,13 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
impl->dst_buffer.resize_destructive(dst_copy_size);
if (src.linear == Fermi2D::MemoryLayout::BlockLinear) {
- UnswizzleSubrect(impl->src_buffer, tmp_buffer, src_bytes_per_pixel, src.width, src.height,
- src.depth, config.src_x0, config.src_y0, src_extent_x, src_extent_y,
- src.block_height, src.block_depth, src_extent_x * src_bytes_per_pixel);
+ UnswizzleSubrect(impl->src_buffer, tmp_buffer, src_bytes_per_pixel, src.width,
+ src_part.height, src.depth, config.src_x0, src_part.origin_y,
+ src_extent_x, src_extent_y, src.block_height, src.block_depth,
+ src_extent_x * src_bytes_per_pixel);
} else {
ProcessPitchLinear<false>(tmp_buffer, impl->src_buffer, src_extent_x, src_extent_y,
- src.pitch, config.src_x0, config.src_y0, src_bytes_per_pixel);
+ src.pitch, config.src_x0, src_part.origin_y, src_bytes_per_pixel);
}
// Conversion Phase
@@ -222,16 +266,20 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
}
const size_t dst_size = get_surface_size(dst, dst_bytes_per_pixel);
+ const SubSurface dst_part = CoveredRows(dst, dst_bytes_per_pixel, config.dst_x0, config.dst_y0,
+ dst_extent_x, dst_extent_y, dst_size);
Tegra::Memory::GpuGuestMemoryScoped<u8, Tegra::Memory::GuestMemoryFlags::SafeReadWrite>
- tmp_buffer2(memory_manager, dst.Address(), dst_size, &impl->tmp_buffer);
+ tmp_buffer2(memory_manager, dst.Address() + dst_part.offset, dst_part.size,
+ &impl->tmp_buffer);
if (dst.linear == Fermi2D::MemoryLayout::BlockLinear) {
- SwizzleSubrect(tmp_buffer2, impl->dst_buffer, dst_bytes_per_pixel, dst.width, dst.height,
- dst.depth, config.dst_x0, config.dst_y0, dst_extent_x, dst_extent_y,
- dst.block_height, dst.block_depth, dst_extent_x * dst_bytes_per_pixel);
+ SwizzleSubrect(tmp_buffer2, impl->dst_buffer, dst_bytes_per_pixel, dst.width,
+ dst_part.height, dst.depth, config.dst_x0, dst_part.origin_y, dst_extent_x,
+ dst_extent_y, dst.block_height, dst.block_depth,
+ dst_extent_x * dst_bytes_per_pixel);
} else {
ProcessPitchLinear<true>(impl->dst_buffer, tmp_buffer2, dst_extent_x, dst_extent_y,
- dst.pitch, config.dst_x0, config.dst_y0,
+ dst.pitch, config.dst_x0, dst_part.origin_y,
static_cast<size_t>(dst_bytes_per_pixel));
}
return true;