aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/buffer_cache
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-11-03 00:03:26 +0100
committercirakg <cirakg@cirakg.xyz>2026-11-03 00:03:26 +0100
commit844ebf0b09c3a8d9641ff095e4287b3d493181dc (patch)
tree7ed9270c599984b5f8299303ce0daf3e5cde4971 /src/video_core/buffer_cache
parentb504f4e82bb0b675d953d14b67e307eac58226d3 (diff)
parent8362c44ff835d7be73d2adad9ce3e2d4467a6aeb (diff)
Merge upstream Djihads80/Eglantine nx2 into fix/nsp-switch2-detection
Diffstat (limited to 'src/video_core/buffer_cache')
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h33
-rw-r--r--src/video_core/buffer_cache/uniform_stream_reuse.h121
2 files changed, 150 insertions, 4 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 2d90a949c3..40369b16ef 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -16,6 +16,7 @@
#include "common/range_sets.inc"
#include "video_core/buffer_cache/buffer_cache_base.h"
+#include "video_core/buffer_cache/uniform_stream_reuse.h"
#include "video_core/guest_memory.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
#include "video_core/nx2.h"
@@ -29,6 +30,9 @@ using Core::DEVICE_PAGESIZE;
template <class P>
BufferCache<P>::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, Runtime& runtime_)
: runtime{runtime_}, device_memory{device_memory_}, memory_tracker{device_memory} {
+ // A new runtime has a new scheduler (its ticks restart) and a new stream buffer: whatever the
+ // stream uniform reuse remembers refers to the old ones. Before the early return below.
+ VideoCommon::ResetUniformStreamReuse<Runtime>();
// Ensure the first slot is used for the null buffer
void(slot_buffers.insert(runtime, NullBufferParams{}));
gpu_modified_ranges.Clear();
@@ -232,8 +236,17 @@ std::optional<VideoCore::RasterizerDownloadArea> BufferCache<P>::GetFlushArea(DA
};
area->preemtive = !IsRegionGpuModified(device_addr_start_aligned,
device_addr_end_aligned - device_addr_start_aligned);
- memory_tracker.MarkRegionAsPreflushable(device_addr_start_aligned,
- device_addr_end_aligned - device_addr_start_aligned);
+ // Mark only data that the GPU wrote. A mark stays for the session and sends later GPU writes
+ // in the range to a download at each fence.
+ if (!area->preemtive) {
+ memory_tracker.MarkRegionAsPreflushable(device_addr_start_aligned,
+ device_addr_end_aligned - device_addr_start_aligned);
+ } else {
+ // The core keeps this area and has no mark. The next GPU write here must arm the range
+ // again, which ends the area and sets the trap.
+ read_trapped_ranges.Subtract(device_addr_start_aligned,
+ device_addr_end_aligned - device_addr_start_aligned);
+ }
return area;
}
@@ -1114,8 +1127,20 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index;
channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
// Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan
- const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
- device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
+ bool streamed = false;
+ if constexpr (!IS_OPENGL && VideoCommon::StreamUniformReusable<Runtime>) {
+ // The same bytes as this slot's previous bind, in the same command buffer: bind that
+ // stream allocation again instead of allocating and copying (uniform_stream_reuse.h).
+ // EDEN_NX2_UBOREUSE=0 turns it off.
+ if (VideoCommon::UniformStreamReuseEnabled()) {
+ streamed = VideoCommon::BindSmallUniformReusing(
+ runtime, device_memory, stage, index, binding_index, device_addr, size);
+ }
+ }
+ if (!streamed) {
+ const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
+ device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
+ }
return;
}
// Classic cached path
diff --git a/src/video_core/buffer_cache/uniform_stream_reuse.h b/src/video_core/buffer_cache/uniform_stream_reuse.h
new file mode 100644
index 0000000000..7ba31c4717
--- /dev/null
+++ b/src/video_core/buffer_cache/uniform_stream_reuse.h
@@ -0,0 +1,121 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <concepts>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <span>
+#include <type_traits>
+
+#include "common/common_types.h"
+
+namespace VideoCommon {
+
+/// What a buffer cache runtime offers for stream uniform reuse.
+/// - StreamUniformRef: a small trivially copyable description of one stream allocation (the
+/// host buffer, its offset and whatever else the runtime needs to add it to a descriptor
+/// payload again, e.g. the device address of the descriptor buffer path).
+/// - LastStreamUniformRef(): the allocation made by the latest BindMappedUniformBuffer call.
+/// - RebindStreamUniform(ref, size): adds `ref` to the descriptor payload exactly as
+/// BindMappedUniformBuffer added the allocation the first time.
+template <class R>
+concept StreamUniformReusable = requires(R& runtime, u32 size) {
+ typename R::StreamUniformRef;
+ requires std::is_trivially_copyable_v<typename R::StreamUniformRef>;
+ { runtime.LastStreamUniformRef() } -> std::same_as<typename R::StreamUniformRef>;
+ runtime.RebindStreamUniform(runtime.LastStreamUniformRef(), size);
+ { runtime.CurrentTick() } -> std::convertible_to<u64>;
+};
+
+/// EDEN_NX2_UBOREUSE=0 turns the reuse off (every small uniform buffer is copied, as before).
+[[nodiscard]] inline bool UniformStreamReuseEnabled() noexcept {
+ static const bool enabled = [] {
+ const char* const value = std::getenv("EDEN_NX2_UBOREUSE");
+ return value == nullptr || value[0] != '0';
+ }();
+ return enabled;
+}
+
+template <class Ref>
+class UniformStreamReuse {
+public:
+ static constexpr u32 MaxBytes = 4096; // BufferCache's DEFAULT_SKIP_CACHE_SIZE
+ static constexpr size_t NumStages = 5;
+ static constexpr size_t NumSlots = 18; // Maxwell constant buffers per stage
+
+ struct Slot {
+ Ref ref{};
+ u64 tick{};
+ u32 size{}; // 0: nothing streamed yet
+ std::array<u8, MaxBytes> bytes{};
+ };
+
+ void Reset() noexcept {
+ for (auto& stage : slots) {
+ for (Slot& slot : stage) {
+ slot.size = 0;
+ slot.tick = 0;
+ }
+ }
+ }
+
+ std::array<std::array<Slot, NumSlots>, NumStages> slots{};
+ std::array<u8, MaxBytes> scratch{};
+};
+
+template <class Ref>
+[[nodiscard]] inline UniformStreamReuse<Ref>& UniformStreamReuseState() noexcept {
+ static UniformStreamReuse<Ref> state;
+ return state;
+}
+
+/// Forgets every slot. Call it when a buffer cache (and so its runtime, scheduler and stream
+/// buffer) is created: ticks restart with a new scheduler and the old references are dead.
+template <class Runtime>
+inline void ResetUniformStreamReuse() noexcept {
+ if constexpr (StreamUniformReusable<Runtime>) {
+ UniformStreamReuseState<typename Runtime::StreamUniformRef>().Reset();
+ }
+}
+
+/// Binds the small uniform buffer at `device_addr` for (stage, cbuf `index`) through the stream
+/// buffer, reusing the slot's previous allocation when the bytes and the command buffer are the
+/// same. Returns false (nothing bound) when the buffer does not qualify, so the caller binds it
+/// the classic way.
+template <class Runtime, class DeviceMemory>
+[[nodiscard]] bool BindSmallUniformReusing(Runtime& runtime, DeviceMemory& device_memory,
+ size_t stage, u32 index, u32 binding_index,
+ u64 device_addr, u32 size) {
+ using Reuse = UniformStreamReuse<typename Runtime::StreamUniformRef>;
+ if (size == 0 || size > Reuse::MaxBytes || stage >= Reuse::NumStages ||
+ index >= Reuse::NumSlots) {
+ return false;
+ }
+ Reuse& reuse = UniformStreamReuseState<typename Runtime::StreamUniformRef>();
+ typename Reuse::Slot& slot = reuse.slots[stage][index];
+ u8* const scratch = reuse.scratch.data();
+ device_memory.ReadBlockUnsafe(device_addr, scratch, size);
+
+ const bool same_bytes =
+ slot.size == size && std::memcmp(slot.bytes.data(), scratch, size) == 0;
+ if (same_bytes && slot.tick == runtime.CurrentTick()) {
+ runtime.RebindStreamUniform(slot.ref, size);
+ return true;
+ }
+ const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
+ std::memcpy(span.data(), scratch, size);
+ slot.ref = runtime.LastStreamUniformRef();
+ // After the allocation: the tick the stream region is tagged with is the one current now.
+ slot.tick = runtime.CurrentTick();
+ if (!same_bytes) {
+ slot.size = size;
+ std::memcpy(slot.bytes.data(), scratch, size);
+ }
+ return true;
+}
+
+} // namespace VideoCommon