diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 21:14:39 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 21:14:39 -0400 |
| commit | 1b7c637c615d01ab23673b7297a3d6d6e8174f2a (patch) | |
| tree | 8a4b89945a39bbb5e47e3e84855325ecb280aebc /src/video_core/engines | |
| parent | b40ad217ab829226841cb71f6e4c5cb05977a225 (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.cpp | 68 |
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; |
