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
|
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <span>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include "video_core/buffer_cache/uniform_stream_reuse.h"
namespace {
void Check(bool condition, const char* message) {
INFO(message);
REQUIRE(condition);
}
struct Memory {
std::array<u8, 8192> bytes{};
void ReadBlockUnsafe(u64 address, void* destination, u32 size) {
Check(address + size <= bytes.size(), "read outside guest memory");
std::memcpy(destination, bytes.data() + address, size);
}
};
struct Runtime {
struct StreamUniformRef {
size_t allocation;
};
std::vector<std::vector<u8>> allocations;
StreamUniformRef last{}, bound{};
u64 tick{1};
u64 CurrentTick() const {
return tick;
}
StreamUniformRef LastStreamUniformRef() const {
return last;
}
std::span<u8> BindMappedUniformBuffer(size_t, u32, u32 size) {
allocations.emplace_back(size);
last = bound = StreamUniformRef{allocations.size() - 1};
return allocations.back();
}
void RebindStreamUniform(StreamUniformRef ref, u32 size) {
Check(ref.allocation < allocations.size(), "reused a recycled allocation");
Check(allocations[ref.allocation].size() == size, "reused wrong allocation size");
bound = ref;
}
void NextTick() {
++tick;
allocations.clear();
}
};
void CheckBound(const Runtime& runtime, const Memory& memory, u64 address, u32 size) {
const auto& bytes = runtime.allocations.at(runtime.bound.allocation);
Check(bytes.size() == size &&
std::memcmp(bytes.data(), memory.bytes.data() + address, size) == 0,
"bound uniform data differs from guest memory");
}
} // namespace
TEST_CASE("Uniform stream reuse preserves data through mutation and session resets",
"[video_core]") {
using VideoCommon::BindSmallUniformReusing;
Memory memory;
Runtime runtime;
VideoCommon::ResetUniformStreamReuse<Runtime>();
auto bind = [&](size_t stage, u32 slot, u64 address, u32 size) {
Check(BindSmallUniformReusing(runtime, memory, stage, slot, slot, address, size),
"valid small uniform rejected");
CheckBound(runtime, memory, address, size);
};
bind(0, 0, 0, 64);
bind(0, 0, 0, 64);
Check(runtime.allocations.size() == 1, "identical bytes were copied twice");
memory.bytes[7] = 23;
bind(0, 0, 0, 64);
Check(runtime.allocations.size() == 2, "changed bytes reused stale data");
bind(0, 1, 0, 64);
bind(1, 0, 0, 64);
Check(runtime.allocations.size() == 4, "stage or slot shared the wrong allocation");
runtime.NextTick();
bind(0, 0, 0, 64);
Check(runtime.allocations.size() == 1, "a new command buffer reused an old allocation");
VideoCommon::ResetUniformStreamReuse<Runtime>();
bind(0, 0, 0, 64);
Check(runtime.allocations.size() == 2, "session reset retained an old allocation");
bind(4, 17, 0, 4096);
const auto before = runtime.allocations.size();
for (auto [stage, slot, size] :
{std::array<u32, 3>{5, 0, 64}, {0, 18, 64}, {0, 0, 0}, {0, 0, 4097}}) {
Check(!BindSmallUniformReusing(runtime, memory, stage, slot, slot, 0, size),
"invalid uniform range did not use the fallback");
}
Check(runtime.allocations.size() == before, "fallback allocated stream memory");
u32 random = 17;
for (u32 index = 0; index < 10000; ++index) {
random = random * 1664525U + 1013904223U;
if (index % 37 == 0)
runtime.NextTick();
if (index % 503 == 0)
VideoCommon::ResetUniformStreamReuse<Runtime>();
const u32 size = 1 + random % 4096;
const u64 address = (random >> 12) % (memory.bytes.size() - 4096);
if (index % 3 == 0)
memory.bytes[address + random % size] ^= u8{0x5a};
const size_t stage = (random >> 20) % 5;
const u32 slot = (random >> 24) % 18;
bind(stage, slot, address, size);
bind(stage, slot, address, size);
}
}
|