// SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include #include #include #include #include #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 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> allocations; StreamUniformRef last{}, bound{}; u64 tick{1}; u64 CurrentTick() const { return tick; } StreamUniformRef LastStreamUniformRef() const { return last; } std::span 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(); 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(); 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{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(); 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); } }