From 0d5534a2883329183ae8aa89b3fe1c7b67291ece Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:43:01 -0400 Subject: 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. --- src/video_core/texture_cache/image_base.h | 1 + src/video_core/texture_cache/texture_cache.h | 61 +++++++++++++++++++---- src/video_core/texture_cache/texture_cache_base.h | 7 +++ src/video_core/texture_cache/util.cpp | 29 +++++++++++ src/video_core/texture_cache/util.h | 3 ++ 5 files changed, 92 insertions(+), 9 deletions(-) (limited to 'src/video_core/texture_cache') 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
::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(); @@ -181,8 +173,47 @@ void TextureCache
::RunGarbageCollector() {
}
}
+template ::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 ::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 ::QueueAsyncDecode(Image& image, ImageId image_id) {
Tegra::Memory::GpuGuestMemory