aboutsummaryrefslogtreecommitdiff
path: root/src/tests/video_core
diff options
context:
space:
mode:
authorDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-02 18:56:59 -0500
committerDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-02 18:56:59 -0500
commit9acd36eb9a291e0631346f5b17d69d0e8eb26cc5 (patch)
tree5142f9867928a2e7f07a6b6b79ca00fa5f900425 /src/tests/video_core
parent11cebf56014a5015db28179a8acc81cad0caf641 (diff)
parent2d81fd5072c060737c18ed4cfc46a04fb8cbb94c (diff)
Merge remote-tracking branch 'origin/nx2' into fe-fsr31
# Conflicts: # src/video_core/renderer_vulkan/blit_image.cpp # src/video_core/renderer_vulkan/blit_image.h
Diffstat (limited to 'src/tests/video_core')
-rw-r--r--src/tests/video_core/descriptor_table.cpp170
-rw-r--r--src/tests/video_core/memory_tracker.cpp38
-rw-r--r--src/tests/video_core/native_macro_fifo.cpp74
-rw-r--r--src/tests/video_core/shader_invalidation.cpp120
-rw-r--r--src/tests/video_core/uniform_stream_reuse.cpp116
-rw-r--r--src/tests/video_core/vertex_repack.cpp33
6 files changed, 534 insertions, 17 deletions
diff --git a/src/tests/video_core/descriptor_table.cpp b/src/tests/video_core/descriptor_table.cpp
new file mode 100644
index 0000000000..8b20564949
--- /dev/null
+++ b/src/tests/video_core/descriptor_table.cpp
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <array>
+#include <chrono>
+#include <cstring>
+#include <iostream>
+#include <limits>
+#include <random>
+#include <catch2/catch_test_macros.hpp>
+#include "video_core/texture_cache/descriptor_table.h"
+
+namespace {
+struct DescriptorMemory {
+ std::array<std::array<u64, 128>, 2> banks{};
+
+ void ReadBlockUnsafe(GPUVAddr address, void* output, size_t size) const {
+ const auto bank = (address - 0x1000) / 0x1000;
+ const auto offset = (address - 0x1000) % 0x1000;
+ std::memcpy(output, reinterpret_cast<const u8*>(banks[bank].data()) + offset, size);
+ }
+};
+} // namespace
+
+TEST_CASE("Descriptor invalidation matches eager refresh across mutations", "[descriptor_table]") {
+ DescriptorMemory memory;
+ VideoCommon::DescriptorTable<u64> table;
+ REQUIRE(table.Synchronize(0x1000, 127));
+ std::array<bool, 128> seen{};
+ std::array<u64, 128> previous{};
+ std::mt19937 random{0x5080};
+ unsigned bank{};
+ u32 limit = 127;
+ for (unsigned i = 0; i < 40000; ++i) {
+ switch (random() % 5) {
+ case 0:
+ table.Invalidate();
+ seen.fill(false);
+ break;
+ case 1:
+ memory.banks[random() % 2][random() % 128] = random();
+ break;
+ case 2: {
+ const auto next_bank = random() % 2;
+ const u32 next_limit = random() % 2 ? 63 : 127;
+ const bool changed = next_bank != bank || next_limit != limit;
+ REQUIRE(table.Synchronize(0x1000 + next_bank * 0x1000, next_limit) == changed);
+ if (changed) {
+ seen.fill(false);
+ }
+ bank = next_bank;
+ limit = next_limit;
+ break;
+ }
+ default: {
+ const u32 index = random() % (limit + 1);
+ const u64 expected = memory.banks[bank][index];
+ const bool changed = !seen[index] || previous[index] != expected;
+ const auto actual = table.Read(memory, index);
+ REQUIRE(actual.first == expected);
+ REQUIRE(actual.second == changed);
+ seen[index] = true;
+ previous[index] = expected;
+ }
+ }
+ }
+}
+
+TEST_CASE("Repeated image deletion cannot reuse stale descriptor view IDs", "[descriptor_table]") {
+ DescriptorMemory memory;
+ std::array<VideoCommon::DescriptorTable<u64>, 5> channels;
+ std::array<std::array<unsigned, 128>, 5> view_ids{};
+ unsigned live_generation = 1;
+ const auto visit = [&](size_t channel, u32 index) {
+ if (channels[channel].Read(memory, index).second) {
+ view_ids[channel][index] = live_generation;
+ }
+ return view_ids[channel][index];
+ };
+ for (size_t channel = 0; channel < channels.size(); ++channel) {
+ REQUIRE(channels[channel].Synchronize(0x1000, 127));
+ for (u32 index = 0; index < 128; ++index) {
+ REQUIRE(visit(channel, index) == live_generation);
+ }
+ }
+ for (unsigned generation = 0; generation < 32; ++generation) {
+ ++live_generation;
+ for (unsigned deletion = 0; deletion < 50; ++deletion) {
+ for (auto& table : channels) {
+ table.Invalidate();
+ }
+ }
+ for (size_t channel = 0; channel < channels.size(); ++channel) {
+ for (u32 index = 0; index < 128; ++index) {
+ REQUIRE(visit(channel, index) == live_generation);
+ REQUIRE_FALSE(channels[channel].Read(memory, index).second);
+ }
+ }
+ }
+}
+
+TEST_CASE("Descriptor limits clear exposed slots after shrink and growth", "[descriptor_table]") {
+ struct AddressMemory {
+ void ReadBlockUnsafe(GPUVAddr address, void* output, size_t size) const {
+ std::memcpy(output, &address, size);
+ }
+ } memory;
+ constexpr std::array<u32, 9> limits{0, 63, 64, 127, 128, 0x7ffff, 0x80000, 0x80001, 0x100000};
+ for (const u32 limit : limits) {
+ VideoCommon::DescriptorTable<u64> table;
+ REQUIRE(table.Synchronize(0x1000, limit));
+ CHECK(table.Read(memory, limit).second);
+ CHECK_FALSE(table.Read(memory, limit).second);
+ table.Refresh(0x1000, 0);
+ table.Invalidate();
+ CHECK(table.Read(memory, 0).second);
+ CHECK_FALSE(table.Read(memory, 0).second);
+ table.Refresh(0x1000, limit);
+ CHECK(table.Read(memory, limit).second);
+ CHECK_FALSE(table.Read(memory, limit).second);
+ table.Invalidate();
+ table.Invalidate();
+ CHECK(table.Read(memory, limit).second);
+ CHECK_FALSE(table.Read(memory, limit).second);
+ }
+ for (const u32 limit :
+ {std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max() - 0x1000}) {
+ VideoCommon::DescriptorTable<u64> table;
+ table.Refresh(0x1000, limit);
+ CHECK(table.Read(memory, 0).second);
+ table.Invalidate();
+ CHECK(table.Read(memory, 0).second);
+ CHECK_FALSE(table.Read(memory, 0).second);
+ }
+}
+
+TEST_CASE("Descriptor invalidation CPU benchmark", "[.][cache_bench]") {
+ DescriptorMemory memory;
+ const auto measure = [&](bool deferred) {
+ std::array<VideoCommon::DescriptorTable<u64>, 5> tables;
+ for (auto& table : tables) {
+ REQUIRE(table.Synchronize(0x1000, 127));
+ }
+ void (*volatile invalidate)(VideoCommon::DescriptorTable<u64>&) =
+ deferred ? +[](VideoCommon::DescriptorTable<u64>& table) { table.Invalidate(); }
+ : +[](VideoCommon::DescriptorTable<u64>& table) {
+ std::ranges::fill(table.read_descriptors, 0);
+ };
+ size_t changed{};
+ const auto start = std::chrono::steady_clock::now();
+ for (unsigned batch = 0; batch < 2000; ++batch) {
+ for (unsigned deletion = 0; deletion < 50; ++deletion) {
+ for (auto& table : tables) {
+ invalidate(table);
+ }
+ }
+ for (auto& table : tables) {
+ changed += table.Read(memory, 0).second;
+ }
+ }
+ const auto ms =
+ std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start)
+ .count();
+ REQUIRE(changed == 10000);
+ return ms;
+ };
+ const double eager = measure(false);
+ const double deferred = measure(true);
+ std::cout << "Descriptor bitmap invalidation: eager=" << eager << " ms deferred=" << deferred
+ << " ms (CPU only, not gameplay FPS)\n";
+}
diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp
index c2236848b5..3ac4cee840 100644
--- a/src/tests/video_core/memory_tracker.cpp
+++ b/src/tests/video_core/memory_tracker.cpp
@@ -30,6 +30,10 @@ class RasterizerInterface {
public:
void UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) {
++update_calls;
+ ApplyPagesCachedCount(addr, size, delta);
+ }
+
+ void ApplyPagesCachedCount(DAddr addr, size_t size, s32 delta) {
calls.emplace_back(addr, size, delta);
const u64 page_start{addr >> Core::DEVICE_PAGEBITS};
const u64 page_end{(addr + size + Core::DEVICE_PAGESIZE - 1) >> Core::DEVICE_PAGEBITS};
@@ -45,7 +49,13 @@ public:
}
void UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta) {
- // TODO: for now assume fine?
+ if (ranges.empty()) {
+ return;
+ }
+ ++update_calls;
+ for (const auto& [addr, size] : ranges) {
+ ApplyPagesCachedCount(addr, size, delta);
+ }
}
[[nodiscard]] size_t UpdateCalls() const noexcept { return update_calls; }
@@ -557,20 +567,32 @@ TEST_CASE("MemoryTracker: Cached write downloads") {
REQUIRE(rasterizer.Count() == 0);
}
-TEST_CASE("MemoryTracker: FlushCachedWrites batching") {
+TEST_CASE("MemoryTracker: cached writes preserve tracking counts") {
RasterizerInterface rasterizer;
std::optional<MemoryTracker> memory_track(rasterizer);
memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+ REQUIRE(rasterizer.UpdateCalls() == 1);
+ REQUIRE(rasterizer.UpdateCallsList().size() == 1);
+ REQUIRE(rasterizer.UpdateCallsList().front() == std::tuple{c, WORD * 2, 1});
+
memory_track->CachedCpuWrite(c + PAGE, PAGE);
memory_track->CachedCpuWrite(c + PAGE * 2, PAGE);
memory_track->CachedCpuWrite(c + PAGE * 4, PAGE);
- REQUIRE(rasterizer.UpdateCalls() == 0);
+ REQUIRE(rasterizer.Count() == 125);
+ REQUIRE(rasterizer.Count(c + PAGE) == 0);
+ REQUIRE(rasterizer.Count(c + PAGE * 2) == 0);
+ REQUIRE(rasterizer.Count(c + PAGE * 3) == 1);
+ REQUIRE(rasterizer.Count(c + PAGE * 4) == 0);
+ const auto update_calls = rasterizer.UpdateCalls();
memory_track->FlushCachedWrites();
- // Now we expect a single batch call (coalesced ranges) to the device memory manager
- REQUIRE(rasterizer.UpdateCalls() == 1);
- const auto& calls = rasterizer.UpdateCallsList();
- REQUIRE(std::get<0>(calls[0]) == c + PAGE);
- REQUIRE(std::get<1>(calls[0]) == PAGE * 3);
+ REQUIRE(rasterizer.UpdateCalls() == update_calls);
+ REQUIRE(rasterizer.Count() == 125);
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 2, PAGE));
+ REQUIRE_FALSE(memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 4, PAGE));
+ memory_track->MarkRegionAsCpuModified(c, WORD * 2);
+ REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("DeviceMemoryManager: UpdatePagesCachedBatch basic") {
diff --git a/src/tests/video_core/native_macro_fifo.cpp b/src/tests/video_core/native_macro_fifo.cpp
new file mode 100644
index 0000000000..e19b146192
--- /dev/null
+++ b/src/tests/video_core/native_macro_fifo.cpp
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <array>
+#include <vector>
+#include <catch2/catch_test_macros.hpp>
+
+#ifdef ARCHITECTURE_x86_64
+#include "video_core/macro_turing_jit.h"
+
+namespace {
+std::array<u32, 3> FifoInstruction(u32 method, bool end) {
+ std::array<u32, 3> bits{};
+ const auto field = [&](u32 offset, u32 count, u32 value) {
+ for (u32 bit = 0; bit < count; ++bit) {
+ bits[(offset + bit) / 32] |= ((value >> bit) & 1) << ((offset + bit) % 32);
+ }
+ };
+ field(0, 1, end);
+ for (u32 lane = 0; lane < 2; ++lane) {
+ field(15 + lane * 36, 5, 24);
+ field(20 + lane * 36, 5, 24);
+ field(25 + lane * 36, 5, 24);
+ }
+ field(30, 16, method);
+ field(82, 3, 5);
+ field(85, 4, 3);
+ return {bits[2], bits[1], bits[0]};
+}
+} // namespace
+
+TEST_CASE("Owned native macro FIFOs preserve loads across DMA appends and repeated calls",
+ "[video_core][native_macro]") {
+ std::vector<u32> code;
+ for (const auto instruction : {FifoInstruction(0x158, false), FifoInstruction(0x200, true),
+ FifoInstruction(0x201, false)}) {
+ code.insert(code.end(), instruction.begin(), instruction.end());
+ }
+ Tegra::TuringMacroJit jit;
+ jit.Prepare(code, 1);
+ std::array<u32, 0xc00> ram{};
+ std::vector<u32> parameters;
+ std::vector<u32> reference;
+ for (u32 invocation = 0; invocation < 1000; ++invocation) {
+ const u32 count = 2 + invocation % 127;
+ parameters.push_back(count);
+ reference.assign(parameters.begin(), parameters.end());
+ const auto run = [&](std::vector<u32>& input) {
+ std::vector<std::pair<u32, u32>> writes;
+ const auto result = jit.Execute(
+ 0, input, ram, [](u32) { return 0U; },
+ [&](u32 method, u32 value) {
+ writes.emplace_back(method, value);
+ if (method == 0x158) {
+ for (u32 word = 0; word < value; ++word) {
+ input.push_back(invocation * 256 + word);
+ }
+ }
+ return true;
+ });
+ CHECK(result.success);
+ CHECK(result.consumed == 3);
+ return writes;
+ };
+ const auto expected = run(reference);
+ const auto actual = run(parameters);
+ REQUIRE(actual.size() == 3);
+ CHECK(actual == expected);
+ CHECK(parameters == reference);
+ CHECK(actual[1] == std::pair<u32, u32>{0x200, invocation * 256});
+ CHECK(actual[2] == std::pair<u32, u32>{0x201, invocation * 256 + 1});
+ parameters.clear();
+ }
+}
+#endif
diff --git a/src/tests/video_core/shader_invalidation.cpp b/src/tests/video_core/shader_invalidation.cpp
new file mode 100644
index 0000000000..806fb5c58d
--- /dev/null
+++ b/src/tests/video_core/shader_invalidation.cpp
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <array>
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <random>
+#include <vector>
+#include <catch2/catch_test_macros.hpp>
+#include "core/device_memory.h"
+#include "video_core/shader_cache.h"
+
+namespace VideoCommon {
+struct ShaderCacheTestAccess {
+ static std::unique_ptr<ShaderCache> Create(Tegra::MaxwellDeviceMemoryManager& memory) {
+ return std::unique_ptr<ShaderCache>{new ShaderCache{memory}};
+ }
+ static void Register(ShaderCache& cache, VAddr address, size_t size, u64 hash) {
+ auto data = std::make_unique<ShaderInfo>();
+ data->unique_hash = hash;
+ cache.Register(std::move(data), address, size);
+ }
+ static const ShaderInfo* Find(const ShaderCache& cache, VAddr address) {
+ return cache.TryGet(address);
+ }
+};
+} // namespace VideoCommon
+
+using VideoCommon::ShaderCacheTestAccess;
+
+TEST_CASE("Shader batch invalidation matches sequential cache contents", "[shader_invalidation]") {
+ Core::DeviceMemory memory;
+ Tegra::MaxwellDeviceMemoryManager device_memory{memory};
+ auto batched = ShaderCacheTestAccess::Create(device_memory);
+ auto sequential = ShaderCacheTestAccess::Create(device_memory);
+ std::mt19937 random{0x16'67};
+ constexpr VAddr base = 0x100000;
+ for (unsigned iteration = 0; iteration < 600; ++iteration) {
+ for (unsigned slot = 0; slot < 128; ++slot) {
+ const auto address = base + slot * 4096;
+ if (!ShaderCacheTestAccess::Find(*batched, address)) {
+ const auto size = 1 + random() % 65536;
+ ShaderCacheTestAccess::Register(*batched, address, size, iteration + slot);
+ ShaderCacheTestAccess::Register(*sequential, address, size, iteration + slot);
+ }
+ }
+ std::vector<std::pair<DAddr, size_t>> writes;
+ for (unsigned i = 0, count = random() % 24; i < count; ++i) {
+ writes.emplace_back(base + random() % (128 * 4096), random() % 32768);
+ if (i % 4 == 0) {
+ writes.push_back(writes.back());
+ }
+ }
+ for (const auto& [address, size] : writes) {
+ sequential->InvalidateRegion(address, size);
+ }
+ batched->InvalidateRegions(writes);
+ for (unsigned slot = 0; slot < 128; ++slot) {
+ const auto address = base + slot * 4096;
+ const auto actual = ShaderCacheTestAccess::Find(*batched, address);
+ const auto expected = ShaderCacheTestAccess::Find(*sequential, address);
+ REQUIRE(bool(actual) == bool(expected));
+ if (actual) {
+ REQUIRE(actual->unique_hash == expected->unique_hash);
+ }
+ }
+ }
+}
+
+TEST_CASE("Shader empty batches preserve pending invalidations", "[shader_invalidation]") {
+ Core::DeviceMemory memory;
+ Tegra::MaxwellDeviceMemoryManager device_memory{memory};
+ auto cache = ShaderCacheTestAccess::Create(device_memory);
+ constexpr VAddr address = 0x100000;
+ ShaderCacheTestAccess::Register(*cache, address, 32768, 123);
+ cache->OnCacheInvalidation(address, 1);
+ cache->InvalidateRegions({});
+ REQUIRE(ShaderCacheTestAccess::Find(*cache, address));
+ const std::array unrelated{std::pair<DAddr, size_t>{address + 0x100000, 4096}};
+ cache->InvalidateRegions(unrelated);
+ REQUIRE_FALSE(ShaderCacheTestAccess::Find(*cache, address));
+}
+
+TEST_CASE("Shader invalidation CPU benchmark", "[.][cache_bench]") {
+ Core::DeviceMemory memory;
+ Tegra::MaxwellDeviceMemoryManager device_memory{memory};
+ auto batched = ShaderCacheTestAccess::Create(device_memory);
+ auto sequential = ShaderCacheTestAccess::Create(device_memory);
+ std::vector<std::pair<DAddr, size_t>> writes;
+ constexpr VAddr base = 0x100000;
+ for (unsigned slot = 0; slot < 768; ++slot) {
+ writes.emplace_back(base + slot * 65536, 1);
+ }
+ double sequential_ms{}, batched_ms{};
+ for (unsigned repeat = 0; repeat < 20; ++repeat) {
+ for (unsigned slot = 0; slot < 1024; ++slot) {
+ const auto address = base + slot * 65536;
+ if (!ShaderCacheTestAccess::Find(*batched, address)) {
+ ShaderCacheTestAccess::Register(*batched, address, 64, slot);
+ ShaderCacheTestAccess::Register(*sequential, address, 64, slot);
+ }
+ }
+ auto start = std::chrono::steady_clock::now();
+ for (const auto& [address, size] : writes) {
+ sequential->InvalidateRegion(address, size);
+ }
+ sequential_ms +=
+ std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start)
+ .count();
+ start = std::chrono::steady_clock::now();
+ batched->InvalidateRegions(writes);
+ batched_ms +=
+ std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start)
+ .count();
+ REQUIRE_FALSE(ShaderCacheTestAccess::Find(*batched, base));
+ REQUIRE(ShaderCacheTestAccess::Find(*batched, base + 1023 * 65536));
+ }
+ std::cout << "Shader invalidation: sequential=" << sequential_ms << " ms batched=" << batched_ms
+ << " ms (CPU only, not gameplay FPS)\n";
+}
diff --git a/src/tests/video_core/uniform_stream_reuse.cpp b/src/tests/video_core/uniform_stream_reuse.cpp
new file mode 100644
index 0000000000..6059b70eeb
--- /dev/null
+++ b/src/tests/video_core/uniform_stream_reuse.cpp
@@ -0,0 +1,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);
+ }
+}
diff --git a/src/tests/video_core/vertex_repack.cpp b/src/tests/video_core/vertex_repack.cpp
index 6409be3ca4..5e21e3714c 100644
--- a/src/tests/video_core/vertex_repack.cpp
+++ b/src/tests/video_core/vertex_repack.cpp
@@ -76,16 +76,22 @@ TEST_CASE("Vertex repacking requires the captured tail and stride", "[vertex_rep
REQUIRE(Vulkan::VertexRepackMask(regs) == 0);
}
-TEST_CASE("Static vertex repacking requires explicit experimental opt-in", "[vertex_repack]") {
+TEST_CASE("Static vertex repacking follows the device stride alignment", "[vertex_repack]") {
REQUIRE(Vulkan::VertexRepackInputSupported(true, false));
REQUIRE_FALSE(Vulkan::VertexRepackInputSupported(false, false));
REQUIRE(Vulkan::VertexRepackInputSupported(false, true));
+ REQUIRE_FALSE(Vulkan::StaticVertexRepackSelected(nullptr, 1));
+ REQUIRE(Vulkan::StaticVertexRepackSelected(nullptr, 4));
+ REQUIRE(Vulkan::StaticVertexRepackSelected("1", 1));
+ REQUIRE_FALSE(Vulkan::StaticVertexRepackSelected("0", 4));
+ REQUIRE(Vulkan::StaticVertexRepackSelected("other", 4));
}
TEST_CASE("Vertex repack planning keeps descriptor offsets aligned", "[vertex_repack]") {
for (const u32 alignment : {4U, 16U, 64U, 256U, 4096U}) {
for (const u32 offset : {0U, 1U, 3U, 255U, 4099U}) {
- const auto plan = Vulkan::MakeVertexRepackParameters(offset, 70, alignment, 1U << 20, 65535);
+ const auto plan = Vulkan::MakeVertexRepackParameters(offset, 70, alignment, 1U << 20,
+ 65535, 1U << 20);
REQUIRE(plan.has_value());
REQUIRE(plan->source_base % alignment == 0);
REQUIRE(plan->source_base + plan->source_bias == offset);
@@ -99,13 +105,22 @@ TEST_CASE("Vertex repack planning keeps descriptor offsets aligned", "[vertex_re
}
TEST_CASE("Vertex repack planning rejects overflow and device-limit violations", "[vertex_repack]") {
- REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 0, 256, 1U << 20, 65535));
- REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 0, 1U << 20, 65535));
- REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 71, 65535));
- REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(255, 69, 256, 323, 65535));
- REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 1U << 20, 0));
- REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 0xffffffffU, 256, ~u64{0}, 0xffffffffU));
- const auto one = Vulkan::MakeVertexRepackParameters(0, 69, 256, 72, 1);
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 0, 256, 1U << 20, 65535, 1U << 20));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 0, 1U << 20, 65535, 1U << 20));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 71, 65535, 1U << 20));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(255, 69, 256, 323, 65535, 1U << 20));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 1U << 20, 0, 1U << 20));
+ REQUIRE_FALSE(
+ Vulkan::MakeVertexRepackParameters(0, 0xffffffffU, 256, ~u64{0}, 0xffffffffU, ~u64{0}));
+ const auto one = Vulkan::MakeVertexRepackParameters(0, 69, 256, 72, 1, 72);
REQUIRE(one.has_value());
REQUIRE(one->source_range == 72);
}
+
+TEST_CASE("Vertex repack planning keeps the source range inside the buffer", "[vertex_repack]") {
+ const auto clamped = Vulkan::MakeVertexRepackParameters(0, 69, 256, 1U << 20, 65535, 69);
+ REQUIRE(clamped.has_value());
+ REQUIRE(clamped->source_range == 69);
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(0, 69, 256, 1U << 20, 65535, 68));
+ REQUIRE_FALSE(Vulkan::MakeVertexRepackParameters(300, 69, 256, 1U << 20, 65535, 256));
+}