199 lines
13 KiB
C++
199 lines
13 KiB
C++
#include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp"
|
|
#include "BdaAbi.hpp"
|
|
#include "Optimization/BindingAllocator.hpp"
|
|
#include "Optimization/ResourceMaterializer.hpp"
|
|
#include <spirv/unified1/spirv.hpp>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <iostream>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
namespace {
|
|
|
|
using AgcDriver::Graphics::Require;
|
|
|
|
struct ProfileInput {
|
|
std::vector<std::uint32_t> capabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess};
|
|
std::array<std::string, 2> extensionStrings{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"};
|
|
std::array<std::string_view, 2> extensions{extensionStrings[0], extensionStrings[1]};
|
|
VkPhysicalDeviceRobustness2FeaturesEXT robustness{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, nullptr, VK_FALSE, VK_FALSE, VK_TRUE};
|
|
VkPhysicalDeviceDescriptorIndexingFeatures indexing{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, &robustness};
|
|
VkPhysicalDevice8BitStorageFeatures bytes{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES, &indexing, VK_TRUE};
|
|
VkPhysicalDeviceBufferDeviceAddressFeatures bda{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, &bytes, VK_TRUE};
|
|
VkPhysicalDeviceFeatures core{};
|
|
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
|
VkPhysicalDeviceLimits limits{};
|
|
|
|
ProfileInput() {
|
|
core.shaderInt64 = VK_TRUE;
|
|
core.vertexPipelineStoresAndAtomics = VK_TRUE;
|
|
core.fragmentStoresAndAtomics = VK_TRUE;
|
|
device.pEnabledFeatures = &core;
|
|
device.pNext = &bda;
|
|
limits.maxPushConstantsSize = 128u;
|
|
limits.maxBoundDescriptorSets = 1u;
|
|
limits.maxStorageBufferRange = sizeof(ShaderRecompiler::RuntimeAbi::ShaderData);
|
|
}
|
|
|
|
ShaderRecompiler::SpirvTarget Target() const {
|
|
ShaderRecompiler::SpirvTarget target{};
|
|
target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version;
|
|
target.supportedCapabilities = capabilities;
|
|
target.supportedExtensions = extensions;
|
|
return target;
|
|
}
|
|
};
|
|
|
|
template<typename TAction>
|
|
void Reject(TAction action, const char* expected) {
|
|
try {
|
|
action();
|
|
} catch (const std::runtime_error& error) {
|
|
Require(std::string(error.what()).find(expected) != std::string::npos, "unexpected profile validation error");
|
|
return;
|
|
}
|
|
throw std::runtime_error("invalid shader device profile was accepted");
|
|
}
|
|
|
|
void CheckProfile() {
|
|
ProfileInput input;
|
|
const AgcDriver::ShaderDeviceProfile profile(input.Target(), input.device, input.limits);
|
|
input.capabilities.clear();
|
|
input.extensionStrings[0] = "changed";
|
|
input.core.shaderInt64 = VK_FALSE;
|
|
input.robustness.nullDescriptor = VK_FALSE;
|
|
input.limits.maxPushConstantsSize = 0u;
|
|
const auto target = profile.Target();
|
|
Require(target.supportedCapabilities.size() == 4u && std::ranges::is_sorted(target.supportedCapabilities), "profile capabilities were not frozen");
|
|
Require(std::ranges::find(target.supportedExtensions, "SPV_KHR_physical_storage_buffer") != target.supportedExtensions.end(), "profile extension storage is borrowed");
|
|
Require(profile.Limits().maxPushConstantsSize == 128u, "profile limits were not frozen");
|
|
Require(profile.NullDescriptors(), "profile null descriptor support was not frozen");
|
|
static_assert(!std::is_copy_constructible_v<AgcDriver::ShaderDeviceProfile> && !std::is_move_constructible_v<AgcDriver::ShaderDeviceProfile>);
|
|
const auto invalid = [](auto mutate, const char* expected) {
|
|
ProfileInput candidate;
|
|
mutate(candidate);
|
|
Reject([&] { AgcDriver::ShaderDeviceProfile rejected(candidate.Target(), candidate.device, candidate.limits); }, expected);
|
|
};
|
|
invalid([](auto& value) { value.device.pEnabledFeatures = nullptr; }, "enabled core features");
|
|
invalid([](auto& value) { value.bda.bufferDeviceAddress = VK_FALSE; }, "bufferDeviceAddress");
|
|
invalid([](auto& value) { value.device.pNext = nullptr; }, "bufferDeviceAddress");
|
|
invalid([](auto& value) { value.core.shaderInt64 = VK_FALSE; }, "shaderInt64");
|
|
invalid([](auto& value) { value.bytes.storageBuffer8BitAccess = VK_FALSE; }, "storageBuffer8BitAccess");
|
|
ProfileInput withoutNull;
|
|
withoutNull.robustness.nullDescriptor = VK_FALSE;
|
|
Require(!AgcDriver::ShaderDeviceProfile(withoutNull.Target(), withoutNull.device, withoutNull.limits).NullDescriptors(), "a device without nullDescriptor was profiled with it");
|
|
ProfileInput withoutRobustness;
|
|
withoutRobustness.indexing.pNext = nullptr;
|
|
Require(!AgcDriver::ShaderDeviceProfile(withoutRobustness.Target(), withoutRobustness.device, withoutRobustness.limits).NullDescriptors(), "a device without VK_EXT_robustness2 was profiled with nullDescriptor");
|
|
invalid([](auto& value) { value.core.fragmentStoresAndAtomics = VK_FALSE; }, "graphics stores and atomics");
|
|
invalid([](auto& value) { value.limits.maxPushConstantsSize = 127u; }, "exceeds device limits");
|
|
invalid([](auto& value) { value.limits.maxBoundDescriptorSets = 0u; }, "exceeds device limits");
|
|
invalid([](auto& value) { --value.limits.maxStorageBufferRange; }, "ShaderData exceeds device limits");
|
|
invalid([](auto& value) { value.capabilities.clear(); }, "missing runtime capabilities");
|
|
invalid([](auto& value) { value.extensions[0] = "missing"; }, "missing runtime extensions");
|
|
invalid([](auto& value) { value.capabilities.push_back(spv::CapabilitySampledImageArrayDynamicIndexing); }, "shaderSampledImageArrayDynamicIndexing");
|
|
invalid([](auto& value) { value.capabilities.push_back(spv::CapabilityStorageImageArrayNonUniformIndexing); }, "shaderStorageImageArrayNonUniformIndexing");
|
|
invalid([](auto& value) { value.capabilities.push_back(spv::CapabilityStorageImageReadWithoutFormat); }, "shaderStorageImageReadWithoutFormat");
|
|
invalid([](auto& value) { value.capabilities.push_back(spv::CapabilityStorageImageMultisample); }, "shaderStorageImageMultisample");
|
|
invalid([](auto& value) { value.capabilities.push_back(spv::CapabilityFragmentBarycentricKHR); }, "fragmentShaderBarycentric");
|
|
invalid([](auto& value) { value.capabilities.push_back(spv::CapabilityMeshShadingEXT); }, "meshShader");
|
|
ProfileInput enabled;
|
|
enabled.capabilities.push_back(spv::CapabilitySampledImageArrayDynamicIndexing);
|
|
enabled.capabilities.push_back(spv::CapabilityStorageImageArrayNonUniformIndexing);
|
|
enabled.core.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
|
|
enabled.indexing.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
|
|
const AgcDriver::ShaderDeviceProfile accepted(enabled.Target(), enabled.device, enabled.limits);
|
|
Require(accepted.Target().supportedCapabilities.size() == 6u, "enabled descriptor indexing was rejected");
|
|
}
|
|
|
|
void CheckAbi() {
|
|
using ShaderRecompiler::RuntimeAbi::Binding;
|
|
using ShaderRecompiler::RuntimeAbi::BindingNumber;
|
|
using ShaderRecompiler::RuntimeAbi::Stage;
|
|
std::set<std::uint32_t> bindings;
|
|
for (std::uint32_t stage = 0u; stage < ShaderRecompiler::RuntimeAbi::StageCount; ++stage) {
|
|
for (std::uint32_t binding = 0u; binding < static_cast<std::uint32_t>(Binding::Count); ++binding) {
|
|
Require(bindings.insert(BindingNumber(static_cast<Stage>(stage), static_cast<Binding>(binding))).second, "runtime ABI bindings overlap");
|
|
}
|
|
}
|
|
Require(BindingNumber(Stage::Main, Binding::ShaderData) == 62u && BindingNumber(Stage::Fragment, Binding::ShaderData) == 125u, "runtime ABI binding numbers changed");
|
|
Reject([] { BindingNumber(static_cast<Stage>(4u), Binding::Buffers); }, "invalid stage or binding");
|
|
Reject([] { BindingNumber(Stage::Main, Binding::Count); }, "invalid stage or binding");
|
|
Reject([] { ShaderRecompiler::RuntimeAbi::RequireVersion(0u); }, "incompatible version");
|
|
ShaderRecompiler::RuntimeAbi::RequireVersion(ShaderRecompiler::RuntimeAbi::Version);
|
|
}
|
|
|
|
void CheckHeaps() {
|
|
using namespace ShaderRecompiler;
|
|
const auto allocate = [](const ImageResource& image, std::uint32_t count, std::uint32_t samplers = 0u) {
|
|
IrProgram program;
|
|
program.Metadata().shaderInfoComplete = true;
|
|
program.Resources().info.images.assign(count, image);
|
|
program.Resources().info.samplers.resize(samplers);
|
|
return BindingAllocator{}.Allocate(program, {0u, 0u, 0u, 128u});
|
|
};
|
|
ImageResource image;
|
|
image.resourceClass = ImageResourceClass::Sampled;
|
|
image.numericClass = IrTextureNumericClass::Float;
|
|
image.dimension = RdnaImageDimension::Dim2D;
|
|
const auto single = allocate(image, 1u);
|
|
const auto sixteenSamplers = allocate(image, 1u, 16u);
|
|
Require(BindingAllocator{}.FindBinding(sixteenSamplers.layout, DescriptorBindingKind::Samplers).resources.size() == 32u, "sixteen logical samplers must fit with both descriptor variants");
|
|
const auto full = allocate(image, RuntimeAbi::SampledHeapCapacity, RuntimeAbi::SamplerHeapCapacity / 2u);
|
|
Require(single.layout.ShaderDataDwords() == full.layout.ShaderDataDwords() && single.layout.memoryOffsetDword == full.layout.memoryOffsetDword && !full.layout.UsesPushData(), "runtime layout depends on resource count");
|
|
Require(full.layout.memoryOffsetDword == 0u && full.layout.DispatchThreadLimitDword() == 0u && full.layout.ShaderDataDwords() == 0u, "direct image resources allocated runtime metadata");
|
|
Reject([&] { allocate(image, RuntimeAbi::SampledHeapCapacity + 1u); }, "heap capacity exceeded");
|
|
Require(BindingAllocator{}.FindBinding(allocate(image, 40u).layout, DescriptorBindingForImage(image)).resources.size() == 40u, "a sampled heap does not hold 40 images of one class");
|
|
Require(BindingAllocator{}.FindBinding(allocate(image, RuntimeAbi::SampledHeapCapacity).layout, DescriptorBindingForImage(image)).resources.size() == RuntimeAbi::SampledHeapCapacity, "a sampled heap does not hold a full class of images");
|
|
Require(ResourceMaterializer::BindlessSlots() == 16u, "bindless image tables changed size with the sampled heap");
|
|
Reject([&] { allocate(image, 1u, RuntimeAbi::SamplerHeapCapacity + 1u); }, "metadata capacity");
|
|
image.resourceClass = ImageResourceClass::Storage;
|
|
image.mipMode = ImageMipMode::DynamicStorage;
|
|
image.mipCount = RuntimeAbi::StorageMipSlots;
|
|
const auto storage = allocate(image, 1u);
|
|
Require(BindingAllocator{}.FindBinding(storage.layout, DescriptorBindingForImage(image)).resources.size() == image.mipCount, "storage heap did not reserve each mip");
|
|
const auto shared = allocate(image, 2u);
|
|
Require(BindingAllocator{}.FindBinding(shared.layout, DescriptorBindingForImage(image)).resources.size() == 2u * image.mipCount, "a storage heap did not hold two dynamic-mip images");
|
|
const auto capacity = RuntimeAbi::StorageHeapCapacity / image.mipCount;
|
|
const auto filled = allocate(image, capacity);
|
|
Require(BindingAllocator{}.FindBinding(filled.layout, DescriptorBindingForImage(image)).resources.size() == RuntimeAbi::StorageHeapCapacity, "a full storage heap was not allocated");
|
|
Reject([&] { allocate(image, capacity + 1u); }, "heap capacity exceeded");
|
|
Reject([&] { allocate(image, 0u, RuntimeAbi::SamplerHeapCapacity / 2u + 1u); }, "sampler pairs");
|
|
const std::array dimensions{RdnaImageDimension::Dim1D, RdnaImageDimension::Dim1DArray, RdnaImageDimension::Dim2D, RdnaImageDimension::Dim2DArray, RdnaImageDimension::Dim3D, RdnaImageDimension::Dim2DMsaa, RdnaImageDimension::Dim2DMsaaArray};
|
|
std::set<std::uint32_t> classes;
|
|
for (std::uint32_t group = 0u; group < 8u; ++group) {
|
|
for (const auto dimension : dimensions) {
|
|
image = {};
|
|
image.resourceClass = group < 4u ? ImageResourceClass::Sampled : ImageResourceClass::Storage;
|
|
image.numericClass = group == 1u || group >= 5u ? IrTextureNumericClass::Uint : group == 2u ? IrTextureNumericClass::Sint : IrTextureNumericClass::Float;
|
|
image.depthCompare = group == 3u;
|
|
image.atomic = group >= 6u;
|
|
image.atomic64 = group == 7u;
|
|
image.dimension = dimension;
|
|
const auto binding = DescriptorBindingForImage(image);
|
|
Require(classes.insert(static_cast<std::uint32_t>(binding)).second, "typed image classes overlap");
|
|
Require(RuntimeAbi::HeapCapacity(binding) == (group < 4u ? RuntimeAbi::SampledHeapCapacity : RuntimeAbi::StorageHeapCapacity), "typed image class has an invalid capacity");
|
|
}
|
|
}
|
|
Require(classes.size() == RuntimeAbi::ImageBindingCount && *classes.begin() == 1u && *classes.rbegin() == RuntimeAbi::ImageBindingCount, "typed image class mapping is incomplete");
|
|
Reject([] { RuntimeAbi::HeapCapacity(RuntimeAbi::Binding::ShaderData); }, "not a typed heap");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
CheckAbi();
|
|
CheckHeaps();
|
|
CheckProfile();
|
|
std::cout << "shader runtime ABI and device profile tests passed\n";
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
}
|