diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 16:21:45 -0500 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 16:21:45 -0500 |
| commit | a41d11108b6478ad862fc7fcac36ab51c83b8602 (patch) | |
| tree | 2c16c13764e6ef2b48c7327055fde64d7af2cd37 /src/video_core/texture_cache/texture_cache.h | |
| parent | 01effa81ab0e151f8cfd674bf4da952d24645943 (diff) | |
| parent | ac22c4087d9a6380c1937337530a5d4e54808ad1 (diff) | |
Merge remote-tracking branch 'origin/nx2' into merge/nx2-experimental-1101
# Conflicts:
# src/core/loader/deconstructed_rom_directory.cpp
Diffstat (limited to 'src/video_core/texture_cache/texture_cache.h')
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 174 |
1 files changed, 152 insertions, 22 deletions
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 63abffed0f..33ca051482 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -225,19 +225,24 @@ void TextureCache<P>::TickFrame() { // VRAM (another game: 7.0 -> 6.4 GB), and collecting only past it means the driver pages // textures out. It is also the room to use: Eden's device memory figure is capped near // 7 GB, which on a 12 GB card (11.2 GB budget) kept title 1 evicting textures with 3 GB - // unused. EDEN_NX2_TC_*_MIB overrides still win. + // unused. Both thresholds stay under it with a margin (1/8 of it, at most 1 GiB): past it + // allocations fail and images end up unbacked (garbage textures), and Windows shrinks the + // budget as the card fills (16 GB card: 13.5 GB left mid-race), hence the re-read every 30 + // frames. EDEN_NX2_TC_*_MIB overrides still win. if constexpr (requires { runtime.GetDeviceMemoryBudget(); }) { - if (native_thresholds_applied && budget_check_frames++ % 60 == 0) { + if (native_thresholds_applied && budget_check_frames++ % 30 == 0) { const u64 budget = runtime.GetDeviceMemoryBudget(); if (budget > 2_GiB) { const u64 old_critical = critical_memory; - critical_memory = budget - 768_MiB; - expected_memory = budget - 1536_MiB; - // Log only real changes (the budget moves by a few MiB all the time). + const u64 margin = (std::min)(budget / 8, 1_GiB); + critical_memory = budget - margin; + expected_memory = budget - 2 * margin; + // Log the first values and real changes (the budget moves by a few MiB all + // the time). const u64 change = critical_memory > old_critical ? critical_memory - old_critical : old_critical - critical_memory; - ApplyThresholdOverrides(change >= 256_MiB); + ApplyThresholdOverrides(budget_check_frames == 1 || change >= 256_MiB); } } } @@ -724,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; @@ -1311,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; @@ -1396,6 +1409,77 @@ 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>::BackSparseTilesWhole(Image& image) { + if constexpr (requires { &Image::BackTilesWhole; }) { + const u64 bound_bytes = ImageMemoryBytes(image); + const bool backed = image.BackTilesWhole(); + if (True(image.flags & ImageFlagBits::Registered)) { + total_used_memory += ImageMemoryBytes(image) - bound_bytes; + } + return backed; + } 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); @@ -1434,7 +1518,8 @@ void TextureCache<P>::RefreshSparseLayers(Image& image) { } } image.dirty_layers.clear(); - const std::vector<u32> bound = layers.empty() ? std::vector<u32>{} : image.BindLayers(layers); + const std::vector<u32> bound = + layers.empty() ? std::vector<u32>{} : BindSparseLayers(image, layers); if (bound.empty()) { runtime.TransitionImageLayout(image); return; @@ -1445,6 +1530,21 @@ void TextureCache<P>::RefreshSparseLayers(Image& image) { } template <class P> +std::vector<u32> TextureCache<P>::BindSparseLayers(Image& image, std::span<const u32> layers) { + if constexpr (requires { &Image::BindLayers; }) { + const u64 bound_bytes = ImageMemoryBytes(image); + std::vector<u32> bound = image.BindLayers(layers); + // 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 {}; + } +} + +template <class P> void TextureCache<P>::UploadLayers(Image& image, std::span<const u32> layers) { if constexpr (requires { &Runtime::AccelerateImageUploadLayers; }) { if (False(image.flags & ImageFlagBits::AcceleratedUpload)) { @@ -1714,6 +1814,26 @@ u64 TextureCache<P>::GetScaledImageSizeBytes(const ImageBase& image) { } template <class P> +u64 TextureCache<P>::ImageMemoryBytes(const Image& image) const { + if (True(image.flags & ImageFlagBits::Unbacked)) { + // No device memory but what BindLayers gave a SparseLayers image: at its full size (GBs + // for title 1's sparse terrain images), deleting it would look to the collector like + // freeing that much. + if constexpr (requires { image.SparseBoundBytes(); }) { + return image.SparseBoundBytes(); + } + return 0; + } + u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); + if ((IsPixelFormatASTC(image.info.format) && + True(image.flags & ImageFlagBits::AcceleratedUpload)) || + True(image.flags & ImageFlagBits::Converted)) { + tentative_size = TranscodedAstcSize(tentative_size, image.info.format); + } + return Common::AlignUp(tentative_size, 1024); +} + +template <class P> void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) { UNIMPLEMENTED_IF(False(image.flags & ImageFlagBits::Converted)); LOG_DEBUG(HW_GPU, "Queuing async texture decode {:#x}", image.gpu_addr); @@ -2613,13 +2733,7 @@ void TextureCache<P>::RegisterImage(ImageId image_id) { ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Trying to register an already registered image"); image.flags |= ImageFlagBits::Registered; - u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); - if ((IsPixelFormatASTC(image.info.format) && - True(image.flags & ImageFlagBits::AcceleratedUpload)) || - True(image.flags & ImageFlagBits::Converted)) { - tentative_size = TranscodedAstcSize(tentative_size, image.info.format); - } - total_used_memory += Common::AlignUp(tentative_size, 1024); + total_used_memory += ImageMemoryBytes(slot_images[image_id]); image.lru_index = lru_cache.Insert(image_id, frame_tick); ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { @@ -2795,13 +2909,8 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) { if (image.HasScaled()) { total_used_memory -= GetScaledImageSizeBytes(image); } - u64 tentative_size = (std::max)(image.guest_size_bytes, image.unswizzled_size_bytes); - if ((IsPixelFormatASTC(image.info.format) && - True(image.flags & ImageFlagBits::AcceleratedUpload)) || - True(image.flags & ImageFlagBits::Converted)) { - tentative_size = TranscodedAstcSize(tentative_size, image.info.format); - } - total_used_memory -= Common::AlignUp(tentative_size, 1024); + // TickFrame sets the total from the driver, which can be under the sum of these estimates. + total_used_memory -= (std::min)(total_used_memory, ImageMemoryBytes(slot_images[image_id])); const GPUVAddr gpu_addr = image.gpu_addr; const auto alloc_it = image_allocs_table.find(gpu_addr); if (alloc_it == image_allocs_table.end()) { @@ -2995,12 +3104,29 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool } } if (is_modification) { + if constexpr (requires { &Image::BackTilesWhole; }) { + if (True(image.flags & ImageFlagBits::SparseTiles) && image.SparseTilesShareZeroBlock()) { + // The render would write the zeroed block all empty blocks share: each gets memory + // of its own first, and the parts that hold data are loaded again. + LOG_INFO(HW_GPU, "GPU write into sparse tiled image {:#x}; backing it whole", + image.gpu_addr); + if (BackSparseTilesWhole(image)) { + image.dirty_ranges.clear(); + RefreshSparseTiles(image); + } else { + LOG_ERROR(HW_GPU, + "Sparse tiled image {:#x}: out of memory backing it whole, the GPU " + "write lands in the shared zeroed block", + image.gpu_addr); + } + } + } if constexpr (requires { &Image::BindLayers; }) { if (True(image.flags & ImageFlagBits::SparseLayers)) { std::vector<u32> layers(static_cast<size_t>(image.info.resources.layers)); std::iota(layers.begin(), layers.end(), 0U); // Layers over the image's memory cap stay unbacked (logged once). - image.BindLayers(layers); + BindSparseLayers(image, layers); } } if (True(image.flags & ImageFlagBits::UnwrittenSparse) && @@ -3033,6 +3159,10 @@ template <class P> void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<ImageCopy> copies) { Image& dst = slot_images[dst_id]; Image& src = slot_images[src_id]; + if (True(dst.flags & ImageFlagBits::UnwrittenSparse) || + True(src.flags & ImageFlagBits::UnwrittenSparse)) { + return; + } if (dst.info.astc_opaque != src.info.astc_opaque) { // BC1 and BC3 blocks differ in size, so the host can't copy between them; both hold the // same guest data, which is uploaded from guest memory instead. |
