mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
refactor renderer
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
namespace EventQueue = Libs::LibKernel::EventQueue;
|
||||
using Libs::LibKernel::KERNEL_ERROR_EBADF;
|
||||
using Libs::LibKernel::KERNEL_ERROR_ENOENT;
|
||||
|
||||
void Check(bool value, const char *text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "EventQueueLifetimeTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckConcurrentResult(int result, const char *text) {
|
||||
Check(result == OK || result == KERNEL_ERROR_EBADF ||
|
||||
result == KERNEL_ERROR_ENOENT,
|
||||
text);
|
||||
}
|
||||
|
||||
void CountDeletedEvent(EventQueue::KernelEqueue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *count = static_cast<std::atomic_uint32_t *>(event->filter.data);
|
||||
count->fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
struct DuplicateEventOwner {
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
};
|
||||
|
||||
void QueueDuplicateEvent(EventQueue::KernelEqueueEvent *event,
|
||||
void *trigger_data) {
|
||||
auto next = event->event;
|
||||
next.data = reinterpret_cast<intptr_t>(trigger_data);
|
||||
if (event->triggered) {
|
||||
event->pending_events.push_back(next);
|
||||
} else {
|
||||
event->event = next;
|
||||
event->triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetDuplicateEvent(EventQueue::KernelEqueueEvent *event) {
|
||||
event->triggered = false;
|
||||
event->event.data = 0;
|
||||
}
|
||||
|
||||
void DeleteDuplicateEvent(EventQueue::KernelEqueue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *owner = static_cast<DuplicateEventOwner *>(event->filter.data);
|
||||
owner->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void PoisonDuplicateEvent(EventQueue::KernelEqueueEvent *, void *) {
|
||||
Check(false, "duplicate add replaced trigger callback");
|
||||
}
|
||||
|
||||
void TestDuplicateAddPreservesEventState() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "duplicate-add") == OK,
|
||||
"create duplicate add queue");
|
||||
|
||||
auto original_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_original = original_owner;
|
||||
EventQueue::KernelEqueueEvent original{};
|
||||
original.event.ident = 17;
|
||||
original.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
original.event.udata = reinterpret_cast<void *>(0x1111);
|
||||
original.filter.data = original_owner.get();
|
||||
original.filter.owner = original_owner;
|
||||
original.filter.trigger_func = QueueDuplicateEvent;
|
||||
original.filter.reset_func = ResetDuplicateEvent;
|
||||
original.filter.delete_event_func = DeleteDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, original) == OK,
|
||||
"add original duplicate event");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void *>(0x1234)) == OK,
|
||||
"queue first trigger");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void *>(0x5678)) == OK,
|
||||
"queue pending trigger");
|
||||
|
||||
auto replacement_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_replacement = replacement_owner;
|
||||
EventQueue::KernelEqueueEvent duplicate{};
|
||||
duplicate.triggered = false;
|
||||
duplicate.deadline_ns = 1;
|
||||
duplicate.event.ident = 17;
|
||||
duplicate.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
duplicate.event.data = 0x7fffffff;
|
||||
duplicate.event.udata = reinterpret_cast<void *>(0x2222);
|
||||
duplicate.filter.data = replacement_owner.get();
|
||||
duplicate.filter.owner = replacement_owner;
|
||||
duplicate.filter.trigger_func = PoisonDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, duplicate) == OK,
|
||||
"update duplicate event");
|
||||
|
||||
duplicate.filter.owner.reset();
|
||||
replacement_owner.reset();
|
||||
Check(weak_replacement.expired(), "duplicate owner is not retained");
|
||||
original.filter.owner.reset();
|
||||
original_owner.reset();
|
||||
Check(!weak_original.expired(), "original event owner remains retained");
|
||||
|
||||
EventQueue::KernelEvent events[2]{};
|
||||
int out = 0;
|
||||
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK,
|
||||
"read queued duplicate triggers");
|
||||
Check(out == 2, "duplicate add preserves pending event count");
|
||||
Check(events[0].data == 0x1234 && events[1].data == 0x5678,
|
||||
"duplicate add preserves current and pending event data");
|
||||
Check(events[0].udata == reinterpret_cast<void *>(0x2222),
|
||||
"duplicate add updates current user data");
|
||||
Check(events[1].udata == reinterpret_cast<void *>(0x2222),
|
||||
"duplicate add updates pending user data");
|
||||
|
||||
EventQueue::KernelEvent timer_event{};
|
||||
Check(EventQueue::KernelWaitEqueue(queue, &timer_event, 1, &out, &timeout) ==
|
||||
OK &&
|
||||
out == 1,
|
||||
"duplicate add updates deadline metadata");
|
||||
Check(timer_event.data == 0 &&
|
||||
timer_event.udata == reinterpret_cast<void *>(0x2222),
|
||||
"deadline trigger retains updated duplicate metadata");
|
||||
|
||||
auto retained_owner = weak_original.lock();
|
||||
Check(retained_owner != nullptr, "original owner alive before delete");
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT) ==
|
||||
OK,
|
||||
"delete duplicate event");
|
||||
Check(retained_owner->delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"duplicate add preserves delete callback");
|
||||
retained_owner.reset();
|
||||
Check(weak_original.expired(), "original owner released on delete");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete duplicate add queue");
|
||||
}
|
||||
|
||||
struct SimulatedVideoOutEventState;
|
||||
|
||||
struct SimulatedVideoOutRegistration {
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<SimulatedVideoOutEventState> state;
|
||||
uint64_t marker = 0x123456789abcdef0ull;
|
||||
};
|
||||
|
||||
struct SimulatedVideoOutEventState {
|
||||
SimulatedVideoOutEventState(std::atomic_uint32_t &stage,
|
||||
std::atomic_uint32_t &destroy_count)
|
||||
: stage(stage), destroy_count(destroy_count) {}
|
||||
|
||||
~SimulatedVideoOutEventState() {
|
||||
destroy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> queues;
|
||||
std::atomic_uint32_t &stage;
|
||||
std::atomic_uint32_t &destroy_count;
|
||||
uint64_t marker = 0xfedcba9876543210ull;
|
||||
};
|
||||
|
||||
void DetachSimulatedVideoOutEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *registration =
|
||||
static_cast<SimulatedVideoOutRegistration *>(event->filter.data);
|
||||
Check(registration != nullptr && registration->handle == queue,
|
||||
"simulated registration identity");
|
||||
auto state = registration->state;
|
||||
Check(state != nullptr, "simulated event owns shared state");
|
||||
state->stage.store(1, std::memory_order_release);
|
||||
while (state->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(state->mutex);
|
||||
const auto entry =
|
||||
std::find_if(state->queues.begin(), state->queues.end(),
|
||||
[registration](const auto &candidate) {
|
||||
return candidate.get() == registration;
|
||||
});
|
||||
if (entry != state->queues.end()) {
|
||||
state->queues.erase(entry);
|
||||
}
|
||||
}
|
||||
event->filter.owner.reset();
|
||||
Check(state->marker == 0xfedcba9876543210ull &&
|
||||
registration->marker == 0x123456789abcdef0ull,
|
||||
"callback state survives simulated port destruction");
|
||||
}
|
||||
|
||||
void TestCallbackStateOutlivesPort() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "shared-port-state") == OK,
|
||||
"create shared port state queue");
|
||||
|
||||
std::atomic_uint32_t stage{0};
|
||||
std::atomic_uint32_t destroy_count{0};
|
||||
auto port_state =
|
||||
std::make_shared<SimulatedVideoOutEventState>(stage, destroy_count);
|
||||
std::weak_ptr<SimulatedVideoOutEventState> weak_state = port_state;
|
||||
auto registration = std::make_shared<SimulatedVideoOutRegistration>();
|
||||
std::weak_ptr<SimulatedVideoOutRegistration> weak_registration =
|
||||
registration;
|
||||
registration->handle = queue;
|
||||
registration->state = port_state;
|
||||
port_state->queues.push_back(registration);
|
||||
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 8;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DetachSimulatedVideoOutEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add shared port state event");
|
||||
event.filter.owner.reset();
|
||||
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete shared port state queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> detached;
|
||||
{
|
||||
std::lock_guard lock(port_state->mutex);
|
||||
detached = std::move(port_state->queues);
|
||||
}
|
||||
registration.reset();
|
||||
detached.clear();
|
||||
port_state.reset();
|
||||
Check(!weak_state.expired(),
|
||||
"callback state outlives simulated port object");
|
||||
Check(!weak_registration.expired(),
|
||||
"registration outlives simulated port object");
|
||||
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(weak_registration.expired(), "detached registration is released");
|
||||
Check(weak_state.expired(), "detached event state is released");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"shared event state is destroyed exactly once");
|
||||
}
|
||||
|
||||
struct OwnedCallbackPayload {
|
||||
OwnedCallbackPayload(std::atomic_uint32_t &stage,
|
||||
std::atomic_uint32_t &delete_count,
|
||||
std::atomic_uint32_t &destroy_count)
|
||||
: stage(stage), delete_count(delete_count), destroy_count(destroy_count) {}
|
||||
|
||||
std::atomic_uint32_t &stage;
|
||||
std::atomic_uint32_t &delete_count;
|
||||
std::atomic_uint32_t &destroy_count;
|
||||
uint64_t marker = 0xc0dec0dec0dec0deull;
|
||||
|
||||
~OwnedCallbackPayload() {
|
||||
destroy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
void DeleteOwnedEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *payload = static_cast<OwnedCallbackPayload *>(event->filter.data);
|
||||
Check(payload != nullptr, "owned callback payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback runs after registry removal");
|
||||
payload->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
event->filter.owner.reset();
|
||||
payload->stage.store(1, std::memory_order_release);
|
||||
while (payload->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
Check(payload->marker == 0xc0dec0dec0dec0deull,
|
||||
"owned callback payload remains valid");
|
||||
}
|
||||
|
||||
void TestCallbackOwnsPayload() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "owned-callback") == OK,
|
||||
"create owned callback queue");
|
||||
|
||||
std::atomic_uint32_t stage{0};
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
std::atomic_uint32_t destroy_count{0};
|
||||
auto registration =
|
||||
std::make_shared<OwnedCallbackPayload>(stage, delete_count, destroy_count);
|
||||
std::weak_ptr<OwnedCallbackPayload> weak_registration = registration;
|
||||
std::vector<std::shared_ptr<OwnedCallbackPayload>> port_registrations{
|
||||
registration};
|
||||
{
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 2;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DeleteOwnedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add owned callback event");
|
||||
}
|
||||
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete owned callback queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback queue removed while callback blocked");
|
||||
port_registrations.clear();
|
||||
registration.reset();
|
||||
Check(!weak_registration.expired(),
|
||||
"delete callback retains detached payload");
|
||||
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback runs exactly once");
|
||||
Check(weak_registration.expired(),
|
||||
"owned callback payload released with event");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback payload destroyed exactly once");
|
||||
}
|
||||
|
||||
void TestPinnedClose() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "pinned-close") == OK,
|
||||
"create pinned queue");
|
||||
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 1;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = &delete_count;
|
||||
event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add callback event");
|
||||
|
||||
auto owner = EventQueue::KernelPinEqueue(queue);
|
||||
Check(owner != nullptr, "pin live queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete pinned queue");
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"close invokes callback once");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "deleted queue leaves registry");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 1,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
nullptr) == KERNEL_ERROR_EBADF,
|
||||
"stale trigger rejected");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == KERNEL_ERROR_EBADF,
|
||||
"second queue delete rejected");
|
||||
|
||||
owner.reset();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"deferred destruction does not repeat callback");
|
||||
}
|
||||
|
||||
void TestStaleHandleNeverAliasesNewQueue() {
|
||||
EventQueue::KernelEqueue stale = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&stale, "stale-handle") == OK,
|
||||
"create stale queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(stale) == OK, "delete stale queue");
|
||||
|
||||
EventQueue::KernelEqueue replacement = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&replacement, "replacement") == OK,
|
||||
"create replacement queue");
|
||||
Check(stale != replacement, "queue handles are never recycled");
|
||||
Check(!EventQueue::KernelPinEqueue(stale), "stale handle does not pin");
|
||||
Check(EventQueue::KernelAddUserEvent(stale, 11) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot mutate replacement");
|
||||
Check(EventQueue::KernelAddUserEvent(replacement, 11) == OK,
|
||||
"replacement handle remains valid");
|
||||
Check(EventQueue::KernelTriggerUserEvent(stale, 11, nullptr) ==
|
||||
KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot trigger replacement");
|
||||
Check(EventQueue::KernelTriggerUserEvent(replacement, 11, nullptr) == OK,
|
||||
"replacement event triggers");
|
||||
Check(EventQueue::KernelDeleteEqueue(replacement) == OK,
|
||||
"delete replacement queue");
|
||||
}
|
||||
|
||||
void TestConcurrentCloseCallback() {
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "callback-race") == OK,
|
||||
"create callback race queue");
|
||||
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
EventQueue::KernelEqueueEvent callback_event{};
|
||||
callback_event.event.ident = 9;
|
||||
callback_event.event.filter = EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
callback_event.filter.data = &delete_count;
|
||||
callback_event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, callback_event) == OK,
|
||||
"add callback race event");
|
||||
|
||||
std::atomic_bool start{false};
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 9, EventQueue::KERNEL_EVFILT_GRAPHICS, nullptr),
|
||||
"callback race trigger result");
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"callback race queue delete");
|
||||
trigger.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"concurrent close invokes callback exactly once");
|
||||
}
|
||||
}
|
||||
|
||||
void TestConcurrentDelete() {
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "concurrent-delete") == OK,
|
||||
"create concurrent queue");
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 7;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_USER;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add concurrent event");
|
||||
|
||||
std::atomic_bool start{false};
|
||||
std::jthread mutate([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelAddEvent(queue, event),
|
||||
"concurrent add result");
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"concurrent trigger result");
|
||||
CheckConcurrentResult(EventQueue::KernelDeleteEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER),
|
||||
"concurrent event delete result");
|
||||
}
|
||||
});
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 128; i++) {
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"parallel trigger result");
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"concurrent queue delete");
|
||||
mutate.join();
|
||||
trigger.join();
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"concurrent queue removed from registry");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestDuplicateAddPreservesEventState();
|
||||
TestCallbackStateOutlivesPort();
|
||||
TestCallbackOwnsPayload();
|
||||
TestPinnedClose();
|
||||
TestStaleHandleNeverAliasesNewQueue();
|
||||
TestConcurrentCloseCallback();
|
||||
TestConcurrentDelete();
|
||||
std::printf("EventQueueLifetimeTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
+206
-178
@@ -1,3 +1,4 @@
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/shader/recompiler/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ResourceTracking.h"
|
||||
@@ -5,7 +6,6 @@
|
||||
#include "graphics/shader/recompiler/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/SrtPatcher.h"
|
||||
#include "graphics/shader/recompiler/SrtWalker.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -164,7 +164,7 @@ const StageOutput *FindOutput(const ShaderInfo &info, StageOutputKind kind,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Prepare(Program& program) {
|
||||
void Prepare(Program &program) {
|
||||
std::string error;
|
||||
if (!BuildScalarProvenance(program, &error) ||
|
||||
!BuildSrtPlan(program, &error) || !PatchSrtReads(program, &error) ||
|
||||
@@ -230,16 +230,17 @@ void TestScalarAndVectorBufferAlias() {
|
||||
void TestBufferImageAliasIsLinkedDuringTracking() {
|
||||
Program program;
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {
|
||||
BufferUse(4, 0), BufferUse(8, 8),
|
||||
ImageUse(12, Opcode::ImageLoad, ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
program.blocks[0].instructions = {BufferUse(4, 0), BufferUse(8, 8),
|
||||
ImageUse(12, Opcode::ImageLoad,
|
||||
ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
|
||||
Prepare(program);
|
||||
Check(program.info.buffers.size() == 2 && program.info.images.size() == 1 &&
|
||||
program.info.buffers[0].image_alias == 0 &&
|
||||
program.info.buffers[1].image_alias == BufferResource::NoImageAlias,
|
||||
"buffer/image descriptor provenance aliases were not linked during tracking");
|
||||
"buffer/image descriptor provenance aliases were not linked during "
|
||||
"tracking");
|
||||
}
|
||||
|
||||
void TestImagesAndSamplers() {
|
||||
@@ -462,9 +463,12 @@ void TestComputeShaderInfoCollection() {
|
||||
error.c_str());
|
||||
Check(program.shader_info_complete && program.info.inputs.size() == 3 &&
|
||||
FindInput(program.info, StageInputKind::WorkgroupId) != nullptr &&
|
||||
FindInput(program.info, StageInputKind::LocalInvocationId) == nullptr &&
|
||||
FindInput(program.info, StageInputKind::LocalInvocationIndex) != nullptr &&
|
||||
FindInput(program.info, StageInputKind::GlobalInvocationId) != nullptr &&
|
||||
FindInput(program.info, StageInputKind::LocalInvocationId) ==
|
||||
nullptr &&
|
||||
FindInput(program.info, StageInputKind::LocalInvocationIndex) !=
|
||||
nullptr &&
|
||||
FindInput(program.info, StageInputKind::GlobalInvocationId) !=
|
||||
nullptr &&
|
||||
program.info.buffers == buffers && program.info.images == images &&
|
||||
program.info.samplers == samplers &&
|
||||
program.info.sampled_pairs == pairs,
|
||||
@@ -473,16 +477,20 @@ void TestComputeShaderInfoCollection() {
|
||||
Program metadata_program;
|
||||
metadata_program.stage = ShaderType::Compute;
|
||||
metadata_program.blocks.resize(1);
|
||||
Instruction xor_address;
|
||||
xor_address.op = Opcode::BitwiseXor3U32;
|
||||
metadata_program.blocks[0].instructions = {xor_address};
|
||||
Prepare(metadata_program);
|
||||
compute = {};
|
||||
compute.thread_ids_num = 2;
|
||||
Check(CollectShaderInfo(metadata_program, {.compute = &compute}, &error),
|
||||
error.c_str());
|
||||
Check(FindInput(metadata_program.info, StageInputKind::LocalInvocationId) !=
|
||||
nullptr &&
|
||||
nullptr &&
|
||||
FindInput(metadata_program.info,
|
||||
StageInputKind::LocalInvocationIndex) != nullptr,
|
||||
"compute thread metadata did not request local invocation inputs");
|
||||
StageInputKind::LocalInvocationIndex) != nullptr &&
|
||||
metadata_program.info.has_bitwise_xor,
|
||||
"compute thread metadata or XOR-address heuristic was not collected");
|
||||
}
|
||||
|
||||
void TestVertexShaderInfoCollection() {
|
||||
@@ -496,16 +504,15 @@ void TestVertexShaderInfoCollection() {
|
||||
Instruction attr2 = attr0;
|
||||
attr2.input_info.attr = 2;
|
||||
attr2.input_info.chan = 0;
|
||||
program.blocks[0].instructions = {
|
||||
attr0, attr2, Export(8, ExportTargetKind::Position),
|
||||
Export(12, ExportTargetKind::Parameter, 2)};
|
||||
program.blocks[0].instructions = {attr0, attr2,
|
||||
Export(8, ExportTargetKind::Position),
|
||||
Export(12, ExportTargetKind::Parameter, 2)};
|
||||
Prepare(program);
|
||||
|
||||
ShaderVertexInputInfo vertex;
|
||||
vertex.resources_num = 3;
|
||||
std::string error;
|
||||
Check(CollectShaderInfo(program, {.vertex = &vertex}, &error),
|
||||
error.c_str());
|
||||
Check(CollectShaderInfo(program, {.vertex = &vertex}, &error), error.c_str());
|
||||
const auto *input0 = FindInput(program.info, StageInputKind::Parameter, 0);
|
||||
const auto *input2 = FindInput(program.info, StageInputKind::Parameter, 2);
|
||||
Check(program.info.inputs.size() == 4 && input0 != nullptr &&
|
||||
@@ -534,11 +541,10 @@ void TestPixelShaderInfoCollection() {
|
||||
Program program;
|
||||
program.stage = ShaderType::Pixel;
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {
|
||||
Export(4, ExportTargetKind::Mrt, 3),
|
||||
Export(8, ExportTargetKind::MrtZ, 0, 0x4),
|
||||
Export(12, ExportTargetKind::Mrt, 3),
|
||||
Export(16, ExportTargetKind::Mrt, 7, 0)};
|
||||
program.blocks[0].instructions = {Export(4, ExportTargetKind::Mrt, 3),
|
||||
Export(8, ExportTargetKind::MrtZ, 0, 0x4),
|
||||
Export(12, ExportTargetKind::Mrt, 3),
|
||||
Export(16, ExportTargetKind::Mrt, 7, 0)};
|
||||
Prepare(program);
|
||||
|
||||
ShaderPixelInputInfo pixel;
|
||||
@@ -548,8 +554,7 @@ void TestPixelShaderInfoCollection() {
|
||||
pixel.ps_depth_export_enable = true;
|
||||
pixel.ps_sample_mask_export_enable = true;
|
||||
std::string error;
|
||||
Check(CollectShaderInfo(program, {.pixel = &pixel}, &error),
|
||||
error.c_str());
|
||||
Check(CollectShaderInfo(program, {.pixel = &pixel}, &error), error.c_str());
|
||||
Check(program.info.inputs.size() == 4 &&
|
||||
FindInput(program.info, StageInputKind::FragCoord) != nullptr &&
|
||||
FindInput(program.info, StageInputKind::FrontFacing) != nullptr &&
|
||||
@@ -560,7 +565,8 @@ void TestPixelShaderInfoCollection() {
|
||||
FindOutput(program.info, StageOutputKind::Mrt, 7) == nullptr &&
|
||||
FindOutput(program.info, StageOutputKind::Depth) == nullptr &&
|
||||
FindOutput(program.info, StageOutputKind::SampleMask) != nullptr,
|
||||
"pixel inputs, disabled exports, or sample-mask-only MRTZ collection was wrong");
|
||||
"pixel inputs, disabled exports, or sample-mask-only MRTZ collection "
|
||||
"was wrong");
|
||||
|
||||
Program depth_program;
|
||||
depth_program.stage = ShaderType::Pixel;
|
||||
@@ -572,7 +578,8 @@ void TestPixelShaderInfoCollection() {
|
||||
error.c_str());
|
||||
Check(depth_program.info.outputs.size() == 1 &&
|
||||
FindOutput(depth_program.info, StageOutputKind::Depth) != nullptr &&
|
||||
FindOutput(depth_program.info, StageOutputKind::SampleMask) == nullptr,
|
||||
FindOutput(depth_program.info, StageOutputKind::SampleMask) ==
|
||||
nullptr,
|
||||
"depth-only MRTZ export incorrectly enabled sample-mask output");
|
||||
}
|
||||
|
||||
@@ -670,13 +677,13 @@ void TestTrackingRequiresSrtPatching() {
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {BufferUse(4, 0)};
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
BuildSrtPlan(program, &error),
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error),
|
||||
error.c_str());
|
||||
const auto source = program.blocks[0].instructions[0].memory.resource_source;
|
||||
Check(!TrackResources(program, &error) &&
|
||||
error.find("SRT reads were not patched") != std::string::npos &&
|
||||
program.blocks[0].instructions[0].memory.resource_source == source &&
|
||||
program.blocks[0].instructions[0].memory.resource_source ==
|
||||
source &&
|
||||
!program.resource_tracking_complete,
|
||||
"resource tracking bypassed SRT patch completion");
|
||||
Check(PatchSrtReads(program, &error) && TrackResources(program, &error),
|
||||
@@ -693,8 +700,7 @@ void TestDynamicSrtReadRemainsExplicit() {
|
||||
load.src_count = 1;
|
||||
insts.push_back(load);
|
||||
}
|
||||
insts.push_back(ImageUse(0x40, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
insts.push_back(ImageUse(0x40, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D));
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
@@ -783,12 +789,13 @@ void TestScalarMemoryGroupsSnapshotOperands() {
|
||||
const auto value = insts[i].scalar_value;
|
||||
const auto &node = program.provenance.values[value];
|
||||
const auto offset_arg = buffer ? 4u : 2u;
|
||||
Check(program.provenance.values[node.args[offset_arg]].op ==
|
||||
ScalarValueOp::UserData &&
|
||||
program.provenance.values[node.args[offset_arg]].imm == 20 &&
|
||||
insts[i].op ==
|
||||
(buffer ? Opcode::SBufferLoadDword : Opcode::SLoadDword),
|
||||
"overlapping scalar-memory offset was read after a component write");
|
||||
Check(
|
||||
program.provenance.values[node.args[offset_arg]].op ==
|
||||
ScalarValueOp::UserData &&
|
||||
program.provenance.values[node.args[offset_arg]].imm == 20 &&
|
||||
insts[i].op ==
|
||||
(buffer ? Opcode::SBufferLoadDword : Opcode::SLoadDword),
|
||||
"overlapping scalar-memory offset was read after a component write");
|
||||
}
|
||||
};
|
||||
CheckOffsetOverlap(false);
|
||||
@@ -801,8 +808,7 @@ void TestSrtPatchingHandlesGvnAndMoveForwarding() {
|
||||
auto &insts = program.blocks[0].instructions;
|
||||
for (uint32_t copy = 0; copy < 2; copy++) {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
insts.push_back(ScalarLoad(copy * 0x20 + i * 4, copy * 4 + i, 16,
|
||||
i * 4));
|
||||
insts.push_back(ScalarLoad(copy * 0x20 + i * 4, copy * 4 + i, 16, i * 4));
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
@@ -817,8 +823,7 @@ void TestSrtPatchingHandlesGvnAndMoveForwarding() {
|
||||
PatchSrtReads(program, &error),
|
||||
error.c_str());
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
Check(insts[i].op == Opcode::LoadSrtDword &&
|
||||
insts[i].src[0].imm == i % 4,
|
||||
Check(insts[i].op == Opcode::LoadSrtDword && insts[i].src[0].imm == i % 4,
|
||||
"all producers of a GVN'd SRT read were not patched");
|
||||
}
|
||||
for (uint32_t i = 8; i < 12; i++) {
|
||||
@@ -867,25 +872,25 @@ void TestSrtPatchPlanValidation() {
|
||||
}
|
||||
insts.push_back(BufferUse(0x20, 0));
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
BuildSrtPlan(program, &error),
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error),
|
||||
error.c_str());
|
||||
program.srt.reads = {program.srt.reads[0], program.srt.reads[0]};
|
||||
const auto instructions = insts;
|
||||
Check(!PatchSrtReads(program, &error) &&
|
||||
error.find("dense value-to-offset bijection") != std::string::npos &&
|
||||
error.find("dense value-to-offset bijection") !=
|
||||
std::string::npos &&
|
||||
insts == instructions && !program.srt_patching_complete,
|
||||
"duplicate SRT flat slots were accepted or partially patched");
|
||||
|
||||
Program dynamic;
|
||||
dynamic.blocks.resize(1);
|
||||
dynamic.blocks[0].instructions = {BufferUse(4, 0)};
|
||||
Check(BuildScalarProvenance(dynamic, &error) &&
|
||||
BuildSrtPlan(dynamic, &error),
|
||||
Check(BuildScalarProvenance(dynamic, &error) && BuildSrtPlan(dynamic, &error),
|
||||
error.c_str());
|
||||
dynamic.srt.dynamic_sources = {ScalarProvenance::Undefined};
|
||||
Check(!PatchSrtReads(dynamic, &error) &&
|
||||
error.find("invalid dynamic descriptor source") != std::string::npos &&
|
||||
error.find("invalid dynamic descriptor source") !=
|
||||
std::string::npos &&
|
||||
!dynamic.srt_patching_complete,
|
||||
"undefined dynamic descriptor source was accepted");
|
||||
}
|
||||
@@ -904,8 +909,7 @@ void TestMaterializationSharesReadConstEvaluation() {
|
||||
}
|
||||
insts.push_back(ImageUse(0x40, Opcode::ImageSample, ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2D));
|
||||
insts.push_back(ImageUse(0x44, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
insts.push_back(ImageUse(0x44, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D));
|
||||
Prepare(program);
|
||||
Check(program.info.images.size() == 2 && program.info.samplers.size() == 1,
|
||||
@@ -928,8 +932,8 @@ void TestMaterializationSharesReadConstEvaluation() {
|
||||
for (uint32_t i = 0; i < memory.words.size(); i++) {
|
||||
memory.words[i] = 0x100 + i;
|
||||
}
|
||||
memory.words[3] |=
|
||||
Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) << 28u;
|
||||
memory.words[3] |= Prospero::GpuEnumValue(Prospero::ImageType::kColor2D)
|
||||
<< 28u;
|
||||
std::array<uint32_t, 32> user_data{};
|
||||
user_data[16] = static_cast<uint32_t>(memory.base);
|
||||
user_data[17] = static_cast<uint32_t>(memory.base >> 32u);
|
||||
@@ -943,12 +947,13 @@ void TestMaterializationSharesReadConstEvaluation() {
|
||||
snapshot.images[0] == snapshot.images[1],
|
||||
"dense runtime snapshot did not preserve resource order or aliases");
|
||||
Check(snapshot.flattened_srt.size() == 8 &&
|
||||
std::equal(snapshot.flattened_srt.begin(), snapshot.flattened_srt.end(),
|
||||
memory.words.begin()) &&
|
||||
snapshot.user_data == std::vector<uint32_t>(user_data.begin(), user_data.end()),
|
||||
std::equal(snapshot.flattened_srt.begin(),
|
||||
snapshot.flattened_srt.end(), memory.words.begin()) &&
|
||||
snapshot.user_data ==
|
||||
std::vector<uint32_t>(user_data.begin(), user_data.end()),
|
||||
"runtime snapshot omitted flattened SRT or current user data");
|
||||
Check(memory.reads == 8,
|
||||
"aliased image views repeated ReadConst evaluation instead of sharing it");
|
||||
Check(memory.reads == 8, "aliased image views repeated ReadConst evaluation "
|
||||
"instead of sharing it");
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
Check(snapshot.images[0].dwords[i] == memory.words[i],
|
||||
"materialized image descriptor contains the wrong dword");
|
||||
@@ -960,16 +965,16 @@ void TestInvalidImagesMaterializeAsNull() {
|
||||
sampled.stage = ShaderType::Pixel;
|
||||
sampled.user_data_count = 8;
|
||||
sampled.blocks.resize(1);
|
||||
sampled.blocks[0].instructions = {
|
||||
ImageUse(0x40, Opcode::ImageLoad, ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
sampled.blocks[0].instructions = {ImageUse(0x40, Opcode::ImageLoad,
|
||||
ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
Prepare(sampled);
|
||||
Check(sampled.info.images.size() == 1 && sampled.info.samplers.empty(),
|
||||
"sampled-image normalization test has unexpected resource topology");
|
||||
|
||||
constexpr std::array<uint32_t, 8> stale = {
|
||||
0x00000004, 0x00000004, 0xc0061060, 0x06000514,
|
||||
0x20010000, 0xa4580290, 0x00000004, 0x00000001};
|
||||
constexpr std::array<uint32_t, 8> stale = {0x00000004, 0x00000004, 0xc0061060,
|
||||
0x06000514, 0x20010000, 0xa4580290,
|
||||
0x00000004, 0x00000001};
|
||||
std::string error;
|
||||
for (uint32_t type = 0; type < 8; type++) {
|
||||
auto descriptor = stale;
|
||||
@@ -997,9 +1002,9 @@ void TestInvalidImagesMaterializeAsNull() {
|
||||
storage.stage = ShaderType::Compute;
|
||||
storage.user_data_count = 8;
|
||||
storage.blocks.resize(1);
|
||||
storage.blocks[0].instructions = {
|
||||
ImageUse(0x40, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
storage.blocks[0].instructions = {ImageUse(0x40, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
Prepare(storage);
|
||||
ResourceSnapshot storage_snapshot;
|
||||
Check(MaterializeResources(storage, {stale}, storage_snapshot, &error) &&
|
||||
@@ -1016,8 +1021,7 @@ void TestMaterializationFailureIsTransactional() {
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
insts.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
||||
}
|
||||
insts.push_back(ImageUse(0x40, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
insts.push_back(ImageUse(0x40, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D));
|
||||
Prepare(program);
|
||||
|
||||
@@ -1056,18 +1060,20 @@ void TestResourceSpecializationIsTypedAndTransactional() {
|
||||
null_snapshot.images[0].dwords[2] = 0x89abcdefu;
|
||||
null_snapshot.images[0].dwords[3] = 0x01234567u;
|
||||
std::string error;
|
||||
Check(SpecializeResources(null_program, null_snapshot, &error) &&
|
||||
ValidateResourceSpecialization(null_program, null_snapshot, &error) &&
|
||||
null_program.info.images[0].kind == ResourceKind::Image &&
|
||||
null_program.info.images[0].dimension == Decoder::ImageDimension::Dim3D,
|
||||
"zero-base image descriptor did not preserve tracked null-image shape");
|
||||
Check(
|
||||
SpecializeResources(null_program, null_snapshot, &error) &&
|
||||
ValidateResourceSpecialization(null_program, null_snapshot, &error) &&
|
||||
null_program.info.images[0].kind == ResourceKind::Image &&
|
||||
null_program.info.images[0].dimension ==
|
||||
Decoder::ImageDimension::Dim3D,
|
||||
"zero-base image descriptor did not preserve tracked null-image shape");
|
||||
|
||||
Program program;
|
||||
program.stage = ShaderType::Compute;
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {
|
||||
ImageUse(0x20, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim3D)};
|
||||
program.blocks[0].instructions = {ImageUse(0x20, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim3D)};
|
||||
Prepare(program);
|
||||
|
||||
ResourceSnapshot snapshot;
|
||||
@@ -1098,7 +1104,8 @@ void TestResourceSpecializationIsTypedAndTransactional() {
|
||||
image.storage_swizzle == 0x3acu &&
|
||||
inst.memory.kind == ResourceKind::StorageImageUint &&
|
||||
inst.memory.image_dimension == Decoder::ImageDimension::Dim2D,
|
||||
"runtime descriptor shape and integer format did not specialize dense IR");
|
||||
"runtime descriptor shape and integer format did not specialize dense "
|
||||
"IR");
|
||||
|
||||
auto stale_swizzle = snapshot;
|
||||
stale_swizzle.images[0].dwords[3] =
|
||||
@@ -1115,9 +1122,12 @@ void TestResourceSpecializationIsTypedAndTransactional() {
|
||||
error.c_str());
|
||||
const auto *binding =
|
||||
FindBinding(program.bindings, DescriptorBindingKind::StorageUint2D);
|
||||
Check(binding != nullptr && binding->resources == std::vector<uint32_t>({0}) &&
|
||||
FindBinding(program.bindings, DescriptorBindingKind::Storage3D) == nullptr,
|
||||
"specialized image topology did not reach the exact native binding group");
|
||||
Check(binding != nullptr &&
|
||||
binding->resources == std::vector<uint32_t>({0}) &&
|
||||
FindBinding(program.bindings, DescriptorBindingKind::Storage3D) ==
|
||||
nullptr,
|
||||
"specialized image topology did not reach the exact native binding "
|
||||
"group");
|
||||
}
|
||||
|
||||
void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
||||
@@ -1132,11 +1142,12 @@ void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
||||
buffer_snapshot.buffers[0].dword_count = 4;
|
||||
buffer_snapshot.buffers[0].dwords[1] = (16u << 16u) | (1u << 31u);
|
||||
buffer_snapshot.buffers[0].dwords[3] =
|
||||
(Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) << 12u) | (2u << 21u) |
|
||||
(1u << 23u);
|
||||
(Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) << 12u) |
|
||||
(2u << 21u) | (1u << 23u);
|
||||
std::string error;
|
||||
Check(SpecializeResources(buffer_program, buffer_snapshot, &error) &&
|
||||
ValidateResourceSpecialization(buffer_program, buffer_snapshot, &error),
|
||||
ValidateResourceSpecialization(buffer_program, buffer_snapshot,
|
||||
&error),
|
||||
error.c_str());
|
||||
Check(buffer_program.info.buffers[0].packed_stride ==
|
||||
(16u | (1u << 14u) | (2u << 16u) | (1u << 20u)) &&
|
||||
@@ -1162,9 +1173,10 @@ void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
||||
error.c_str());
|
||||
auto truncated_user_data = buffer_snapshot;
|
||||
truncated_user_data.user_data.resize(3);
|
||||
Check(!ValidateResourceSnapshot(buffer_program, truncated_user_data, &error) &&
|
||||
error.find("user SGPR 3") != std::string::npos,
|
||||
"truncated cache snapshot user-data window was accepted");
|
||||
Check(
|
||||
!ValidateResourceSnapshot(buffer_program, truncated_user_data, &error) &&
|
||||
error.find("user SGPR 3") != std::string::npos,
|
||||
"truncated cache snapshot user-data window was accepted");
|
||||
|
||||
Program based_program;
|
||||
based_program.stage = ShaderType::Compute;
|
||||
@@ -1173,8 +1185,8 @@ void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
||||
raw.memory.offset = static_cast<uint32_t>(-4);
|
||||
raw.src[0] = Sgpr(20);
|
||||
raw.src_count = 1;
|
||||
based_program.blocks[0].instructions = {
|
||||
MoveImmediate(0, 16, 0x1000), MoveImmediate(4, 17, 0), raw};
|
||||
based_program.blocks[0].instructions = {MoveImmediate(0, 16, 0x1000),
|
||||
MoveImmediate(4, 17, 0), raw};
|
||||
Prepare(based_program);
|
||||
ResourceSnapshot based_snapshot;
|
||||
Check(MaterializeResources(based_program, {}, based_snapshot, &error) &&
|
||||
@@ -1184,7 +1196,8 @@ void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
||||
relocated.addresses[0].guest_base += 0x1000;
|
||||
relocated.addresses[0].binding_base += 0x1000;
|
||||
Check(ValidateResourceSpecialization(based_program, relocated, &error),
|
||||
"relocated based address with identical relative bias changed specialization");
|
||||
"relocated based address with identical relative bias changed "
|
||||
"specialization");
|
||||
relocated.addresses[0].binding_base++;
|
||||
Check(!ValidateResourceSpecialization(based_program, relocated, &error) &&
|
||||
error.find("address resource 0") != std::string::npos,
|
||||
@@ -1199,19 +1212,22 @@ void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
||||
flat_program.blocks[0].instructions = {flat};
|
||||
Prepare(flat_program);
|
||||
ResourceSnapshot flat_snapshot;
|
||||
flat_snapshot.addresses = {{0x11u, 0x10u}};
|
||||
flat_snapshot.addresses = {{0x11u, 0x10u}};
|
||||
flat_snapshot.user_data = {0xdeadbeefu};
|
||||
const auto prior_flat_snapshot = flat_snapshot;
|
||||
Check(!MaterializeResources(flat_program, {}, flat_snapshot, &error) &&
|
||||
error.find("requires runtime guest-address translation") != std::string::npos &&
|
||||
flat_snapshot.addresses == prior_flat_snapshot.addresses &&
|
||||
flat_snapshot.user_data == prior_flat_snapshot.user_data,
|
||||
"unbased flat memory without a translator did not fail transactionally");
|
||||
Check(
|
||||
!MaterializeResources(flat_program, {}, flat_snapshot, &error) &&
|
||||
error.find("requires runtime guest-address translation") !=
|
||||
std::string::npos &&
|
||||
flat_snapshot.addresses == prior_flat_snapshot.addresses &&
|
||||
flat_snapshot.user_data == prior_flat_snapshot.user_data,
|
||||
"unbased flat memory without a translator did not fail transactionally");
|
||||
SrtRuntime flat_runtime;
|
||||
flat_runtime.flat_memory_base = 0x100000000ull;
|
||||
Check(MaterializeResources(flat_program, flat_runtime, flat_snapshot, &error) &&
|
||||
SpecializeResources(flat_program, flat_snapshot, &error),
|
||||
error.c_str());
|
||||
Check(
|
||||
MaterializeResources(flat_program, flat_runtime, flat_snapshot, &error) &&
|
||||
SpecializeResources(flat_program, flat_snapshot, &error),
|
||||
error.c_str());
|
||||
auto stale_flat = flat_snapshot;
|
||||
stale_flat.addresses[0].guest_base += 0x1000;
|
||||
stale_flat.addresses[0].binding_base += 0x1000;
|
||||
@@ -1232,8 +1248,7 @@ void TestTrackedProgramIsImmutable() {
|
||||
}
|
||||
insts.push_back(ImageUse(0x50, Opcode::ImageSample, ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2D));
|
||||
insts.push_back(ImageUse(0x54, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
insts.push_back(ImageUse(0x54, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D));
|
||||
insts.push_back(BufferUse(0x58, 12));
|
||||
Prepare(program);
|
||||
@@ -1263,8 +1278,9 @@ void TestTrackedProgramIsImmutable() {
|
||||
insts.size() == memory.size(),
|
||||
"rejected post-track pass mutated immutable program state");
|
||||
for (uint32_t i = 0; i < memory.size(); i++) {
|
||||
Check(insts[i].memory == memory[i],
|
||||
"rejected post-track pass mutated a dense operand or source handle");
|
||||
Check(
|
||||
insts[i].memory == memory[i],
|
||||
"rejected post-track pass mutated a dense operand or source handle");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1289,8 +1305,7 @@ void TestNativeBindingLayout() {
|
||||
Decoder::ImageDimension::Dim2D, 0, 2));
|
||||
insts.push_back(ImageUse(12, Opcode::ImageSample, ResourceKind::Image,
|
||||
Decoder::ImageDimension::Dim2DArray, 0, 2));
|
||||
insts.push_back(ImageUse(16, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
insts.push_back(ImageUse(16, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim3D, 4));
|
||||
insts.push_back(ImageUse(20, Opcode::ImageStore,
|
||||
ResourceKind::StorageImageUint,
|
||||
@@ -1315,25 +1330,26 @@ void TestNativeBindingLayout() {
|
||||
FindBinding(program.bindings, DescriptorBindingKind::StorageUint2DArray);
|
||||
const auto *samplers =
|
||||
FindBinding(program.bindings, DescriptorBindingKind::Samplers);
|
||||
Check(program.bindings.descriptor_set == 3 && buffers != nullptr &&
|
||||
sampled2d != nullptr && sampled_array != nullptr &&
|
||||
storage3d != nullptr && storage_uint_array != nullptr &&
|
||||
samplers != nullptr && buffers->binding == 0 &&
|
||||
sampled2d->binding == 1 && sampled_array->binding == 2 &&
|
||||
storage3d->binding == 3 && storage_uint_array->binding == 4 &&
|
||||
samplers->binding == 5 && buffers->resources ==
|
||||
std::vector<uint32_t>{0} &&
|
||||
sampled2d->resources == std::vector<uint32_t>{0} &&
|
||||
sampled_array->resources == std::vector<uint32_t>{1} &&
|
||||
storage3d->resources == std::vector<uint32_t>{2} &&
|
||||
storage_uint_array->resources == std::vector<uint32_t>{3} &&
|
||||
samplers->resources == std::vector<uint32_t>{0} &&
|
||||
!program.bindings.user_data_registers.empty() &&
|
||||
program.bindings.push_constant_size != 0 &&
|
||||
FindBinding(program.bindings,
|
||||
DescriptorBindingKind::FlattenedSrt) == nullptr &&
|
||||
program.binding_layout_complete,
|
||||
"native binding allocator did not preserve dense typed resource groups");
|
||||
Check(
|
||||
program.bindings.descriptor_set == 3 && buffers != nullptr &&
|
||||
sampled2d != nullptr && sampled_array != nullptr &&
|
||||
storage3d != nullptr && storage_uint_array != nullptr &&
|
||||
samplers != nullptr && buffers->binding == 0 &&
|
||||
sampled2d->binding == 1 && sampled_array->binding == 2 &&
|
||||
storage3d->binding == 3 && storage_uint_array->binding == 4 &&
|
||||
samplers->binding == 5 &&
|
||||
buffers->resources == std::vector<uint32_t>{0} &&
|
||||
sampled2d->resources == std::vector<uint32_t>{0} &&
|
||||
sampled_array->resources == std::vector<uint32_t>{1} &&
|
||||
storage3d->resources == std::vector<uint32_t>{2} &&
|
||||
storage_uint_array->resources == std::vector<uint32_t>{3} &&
|
||||
samplers->resources == std::vector<uint32_t>{0} &&
|
||||
!program.bindings.user_data_registers.empty() &&
|
||||
program.bindings.push_constant_size != 0 &&
|
||||
FindBinding(program.bindings, DescriptorBindingKind::FlattenedSrt) ==
|
||||
nullptr &&
|
||||
program.binding_layout_complete,
|
||||
"native binding allocator did not preserve dense typed resource groups");
|
||||
}
|
||||
|
||||
void TestNativeBindingLayoutSrtAndUserDataOverflow() {
|
||||
@@ -1341,8 +1357,7 @@ void TestNativeBindingLayoutSrtAndUserDataOverflow() {
|
||||
srt.stage = ShaderType::Compute;
|
||||
srt.blocks.resize(1);
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
srt.blocks[0].instructions.push_back(
|
||||
ScalarLoad(i * 4, i, 16, i * 4));
|
||||
srt.blocks[0].instructions.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
||||
}
|
||||
srt.blocks[0].instructions.push_back(BufferUse(0x20, 0));
|
||||
Prepare(srt);
|
||||
@@ -1392,7 +1407,7 @@ void TestNativeBindingLayoutGds() {
|
||||
program.blocks.resize(1);
|
||||
Instruction append;
|
||||
append.op = Opcode::DsAppend;
|
||||
append.memory.kind = ResourceKind::Gds;
|
||||
append.memory.kind = ResourceKind::Gds;
|
||||
program.blocks[0].instructions.push_back(append);
|
||||
|
||||
ShaderComputeInputInfo compute;
|
||||
@@ -1407,18 +1422,18 @@ void TestNativeBindingLayoutGds() {
|
||||
Check(gds != nullptr && gds->binding == 0 && gds->resources.empty(),
|
||||
"GDS append/consume did not allocate one native storage binding");
|
||||
|
||||
Program lds;
|
||||
lds.stage = ShaderType::Compute;
|
||||
lds.blocks.resize(1);
|
||||
append.memory.kind = ResourceKind::Lds;
|
||||
lds.blocks[0].instructions.push_back(append);
|
||||
Check(BuildScalarProvenance(lds, &error) && BuildSrtPlan(lds, &error) &&
|
||||
PatchSrtReads(lds, &error) && TrackResources(lds, &error) &&
|
||||
CollectShaderInfo(lds, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(lds, {}, &error),
|
||||
error.c_str());
|
||||
Check(FindBinding(lds.bindings, DescriptorBindingKind::Gds) == nullptr,
|
||||
"LDS append/consume incorrectly allocated a GDS binding");
|
||||
Program lds;
|
||||
lds.stage = ShaderType::Compute;
|
||||
lds.blocks.resize(1);
|
||||
append.memory.kind = ResourceKind::Lds;
|
||||
lds.blocks[0].instructions.push_back(append);
|
||||
Check(BuildScalarProvenance(lds, &error) && BuildSrtPlan(lds, &error) &&
|
||||
PatchSrtReads(lds, &error) && TrackResources(lds, &error) &&
|
||||
CollectShaderInfo(lds, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(lds, {}, &error),
|
||||
error.c_str());
|
||||
Check(FindBinding(lds.bindings, DescriptorBindingKind::Gds) == nullptr,
|
||||
"LDS append/consume incorrectly allocated a GDS binding");
|
||||
}
|
||||
|
||||
void TestNativeBindingLayoutIsTransactional() {
|
||||
@@ -1466,7 +1481,8 @@ void TestNativeBindingLayoutIsTransactional() {
|
||||
CollectShaderInfo(tail, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(tail, {.push_constant_offset = 124}, &error) &&
|
||||
tail.bindings.push_constant_size == 4 &&
|
||||
FindBinding(tail.bindings, DescriptorBindingKind::UserData) == nullptr,
|
||||
FindBinding(tail.bindings, DescriptorBindingKind::UserData) ==
|
||||
nullptr,
|
||||
"valid final dword of the guaranteed push-constant range was rejected");
|
||||
}
|
||||
|
||||
@@ -1529,12 +1545,11 @@ void TestNativeBindingLayoutRejectsUnknownShapeAndBadProvenance() {
|
||||
Program image;
|
||||
image.stage = ShaderType::Compute;
|
||||
image.blocks.resize(1);
|
||||
image.blocks[0].instructions = {
|
||||
ImageUse(4, Opcode::ImageStore, ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
image.blocks[0].instructions = {ImageUse(4, Opcode::ImageStore,
|
||||
ResourceKind::StorageImage,
|
||||
Decoder::ImageDimension::Dim2D)};
|
||||
Prepare(image);
|
||||
Check(CollectShaderInfo(image, {.compute = &compute}, &error),
|
||||
error.c_str());
|
||||
Check(CollectShaderInfo(image, {.compute = &compute}, &error), error.c_str());
|
||||
image.info.images[0].dimension = Decoder::ImageDimension::Unknown;
|
||||
image.bindings.descriptor_set = 77;
|
||||
const auto image_layout = image.bindings;
|
||||
@@ -1618,8 +1633,8 @@ void TestNativeBindingLayoutTracksRawScalarMemoryBase() {
|
||||
compute.thread_ids_num = 1;
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
BuildSrtPlan(program, &error) && program.srt.reads.empty() &&
|
||||
program.srt.dynamic_reads.empty() && PatchSrtReads(program, &error) &&
|
||||
TrackResources(program, &error) &&
|
||||
program.srt.dynamic_reads.empty() &&
|
||||
PatchSrtReads(program, &error) && TrackResources(program, &error) &&
|
||||
CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(program, {}, &error),
|
||||
error.c_str());
|
||||
@@ -1649,23 +1664,27 @@ void TestRawScalarMemoryTracksReachingBaseIdentity() {
|
||||
second.src[0] = Sgpr(20);
|
||||
second.src_count = 1;
|
||||
program.blocks[0].instructions = {
|
||||
MoveImmediate(0, 16, 0x1000), MoveImmediate(4, 17, 0), first,
|
||||
MoveImmediate(0, 16, 0x1000), MoveImmediate(4, 17, 0), first,
|
||||
MoveImmediate(12, 16, 0x2000), MoveImmediate(16, 17, 0), second};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error) &&
|
||||
PatchSrtReads(program, &error) && TrackResources(program, &error),
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error) &&
|
||||
TrackResources(program, &error),
|
||||
error.c_str());
|
||||
Check(program.info.addresses.size() == 2 &&
|
||||
program.info.addresses[0].source != program.info.addresses[1].source &&
|
||||
program.info.addresses[0].source !=
|
||||
program.info.addresses[1].source &&
|
||||
program.blocks[0].instructions[2].memory.resource == 0 &&
|
||||
program.blocks[0].instructions[5].memory.resource == 1,
|
||||
"raw scalar loads with redefined SBASE collapsed to one address resource");
|
||||
"raw scalar loads with redefined SBASE collapsed to one address "
|
||||
"resource");
|
||||
|
||||
std::array<uint32_t, 64> user_data{};
|
||||
user_data[20] = 4;
|
||||
ResourceSnapshot snapshot;
|
||||
Check(MaterializeResources(program, {user_data}, snapshot, &error), error.c_str());
|
||||
Check(MaterializeResources(program, {user_data}, snapshot, &error),
|
||||
error.c_str());
|
||||
Check(snapshot.addresses.size() == 2 &&
|
||||
snapshot.addresses[0].guest_base == 0x1000 &&
|
||||
snapshot.addresses[0].binding_base == 0x0ffc &&
|
||||
@@ -1692,11 +1711,13 @@ void TestNativeBindingLayoutUsesExplicitFlatMemory() {
|
||||
error.c_str());
|
||||
const auto *flat =
|
||||
FindBinding(program.bindings, DescriptorBindingKind::AddressMemory);
|
||||
Check(flat != nullptr && flat->resources == std::vector<uint32_t>({0}) &&
|
||||
program.info.addresses.size() == 1 &&
|
||||
program.info.addresses[0].kind == ResourceKind::Flat &&
|
||||
FindBinding(program.bindings, DescriptorBindingKind::Buffers) == nullptr,
|
||||
"flat address space was aliased to an ordinary buffer descriptor group");
|
||||
Check(
|
||||
flat != nullptr && flat->resources == std::vector<uint32_t>({0}) &&
|
||||
program.info.addresses.size() == 1 &&
|
||||
program.info.addresses[0].kind == ResourceKind::Flat &&
|
||||
FindBinding(program.bindings, DescriptorBindingKind::Buffers) ==
|
||||
nullptr,
|
||||
"flat address space was aliased to an ordinary buffer descriptor group");
|
||||
|
||||
ResourceSnapshot snapshot;
|
||||
SrtRuntime runtime;
|
||||
@@ -1727,8 +1748,8 @@ void TestNativeBindingLayoutHonorsUserDataCount() {
|
||||
ShaderComputeInputInfo compute;
|
||||
compute.thread_ids_num = 1;
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
program.provenance.values[program.blocks[0].instructions[0]
|
||||
.scalar_sources[0]]
|
||||
program.provenance
|
||||
.values[program.blocks[0].instructions[0].scalar_sources[0]]
|
||||
.op == ScalarValueOp::UserData &&
|
||||
program.blocks[0].instructions[1].scalar_sources[0] ==
|
||||
ScalarProvenance::Unknown &&
|
||||
@@ -1753,28 +1774,29 @@ void TestNativeBindingLayoutHonorsUserDataBase() {
|
||||
program.user_data_base = 8;
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {
|
||||
Move(0, 20, 7), Move(4, 21, 8), Move(8, 22, 15), Move(12, 23, 16),
|
||||
BufferUse(16, 8)};
|
||||
program.blocks[0].instructions = {Move(0, 20, 7), Move(4, 21, 8),
|
||||
Move(8, 22, 15), Move(12, 23, 16),
|
||||
BufferUse(16, 8)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) &&
|
||||
program.blocks[0].instructions[0].scalar_sources[0] ==
|
||||
ScalarProvenance::Unknown &&
|
||||
program.provenance.values[
|
||||
program.blocks[0].instructions[1].scalar_sources[0]].op ==
|
||||
ScalarValueOp::UserData &&
|
||||
program.provenance
|
||||
.values[program.blocks[0].instructions[1].scalar_sources[0]]
|
||||
.op == ScalarValueOp::UserData &&
|
||||
program.blocks[0].instructions[3].scalar_sources[0] ==
|
||||
ScalarProvenance::Unknown &&
|
||||
BuildSrtPlan(program, &error),
|
||||
error.c_str());
|
||||
|
||||
const std::array<uint32_t, 8> user_data = {
|
||||
0x11111111, 0x22222222, 0x33333333, 0x44444444,
|
||||
0, 0, 0, 0xaaaaaaaa};
|
||||
0x11111111, 0x22222222, 0x33333333, 0x44444444, 0, 0, 0, 0xaaaaaaaa};
|
||||
DescriptorValue descriptor;
|
||||
const auto source = program.blocks[0].instructions.back().memory.resource_source;
|
||||
Check(EvaluateDescriptorSource(program, source, 16, {user_data}, descriptor, &error) &&
|
||||
const auto source =
|
||||
program.blocks[0].instructions.back().memory.resource_source;
|
||||
Check(EvaluateDescriptorSource(program, source, 16, {user_data}, descriptor,
|
||||
&error) &&
|
||||
descriptor.dwords[0] == user_data[0] &&
|
||||
descriptor.dwords[3] == user_data[3],
|
||||
"vertex user-data base was not translated to runtime-local indices");
|
||||
@@ -1784,18 +1806,21 @@ void TestNativeBindingLayoutHonorsUserDataBase() {
|
||||
CollectShaderInfo(program, {.vertex = &vertex}, &error) &&
|
||||
AllocateBindings(program, {}, &error),
|
||||
error.c_str());
|
||||
Check(program.bindings.user_data_registers ==
|
||||
std::vector<uint32_t>({8, 9, 10, 11, 15}),
|
||||
"native binding plan did not retain physical shifted user-SGPR indices");
|
||||
Check(
|
||||
program.bindings.user_data_registers ==
|
||||
std::vector<uint32_t>({8, 9, 10, 11, 15}),
|
||||
"native binding plan did not retain physical shifted user-SGPR indices");
|
||||
}
|
||||
|
||||
void TestTextureNullDescriptorUsesAddressBits() {
|
||||
ShaderTextureResource texture;
|
||||
const uint32_t captured[8] = {0x00000000, 0xc3800000, 0x0059c09f, 0x91b00fac,
|
||||
0x00000000, 0x00700000, 0x00000000, 0x00000000};
|
||||
std::copy(std::begin(captured), std::end(captured), std::begin(texture.fields));
|
||||
Check(texture.IsNull(),
|
||||
"zero-address image with populated metadata was not classified as null");
|
||||
std::copy(std::begin(captured), std::end(captured),
|
||||
std::begin(texture.fields));
|
||||
Check(
|
||||
texture.IsNull(),
|
||||
"zero-address image with populated metadata was not classified as null");
|
||||
texture.fields[0] = 1;
|
||||
Check(!texture.IsNull(), "nonzero image base address was classified as null");
|
||||
}
|
||||
@@ -1803,8 +1828,10 @@ void TestTextureNullDescriptorUsesAddressBits() {
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const char* current = "startup";
|
||||
#define RUN(test) current = #test; test()
|
||||
const char *current = "startup";
|
||||
#define RUN(test) \
|
||||
current = #test; \
|
||||
test()
|
||||
try {
|
||||
RUN(TestDenseBufferPatching);
|
||||
RUN(TestScalarAndVectorBufferAlias);
|
||||
@@ -1849,7 +1876,8 @@ int main() {
|
||||
std::cout << "ResourceTrackingTests: all cases passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "ResourceTrackingTests: " << current << " failed: " << e.what() << '\n';
|
||||
std::cerr << "ResourceTrackingTests: " << current << " failed: " << e.what()
|
||||
<< '\n';
|
||||
return 1;
|
||||
}
|
||||
#undef RUN
|
||||
|
||||
+6246
-3443
File diff suppressed because it is too large
Load Diff
+57
-15
@@ -5,6 +5,7 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
|
||||
#include "graphics/shader/recompiler/ExecMask.h"
|
||||
@@ -446,23 +447,9 @@ void TestNativeShaderResourceDependencies() {
|
||||
Check(writes == std::vector<ShaderBufferWriteRange>({{0x1000, 48}, {0x2000, 64}}),
|
||||
"graphics/compute write collector lost or invented a storage-buffer range");
|
||||
|
||||
VulkanImage image {VulkanImageType::StorageTexture};
|
||||
image.image = reinterpret_cast<vk::Image::CType>(uintptr_t {1});
|
||||
image.layout = vk::ImageLayout::eGeneral;
|
||||
image.layers = 3;
|
||||
const auto image_barrier = MakeStorageImageDependency(image, true, true);
|
||||
Check(image_barrier.oldLayout == vk::ImageLayout::eGeneral &&
|
||||
image_barrier.newLayout == vk::ImageLayout::eGeneral &&
|
||||
(image_barrier.srcAccessMask & vk::AccessFlagBits::eMemoryWrite) &&
|
||||
(image_barrier.dstAccessMask & vk::AccessFlagBits::eShaderRead) &&
|
||||
(image_barrier.dstAccessMask & vk::AccessFlagBits::eShaderWrite) &&
|
||||
image_barrier.subresourceRange.levelCount == VK_REMAINING_MIP_LEVELS &&
|
||||
image_barrier.subresourceRange.layerCount == image.layers,
|
||||
"GENERAL storage-image dependency lacks a complete memory barrier");
|
||||
|
||||
VulkanBuffer buffer;
|
||||
buffer.buffer = reinterpret_cast<vk::Buffer::CType>(uintptr_t {1});
|
||||
const auto gds_barrier = MakeGdsDependency(buffer);
|
||||
const auto gds_barrier = MakeGdsDependency(buffer.buffer);
|
||||
Check((gds_barrier.srcAccessMask & vk::AccessFlagBits::eHostWrite) &&
|
||||
(gds_barrier.srcAccessMask & vk::AccessFlagBits::eTransferWrite) &&
|
||||
(gds_barrier.srcAccessMask & vk::AccessFlagBits::eShaderWrite) &&
|
||||
@@ -472,6 +459,60 @@ void TestNativeShaderResourceDependencies() {
|
||||
"GDS dependency does not order host/transfer/shader writes");
|
||||
}
|
||||
|
||||
void TestNormalizedImageContracts() {
|
||||
ImageInfo container {};
|
||||
container.data = {0x10000, 0x15000};
|
||||
container.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
||||
container.guest_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm);
|
||||
container.type = Prospero::ImageType::kColor2D;
|
||||
container.extent = {64, 64, 1};
|
||||
container.resources = {3, 4};
|
||||
container.pitch = 64;
|
||||
container.bytes_per_block = 4;
|
||||
container.samples = 1;
|
||||
container.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
|
||||
container.mip_layout[0] = {0, 0x10000, 64, 64};
|
||||
container.mip_layout[1] = {0x10000, 0x4000, 32, 32};
|
||||
container.mip_layout[2] = {0x14000, 0x1000, 16, 16};
|
||||
|
||||
ImageInfo subresource = container;
|
||||
subresource.data = {0x22000, 0x1000};
|
||||
subresource.extent = {32, 32, 1};
|
||||
subresource.resources = {1, 1};
|
||||
subresource.pitch = 32;
|
||||
subresource.mip_layout = {};
|
||||
subresource.mip_layout[0] = {0, 0x1000, 32, 32};
|
||||
|
||||
Check(container.BlockExtent() == vk::Extent2D {64, 64},
|
||||
"normalized image block extent changed");
|
||||
Check(subresource.IsCompatible(container), "normalized compatible image was rejected");
|
||||
Check(subresource.MipOf(container) == 1, "normalized mip lookup missed a subresource");
|
||||
Check(subresource.SliceOf(container, 1) == 2,
|
||||
"normalized slice lookup missed a subresource");
|
||||
|
||||
auto incompatible = subresource;
|
||||
incompatible.samples = 2;
|
||||
Check(!incompatible.IsCompatible(container) && incompatible.MipOf(container) == -1,
|
||||
"sample-count mismatch was accepted as a compatible image");
|
||||
|
||||
auto compressed = container;
|
||||
compressed.guest_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::kBc3UNorm);
|
||||
compressed.pitch = 128;
|
||||
compressed.extent.height = 64;
|
||||
Check(compressed.BlockExtent() == vk::Extent2D {32, 16},
|
||||
"block-compressed extent was not expressed in blocks");
|
||||
|
||||
Check(ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm,
|
||||
vk::Format::eR8G8B8A8Uint) &&
|
||||
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat,
|
||||
vk::Format::eR32Sfloat) &&
|
||||
ImageViewOps::FormatsCompatible(vk::Format::eBc3UnormBlock,
|
||||
vk::Format::eR32G32B32A32Uint),
|
||||
"Vulkan image-view compatibility classes diverged from production");
|
||||
}
|
||||
|
||||
void TestNativeSubgroupPolicy() {
|
||||
GraphicContext context;
|
||||
context.subgroup_size = 32;
|
||||
@@ -6743,6 +6784,7 @@ int main() {
|
||||
|
||||
TestResourceDescriptorClassification();
|
||||
TestNativeShaderResourceDependencies();
|
||||
TestNormalizedImageContracts();
|
||||
TestNativeSubgroupPolicy();
|
||||
TestNewShaderRecompilerSMovB32();
|
||||
TestNewShaderRecompilerSoppMarkers();
|
||||
|
||||
Reference in New Issue
Block a user