graphics: add RDNA2 FLAT BDA buffer cache and faults

This commit is contained in:
nmzik committed 2026-08-27 03:45:41 +02:00
1 parent 8072718462
commit e738535294
37 files changed
+1002 -684

No files matched your search

+23 -36
View File
@@ -295,7 +295,7 @@ void TestInvariantIndirectImageMaterialization() {
const auto same_snapshot = [](const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
lhs.samplers == rhs.samplers && lhs.addresses == rhs.addresses &&
lhs.samplers == rhs.samplers &&
lhs.flattened_srt == rhs.flattened_srt &&
lhs.user_data == rhs.user_data &&
lhs.indirect_images.empty() == rhs.indirect_images.empty();
@@ -848,7 +848,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
Check(fixture.program.srt_reads.size() == 1,
"shared typed scalar read did not receive one flat SRT slot");
Check(fixture.program.info.buffers.size() == 1 &&
fixture.program.info.addresses.empty(),
!fixture.program.info.uses_dma,
"planning-only scalar reads leaked into resource topology");
Check(fixture.program.memory_info[0].planning_only,
"canonical runtime scalar read was not marked planning-only");
@@ -910,7 +910,7 @@ void TestDynamicSrtReadRemainsExplicit() {
Check(fixture.program.srt_reads.empty() &&
fixture.program.dynamic_reads.size() == 1 &&
fixture.program.info.addresses.size() == 1,
fixture.program.info.uses_dma,
"dynamic scalar read was incorrectly flattened or lost");
std::array<uint32_t, 3> user_data{0x1000u, 0u, 4u};
TestMemory memory;
@@ -932,11 +932,13 @@ void TestDynamicSrtReadRemainsExplicit() {
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::AddressMemory) != nullptr,
DescriptorBindingKind::BdaPagetable) != nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FaultBuffer) != nullptr,
"dynamic scalar read received the wrong resource bindings");
Check(fixture.program.bindings.memory_offset_dword ==
fixture.program.bindings.user_data_registers.size() &&
fixture.program.bindings.memory_offset_count == 2u &&
fixture.program.bindings.memory_offset_count == 1u &&
fixture.program.bindings.ShaderDataDwords() ==
fixture.program.bindings.memory_offset_dword + 1u,
"unified memory-offset layout is inconsistent");
@@ -1029,7 +1031,7 @@ void TestInvariantLoopPhi() {
"loop-invariant descriptor phi was not evaluated through typed SSA");
}
void TestAddressMaterializationAndSpecialization() {
void TestDmaAddressMaterialization() {
Fixture fixture;
const auto based =
fixture.Address(fixture.UserData(0), fixture.UserData(1), 4);
@@ -1050,29 +1052,19 @@ void TestAddressMaterializationAndSpecialization() {
fixture.AddMemory(flat, 8));
fixture.PlanAndTrack();
Check(fixture.program.info.addresses.size() == 2 &&
!fixture.program.info.addresses[0].unbased &&
fixture.program.info.addresses[0].min_offset == -8 &&
fixture.program.info.addresses[1].unbased,
"typed based and unbased addresses were classified incorrectly");
Check(fixture.program.info.uses_dma,
"typed address operations did not enable DMA");
std::array<uint32_t, 2> user_data{0x2008u, 0u};
SrtRuntime runtime{.user_data = user_data, .flat_memory_base = 0x9000u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
"address resources did not materialize");
Check(snapshot.addresses.size() == 2 &&
snapshot.addresses[0].guest_base == 0x2008u &&
snapshot.addresses[0].binding_base == 0x2000u &&
snapshot.addresses[1].binding_base == 0x9000u,
"materialized address windows are incorrect");
Check(SpecializeResources(fixture.program, snapshot, &error) &&
fixture.program.info.addresses[0].specialized_base == 8u &&
fixture.program.info.addresses[1].specialized_base == 0x9000u,
"typed address specialization was not applied");
"DMA shader resources did not materialize");
Check(SpecializeResources(fixture.program, snapshot, &error),
"DMA shader resources were not specialized");
}
void TestExecMaskedFlatAddressProvenance() {
void TestDynamicFlatAddressesUseDma() {
Fixture fixture;
const auto low_root = fixture.UserData(0);
const auto high_root = fixture.UserData(1);
@@ -1092,18 +1084,14 @@ void TestExecMaskedFlatAddressProvenance() {
fixture.AddMemory(flat, 0xa4));
fixture.PlanAndTrack();
Check(fixture.program.info.addresses.size() == 1 &&
!fixture.program.info.addresses[0].unbased,
"exec-masked FLAT address lost its active user-data root");
Check(fixture.program.info.uses_dma,
"exec-masked FLAT address did not enable DMA");
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error) &&
snapshot.addresses.size() == 1 &&
snapshot.addresses[0].guest_base == 0x0000000123456780ull &&
snapshot.addresses[0].binding_base == 0x0000000123450000ull,
"exec-masked FLAT address materialized the wrong user-data root");
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
"exec-masked FLAT shader resources did not materialize");
Fixture mismatch;
const auto mismatch_active = mismatch.Emit(ValueOpcode::INotEqual32,
@@ -1122,9 +1110,8 @@ void TestExecMaskedFlatAddressProvenance() {
{mismatch_address, mismatch_low, mismatch_high, other_active},
mismatch.AddMemory(flat, 0xa4));
mismatch.PlanAndTrack();
Check(mismatch.program.info.addresses.size() == 1 &&
mismatch.program.info.addresses[0].unbased,
"FLAT address used a select arm guarded by a different active mask");
Check(mismatch.program.info.uses_dma,
"dynamic FLAT address did not enable DMA");
}
void TestBufferSwizzleSpecialization() {
@@ -1393,8 +1380,8 @@ int main() {
Run("phi validation", TestPhiValidation);
Run("runtime-rooted loop", TestLoopCycleEnteredThroughRuntimeValue);
Run("invariant loop phi", TestInvariantLoopPhi);
Run("address materialization", TestAddressMaterializationAndSpecialization);
Run("exec-masked FLAT address", TestExecMaskedFlatAddressProvenance);
Run("DMA address materialization", TestDmaAddressMaterialization);
Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma);
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
Run("graphics push constants", TestGraphicsPushConstantLayout);
+195 -54
View File
@@ -919,6 +919,11 @@ void CheckLeastRecentlyUsedCacheOrdering() {
std::printf("[host] %-32s ok\n", "LeastRecentlyUsedCache");
}
struct BdaMapping {
uint64_t guest_base = 0;
u32 backing_offset = 0;
};
struct TestCase {
const char *name = "";
std::vector<u32> code;
@@ -959,15 +964,15 @@ struct TestCase {
bool compile_only = false;
size_t storage_buffer_range_dwords = 0;
std::vector<u32> storage_buffer_offsets;
std::vector<u32> address_memory_offsets;
std::vector<BdaMapping> bda_mappings;
bool expand_shader_data_storage = false;
bool expected_force_point_sampler = false;
std::optional<uint64_t> flat_memory_base;
std::vector<u32> gds_initial;
std::vector<u32> expected_gds;
std::vector<std::pair<std::string, size_t>> decoded_counts;
std::vector<std::pair<std::string, size_t>> ir_counts;
u32 expected_storage_mip_descriptors = 0;
std::string expected_compile_error;
};
struct GraphicsCase {
@@ -1168,7 +1173,6 @@ CompiledShader CompileCase(const TestCase &test) {
options.user_data = user_data.data();
options.read_memory = ReadTestMemory;
options.read_memory_data = const_cast<std::vector<u32> *>(&test.initial);
options.flat_memory_base = test.flat_memory_base;
options.scratch_dwords = test.compute_info.scratch_size_dwords;
if (test.has_compute_info) {
options.wave_size = test.compute_info.wave_size;
@@ -1177,8 +1181,14 @@ CompiledShader CompileCase(const TestCase &test) {
ShaderRecompiler::CompileResult result;
std::string error;
if (!ShaderRecompiler::TryRecompile(test.code, options, result, &error)) {
if (!test.expected_compile_error.empty() &&
error.find(test.expected_compile_error) != std::string::npos) {
return {};
}
Fail(test.name, "decode/IR", error.c_str());
}
Require(test.name, "decode/IR", test.expected_compile_error.empty(),
"shader unexpectedly compiled despite the required rejection");
for (const auto &[text, expected] : test.decoded_counts) {
const auto actual = CountText(result.decoded_dump, text);
Require(test.name, "decoded RDNA2", actual == expected,
@@ -1267,17 +1277,6 @@ CompiledShader CompileCase(const TestCase &test) {
packed_user_data[result.program.bindings.memory_offset_dword + i / 4u] |=
offset << ((i % 4u) * 8u);
}
for (u32 i = 0; i < result.program.info.addresses.size(); i++) {
u32 offset = 0;
if (i < test.address_memory_offsets.size()) {
offset = test.address_memory_offsets[i];
}
Require(test.name, "shader data", offset < 256,
"address-memory offset is not representable");
const auto index = buffer_count + i;
packed_user_data[result.program.bindings.memory_offset_dword +
index / 4u] |= offset << ((index % 4u) * 8u);
}
return {std::move(result.spirv), std::move(result.program),
std::move(result.resources.flattened_srt),
std::move(packed_user_data)};
@@ -1469,6 +1468,7 @@ public:
struct Buffer {
vk::Buffer buffer = nullptr;
vk::DeviceMemory memory = nullptr;
vk::DeviceAddress device_address = 0;
vk::DeviceSize size = 0;
bool coherent = false;
};
@@ -3157,7 +3157,7 @@ public:
"chunked host copy lost bytes across its scratch boundary");
constexpr uint64_t index_offset = 0x180000;
constexpr uint64_t index_page = BufferCache::CACHING_PAGE_SIZE;
constexpr uint64_t index_page = BufferCache::CACHING_PAGESIZE;
constexpr uint64_t index_span = 3 * index_page;
const uint64_t index_begin = base + index_offset;
Require(name, "registered-range empty lookup",
@@ -3386,7 +3386,7 @@ public:
"Buffer ownership");
constexpr uint64_t starvation_offset = 0x100000;
constexpr uint64_t starvation_stride = 2 * BufferCache::CACHING_PAGE_SIZE;
constexpr uint64_t starvation_stride = 2 * BufferCache::CACHING_PAGESIZE;
constexpr uint32_t starvation_count = 33;
constexpr uint32_t starvation_value = 0x31415926u;
BufferCacheTestAccess::SetGarbageCollectionThresholds(
@@ -3443,7 +3443,7 @@ public:
constexpr uint64_t lookup_only_offset = 0x218000;
constexpr uint64_t obtained_offset = 0x220000;
constexpr uint64_t residency_size =
2 * BufferCache::CACHING_PAGE_SIZE;
2 * BufferCache::CACHING_PAGESIZE;
BufferCacheTestAccess::SetGarbageCollectionThresholds(
cache, std::numeric_limits<uint64_t>::max(),
std::numeric_limits<uint64_t>::max());
@@ -4199,7 +4199,7 @@ public:
const uint64_t mip_guest_size = mip_total.size + 256;
Require(name, "formatted mip fixture",
mip_sizes[0].offset == 0 &&
mip_prefix_size > BufferCache::CACHING_PAGE_SIZE &&
mip_prefix_size > BufferCache::CACHING_PAGESIZE &&
mip_prefix_size < mip_guest_size &&
mip_guest_size % sizeof(uint32_t) == 0,
"single-mip fixture did not expose a cache-sized fitting backing "
@@ -8946,7 +8946,7 @@ public:
Buffer CreateStorageBuffer(const char *shader_name,
const std::vector<u32> &initial,
size_t dword_count) {
size_t dword_count, bool device_address = false) {
Buffer ret;
ret.size = static_cast<vk::DeviceSize>(std::max<size_t>(dword_count, 1u) *
sizeof(u32));
@@ -8955,6 +8955,9 @@ public:
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
buffer_info.size = ret.size;
buffer_info.usage = vk::BufferUsageFlagBits::eStorageBuffer;
if (device_address) {
buffer_info.usage |= vk::BufferUsageFlagBits::eShaderDeviceAddress;
}
buffer_info.sharingMode = vk::SharingMode::eExclusive;
RequireVk(shader_name, "dispatch",
m_device.createBuffer(&buffer_info, nullptr, &ret.buffer),
@@ -8979,12 +8982,26 @@ public:
alloc.sType = vk::StructureType::eMemoryAllocateInfo;
alloc.allocationSize = req.size;
alloc.memoryTypeIndex = memory_type;
vk::MemoryAllocateFlagsInfo flags{};
if (device_address) {
flags.sType = vk::StructureType::eMemoryAllocateFlagsInfo;
flags.flags = vk::MemoryAllocateFlagBits::eDeviceAddress;
alloc.pNext = &flags;
}
RequireVk(shader_name, "dispatch",
m_device.allocateMemory(&alloc, nullptr, &ret.memory),
"vkAllocateMemory");
RequireVk(shader_name, "dispatch",
m_device.bindBufferMemory(ret.buffer, ret.memory, 0),
"vkBindBufferMemory");
if (device_address) {
vk::BufferDeviceAddressInfo address_info{};
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
address_info.buffer = ret.buffer;
ret.device_address = m_device.getBufferAddress(address_info);
Require(shader_name, "dispatch", ret.device_address != 0,
"storage buffer has no device address");
}
std::vector<u32> contents(dword_count, 0);
for (size_t i = 0; i < initial.size() && i < contents.size(); i++) {
@@ -9366,7 +9383,9 @@ public:
pool_sizes.push_back({type, count});
};
add_pool_size(vk::DescriptorType::eStorageBuffer,
Count(Kind::Buffers) + Count(Kind::AddressMemory) +
Count(Kind::Buffers) +
(Binding(Kind::BdaPagetable) != nullptr ? 1u : 0u) +
(Binding(Kind::FaultBuffer) != nullptr ? 1u : 0u) +
Count(Kind::Gds) +
(Binding(Kind::FlattenedSrt) != nullptr ? 1u : 0u) +
(Binding(Kind::UserData) != nullptr ? 1u : 0u));
@@ -9411,7 +9430,6 @@ public:
std::vector<vk::WriteDescriptorSet> writes;
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorBufferInfo> address_memory_infos;
std::vector<vk::DescriptorImageInfo> sampled_infos;
std::vector<vk::DescriptorImageInfo> storage_infos;
std::vector<vk::DescriptorImageInfo> storage_uint_infos;
@@ -9422,6 +9440,33 @@ public:
vk::DescriptorBufferInfo flattened_info{};
vk::DescriptorBufferInfo user_data_info{};
vk::DescriptorBufferInfo gds_info{};
vk::DescriptorBufferInfo bda_pagetable_info{};
vk::DescriptorBufferInfo fault_buffer_info{};
const bool uses_bda = Binding(Kind::BdaPagetable) != nullptr;
Require(test.name, "dispatch",
uses_bda == (Binding(Kind::FaultBuffer) != nullptr),
"BDA page table and fault buffer must be bound together");
if (uses_bda) {
EnsureBdaBuffers(test.name);
bda_pagetable_info = {m_bda_pagetable_buffer.buffer, 0,
m_bda_pagetable_buffer.size};
fault_buffer_info = {m_fault_buffer.buffer, 0, m_fault_buffer.size};
const std::array special_buffers{
std::pair{Kind::BdaPagetable, &bda_pagetable_info},
std::pair{Kind::FaultBuffer, &fault_buffer_info},
};
for (const auto &[kind, info] : special_buffers) {
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = Native(kind);
write.descriptorCount = 1;
write.descriptorType = vk::DescriptorType::eStorageBuffer;
write.pBufferInfo = info;
writes.push_back(write);
}
}
const auto *buffers = Binding(Kind::Buffers);
if (buffers != nullptr) {
@@ -9450,21 +9495,6 @@ public:
write.pBufferInfo = buffer_infos.data();
writes.push_back(write);
}
if (const auto *address = Binding(Kind::AddressMemory);
address != nullptr) {
address_memory_infos.resize(address->resources.size());
for (auto &info : address_memory_infos) {
info = {buffer.buffer, 0, buffer.size};
}
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = Native(Kind::AddressMemory);
write.descriptorCount = static_cast<u32>(address_memory_infos.size());
write.descriptorType = vk::DescriptorType::eStorageBuffer;
write.pBufferInfo = address_memory_infos.data();
writes.push_back(write);
}
if (const auto *flattened = Binding(Kind::FlattenedSrt);
flattened != nullptr) {
flattened_buffer = CreateStorageBuffer(test.name, compiled.flattened_srt,
@@ -9590,6 +9620,52 @@ public:
}
vk::CommandBuffer cmd = BeginCommands(test.name, "dispatch");
if (uses_bda) {
Require(test.name, "dispatch", buffer.device_address != 0,
"BDA test backing has no device address");
Require(test.name, "dispatch", !test.bda_mappings.empty(),
"BDA test has no explicit guest mapping");
cmd.fillBuffer(m_bda_pagetable_buffer.buffer, 0,
m_bda_pagetable_buffer.size, 0);
cmd.fillBuffer(m_fault_buffer.buffer, 0, m_fault_buffer.size, 0);
for (const auto &[guest_base, backing] : test.bda_mappings) {
const auto page_offset = guest_base &
(BufferCache::CACHING_PAGESIZE - 1);
Require(test.name, "dispatch",
backing >= page_offset && backing < buffer.size,
"BDA test mapping begins outside its backing buffer");
const auto bytes = page_offset + buffer.size - backing;
const auto pages =
(bytes + BufferCache::CACHING_PAGESIZE - 1) /
BufferCache::CACHING_PAGESIZE;
auto address = buffer.device_address + backing - page_offset;
auto page = guest_base >> BufferCache::CACHING_PAGEBITS;
for (uint64_t mapped = 0; mapped < pages; mapped++) {
cmd.updateBuffer(m_bda_pagetable_buffer.buffer,
(page + mapped) * sizeof(vk::DeviceAddress),
sizeof(address), &address);
address += BufferCache::CACHING_PAGESIZE;
}
}
std::array<vk::BufferMemoryBarrier, 2> barriers{};
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[0].srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].buffer = m_bda_pagetable_buffer.buffer;
barriers[0].offset = 0;
barriers[0].size = m_bda_pagetable_buffer.size;
barriers[1] = barriers[0];
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead |
vk::AccessFlagBits::eShaderWrite;
barriers[1].buffer = m_fault_buffer.buffer;
barriers[1].size = m_fault_buffer.size;
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eComputeShader, {}, 0,
nullptr, static_cast<u32>(barriers.size()),
barriers.data(), 0, nullptr);
}
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline);
cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipeline_layout, 0,
1, &descriptor_set, 0, nullptr);
@@ -10951,6 +11027,7 @@ private:
allocator_info.device = m_device;
allocator_info.pVulkanFunctions = &functions;
allocator_info.vulkanApiVersion = VK_API_VERSION_1_3;
allocator_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
RequireVk("VulkanHarness", "runtime context",
static_cast<vk::Result>(vmaCreateAllocator(
&allocator_info, &m_runtime_context.allocator)),
@@ -11001,13 +11078,18 @@ private:
if ((queues[i].queueFlags &
(vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) ==
(vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) {
vk::PhysicalDeviceVulkan12Features features12{};
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &features12;
vk::PhysicalDeviceFeatures2 features{};
features.sType = vk::StructureType::ePhysicalDeviceFeatures2;
features.pNext = &barycentric;
physical.getFeatures2(&features);
if (barycentric.fragmentShaderBarycentric != true) {
if (barycentric.fragmentShaderBarycentric != true ||
features.features.shaderInt64 != true ||
features12.bufferDeviceAddress != true) {
continue;
}
m_physical_device = physical;
@@ -11051,6 +11133,11 @@ private:
Require("VulkanHarness", "dispatch",
available_features.sampleRateShading == true,
"sample-rate shading is not supported");
Require("VulkanHarness", "dispatch", available_features.shaderInt64 == true,
"shaderInt64 is not supported");
Require("VulkanHarness", "dispatch",
available_features12.bufferDeviceAddress == true,
"bufferDeviceAddress is not supported");
float priority = 1.0f;
vk::DeviceQueueCreateInfo queue_info{};
@@ -11067,6 +11154,7 @@ private:
device_features12.sType =
vk::StructureType::ePhysicalDeviceVulkan12Features;
device_features12.timelineSemaphore = true;
device_features12.bufferDeviceAddress = true;
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
barycentric.pNext = &device_features12;
@@ -11081,6 +11169,7 @@ private:
vk::PhysicalDeviceFeatures device_features{};
device_features.shaderStorageImageWriteWithoutFormat = true;
device_features.sampleRateShading = true;
device_features.shaderInt64 = true;
device_info.pEnabledFeatures = &device_features;
constexpr const char *device_extensions[] = {
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
@@ -11105,6 +11194,8 @@ private:
void Destroy() {
if (m_device != nullptr) {
RequireVulkanSuccess(m_device.waitIdle(), "vkDeviceWaitIdle");
DestroyBuffer(&m_fault_buffer);
DestroyBuffer(&m_bda_pagetable_buffer);
if (m_runtime_context.allocator != nullptr) {
m_renderer.reset();
vmaDestroyAllocator(m_runtime_context.allocator);
@@ -11276,6 +11367,53 @@ private:
image->layout = final_layout;
}
Buffer CreateDeviceBuffer(const char *shader_name, vk::DeviceSize size,
vk::BufferUsageFlags usage) {
Buffer ret;
ret.size = size;
vk::BufferCreateInfo buffer_info{};
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
buffer_info.size = size;
buffer_info.usage = usage;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
RequireVk(shader_name, "dispatch",
m_device.createBuffer(&buffer_info, nullptr, &ret.buffer),
"vkCreateBuffer");
vk::MemoryRequirements requirements{};
m_device.getBufferMemoryRequirements(ret.buffer, &requirements);
u32 memory_type = 0;
Require(shader_name, "dispatch",
FindMemoryType(requirements.memoryTypeBits,
vk::MemoryPropertyFlagBits::eDeviceLocal,
&memory_type) ||
FindMemoryType(requirements.memoryTypeBits, {}, &memory_type),
"no memory type for BDA support buffer");
vk::MemoryAllocateInfo allocation{};
allocation.sType = vk::StructureType::eMemoryAllocateInfo;
allocation.allocationSize = requirements.size;
allocation.memoryTypeIndex = memory_type;
RequireVk(shader_name, "dispatch",
m_device.allocateMemory(&allocation, nullptr, &ret.memory),
"vkAllocateMemory");
RequireVk(shader_name, "dispatch",
m_device.bindBufferMemory(ret.buffer, ret.memory, 0),
"vkBindBufferMemory");
return ret;
}
void EnsureBdaBuffers(const char *shader_name) {
if (m_bda_pagetable_buffer.buffer != nullptr) {
return;
}
const auto usage = vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eTransferDst;
m_bda_pagetable_buffer =
CreateDeviceBuffer(shader_name, BufferCache::BDA_PAGETABLE_SIZE, usage);
m_fault_buffer = CreateDeviceBuffer(
shader_name, BufferCache::CACHING_NUMPAGES / 8, usage);
}
Buffer CreateHostBuffer(const char *shader_name, vk::DeviceSize size,
vk::BufferUsageFlags usage,
const std::vector<u32> &contents) {
@@ -11349,6 +11487,8 @@ private:
vk::CommandPool m_command_pool = nullptr;
u32 m_queue_family = 0;
vk::PhysicalDeviceMemoryProperties m_memory_properties{};
Buffer m_bda_pagetable_buffer;
Buffer m_fault_buffer;
GraphicContext m_runtime_context{};
std::unique_ptr<RenderContext> m_renderer;
};
@@ -11392,6 +11532,10 @@ void CompareGraphicsWords(const GraphicsCase &test,
void RunCase(VulkanHarness *vulkan, const TestCase &test) {
auto compiled = CompileCase(test);
if (!test.expected_compile_error.empty()) {
std::printf("[compute] %-32s ok\n", test.name);
return;
}
if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7)) {
Require(test.name, "resource specialization",
!compiled.program.info.images.empty() &&
@@ -11410,15 +11554,15 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
std::printf("[compute] %-32s ok\n", test.name);
return;
}
const auto dwords = std::max<size_t>(
{test.initial.size(), test.expected.size(), static_cast<size_t>(1)});
auto buffer = vulkan->CreateStorageBuffer(test.name, test.initial, dwords);
using Kind = ShaderRecompiler::IR::DescriptorBindingKind;
auto Has = [&](Kind kind) {
return ShaderRecompiler::IR::FindBinding(compiled.program.bindings, kind) !=
nullptr;
};
const auto dwords = std::max<size_t>(
{test.initial.size(), test.expected.size(), static_cast<size_t>(1)});
auto buffer = vulkan->CreateStorageBuffer(
test.name, test.initial, dwords, Has(Kind::BdaPagetable));
VulkanHarness::Image sampled_image;
VulkanHarness::Image storage_image;
VulkanHarness::Image storage_image_uint;
@@ -17341,16 +17485,16 @@ TestCase FlatLoadVariants() {
O::FLAT_LOAD_USHORT, O::FLAT_LOAD_SSHORT, O::FLAT_LOAD_DWORD,
O::FLAT_LOAD_DWORDX2, O::FLAT_LOAD_DWORDX3,
O::FLAT_LOAD_DWORDX4, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
test.flat_memory_base = 0;
test.address_memory_offsets = {static_cast<u32>(byte_offset)};
test.bda_mappings = {{0, static_cast<u32>(byte_offset)}};
return test;
}
TestCase FlatSubdwordLoadsApplyByteOffset() {
using O = ShaderOpcode;
constexpr size_t byte_offset = 3;
std::vector<u32> code;
AppendVMovU32(&code, 20, 0);
AppendVMovU32(&code, 20, static_cast<u32>(byte_offset));
AppendVMovU32(&code, 21, 0);
code.push_back(EncodeFlat0(0x08, 0, 0));
code.push_back(EncodeFlat1(0, 0x7d, 0, 20));
@@ -17360,7 +17504,6 @@ TestCase FlatSubdwordLoadsApplyByteOffset() {
AppendStoreVgpr(&code, 1, 1);
AppendEnd(&code);
constexpr size_t byte_offset = 3;
constexpr u32 guest_word = 0x80ff7f01u;
std::vector<u32> host_memory(
(byte_offset + sizeof(guest_word) + sizeof(u32) - 1u) / sizeof(u32));
@@ -17372,8 +17515,7 @@ TestCase FlatSubdwordLoadsApplyByteOffset() {
test.code = std::move(code);
test.initial = std::move(host_memory);
test.expected = {0x01u, 0x0000ff7fu};
test.flat_memory_base = 0;
test.address_memory_offsets = {static_cast<u32>(byte_offset)};
test.bda_mappings = {{0, 0}};
test.opcodes = {O::V_MOV_B32, O::FLAT_LOAD_UBYTE, O::FLAT_LOAD_USHORT,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
@@ -17457,8 +17599,7 @@ TestCase FlatVirtualAddressRebasesGuestAllocation() {
test.code = std::move(code);
test.initial = {0xfeedfaceu, 0xcafebabeu, 0, 0x12345678u};
test.expected = {0x12345678u, 0x12345678u, 0};
test.flat_memory_base = GuestBase;
test.address_memory_offsets = {8, 8, 8};
test.bda_mappings = {{GuestBase, 8}};
test.opcodes = {O::V_MOV_B32, O::FLAT_LOAD_DWORD, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
return test;
@@ -17480,10 +17621,9 @@ TestCase GlobalSignedImmediateRebasesBeforeSaddr() {
TestCase test;
test.name = "GlobalSignedImmediateRebasesBeforeSaddr";
test.code = std::move(code);
test.initial = {0xfeedfaceu, 0xcafebabeu, 0x11111111u, 0x22222222u,
0x12345678u};
test.initial = {0xfeedfaceu, 0xcafebabeu, 0x11111111u, 0x12345678u};
test.expected = {0x12345678u};
test.address_memory_offsets = {8};
test.bda_mappings = {{GuestBase, 8}};
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::FLAT_LOAD_DWORD,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
@@ -17517,7 +17657,7 @@ TestCase FlatSegmentIgnoresSaddrAndMasksOffsetMsb() {
test.expected = {0x11111111u, 0x11111111u};
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::FLAT_LOAD_DWORD,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.flat_memory_base = 0;
test.bda_mappings = {{0, 0}};
return test;
}
@@ -17596,7 +17736,8 @@ TestCase FlatStoreVariants() {
{O::V_MOV_B32, O::FLAT_STORE_BYTE, O::FLAT_STORE_SHORT,
O::FLAT_STORE_DWORD, O::FLAT_STORE_DWORDX2,
O::FLAT_STORE_DWORDX3, O::FLAT_STORE_DWORDX4, O::S_ENDPGM}};
test.flat_memory_base = 0;
test.expected_compile_error =
"writable FLAT/GLOBAL addresses require GPU ownership tracking";
return test;
}
+6 -14
View File
@@ -73,12 +73,7 @@ UnbasedFlatProgram() {
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
AddValueBlock(program);
AddressResource address;
address.source = UINT32_MAX;
address.first_use_pc = 0x80;
address.kind = ResourceKind::Flat;
address.unbased = true;
program.info.addresses.push_back(address);
program.info.uses_dma = true;
return std::make_shared<const Program>(std::move(program));
}
@@ -102,7 +97,7 @@ void TestMappedSrtUsesDirectReaderByDefault() {
"cache rematerialization did not use the direct reader by default");
}
void TestUnbasedFlatCacheHitFailsClosed() {
void TestUnbasedFlatCacheHitMaterializes() {
using namespace Libs::Graphics;
auto cached_program = UnbasedFlatProgram();
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
@@ -110,12 +105,9 @@ void TestUnbasedFlatCacheHitFailsClosed() {
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
ShaderStageRuntime stage{prior_program, prior_resources};
std::string error;
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error) &&
error.find("requires runtime guest-address translation") !=
std::string::npos,
"unbased FLAT cache hit did not fail without a runtime translator");
Check(stage.program == prior_program && stage.resources == prior_resources,
"unbased FLAT cache rejection replaced the prior stage snapshot");
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
Check(stage.program == cached_program && stage.resources != prior_resources,
"unbased FLAT cache hit did not publish its BDA stage snapshot");
}
} // namespace
@@ -130,7 +122,7 @@ void DbgExit(int) { std::abort(); }
int main() {
TestMappedSrtUsesDirectReaderByDefault();
TestUnbasedFlatCacheHitFailsClosed();
TestUnbasedFlatCacheHitMaterializes();
std::puts("ShaderStageRuntimeTests: all cases passed");
return 0;
}
+48 -61
View File
@@ -4319,14 +4319,14 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
error.c_str());
const auto *address_binding = ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory);
Check(raw.program.info.addresses.size() == 1u &&
raw.program.info.addresses[0].kind ==
ShaderRecompiler::IR::ResourceKind::ScalarAddress &&
raw.program.info.addresses[0].read &&
!raw.program.info.addresses[0].written &&
raw.program.info.buffers.empty() && address_binding != nullptr &&
address_binding->resources == std::vector<uint32_t>{0u} &&
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable);
const auto *fault_binding = ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer);
Check(raw.program.info.uses_dma && raw.program.info.buffers.empty() &&
address_binding != nullptr && fault_binding != nullptr &&
address_binding->resources.empty() &&
fault_binding->resources.empty() &&
ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::Buffers) ==
@@ -4335,17 +4335,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
nullptr &&
raw.program.bindings.memory_offset_count == 1u,
"raw scalar load did not use only the address-memory domain");
raw.program.bindings.memory_offset_count == 0u,
"raw scalar load did not use only the DMA domain");
Check(count_live_memory_ops(
raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32,
ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 1u,
"raw scalar load did not remain a live typed address operation");
Check(raw.resources.addresses.size() == 1u &&
raw.resources.addresses[0].guest_base == 0x1000u &&
raw.resources.addresses[0].binding_base == 0x1000u &&
raw.program.info.addresses[0].specialized_base == 0u,
"raw scalar address specialization was incorrect");
Check(SpirvContainsOpcode(raw.spirv, 199),
"raw scalar SOFFSET alignment was not emitted");
CheckSpirvBinaryValidates(raw.spirv);
@@ -4377,12 +4372,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
ShaderRecompiler::IR::DescriptorBindingKind::Buffers);
Check(buffer.program.info.buffers.size() == 1u &&
buffer.program.info.buffers[0].scalar &&
buffer.program.info.addresses.empty() &&
!buffer.program.info.uses_dma &&
buffer_binding != nullptr &&
buffer_binding->resources == std::vector<uint32_t>{0u} &&
ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory) ==
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) ==
nullptr &&
ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
@@ -6201,7 +6196,6 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
@@ -6224,23 +6218,32 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerUnbasedFlatRequiresTranslator() {
void TestNewShaderRecompilerUnbasedFlatUsesBda() {
const uint32_t shader[] = {
EncodeFlat0(0x0c, 0, 0),
EncodeFlat1(0, 0x7d, 0, 1),
EncodeFlat0(0x1c, 0, 0),
EncodeFlat1(0, 0x7d, 0, 2),
0xbf810000u,
EncodeExp0(0x00, 0x1),
EncodeExp1(0, 0, 0, 0),
EncodeSopp(0x01),
};
auto options = MakeCompileOptions(ShaderType::Compute);
auto options = MakeCompileOptions(ShaderType::Pixel);
ShaderRecompiler::CompileResult result;
std::string error;
Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
Common::ContainsStr(error,
"requires runtime guest-address translation"),
"unbased FLAT compiled without an explicit translator");
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
Check(result.program.info.uses_dma &&
ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) !=
nullptr &&
ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer) !=
nullptr,
"unbased FLAT did not compile through BDA");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerFlatSignedLoadTranslation() {
@@ -6256,7 +6259,6 @@ void TestNewShaderRecompilerFlatSignedLoadTranslation() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
@@ -6311,7 +6313,6 @@ void TestNewShaderRecompilerFlatStoreTranslation() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
@@ -11966,49 +11967,40 @@ void TestNewShaderRecompilerUnsupportedMemoryDecode() {
"MTBUF", "opcode=0x08");
}
void TestNewShaderRecompilerFlatUserPointerProvenance() {
void TestNewShaderRecompilerFlatUserPointerUsesDma() {
const uint32_t shader[] = {
EncodeVop2(0x25, 0, 0, 2), // v_add_nc_u32 v0, s0, v2
EncodeVop1(0x01, 1, 1), // v_mov_b32 v1, s1
EncodeFlat0(0x1c, 0, 0),
EncodeFlat1(0, 0x7d, 3, 0), // flat_store_dword v3, v[0:1]
0xbf810000u,
EncodeFlat0(0x0c, 0, 0),
EncodeFlat1(3, 0x7d, 0, 0), // flat_load_dword v3, v[0:1]
EncodeExp0(0x00, 0x1),
EncodeExp1(3, 0, 0, 0),
EncodeSopp(0x01),
};
const uint32_t user_data[] = {0x34567000u, 0x00000012u};
auto options = MakeCompileOptions(ShaderType::Compute);
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data = user_data;
options.user_data_count = static_cast<uint32_t>(std::size(user_data));
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
const bool compiled =
ShaderRecompiler::TryRecompile(shader, options, result, &error);
Check(compiled, error.c_str());
Check(result.program.info.addresses.size() == 1 &&
!result.program.info.addresses[0].unbased &&
result.program.info.addresses[0].source <
result.program.descriptor_sources.size(),
"FLAT user pointer provenance was not attached");
Check(result.resources.addresses.size() == 1 &&
result.resources.addresses[0].guest_base == 0x0000001234567000ull &&
result.resources.addresses[0].binding_base ==
0x0000001234560000ull &&
result.program.info.addresses[0].specialized_base ==
0x0000001234560000ull,
"FLAT user pointer did not materialize its guest base");
Check(result.program.info.uses_dma,
"FLAT user pointer did not enable DMA");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
void TestNewShaderRecompilerFlatAddressDomainsUseDma() {
const uint32_t segmented_shader[] = {
EncodeVop2(0x25, 0, 0, 2),
EncodeVop1(0x01, 1, 1),
EncodeFlat0(0x1c, 2, 0),
EncodeFlat1(0, 0x7d, 3, 0), // global_store_dword v3, v[0:1]
EncodeFlat0(0x0c, 2, 0),
EncodeFlat1(3, 0x7d, 0, 0), // global_load_dword v3, v[0:1]
EncodeFlat0(0x1c, 1, 0),
EncodeFlat1(0, 0x7d, 4, 0), // scratch_store_dword v4, v[0:1]
EncodeFlat1(0, 0x7d, 3, 0), // scratch_store_dword v3, v[0:1]
0xbf810000u,
};
const uint32_t user_data[] = {0x34567000u, 0u, 0x1000u};
@@ -12016,19 +12008,14 @@ void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data = user_data;
options.user_data_count = static_cast<uint32_t>(std::size(user_data));
options.flat_memory_base = 0;
options.scratch_dwords = 1;
ShaderRecompiler::CompileResult result;
std::string error;
const bool compiled =
ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
Check(compiled, error.c_str());
Check(result.program.info.addresses.size() == 1 &&
result.program.info.addresses[0].kind ==
ShaderRecompiler::IR::ResourceKind::Global &&
result.program.info.addresses[0].unbased &&
result.program.info.addresses[0].source == UINT32_MAX,
"GLOBAL null-SADDR did not remain an unbased guest address");
Check(result.program.info.uses_dma,
"GLOBAL null-SADDR did not enable DMA");
Check(Common::ContainsStr(result.ir_dump, "GetScratchResource") &&
result.program.scratch_dwords == 1,
"SCRATCH incorrectly entered guest address tracking");
@@ -12324,9 +12311,9 @@ int main() {
TestNewShaderRecompilerRejectsDppOn64BitCompares();
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
TestPsInputCountRegisterDecode();
TestNewShaderRecompilerUnbasedFlatRequiresTranslator();
TestNewShaderRecompilerFlatUserPointerProvenance();
TestNewShaderRecompilerFlatAddressProvenanceBoundaries();
TestNewShaderRecompilerUnbasedFlatUsesBda();
TestNewShaderRecompilerFlatUserPointerUsesDma();
TestNewShaderRecompilerFlatAddressDomainsUseDma();
TestNewShaderRecompilerCfgStraightLine();
TestNewShaderRecompilerCfgIfElse();
TestNewShaderRecompilerCfgConsecutiveNativePhis();