refactor renderer

This commit is contained in:
nmzik
2026-07-27 02:24:50 +02:00
parent a720b28b7f
commit a20cf5d298
112 changed files with 18269 additions and 18368 deletions
+500
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+57 -15
View File
@@ -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();