mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
perf: sync only GPU-dirty readbacks
This commit is contained in:
@@ -6,7 +6,9 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <semaphore>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -265,6 +267,7 @@ void TestRangeInvalidation() {
|
||||
flushes++;
|
||||
tracker.ForEachDownloadRange<true>(address + 16, size - 32,
|
||||
[](uint64_t, uint64_t) noexcept {});
|
||||
tracker.MarkRegionAsCpuModified(address + 16, size - 32);
|
||||
});
|
||||
Check(flushes == 1 && !tracker.IsRegionGpuModified(address, size) &&
|
||||
tracker.IsRegionCpuModified(address, size) && IsWritable(memory) &&
|
||||
@@ -277,6 +280,53 @@ void TestRangeInvalidation() {
|
||||
Release(page_manager, memory, size);
|
||||
}
|
||||
|
||||
void TestGpuReacquisitionAfterInvalidation() {
|
||||
TrackerHarness harness;
|
||||
auto &tracker = harness.tracker;
|
||||
auto &page_manager = harness.page_manager;
|
||||
const auto page_size = page_manager.GetPageSize();
|
||||
auto *memory = Allocate(page_manager, 1);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
tracker.ForEachUploadRange(
|
||||
address, page_size, true, [](uint64_t, uint64_t) noexcept {},
|
||||
[]() noexcept {});
|
||||
Check(tracker.IsRegionGpuModified(address, page_size) &&
|
||||
!tracker.IsRegionCpuModified(address, page_size),
|
||||
"reacquisition setup did not establish GPU ownership");
|
||||
|
||||
uint32_t flushes = 0;
|
||||
uint32_t uploads = 0;
|
||||
std::binary_semaphore reacquire{0};
|
||||
std::binary_semaphore reacquired{0};
|
||||
std::jthread publisher([&] {
|
||||
reacquire.acquire();
|
||||
tracker.ForEachUploadRange(
|
||||
address + 16, 32, true,
|
||||
[&](uint64_t, uint64_t) noexcept { uploads++; }, []() noexcept {});
|
||||
reacquired.release();
|
||||
});
|
||||
tracker.InvalidateRegion(address + 16, 32, [&] {
|
||||
flushes++;
|
||||
tracker.ForEachDownloadRange<true>(address + 16, 32,
|
||||
[](uint64_t, uint64_t) noexcept {});
|
||||
tracker.MarkRegionAsCpuModified(address + 16, 32);
|
||||
reacquire.release();
|
||||
reacquired.acquire();
|
||||
});
|
||||
publisher.join();
|
||||
Check(flushes == 1 && uploads == 1 &&
|
||||
tracker.IsRegionGpuModified(address, page_size) &&
|
||||
!tracker.IsRegionCpuModified(address, page_size) &&
|
||||
!IsWritable(memory),
|
||||
"invalidation rejected a new generation of GPU ownership");
|
||||
|
||||
tracker.UnmarkRegionAsGpuModified(address, page_size);
|
||||
tracker.MarkRegionAsCpuModified(address, page_size);
|
||||
tracker.UntrackMemory(address, page_size);
|
||||
Release(page_manager, memory, page_size);
|
||||
}
|
||||
|
||||
void TestGpuDirtyBits() {
|
||||
TrackerHarness harness;
|
||||
auto &tracker = harness.tracker;
|
||||
@@ -635,6 +685,7 @@ int main(int argc, char **argv) {
|
||||
TestQueriesDoNotRequireMappedOwnership();
|
||||
TestCpuDirtyUpload();
|
||||
TestRangeInvalidation();
|
||||
TestGpuReacquisitionAfterInvalidation();
|
||||
TestGpuDirtyBits();
|
||||
TestExactDirtyIntervalsSharingTrackerPage();
|
||||
TestGpuDownloadProtectionMirrors();
|
||||
|
||||
+28
-152
@@ -35,171 +35,50 @@ void YieldMany() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestFaultBlocksPublisher() {
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread publisher;
|
||||
void TestOwnership() {
|
||||
ResourceMutex mutex;
|
||||
Check(!mutex.IsOwnedByCurrentThread(), "fresh mutex reported an owner");
|
||||
{
|
||||
ResourceMutex::FaultScope fault(mutex);
|
||||
publisher = std::thread([&] {
|
||||
publisher_started.store(true, std::memory_order_release);
|
||||
std::lock_guard lock(mutex);
|
||||
publisher_entered.store(true, std::memory_order_release);
|
||||
});
|
||||
while (!publisher_started.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
YieldMany();
|
||||
Check(!publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher entered during active fault transaction");
|
||||
std::lock_guard lock(mutex);
|
||||
Check(mutex.IsOwnedByCurrentThread(), "owner tracking was not installed");
|
||||
}
|
||||
publisher.join();
|
||||
Check(publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher did not resume after fault transaction");
|
||||
Check(!mutex.IsOwnedByCurrentThread(), "owner tracking survived unlock");
|
||||
}
|
||||
|
||||
void TestFaultDrainsExistingOwner() {
|
||||
void TestSerializesTransactions() {
|
||||
ResourceMutex mutex;
|
||||
std::unique_lock owner(mutex);
|
||||
std::atomic_bool fault_started {false};
|
||||
std::atomic_bool fault_entered {false};
|
||||
std::atomic_bool release_fault {false};
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread fault([&] {
|
||||
fault_started.store(true, std::memory_order_release);
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
fault_entered.store(true, std::memory_order_release);
|
||||
while (!release_fault.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
while (!fault_started.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
YieldMany();
|
||||
Check(!fault_entered.load(std::memory_order_acquire),
|
||||
"fault transaction did not wait for existing owner");
|
||||
owner.unlock();
|
||||
while (!fault_entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::thread publisher([&] {
|
||||
publisher_started.store(true, std::memory_order_release);
|
||||
std::atomic_bool contender_started {false};
|
||||
std::atomic_bool contender_entered {false};
|
||||
std::thread contender([&] {
|
||||
contender_started.store(true, std::memory_order_release);
|
||||
std::lock_guard lock(mutex);
|
||||
publisher_entered.store(true, std::memory_order_release);
|
||||
contender_entered.store(true, std::memory_order_release);
|
||||
});
|
||||
while (!publisher_started.load(std::memory_order_acquire)) {
|
||||
while (!contender_started.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
YieldMany();
|
||||
Check(!publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher entered before drained fault completed");
|
||||
release_fault.store(true, std::memory_order_release);
|
||||
fault.join();
|
||||
publisher.join();
|
||||
Check(publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher did not resume after drained fault");
|
||||
}
|
||||
|
||||
void TestFaultScopesSerialize() {
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool first_entered {false};
|
||||
std::atomic_bool second_started {false};
|
||||
std::atomic_bool second_entered {false};
|
||||
std::atomic_bool release_first {false};
|
||||
std::atomic_bool release_second {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread first([&] {
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
first_entered.store(true, std::memory_order_release);
|
||||
while (!release_first.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
while (!first_entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::thread second([&] {
|
||||
second_started.store(true, std::memory_order_release);
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
second_entered.store(true, std::memory_order_release);
|
||||
while (!release_second.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
while (!second_started.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
YieldMany();
|
||||
Check(!second_entered.load(std::memory_order_acquire),
|
||||
"second fault transaction entered before first completed");
|
||||
release_first.store(true, std::memory_order_release);
|
||||
while (!second_entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::thread publisher([&] {
|
||||
std::lock_guard lock(mutex);
|
||||
publisher_entered.store(true, std::memory_order_release);
|
||||
});
|
||||
YieldMany();
|
||||
Check(!publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher entered during second fault transaction");
|
||||
release_second.store(true, std::memory_order_release);
|
||||
first.join();
|
||||
second.join();
|
||||
publisher.join();
|
||||
Check(publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher did not resume after serialized faults");
|
||||
}
|
||||
|
||||
void TestPreownedFaultKeepsResourceTransaction() {
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread publisher;
|
||||
std::unique_lock owner(mutex);
|
||||
{
|
||||
ResourceMutex::FaultScope fault(mutex);
|
||||
publisher = std::thread([&] {
|
||||
publisher_started.store(true, std::memory_order_release);
|
||||
std::lock_guard lock(mutex);
|
||||
publisher_entered.store(true, std::memory_order_release);
|
||||
});
|
||||
while (!publisher_started.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
YieldMany();
|
||||
Check(!publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher entered during preowned fault transaction");
|
||||
}
|
||||
YieldMany();
|
||||
Check(!publisher_entered.load(std::memory_order_acquire),
|
||||
"preowned fault released its outer resource transaction");
|
||||
Check(!contender_entered.load(std::memory_order_acquire),
|
||||
"contender entered an active resource transaction");
|
||||
owner.unlock();
|
||||
publisher.join();
|
||||
Check(publisher_entered.load(std::memory_order_acquire),
|
||||
"publisher did not resume after outer transaction");
|
||||
contender.join();
|
||||
Check(contender_entered.load(std::memory_order_acquire),
|
||||
"contender did not resume after resource transaction");
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
[[noreturn]] void RunDeathCase(const char* name) {
|
||||
[[noreturn]] void RunDeathCase() {
|
||||
ResourceMutex mutex;
|
||||
if (std::strcmp(name, "recursive-lock") == 0) {
|
||||
std::lock_guard first(mutex);
|
||||
std::lock_guard second(mutex);
|
||||
} else if (std::strcmp(name, "nested-fault") == 0) {
|
||||
ResourceMutex::FaultScope first(mutex);
|
||||
ResourceMutex::FaultScope second(mutex);
|
||||
}
|
||||
std::lock_guard first(mutex);
|
||||
std::lock_guard second(mutex);
|
||||
std::_Exit(0x7f);
|
||||
}
|
||||
|
||||
void CheckDeathCase(const char* name) {
|
||||
void CheckDeathCase() {
|
||||
char path[MAX_PATH] {};
|
||||
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
std::string command = std::string("\"") + path + "\" --death";
|
||||
std::vector<char> mutable_command(command.begin(), command.end());
|
||||
mutable_command.push_back('\0');
|
||||
STARTUPINFOA startup {sizeof(startup)};
|
||||
@@ -224,20 +103,17 @@ void CheckDeathCase(const char* name) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
|
||||
RunDeathCase(argv[2]);
|
||||
if (argc == 2 && std::strcmp(argv[1], "--death") == 0) {
|
||||
RunDeathCase();
|
||||
}
|
||||
#else
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#endif
|
||||
TestFaultBlocksPublisher();
|
||||
TestFaultDrainsExistingOwner();
|
||||
TestFaultScopesSerialize();
|
||||
TestPreownedFaultKeepsResourceTransaction();
|
||||
TestOwnership();
|
||||
TestSerializesTransactions();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
CheckDeathCase("recursive-lock");
|
||||
CheckDeathCase("nested-fault");
|
||||
CheckDeathCase();
|
||||
#endif
|
||||
std::puts("ResourceMutexTests: all cases passed");
|
||||
return 0;
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
@@ -1372,12 +1373,14 @@ public:
|
||||
|
||||
void CheckGpuMappedRangeLifecycle() {
|
||||
EnsureRuntimeContext();
|
||||
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
||||
auto& context = Renderer();
|
||||
CommandScheduler scheduler(context, m_runtime_context);
|
||||
HW::Context registers {};
|
||||
HW::UserConfig user_config {};
|
||||
HW::Shader shaders {};
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
Gpu gpu(Renderer());
|
||||
context.InitializeGpu(nullptr);
|
||||
auto& gpu = context.GetGpu();
|
||||
GpuResourceManager resources(m_runtime_context, scheduler);
|
||||
resources.SetGpu(&gpu);
|
||||
|
||||
@@ -1416,6 +1419,7 @@ public:
|
||||
|
||||
resources.SetGpu(nullptr);
|
||||
scheduler.Finish();
|
||||
context.ShutdownGpu();
|
||||
std::printf("[host] %-32s ok\n", "GpuMappedRangeLifecycle");
|
||||
}
|
||||
|
||||
@@ -1567,21 +1571,73 @@ public:
|
||||
gpu_thread = std::this_thread::get_id();
|
||||
Require("GpuCommandLane", "FIFO", order == 1,
|
||||
"synchronous command overtook an older host command");
|
||||
Require("GpuCommandLane", "generic context",
|
||||
!Gpu::IsCommandProcessorThread() && Gpu::CurrentCommandProcessor() == nullptr,
|
||||
"generic host command manufactured a PM4 processor context");
|
||||
Require("GpuCommandLane", "GPU context", Gpu::IsGpuThread(),
|
||||
"host command did not run on the GPU thread");
|
||||
gpu.SendCommandSync([&nested_thread] { nested_thread = std::this_thread::get_id(); });
|
||||
order = 2;
|
||||
});
|
||||
gpu.SendCommandSyncWithProcessor([&](CommandProcessor& processor) {
|
||||
Require("GpuCommandLane", "processor context",
|
||||
Gpu::IsCommandProcessorThread() && Gpu::CurrentCommandProcessor() == &processor,
|
||||
"resource command did not receive its explicit processor context");
|
||||
});
|
||||
Require("GpuCommandLane", "dispatch thread",
|
||||
order == 2 && gpu_thread != caller_thread && nested_thread == gpu_thread,
|
||||
"host command did not run on the GPU thread or nested sync deadlocked");
|
||||
|
||||
alignas(uint32_t) uint32_t packet_marker_a = 0;
|
||||
alignas(uint32_t) uint32_t packet_marker_b = 0;
|
||||
const auto write_packet = [](uint32_t* packet, uint32_t* destination, uint32_t value) {
|
||||
const auto destination_address = reinterpret_cast<uint64_t>(destination);
|
||||
packet[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
||||
packet[1] = 0;
|
||||
packet[2] = static_cast<uint32_t>(destination_address);
|
||||
packet[3] = static_cast<uint32_t>(destination_address >> 32u);
|
||||
packet[4] = value;
|
||||
};
|
||||
std::array<uint32_t, 1024> polling_loop {};
|
||||
for (size_t i = 0; i < polling_loop.size() - 4; i += 2) {
|
||||
polling_loop[i] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO);
|
||||
polling_loop[i + 1] = 0;
|
||||
}
|
||||
const auto loop_address = reinterpret_cast<uint64_t>(polling_loop.data());
|
||||
polling_loop[1020] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
|
||||
polling_loop[1021] = static_cast<uint32_t>(loop_address);
|
||||
polling_loop[1022] = static_cast<uint32_t>(loop_address >> 32u);
|
||||
polling_loop[1023] = 0x0f200000u | static_cast<uint32_t>(polling_loop.size());
|
||||
|
||||
std::array<uint32_t, 14> polling_commands {};
|
||||
write_packet(polling_commands.data(), &packet_marker_a, 11);
|
||||
polling_commands[5] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
|
||||
polling_commands[6] = static_cast<uint32_t>(loop_address);
|
||||
polling_commands[7] = static_cast<uint32_t>(loop_address >> 32u);
|
||||
polling_commands[8] = 0x0f200000u | static_cast<uint32_t>(polling_loop.size());
|
||||
write_packet(polling_commands.data() + 9, &packet_marker_b, 22);
|
||||
std::binary_semaphore stream_gate_entered {0};
|
||||
std::binary_semaphore stream_gate_release {0};
|
||||
gpu.SendCommand([&] {
|
||||
stream_gate_entered.release();
|
||||
stream_gate_release.acquire();
|
||||
});
|
||||
gpu.Submit(polling_commands.data(), polling_commands.size(), nullptr, 0);
|
||||
stream_gate_entered.acquire();
|
||||
stream_gate_release.release();
|
||||
uint32_t packet_marker_a_at_callback = UINT32_MAX;
|
||||
uint32_t packet_marker_b_at_callback = UINT32_MAX;
|
||||
for (uint32_t attempt = 0; attempt < 8 && packet_marker_a_at_callback != 11; attempt++) {
|
||||
gpu.SendCommandSync([&] {
|
||||
if (packet_marker_a == 11 && packet_marker_b == 0) {
|
||||
packet_marker_a_at_callback = packet_marker_a;
|
||||
packet_marker_b_at_callback = packet_marker_b;
|
||||
polling_loop[1020] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO);
|
||||
polling_loop[1021] = 0;
|
||||
polling_loop[1022] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO);
|
||||
polling_loop[1023] = 0;
|
||||
}
|
||||
});
|
||||
std::this_thread::yield();
|
||||
}
|
||||
gpu.Done();
|
||||
Require("GpuCommandLane", "packet-boundary command polling",
|
||||
packet_marker_a_at_callback == 11 && packet_marker_b_at_callback == 0 &&
|
||||
packet_marker_a == 11 && packet_marker_b == 22,
|
||||
"host command waited for an entire non-suspended PM4 stream");
|
||||
|
||||
uint32_t label = 0;
|
||||
uint32_t prefix = 0;
|
||||
uint32_t suffix = 0;
|
||||
@@ -1760,7 +1816,7 @@ public:
|
||||
gpu.Submit(dma_commands.data(), static_cast<uint32_t>(dma_commands.size()), nullptr, 0);
|
||||
gpu.Done();
|
||||
constexpr uint32_t clean_fill_value = 0xdecafbad;
|
||||
gpu.SendCommandSyncWithProcessor([&](CommandProcessor&) {
|
||||
gpu.SendCommandSync([&] {
|
||||
auto& buffer_cache = resources.GetBufferCache();
|
||||
Require("GpuCommandLane", "clean cached setup",
|
||||
buffer_cache.HasPageOverlap(clean_cached_fill, sizeof(source_words)) &&
|
||||
@@ -1840,14 +1896,116 @@ public:
|
||||
clean_copy_words[1]},
|
||||
"host-memory fill/copy did not update the clean cached mirror");
|
||||
|
||||
gpu.SendCommandSyncWithProcessor([&](CommandProcessor& processor) {
|
||||
Require("GpuCommandLane", "processor fault context",
|
||||
Gpu::CurrentCommandProcessor() == &processor,
|
||||
"processor resource test lost its command context");
|
||||
Require("GpuCommandLane", "processor memory invalidation",
|
||||
gpu.SendCommandSync([&] {
|
||||
Require("GpuCommandLane", "GPU memory invalidation",
|
||||
resources.InvalidateMemory(fault_base, sizeof(uint32_t)),
|
||||
"processor memory invalidation did not find its mapped range");
|
||||
"GPU memory invalidation did not find its mapped range");
|
||||
});
|
||||
|
||||
auto& buffer_cache = resources.GetBufferCache();
|
||||
Require("GpuCommandLane", "clean tracked fault setup",
|
||||
buffer_cache.HasPageOverlap(immediate_dst, sizeof(uint32_t)) &&
|
||||
!buffer_cache.HasGpuDirtyBytes(immediate_dst, sizeof(uint32_t)),
|
||||
"clean write-fault test address is not backed by a clean cached Buffer");
|
||||
constexpr uint64_t dirty_fault_address = fault_base + 0xd000;
|
||||
constexpr uint32_t dirty_fault_value = 0x5a17c0deu;
|
||||
constexpr uint32_t dirty_fault_stale = 0x0ddba11u;
|
||||
std::memcpy(reinterpret_cast<void*>(dirty_fault_address), &dirty_fault_stale,
|
||||
sizeof(dirty_fault_stale));
|
||||
gpu.SendCommandSync([&] {
|
||||
auto dirty = buffer_cache.ObtainBuffer(scheduler.Current(), dirty_fault_address,
|
||||
sizeof(dirty_fault_value), true, false);
|
||||
Require("GpuCommandLane", "dirty tracked fault setup", dirty.owner != nullptr,
|
||||
"dirty write-fault test could not create a cached Buffer");
|
||||
scheduler.Current().RetainResourceUntilFence(dirty.owner);
|
||||
buffer_cache.FillBuffer(dirty_fault_address, sizeof(dirty_fault_value),
|
||||
dirty_fault_value);
|
||||
});
|
||||
Require("GpuCommandLane", "dirty tracked fault ownership",
|
||||
buffer_cache.HasGpuDirtyBytes(dirty_fault_address, sizeof(dirty_fault_value)),
|
||||
"dirty write-fault test address did not acquire GPU ownership");
|
||||
|
||||
std::binary_semaphore block_entered {0};
|
||||
std::binary_semaphore block_release {0};
|
||||
std::binary_semaphore clean_fault_complete {0};
|
||||
bool clean_fault_handled = false;
|
||||
gpu.SendCommand([&] {
|
||||
block_entered.release();
|
||||
block_release.acquire();
|
||||
});
|
||||
block_entered.acquire();
|
||||
std::jthread clean_fault_thread([&] {
|
||||
clean_fault_handled = resources.HandleFault(PageFaultAccess::Write, immediate_dst);
|
||||
clean_fault_complete.release();
|
||||
});
|
||||
const bool clean_fault_was_direct =
|
||||
clean_fault_complete.try_acquire_for(std::chrono::seconds(1));
|
||||
if (!clean_fault_was_direct) {
|
||||
block_release.release();
|
||||
clean_fault_complete.acquire();
|
||||
}
|
||||
clean_fault_thread.join();
|
||||
Require("GpuCommandLane", "clean fault direct path",
|
||||
clean_fault_was_direct && clean_fault_handled,
|
||||
"a clean CPU write fault waited for the blocked GPU command lane");
|
||||
|
||||
std::binary_semaphore dirty_fault_complete {0};
|
||||
bool dirty_fault_handled = false;
|
||||
std::jthread dirty_fault_thread([&] {
|
||||
dirty_fault_handled =
|
||||
resources.HandleFault(PageFaultAccess::Write, dirty_fault_address);
|
||||
dirty_fault_complete.release();
|
||||
});
|
||||
const bool dirty_fault_bypassed_lane =
|
||||
dirty_fault_complete.try_acquire_for(std::chrono::milliseconds(200));
|
||||
block_release.release();
|
||||
if (!dirty_fault_bypassed_lane) {
|
||||
dirty_fault_complete.acquire();
|
||||
}
|
||||
dirty_fault_thread.join();
|
||||
uint32_t dirty_fault_backing = 0;
|
||||
std::memcpy(&dirty_fault_backing, reinterpret_cast<const void*>(dirty_fault_address),
|
||||
sizeof(dirty_fault_backing));
|
||||
Require("GpuCommandLane", "dirty fault synchronization",
|
||||
!dirty_fault_bypassed_lane && dirty_fault_handled &&
|
||||
dirty_fault_backing == dirty_fault_value &&
|
||||
!buffer_cache.HasGpuDirtyBytes(dirty_fault_address, sizeof(dirty_fault_value)),
|
||||
"a GPU-dirty write fault bypassed synchronization or lost the published bytes");
|
||||
|
||||
constexpr uint64_t empty_copy_fault_address = fault_base + 0xe000;
|
||||
constexpr uint32_t empty_copy_fault_value = 0x6b28d1efu;
|
||||
gpu.SendCommandSync([&] {
|
||||
auto dirty = buffer_cache.ObtainBuffer(scheduler.Current(), empty_copy_fault_address,
|
||||
sizeof(empty_copy_fault_value), true, false);
|
||||
Require("GpuCommandLane", "empty-copy write setup", dirty.owner != nullptr,
|
||||
"empty-copy write test could not create a cached Buffer");
|
||||
scheduler.Current().RetainResourceUntilFence(dirty.owner);
|
||||
buffer_cache.FillBuffer(empty_copy_fault_address, sizeof(empty_copy_fault_value),
|
||||
empty_copy_fault_value);
|
||||
buffer_cache.ReadMemory(empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
||||
const bool gpu_owned = buffer_cache.IsRegionGpuModified(
|
||||
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
||||
const bool cpu_owned = buffer_cache.IsRegionCpuModified(
|
||||
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
||||
Require("GpuCommandLane", "empty-copy read ownership",
|
||||
!gpu_owned && !cpu_owned,
|
||||
"readback did not leave clean ownership for the queued write invalidation");
|
||||
buffer_cache.ReadMemory(empty_copy_fault_address, sizeof(empty_copy_fault_value), true);
|
||||
});
|
||||
uint32_t empty_copy_fault_backing = 0;
|
||||
std::memcpy(&empty_copy_fault_backing, reinterpret_cast<const void*>(empty_copy_fault_address),
|
||||
sizeof(empty_copy_fault_backing));
|
||||
const bool empty_copy_cpu_owned = buffer_cache.IsRegionCpuModified(
|
||||
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
||||
const bool empty_copy_gpu_owned = buffer_cache.IsRegionGpuModified(
|
||||
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
||||
const bool empty_copy_dirty =
|
||||
buffer_cache.HasGpuDirtyBytes(empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
||||
Require("GpuCommandLane", "empty-copy write ownership",
|
||||
empty_copy_fault_backing == empty_copy_fault_value &&
|
||||
empty_copy_cpu_owned && !empty_copy_gpu_owned && !empty_copy_dirty,
|
||||
"write invalidation without a remaining copy did not establish CPU ownership");
|
||||
|
||||
resources.UnmapMemory(fault_base, fault_size);
|
||||
Require("GpuCommandLane", "processor fault unmap",
|
||||
Libs::LibKernel::Memory::KernelMunmap(fault_base, fault_size) == 0,
|
||||
@@ -2073,12 +2231,14 @@ public:
|
||||
constexpr uint32_t ring_fault_second_value = 0x4e5f6071u;
|
||||
|
||||
EnsureRuntimeContext();
|
||||
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
||||
auto& context = Renderer();
|
||||
CommandScheduler scheduler(context, m_runtime_context);
|
||||
HW::Context registers {};
|
||||
HW::UserConfig user_config {};
|
||||
HW::Shader shaders {};
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
Gpu gpu(Renderer());
|
||||
context.InitializeGpu(nullptr);
|
||||
auto& gpu = context.GetGpu();
|
||||
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
@@ -2471,7 +2631,7 @@ public:
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
gpu.Shutdown();
|
||||
context.ShutdownGpu();
|
||||
Require(name, "unmap direct backing",
|
||||
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
||||
"dirty-GC direct-memory mapping release failed");
|
||||
@@ -2488,12 +2648,14 @@ public:
|
||||
constexpr uint64_t allocation_size = 0x2800000;
|
||||
constexpr uint64_t allocation_alignment = 0x200000;
|
||||
EnsureRuntimeContext();
|
||||
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
||||
auto& context = Renderer();
|
||||
CommandScheduler scheduler(context, m_runtime_context);
|
||||
HW::Context registers {};
|
||||
HW::UserConfig user_config {};
|
||||
HW::Shader shaders {};
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
Gpu gpu(Renderer());
|
||||
context.InitializeGpu(nullptr);
|
||||
auto& gpu = context.GetGpu();
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
@@ -4949,7 +5111,7 @@ public:
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
gpu.Shutdown();
|
||||
context.ShutdownGpu();
|
||||
Require(name, "unmap direct backing",
|
||||
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
||||
"cache direct-memory mapping release failed");
|
||||
|
||||
Reference in New Issue
Block a user