aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/texture_cache
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 20:43:01 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 20:43:01 -0400
commit0d5534a2883329183ae8aa89b3fe1c7b67291ece (patch)
treeaabad4260f301a8a52af16e15e7fb76a700d642f /src/video_core/texture_cache
parente4deef6ba4ea76aac997ed3102f8e9801fd170b9 (diff)
ASTC: decode and re-compress to BC1/BC3 on the GPU; texture cache budget
- accelerate_astc=Gpu with astc_recompression=Bc1/Bc3 now decodes ASTC with the GPU decoder into a temporary RGBA8 image (dedicated memory, freed once the GPU is past it) and compresses it with a new compute shader (bc_encoder.comp: bounding-box endpoints along the colours' covariance, inset 1/16, nearest palette entry; BC3 alpha ramp; BC1 punch-through), then copies the blocks into the image. No CPU decode, so streamed textures are ready the frame they are first used instead of showing placeholders for seconds; the decode pass no longer waits for the GPU on this path. - The CPU path (CpuAsynchronous) fills a new image with zero blocks before its first decode instead of showing stale VRAM, and the transcode pool is a quarter of the hardware threads. - Switch 2 titles: collector thresholds near the device budget, re-read every 60 frames from VK_EXT_memory_budget (another game shrank it from 7.0 to 5.4 GB). - EDEN_NX2_HIDE_WINDOW=0 now leaves the window visible.
Diffstat (limited to 'src/video_core/texture_cache')
-rw-r--r--src/video_core/texture_cache/image_base.h1
-rw-r--r--src/video_core/texture_cache/texture_cache.h61
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h7
-rw-r--r--src/video_core/texture_cache/util.cpp29
-rw-r--r--src/video_core/texture_cache/util.h3
5 files changed, 92 insertions, 9 deletions
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index d0af53631d..47bab9617a 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -49,6 +49,7 @@ enum class ImageFlagBits : u32 {
/// The host image keeps only mip levels >= host_level_offset (its decoded copy would not fit
/// in VRAM); uploads skip the rest, downloads are never done.
TrimmedLevels = 1 << 19,
+ ZeroFilled = 1 << 20, ///< Cleared to zero blocks before its first asynchronous decode.
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index fbcd3eefbb..75df31dc27 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -80,15 +80,7 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
minimum_memory = 0;
}
- // EDEN_NX2_TC_EXPECTED_MIB / EDEN_NX2_TC_CRITICAL_MIB override the collector's thresholds.
- for (const auto& [name, target] : {std::pair{"EDEN_NX2_TC_EXPECTED_MIB", &expected_memory},
- std::pair{"EDEN_NX2_TC_CRITICAL_MIB", &critical_memory}}) {
- if (const char* value = std::getenv(name)) {
- *target = std::strtoull(value, nullptr, 10) << 20;
- }
- }
- LOG_INFO(HW_GPU, "Texture cache thresholds: expected {} MiB, critical {} MiB, minimum {} MiB",
- expected_memory >> 20, critical_memory >> 20, minimum_memory >> 20);
+ ApplyThresholdOverrides();
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
@@ -182,7 +174,46 @@ void TextureCache<P>::RunGarbageCollector() {
}
template <class P>
+void TextureCache<P>::ApplyThresholdOverrides() {
+ // EDEN_NX2_TC_EXPECTED_MIB / EDEN_NX2_TC_CRITICAL_MIB override the collector's thresholds.
+ for (const auto& [name, target] : {std::pair{"EDEN_NX2_TC_EXPECTED_MIB", &expected_memory},
+ std::pair{"EDEN_NX2_TC_CRITICAL_MIB", &critical_memory}}) {
+ if (const char* value = std::getenv(name)) {
+ *target = std::strtoull(value, nullptr, 10) << 20;
+ }
+ }
+ LOG_INFO(HW_GPU, "Texture cache thresholds: expected {} MiB, critical {} MiB, minimum {} MiB",
+ expected_memory >> 20, critical_memory >> 20, minimum_memory >> 20);
+}
+
+template <class P>
void TextureCache<P>::TickFrame() {
+ if constexpr (HAS_DEVICE_MEMORY_INFO) {
+ // Switch 2 titles (native tiling) stream far more texture data than Eden's thresholds
+ // were tuned for: title 1's attract loop alone keeps ~5.3 GB resident, so the collector
+ // sat in aggressive mode evicting decoded ASTC (and decoding it again). Total use is
+ // measured from the driver, so collect only near the device budget instead.
+ if (!native_thresholds_applied && gpu_memory != nullptr && gpu_memory->UsesNativeTiling()) {
+ native_thresholds_applied = true;
+ const u64 device_local = runtime.GetDeviceLocalMemory();
+ critical_memory = (std::max)(critical_memory, device_local - 256_MiB);
+ expected_memory = (std::max)(expected_memory, device_local - 1280_MiB);
+ ApplyThresholdOverrides();
+ native_critical_memory = critical_memory;
+ native_expected_memory = expected_memory;
+ }
+ // The budget shrinks when other programs use VRAM (another game: 7.0 -> 6.4 GB), and
+ // collecting only past it means the driver pages textures out. Re-read it now and then.
+ if constexpr (requires { runtime.GetDeviceMemoryBudget(); }) {
+ if (native_thresholds_applied && ++budget_check_frames % 60 == 0) {
+ const u64 budget = runtime.GetDeviceMemoryBudget();
+ if (budget > 2_GiB) {
+ critical_memory = (std::min)(native_critical_memory, budget - 512_MiB);
+ expected_memory = (std::min)(native_expected_memory, budget - 1280_MiB);
+ }
+ }
+ }
+ }
// If we can obtain the memory info, use it instead of the estimate.
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
@@ -1492,6 +1523,18 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
const size_t out_size = MapSizeBytes(image);
+ if (False(image.flags & ImageFlagBits::ZeroFilled)) {
+ // Until the decode lands (a second or more while title 1 streams ~40 textures a second)
+ // a new image showed whatever its memory last held, often other textures. Zero blocks
+ // read as opaque black (BC1) or transparent (BC3) instead.
+ image.flags |= ImageFlagBits::ZeroFilled;
+ auto zero_copies = copies;
+ ConvertedLayout(image.info, zero_copies);
+ auto staging = runtime.UploadStagingBuffer(out_size);
+ std::memset(staging.mapped_span.data(), 0, out_size);
+ image.UploadMemory(staging, zero_copies);
+ runtime.InsertUploadMemoryBarrier();
+ }
auto func = [out_size, copies, info = image.info,
input = std::move(local_unswizzle_data_buffer),
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index f6f083e10a..ce1de8e867 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -126,6 +126,10 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
u64 gc_costly_deletes{};
u64 async_decodes_queued{};
u64 images_created{};
+ bool native_thresholds_applied{};
+ u64 native_critical_memory{};
+ u64 native_expected_memory{};
+ u64 budget_check_frames{};
u64 astc_opaque_images{};
u64 astc_alpha_images{};
std::vector<u8> astc_scan_buffer;
@@ -462,6 +466,9 @@ private:
DAddr virtual_invalid_space{};
+ /// Applies EDEN_NX2_TC_EXPECTED_MIB / EDEN_NX2_TC_CRITICAL_MIB and logs the thresholds.
+ void ApplyThresholdOverrides();
+
/// The CPU address an image at gpu_addr is keyed by: its backing, or a fixed stand-in.
[[nodiscard]] std::optional<DAddr> ImageCpuAddress(GPUVAddr gpu_addr, size_t size) const;
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 7a1a1d89c0..ffc1d59ec3 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -929,6 +929,35 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
return copies;
}
+void ConvertedLayout(const ImageInfo& info, std::span<BufferImageCopy> copies) {
+ // Same offsets and sizes as ConvertImage below.
+ const auto recompression = Settings::values.astc_recompression.GetValue();
+ const bool astc = IsPixelFormatASTC(info.format);
+ u64 output_offset = 0;
+ for (BufferImageCopy& copy : copies) {
+ const Extent3D mip_size = AdjustMipSize(info.size, copy.image_subresource.base_level);
+ const u64 layers = static_cast<u64>(copy.image_subresource.num_layers) * copy.image_extent.depth;
+ u64 size;
+ if (astc && recompression == Settings::AstcRecompression::Uncompressed) {
+ size = u64{copy.image_extent.width} * copy.image_extent.height * layers *
+ BytesPerBlock(PixelFormat::A8B8G8R8_UNORM);
+ } else if (astc) {
+ const u64 bpp_div =
+ recompression == Settings::AstcRecompression::Bc1 || info.astc_opaque ? 2 : 1;
+ size = u64{Common::AlignUp(copy.image_extent.width, 4U)} *
+ Common::AlignUp(copy.image_extent.height, 4U) * layers / bpp_div;
+ } else {
+ size = u64{copy.image_extent.width} * copy.image_extent.height * layers *
+ ConvertedBytesPerBlock(info.format);
+ }
+ copy.buffer_offset = output_offset;
+ copy.buffer_size = size;
+ copy.buffer_row_length = mip_size.width;
+ copy.buffer_image_height = mip_size.height;
+ output_offset += size;
+ }
+}
+
void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
std::span<BufferImageCopy> copies) {
u32 output_offset = 0;
diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h
index 89f85e4e8a..0381d2d24b 100644
--- a/src/video_core/texture_cache/util.h
+++ b/src/video_core/texture_cache/util.h
@@ -72,6 +72,9 @@ struct OverlapResult {
Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
std::span<const u8> input, std::span<u8> output);
+/// Rewrites `copies` (from UnswizzleImage) to the layout ConvertImage produces, without converting.
+void ConvertedLayout(const ImageInfo& info, std::span<BufferImageCopy> copies);
+
void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
std::span<BufferImageCopy> copies);