1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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
|