aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/texture_cache/texture_cache.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/texture_cache/texture_cache.h')
-rw-r--r--src/video_core/texture_cache/texture_cache.h174
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.