diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 00:36:38 -0400 |
|---|---|---|
| committer | kraibse <33869366+kraibse@users.noreply.github.com> | 2026-11-01 17:29:56 +0100 |
| commit | 1c794af56407f324eedad99478388ba0c9cf9ad6 (patch) | |
| tree | 09c4f7e4e892a902c9f2613b63de8668986f5d66 /src/video_core/texture_cache/texture_cache.h | |
| parent | a855e8373f5575fddd172ec7ffc56a8c3904c3fb (diff) | |
texture_cache: back large sparse 2D images by sparse block
Mario Kart World's nine world-sized masks (R8 16384x16384, RG16 and
R16 8192x8192, 170-341 MB each) live in sparse regions the game maps
its own memory into, a few MiB of each. Since a mapped tile counts as
written, the texture cache backed and uploaded each mask in full: about
2.9 GB of device memory, and a whole-mask upload for every tile the
game streamed in.
Such images (single layer, one texel per block, colour, in a sparse
region, 32 MiB or more) are now unbacked SparseTiles images. The
texture cache records the guest ranges written to them; Image::BindTiles
gives the host sparse blocks under those ranges memory, and only those
rectangles are unswizzled, a row of whole GOB blocks at a time, and
uploaded. A new image starts with the tiles the game maps there (nx2:gpu
216 now records them per 64 KiB), or the written blocks where it maps
none. Unmapped tiles keep their blocks and are loaded as zeros.
Checked against the full unswizzle on 400 ordinary game images (row
padding, mip chains, odd sizes): no row differs. Not on AMD drivers,
which cannot sample images with unbound memory.
Mario Kart World title screen, 150 s, against backing the masks whole:
dedicated VRAM 6.6 -> 6.2 GB, shared 1.0 -> 0.3 GB, peak 8.2 -> 6.9 GB.
Diffstat (limited to 'src/video_core/texture_cache/texture_cache.h')
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 3b783fc6c6..4b93cd135b 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -729,6 +729,10 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) { image.flags |= ImageFlagBits::CpuModified | ImageFlagBits::Remapped; return; } + if (True(image.flags & ImageFlagBits::SparseTiles)) { + MarkRangeWritten(image, cpu_addr, size); + return; + } if (UploadsByLayer(image)) { MarkLayersWritten(image, cpu_addr, size); return; @@ -1316,6 +1320,10 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) { RefreshSparseLayers(image); return; } + if (True(image.flags & ImageFlagBits::SparseTiles)) { + RefreshSparseTiles(image); + return; + } if (!image.dirty_layers.empty() && UploadWrittenLayers(image)) { runtime.InsertUploadMemoryBarrier(); return; @@ -1401,6 +1409,63 @@ void TextureCache<P>::MarkLayersWritten(Image& image, DAddr cpu_addr, size_t siz } template <class P> +void TextureCache<P>::MarkRangeWritten(Image& image, DAddr cpu_addr, size_t size) { + if (True(image.flags & ImageFlagBits::CpuModified) && image.dirty_ranges.empty()) { + return; // all of it is dirty already + } + const DAddr begin = (std::max<DAddr>)(cpu_addr, image.cpu_addr); + const DAddr end = (std::min<DAddr>)(cpu_addr + size, image.cpu_addr + image.guest_size_bytes); + if (end <= begin) { + return; + } + image.flags |= ImageFlagBits::CpuModified; + image.dirty_ranges.emplace_back(begin - image.cpu_addr, end - begin); +} + +template <class P> +void TextureCache<P>::RefreshSparseTiles(Image& image) { + if constexpr (requires { &Image::BindTiles; }) { + // The ranges written since the last upload, or, for a new image, all of it that can hold + // data (the tiles the game maps there, or the blocks written). + std::vector<std::pair<u64, u64>> ranges = std::move(image.dirty_ranges); + image.dirty_ranges.clear(); + if (ranges.empty()) { + ranges = Tegra::Nx2SparseDataRanges(image.gpu_addr, image.guest_size_bytes); + } + const std::vector<LevelRect> rects = + SparseTileRects(image.info, ranges, image.SparseTileWidth(), image.SparseTileHeight(), + image.SparseTailLevel()); + if (rects.empty() || !BindSparseTiles(image, rects)) { + runtime.TransitionImageLayout(image); + return; + } + const size_t bytes = UnswizzledRectsBytes(image.info, rects); + auto staging = runtime.UploadStagingBuffer(bytes); + const std::vector<BufferImageCopy> copies = + UnswizzleImageRects(*gpu_memory, image.gpu_addr, image.info, rects, staging.mapped_span); + image.UploadMemory(staging, copies); + runtime.InsertUploadMemoryBarrier(); + ++images_uploaded; + uploaded_bytes += bytes; + } +} + +template <class P> +bool TextureCache<P>::BindSparseTiles(Image& image, std::span<const LevelRect> rects) { + if constexpr (requires { &Image::BindTiles; }) { + const u64 bound_bytes = ImageMemoryBytes(image); + const bool bound = image.BindTiles(rects); + // RegisterImage counts what an image has bound by then; later binds are added here. + if (True(image.flags & ImageFlagBits::Registered)) { + total_used_memory += ImageMemoryBytes(image) - bound_bytes; + } + return bound; + } else { + return false; + } +} + +template <class P> bool TextureCache<P>::UploadWrittenLayers(Image& image) { if constexpr (requires { &Runtime::AccelerateImageUploadLayers; }) { const u32 num_layers = static_cast<u32>(image.info.resources.layers); |
