From 808feaf3bf34b3c108ff30baae1d2261f216f4f3 Mon Sep 17 00:00:00 2001 From: Duck912 <350528965+Duck912@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:42:19 -0500 Subject: video_core: keep root tables and bind table 0 as constant bank 17 The 3D methods 0x504/0x508 write eight 256-byte root tables, which shaders read as constant banks 24-31; NVN2 keeps push constants in table 0 (bank 17 after the Ampere lowering). Fire Emblem: Fortune's Weave keeps its material constants there and never binds bank 17, and the blit constants pass replaced those reads with render-target sizes, flooding its 3D scenes red. Maxwell3D now keeps the tables, the buffer cache streams table 0 as bank 17 when the game has not bound one, and the blit constants pass leaves shaders that read bank 17 outside its parameter block alone. Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com> --- src/video_core/buffer_cache/buffer_cache.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/video_core/buffer_cache/buffer_cache.h') diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e90af3214d..2fc74cb6c8 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -994,6 +994,26 @@ void BufferCache
::BindHostGraphicsUniformBuffers(size_t stage) {
template ::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind) {
++channel_state->uniform_cache_shots[0];
+ // Bank 24 (root table 0, NVN2's push constants) is read from bank 17, which such games do not
+ // bind: stream the table's current words as that buffer.
+ if (index == 17 && maxwell3d->root_tables.loaded && channel_state->uniform_buffers[stage][index].size == 0) {
+ const auto& words = maxwell3d->root_tables.tables[0];
+ const u32 size = (*channel_state->uniform_buffer_sizes)[stage][index];
+ const u32 copied = (std::min)(size, static_cast ::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
u32 size) {
const std::optional ::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr
template ::DisableGraphicsUniformBuffer(size_t stage, u32 index) {
- channel_state->uniform_buffers[stage][index] = NULL_BINDING;
+ auto& current = channel_state->uniform_buffers[stage][index];
+ if (current.device_addr == 0 && current.size == 0 && !current.buffer_id) [[likely]] {
+ return;
+ }
+ current = NULL_BINDING;
}
template ::UpdateDrawIndirect() {
template ::UpdateUniformBuffers(size_t stage) {
+ if (channel_state->enabled_uniform_buffer_masks[stage] == 0) [[likely]] {
+ return;
+ }
ForEachEnabledBit(channel_state->enabled_uniform_buffer_masks[stage], [&](u32 index) {
Binding& binding = channel_state->uniform_buffers[stage][index];
if (binding.buffer_id) {
@@ -1531,6 +1542,9 @@ void BufferCache ::UpdateUniformBuffers(size_t stage) {
template ::UpdateStorageBuffers(size_t stage) {
+ if (channel_state->enabled_storage_buffers[stage] == 0) [[likely]] {
+ return;
+ }
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
// Resolve buffer
Binding& binding = channel_state->storage_buffers[stage][index];
@@ -1543,6 +1557,9 @@ void BufferCache ::UpdateStorageBuffers(size_t stage) {
template ::UpdateTextureBuffers(size_t stage) {
+ if (channel_state->enabled_texture_buffers[stage] == 0) [[likely]] {
+ return;
+ }
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
Binding& binding = channel_state->texture_buffers[stage][index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
--
cgit v1.2.3
From a407703f6d29a5ab73328b4ededb12cf29b2758d Mon Sep 17 00:00:00 2001
From: Whovian64 <86082882+whovian64@users.noreply.github.com>
Date: Mon, 2 Nov 2026 02:45:39 -0500
Subject: Linux and AMD: BC3 flashing on RADV, ASTC decode stalls, lower RAM
usage, a buffer cache crash (#260)
* host_memory: punch holes in the backing on Linux instead of zero filling freed memory
Freeing guest memory zero filled the memfd backing with memset, which
commits every page it touches. fallocate(PUNCH_HOLE) hands the pages back
to the kernel instead, and they read as zero afterwards. Only the partial
pages at the ends are still cleared by hand.
Mario Kart World on Linux (RX 9070 XT, 31 GB): peak RSS 23.1 -> 15.9 GB.
EDEN_PUNCH_HOLE=0 restores the memset.
Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts)
* fiber: don't zero fill the stacks, untouched pages stay unbacked
The value initialised stack arrays were zero filled when each fiber was
made, which commits the whole stack. Left uninitialised, the pages a
fiber never touches stay unbacked. Together with the descriptor ring
change, about 1.2 GB less RSS in Mario Kart World.
Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts)
* vulkan: don't zero fill the descriptor update ring
Same as the fiber stacks: make_unique value initialised the whole ring
for every frame in flight. Entries are always written before they are
read, so allocate it without initialising it.
Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts)
* vulkan: wait for the GPU after an ASTC transcode decode like the other ASTC decodes
TranscodeAstcToBcn (astc_recompression Bc1/Bc3 with GPU decode) was the
only ASTC decode recorded with wait = false. On RADV (RX 9070 XT, Mesa)
Mario Kart World with BC3 flashed heavily all over the screen, while the
uncompressed GPU decode, which waits, did not. CPU decode with BC3 does
not flash either. Waiting after the transcode removes the flashing; full
pipeline barriers around its copy did not, so the wait hides a race
somewhere else rather than fixing one in the transcode itself.
Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts)
* vulkan: submit after an ASTC decode instead of waiting for the GPU by default
Every ASTC decode ended with scheduler.Finish(). Over a 19 minute Mario
Kart World Grand Prix that stalled the GPU thread 30.7 s in total, up to
1.2 s out of every 5 s while new textures stream in (track sections,
race starts), which is where the hitches were. Flushing keeps the
ordering that stops the BC3 flashing on RADV without the CPU waiting:
no flashing in play, and frames over 100 ms went from dozens to 3 in
about 7 minutes. EDEN_NX2_ASTC_FLUSH=0 restores the wait.
Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts)
* texture_cache: over the expected use, evict costly images unused for 1800 frames first
The collector never deletes CostlyLoad images outside aggressive mode, so
once a cup moves on, the previous tracks' textures stay resident and the
cache sits over its expected use deleting the cheap images still in use.
When over the expected use, costly images unused for 1800 frames (half a
minute) now go first.
Mario Kart World Grand Prix on an RX 9070 XT: frames over 20 ms 8.8% ->
4.0%, 58.7 fps average. EDEN_NX2_TC_OLD_AGE= ::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
template ::FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible) {
- if (device_addr == 0) {
+ const u64 page = device_addr >> CACHING_PAGEBITS;
+ if (device_addr == 0 || page >= page_table.size()) {
return NULL_BUFFER_ID;
}
- const u64 page = device_addr >> CACHING_PAGEBITS;
const BufferId buffer_id = page_table[page];
if (buffer_id) {
Buffer& buffer = slot_buffers[buffer_id];
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 691be728d2..cb8dcde42f 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -801,11 +801,11 @@ void ASTCDecoderPass::AssembleInto(const VideoCommon::ImageInfo& info, u32 skip,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier);
});
- // Each decode ends with the GPU thread waiting for the host GPU to go idle. Measured here
- // (count and time per 5 s); EDEN_NX2_ASTC_FLUSH=1 submits without waiting instead.
+ // Each decode ends with a submit to the host GPU. Measured here
+ // (count and time per 5 s); EDEN_NX2_ASTC_FLUSH=0 waits for the host GPU to go idle instead.
static const bool flush_only = [] {
const char* value = std::getenv("EDEN_NX2_ASTC_FLUSH");
- return value != nullptr && value[0] == '1';
+ return value == nullptr || value[0] != '0';
}();
if (!target.wait) {
return;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 4e428229f6..b0e5eacb32 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -4267,7 +4267,7 @@ void TextureCacheRuntime::TranscodeAstcToBcn(
.aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT,
.initialized = false,
.level_view = [&pending](s32 level) { return *pending.views[level]; },
- .wait = false,
+ .wait = true,
},
map, swizzles);
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
index 742a8d9969..29f3ee71a1 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
@@ -20,10 +20,10 @@ UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, size_t frame
bool supports_descriptor_buffer_)
: device{device_}, frame_payload_size{frame_payload_size_},
supports_descriptor_buffer{supports_descriptor_buffer_},
- payload(frame_payload_size_ * FRAMES_IN_FLIGHT)
+ payload(std::make_unique_for_overwrite ::RunGarbageCollector() {
bool high_priority_mode = false;
bool aggressive_mode = false;
+ bool evict_old_costly = false;
u64 ticks_to_destroy = 0;
size_t num_iterations = 0;
const auto Configure = [&](bool allow_aggressive) {
@@ -139,7 +140,8 @@ void TextureCache ::RunGarbageCollector() {
ticks_to_destroy = (std::max)(ticks_to_destroy, min_age);
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
};
- const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) {
+ const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode,
+ &evict_old_costly](ImageId image_id) {
if (num_iterations == 0) {
return true;
}
@@ -149,7 +151,8 @@ void TextureCache ::RunGarbageCollector() {
return false;
}
const bool must_download = IsDownloadable(image) && False(image.flags & ImageFlagBits::BadOverlap);
- if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) {
+ if ((!aggressive_mode && !evict_old_costly && True(image.flags & ImageFlagBits::CostlyLoad)) ||
+ (!high_priority_mode && must_download)) {
return false;
}
if (must_download) {
@@ -176,6 +179,15 @@ void TextureCache ::RunGarbageCollector() {
return false;
};
Configure(false);
+ static const u64 old_age = [] {
+ const char* value = std::getenv("EDEN_NX2_TC_OLD_AGE");
+ return value != nullptr ? std::strtoull(value, nullptr, 10) : 1800ULL;
+ }();
+ if (high_priority_mode && old_age != 0 && frame_tick > old_age) {
+ evict_old_costly = true;
+ lru_cache.ForEachItemBelow(frame_tick - old_age, Cleanup);
+ evict_old_costly = false;
+ }
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
if (total_used_memory >= critical_memory) {
Configure(true);
--
cgit v1.2.3