perf(recompiler): read depth-compare state and export mappings at run time when modules are built at registration

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-11 13:53:14 +02:00
parent 765ea6a10f
commit baf3437465
16 changed files with 105 additions and 27 deletions

View File

@ -184,13 +184,13 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
{64, 0, {}, std::nullopt, pixel, std::nullopt, pixelMemory},
target,
{0, 0, meshPush, ShaderRecompiler::MeshDrawPushOffsetBytes - meshPush},
target.fixedPushSlots ? PixelPushLayout(meshPush, target) : ShaderRecompiler::BindingLayout{0, 0, meshPush, ShaderRecompiler::MeshDrawPushOffsetBytes - meshPush},
std::nullopt
};
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
{ShaderStage::Mesh, &meshResult, 0},
{ShaderStage::Fragment, &pixelResult, meshPush}
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(meshPush, target)}
}};
AgcDriver::Graphics::State state{};

View File

@ -73,6 +73,7 @@ void Run(AgcDriver::VulkanDevice& device, bool registrationModules) {
{"absent", 0, 128, false},
}};
const void* first = nullptr;
std::uint64_t variant = 0;
for (const auto& test : cases) {
Output.fill(Fill);
const auto input = test.present ? BufferDescriptor(Input.data() + test.baseOffset, test.records) : std::array<std::uint32_t, 4>{0u, 0u, 0u, 0x31016facu};
@ -83,8 +84,11 @@ void Run(AgcDriver::VulkanDevice& device, bool registrationModules) {
runtime.userData = userData;
const auto capture = ShaderRecompiler::CaptureResources(request, runtime, *handle);
const auto result = ShaderRecompiler::MaterializeShader(request, *capture, *handle);
if (first == nullptr) first = result->spirv.data();
if (registrationModules) Require(result->spirv.data() == first, "buffer descriptor runtime, " + mode + ", " + test.name + ": the V# specialized the module");
if (first == nullptr) {
first = result->spirv.data();
variant = result->PipelineVariantId();
}
if (registrationModules) Require(result->spirv.data() == first && result->PipelineVariantId() == variant, "buffer descriptor runtime, " + mode + ", " + test.name + ": the V# specialized the module");
device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(Code.data()));
device.WaitIdle();
for (std::uint32_t lane = 0; lane < Lanes; ++lane) {

View File

@ -4,6 +4,7 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
@ -96,7 +97,7 @@ inline Shaders Compile(AgcDriver::VulkanDevice& device) {
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
{32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
target,
{0, 0, shaders.vertexPush, 128 - shaders.vertexPush}
PixelPushLayout(shaders.vertexPush, target)
};
fragment.useCache = false;
shaders.pixel = ShaderRecompiler::Recompile(fragment);
@ -107,7 +108,7 @@ inline std::uint32_t Draw(AgcDriver::VulkanDevice& device, const Shaders& compil
Pixels.fill(std::byte{0});
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
{ShaderStage::Vertex, &compiled.vertex, 0},
{ShaderStage::Fragment, &compiled.pixel, compiled.vertexPush}
{ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())}
}};
AgcDriver::Graphics::State state{};
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};

View File

@ -29,7 +29,7 @@ void Run(AgcDriver::VulkanDevice& device, bool integer) {
const std::array<std::uint32_t, 4> values = integer ? std::array<std::uint32_t, 4>{11, 22, 33, 44} : std::array<std::uint32_t, 4>{0, 0x3e800000u, 0x3f000000u, 0x3f800000u};
const std::array<std::uint32_t, 13> code{0x7e0002ffu, values[0], 0x7e0202ffu, values[1], 0x7e0402ffu, values[2], 0x7e0602ffu, values[3], 0xf800100fu, 0x03020100u, 0xf800183fu, 0x03020100u, 0xbf810000u};
AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, integer ? 0x7007u : 0x9009u}};
RecompileRequest request{{ShaderStage::Fragment, 0x20000, code, 0, {}}, {32, 0, {}, {}, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), {}, {}}, device.Target(), {0, 0, 0, 128}};
RecompileRequest request{{ShaderStage::Fragment, 0x20000, code, 0, {}}, {32, 0, {}, {}, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), {}, {}}, device.Target(), PixelPushLayout(0u, device.Target())};
const auto handle = PrepareShader(request);
const auto& artifact = GetPreparedArtifact(*handle);
std::uint32_t scalarType = 0;
@ -85,6 +85,10 @@ void Run(AgcDriver::VulkanDevice& device, bool integer) {
request.useCache = cached;
const auto materialized = MaterializeShader(request, *capture, *handle);
Require(materialized->spirv.data() == pixel->spirv.data() && materialized->PipelineVariantId() == pixel->PipelineVariantId(), "export mapping did not reuse its specialized module");
if (device.Target().registrationModules) {
Require(materialized->PipelineVariantId() == first->PipelineVariantId(), "registration modules: the export mapping specialized the fragment module");
continue;
}
for (std::size_t cursor = 5; cursor < materialized->spirv.size();) {
const auto count = materialized->spirv[cursor] >> 16u;
Require(count != 0u && count <= materialized->spirv.size() - cursor, "invalid specialized fragment instruction");
@ -94,7 +98,7 @@ void Run(AgcDriver::VulkanDevice& device, bool integer) {
}
request.useCache = true;
if (integer) continue;
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::Fragment, pixel.get(), 0}}};
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::Fragment, pixel.get(), PixelPushOffset(0u, device.Target())}}};
pixels.fill(0xdeadbeef);
otherPixels.fill(0xdeadbeef);
device.Draw(state, draw, shaders);

View File

@ -50,7 +50,7 @@ ShaderRecompiler::RecompileResult GuestPixel(const ShaderRecompiler::SpirvTarget
request.context.waveSize = 64;
request.context.pixel = pixel;
request.target = target;
request.layout.pushConstantSizeBytes = 128;
request.layout = PixelPushLayout(0u, target);
request.useCache = false;
auto result = ShaderRecompiler::Recompile(request);
result.variantId = 3;
@ -130,7 +130,7 @@ int main() {
const auto guestPixel = GuestPixel(device->Target());
const std::array<AgcDriver::Graphics::CompiledShader, 2> guestShaders{{
{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0},
{ShaderRecompiler::ShaderStage::Fragment, &guestPixel, 0}
{ShaderRecompiler::ShaderStage::Fragment, &guestPixel, PixelPushOffset(0u, device->Target())}
}};
for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}) Run(*device, topology, guestShaders, true, "guest P0");
std::puts("provoking vertex rendering tests passed: GLSL and guest P0, triangle list and strip, first/last/first");

View File

@ -200,13 +200,13 @@ void Draw(AgcDriver::VulkanDevice& device, GuestBuffer& pixels, GuestBuffer& ver
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
{64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
target,
{0, 0, vertexPush, 128 - vertexPush}
PixelPushLayout(vertexPush, target)
};
fragment.useCache = false;
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
{ShaderStage::Vertex, &vertexResult, 0},
{ShaderStage::Fragment, &pixelResult, vertexPush}
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)}
}};
AgcDriver::Graphics::State state{};

View File

@ -85,9 +85,9 @@ void Run(AgcDriver::VulkanDevice& device) {
pixelInfo.wave32 = true;
pixelInfo.targetOutputMode[0] = 9u;
pixelInfo.targetExportMapping.fill(0xe4u);
ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), {0u, 0u, pushBytes, 128u - pushBytes}};
ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), PixelPushLayout(pushBytes, device.Target())};
const auto pixel = ShaderRecompiler::Recompile(pixelRequest);
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, pushBytes}}};
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, PixelPushOffset(pushBytes, device.Target())}}};
AgcDriver::Graphics::State state{};
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
state.color = {reinterpret_cast<std::uintptr_t>(pixels.data()), {Extent, Extent}, VK_FORMAT_R8G8B8A8_UNORM, pixels.size(), 0xe4u};

View File

@ -43,6 +43,7 @@ struct IrBindingLayout {
std::uint32_t memoryOffsetCount = 0;
bool dispatchThreadLimit = false;
std::uint32_t runtimeImageCount = 0;
std::uint32_t runtimeStateDwords = 0;
std::vector<std::uint32_t> userDataRegisters;
std::vector<IrDescriptorBinding> descriptors;
@ -52,9 +53,12 @@ struct IrBindingLayout {
[[nodiscard]] std::uint32_t ImageMetadataDword() const {
return DispatchThreadLimitDword() + (dispatchThreadLimit ? 3u : 0u);
}
[[nodiscard]] std::uint32_t ShaderDataDwords() const {
[[nodiscard]] std::uint32_t RuntimeStateDword() const {
return ImageMetadataDword() + runtimeImageCount * (sizeof(RuntimeAbi::ResourceMetadata) / sizeof(std::uint32_t));
}
[[nodiscard]] std::uint32_t ShaderDataDwords() const {
return RuntimeStateDword() + runtimeStateDwords;
}
[[nodiscard]] bool UsesPushData() const {
return pushDataStartDword != PushData::NoStart;
}

View File

@ -29,6 +29,7 @@ struct DescriptorBindingPlan {
std::vector<PreparedDescriptorBinding> bindings;
std::vector<PreparedImageMetadata> imageMetadata;
std::vector<PipelineSpecializationConstant> specialization;
std::vector<std::uint32_t> runtimeState;
bool storageData = false;
};

View File

@ -103,6 +103,7 @@ BindingAllocationResult BindingAllocator::Allocate(IrProgram& program, const Bin
if (info.buffers.size() > RuntimeAbi::BufferCapacity || info.images.size() > RuntimeAbi::ImageCapacity || info.samplers.size() > RuntimeAbi::SamplerHeapCapacity) fail("shader binding layout exceeds runtime metadata capacity");
if (std::ranges::any_of(next.userDataRegisters, [](auto reg) { return reg >= RuntimeAbi::UserDataCapacity; })) fail("shader user data exceeds runtime ABI capacity");
if (registrationModules || std::ranges::any_of(info.images, [](const auto& image) { return image.indirectRoot != ImageResource::NoIndirectImage; })) next.runtimeImageCount = static_cast<std::uint32_t>(info.images.size());
if (registrationModules && (program.Resources().stage == IrShaderStage::Pixel || std::ranges::any_of(info.samplers, [](const auto& sampler) { return FoldsTexelOffsets(sampler); }))) next.runtimeStateDwords = RuntimeAbi::StateDwords;
const auto pushSize = layout.pushConstantSizeBytes / 4u;
next.pushDataStartDword = next.runtimeImageCount == 0u && next.ShaderDataDwords() != 0u && next.ShaderDataDwords() <= pushSize ? layout.pushConstantOffsetBytes / 4u : PushData::NoStart;

View File

@ -208,7 +208,7 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh
return proof;
}
std::vector<std::uint32_t> ShaderDataDwordsFor(const IrBindingLayout& layout, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array<std::uint32_t, 3>& partialThreads, const std::vector<PreparedImageMetadata>& imageMetadata) {
std::vector<std::uint32_t> ShaderDataDwordsFor(const IrBindingLayout& layout, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array<std::uint32_t, 3>& partialThreads, const std::vector<PreparedImageMetadata>& imageMetadata, std::span<const std::uint32_t> runtimeState) {
std::vector<std::uint32_t> result(layout.ShaderDataDwords(), 0u);
for (std::size_t i = 0; i < layout.userDataRegisters.size(); ++i) {
const auto reg = layout.userDataRegisters[i];
@ -226,6 +226,8 @@ std::vector<std::uint32_t> ShaderDataDwordsFor(const IrBindingLayout& layout, st
std::copy_n(descriptor.dwords.begin(), metadata.descriptor.size(), metadata.descriptor.begin());
std::memcpy(result.data() + entry.offset, &metadata, sizeof(metadata));
}
if (runtimeState.size() != layout.runtimeStateDwords) fail("runtime state does not match its shader data layout");
std::copy(runtimeState.begin(), runtimeState.end(), result.begin() + layout.RuntimeStateDword());
return result;
}
@ -255,8 +257,11 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
if (info.images.size() > ShaderInfo::MaxImages) fail("runtime image count exceeds the static capacity");
DescriptorBindingPlan plan;
const auto unnormalized = ProveUnnormalized(info, snapshot);
std::uint32_t unnormalizedSamplers = 0u;
for (std::uint32_t index = 0; index < info.samplers.size(); ++index) {
if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u});
if (!FoldsTexelOffsets(info.samplers[index])) continue;
if (!registrationModules) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u});
else if (unnormalized.samplers[index]) unnormalizedSamplers |= 1u << index;
}
std::vector<std::uint32_t> compareStates(info.images.size());
for (std::uint32_t index = 0; index < info.images.size(); ++index) {
@ -264,7 +269,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
const auto compare = compareStates[index] = ResourceMaterializer::EmulatedCompareState(info, snapshot, index);
const auto first = PipelineSpecialization::CompareBase + index * PipelineSpecialization::CompareWords;
const std::array<std::uint32_t, 6> values{EmulatedCompare::Function(compare), (compare & EmulatedCompare::Linear) != 0u, EmulatedCompare::AddressX(compare), EmulatedCompare::AddressY(compare), EmulatedCompare::Reference(compare), (compare & EmulatedCompare::BorderWhite) != 0u};
for (std::uint32_t word = 0; word < values.size(); ++word) plan.specialization.push_back({first + word, values[word]});
for (std::uint32_t word = 0; word < values.size() && !registrationModules; ++word) plan.specialization.push_back({first + word, values[word]});
}
std::vector<std::uint32_t> imageModes(info.images.size());
for (std::size_t index = 0; index < info.images.size(); ++index) imageModes[index] = ResourceMaterializer::RuntimeImageMode(info.images[index], snapshot.images.at(index), info.runtimeImageModes.at(index));
@ -301,7 +306,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
plan.specialization.push_back({first, imageModes[index]});
for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({first + 1u + component, (snapshot.images.at(index).dwords[3] >> (component * 3u)) & 7u});
}
for (std::uint32_t target = 0; target < exportMappings.size(); ++target) {
for (std::uint32_t target = 0; target < exportMappings.size() && !registrationModules; ++target) {
for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({PipelineSpecialization::ExportBase + target * 4u + component, (exportMappings[target] >> (component * 2u)) & 3u});
}
std::vector<std::uint32_t> samplerModes(info.samplers.size(), 0u);
@ -416,7 +421,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
metadata.binding = static_cast<std::uint32_t>(compact.kind);
metadata.firstElement = static_cast<std::uint32_t>(element);
metadata.elementCount = 1u;
metadata.flags = imageModes[resource] << 1u;
metadata.flags = imageModes[resource] << 1u | (registrationModules ? (compareStates[resource] & RuntimeAbi::CompareStateMask) << RuntimeAbi::CompareStateShift : 0u);
if (descriptor.dwords[0] == 0u && (descriptor.dwords[1] & 0xffu) == 0u) metadata.flags |= 1u;
const auto offset = layout.ImageMetadataDword() + resource * static_cast<std::uint32_t>(sizeof(metadata) / sizeof(std::uint32_t));
plan.imageMetadata.push_back({resource, offset, metadata});
@ -447,10 +452,16 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
RuntimeAbi::ResourceMetadata metadata{};
if (descriptor.dwordCount != metadata.descriptor.size()) fail("invalid runtime image descriptor width");
metadata.elementCount = 1u;
metadata.flags = imageModes[resource] << 1u;
if (imageModes[resource] > RuntimeAbi::ImageModeMask) fail("runtime image mode exceeds its metadata field");
metadata.flags = imageModes[resource] << 1u | (registrationModules ? (compareStates[resource] & RuntimeAbi::CompareStateMask) << RuntimeAbi::CompareStateShift : 0u);
if (descriptor.dwords[0] == 0u && (descriptor.dwords[1] & 0xffu) == 0u) metadata.flags |= 1u;
plan.imageMetadata.push_back({resource, layout.ImageMetadataDword() + resource * static_cast<std::uint32_t>(sizeof(metadata) / sizeof(std::uint32_t)), metadata});
}
if (layout.runtimeStateDwords != 0u) {
plan.runtimeState.assign(layout.runtimeStateDwords, 0u);
plan.runtimeState.at(RuntimeAbi::StateUnnormalizedSamplers) = unnormalizedSamplers;
for (std::uint32_t target = 0; target < exportMappings.size(); ++target) plan.runtimeState.at(RuntimeAbi::StateExportMappings + target / 4u) |= static_cast<std::uint32_t>(exportMappings[target]) << (target % 4u * 8u);
}
std::vector<std::uint32_t> samplerMasks(info.samplers.size());
for (const auto& binding : plan.bindings) {
if (binding.descriptor.role != DescriptorRole::GuestSamplers) continue;
@ -488,13 +499,14 @@ DescriptorBindingPlan DescriptorBindingBuilder::Select(const DescriptorBindingPl
}
}
selected.imageMetadata = plan.imageMetadata;
selected.runtimeState = plan.runtimeState;
return selected;
}
void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, const CompiledBindingLayout& compiled, const DescriptorBindingPlan& plan, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array<std::uint32_t, 3>& partialThreads) const {
const auto& layout = compiled.layout;
const bool needsShaderData = layout.UsesPushData() || plan.storageData;
auto shaderData = needsShaderData ? ShaderDataDwordsFor(layout, userDataBase, snapshot, partialThreads, plan.imageMetadata) : std::vector<std::uint32_t>{};
auto shaderData = needsShaderData ? ShaderDataDwordsFor(layout, userDataBase, snapshot, partialThreads, plan.imageMetadata, plan.runtimeState) : std::vector<std::uint32_t>{};
std::vector<DescriptorBinding> bindings;
bindings.reserve(plan.bindings.size());
std::size_t writtenHere = 0;

View File

@ -12,6 +12,7 @@
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include "ShaderDiskCache.hpp"
#include <list>
#include <functional>
#include <map>
#include <set>
#include <mutex>
@ -602,6 +603,38 @@ struct PreparedBindingPlan {
std::map<std::vector<std::uint32_t>, std::shared_ptr<PreparedModuleEntry>> modules;
};
struct RegistrationModuleEntry {
std::once_flag ready;
std::exception_ptr failure;
std::shared_ptr<const SpecializedModule> module;
};
std::shared_ptr<const SpecializedModule> registrationModule(std::uint64_t variantId, std::span<const std::uint32_t> moduleKey, const std::function<std::shared_ptr<const SpecializedModule>()>& specialize) {
std::vector<std::uint64_t> key{variantId};
key.insert(key.end(), moduleKey.begin(), moduleKey.end());
static std::shared_mutex mutex;
static std::map<std::vector<std::uint64_t>, std::shared_ptr<RegistrationModuleEntry>> modules;
std::shared_ptr<RegistrationModuleEntry> entry;
{
std::shared_lock lock(mutex);
if (const auto found = modules.find(key); found != modules.end()) entry = found->second;
}
if (entry == nullptr) {
std::unique_lock lock(mutex);
const auto found = modules.find(key);
entry = found != modules.end() ? found->second : modules.emplace(key, std::make_shared<RegistrationModuleEntry>()).first->second;
}
std::call_once(entry->ready, [&] {
try {
entry->module = specialize();
} catch (...) {
entry->failure = std::current_exception();
}
});
if (entry->failure) std::rethrow_exception(entry->failure);
return entry->module;
}
std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exports, bool registrationModules) {
struct BindingPlanKeyStorage {};
auto& key = HostThreadLocal<std::vector<std::uint64_t>, BindingPlanKeyStorage>();
@ -769,7 +802,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
constants.push_back({first + 5u, kind});
}
}
const auto module = specializeModule(variant.artifact, vertexClasses, constants, request.target);
const auto module = request.target.registrationModules && plan->bindings.specialization.empty() ? registrationModule(artifact.variantId, moduleKey, [&] { return specializeModule(variant.artifact, vertexClasses, constants, request.target); }) : specializeModule(variant.artifact, vertexClasses, constants, request.target);
if (variant.bindings.layout.UsesPushData() && !module->pushData) throw std::runtime_error("specialization removed the prepared push constant interface");
auto selected = DescriptorBindingBuilder{}.Select(plan->bindings, module->bindings);
entry->bindings = std::move(selected);

View File

@ -39,6 +39,12 @@ inline constexpr std::uint32_t BindlessTableSlots = 16u;
inline constexpr std::uint32_t StorageMipSlots = 4u;
inline constexpr std::uint32_t StorageHeapCapacity = 16u;
inline constexpr std::uint32_t SamplerHeapCapacity = 32u;
inline constexpr std::uint32_t StateUnnormalizedSamplers = 0u;
inline constexpr std::uint32_t StateExportMappings = 1u;
inline constexpr std::uint32_t StateDwords = 3u;
inline constexpr std::uint32_t ImageModeMask = 0xffu;
inline constexpr std::uint32_t CompareStateShift = 9u;
inline constexpr std::uint32_t CompareStateMask = 0x1fffu;
struct ResourceMetadata {
std::uint32_t binding;

View File

@ -416,6 +416,7 @@ void encodeLayout(Writer& writer, const IrBindingLayout& layout) {
writer.Value(layout.memoryOffsetCount);
writer.Value(layout.dispatchThreadLimit);
writer.Value(layout.runtimeImageCount);
writer.Value(layout.runtimeStateDwords);
writer.Values(std::span<const std::uint32_t>(layout.userDataRegisters));
writer.List(layout.descriptors, [](Writer& out, const IrDescriptorBinding& descriptor) {
out.Value(descriptor.kind);
@ -429,6 +430,7 @@ void decodeLayout(Reader& reader, IrBindingLayout& layout) {
reader.Value(layout.memoryOffsetCount);
reader.Value(layout.dispatchThreadLimit);
reader.Value(layout.runtimeImageCount);
reader.Value(layout.runtimeStateDwords);
reader.Values(layout.userDataRegisters);
reader.List(layout.descriptors, 12, [](Reader& in, IrDescriptorBinding& descriptor) {
in.Value(descriptor.kind);

View File

@ -47,6 +47,11 @@ struct SampleSetup {
std::uint32_t coord;
};
std::uint32_t RuntimeImageMode(SpirvEmitterState& state, std::uint32_t resource) {
const auto mode = Binary(state, spv::OpShiftRightLogical, TypeU32(state), RuntimeImageDword(state, resource, offsetof(RuntimeAbi::ResourceMetadata, flags) / sizeof(std::uint32_t)), ConstantU32(state, 1u));
return state.registrationModules ? Binary(state, spv::OpBitwiseAnd, TypeU32(state), mode, ConstantU32(state, RuntimeAbi::ImageModeMask)) : mode;
}
std::uint32_t RuntimeImageSwizzle(SpirvEmitterState& state, std::uint32_t resource, std::uint32_t component) {
if (state.program.Info().images.at(resource).indirectRoot == ImageResource::NoIndirectImage && !state.registrationModules) return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ImageBase + resource * PipelineSpecialization::ImageWords + 1u + component, component + 4u);
const auto descriptor = RuntimeImageDword(state, resource, offsetof(RuntimeAbi::ResourceMetadata, descriptor) / sizeof(std::uint32_t) + 3u);
@ -1264,7 +1269,12 @@ void EmitEmulatedCompareSample(SpirvValueEmitContext& ctx, const ImageEmitAccess
auto& state = ctx.state;
const auto& mem = access.mem;
const auto& image = access.image;
const auto parameter = [&](std::uint32_t word) { return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::CompareBase + mem.resource * PipelineSpecialization::CompareWords + word, 0u); };
const auto compare = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), RuntimeImageDword(state, mem.resource, offsetof(RuntimeAbi::ResourceMetadata, flags) / sizeof(std::uint32_t)), ConstantU32(state, RuntimeAbi::CompareStateShift)), RuntimeAbi::CompareStateMask) : 0u;
const auto parameter = [&](std::uint32_t word) {
if (!state.registrationModules) return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::CompareBase + mem.resource * PipelineSpecialization::CompareWords + word, 0u);
constexpr std::array<std::pair<std::uint32_t, std::uint32_t>, 6> fields{{{EmulatedCompare::FunctionShift, 7u}, {4u, 1u}, {EmulatedCompare::ClampXShift, 3u}, {EmulatedCompare::ClampYShift, 3u}, {EmulatedCompare::ReferenceShift, 3u}, {12u, 1u}}};
return EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), compare, ConstantU32(state, fields.at(word).first)), fields.at(word).second);
};
const auto equal = [&](std::uint32_t value, std::uint32_t literal) { return Binary(state, spv::OpIEqual, TypeBool(state), value, ConstantU32(state, literal)); };
if (access.slot != 0) ctx.Fail(access.inst, "compares against a color texture through a bindless image table, which is not implemented");
if (HasFlag(mem, RdnaImageSampleFlagDerivative) || HasFlag(mem, RdnaImageSampleFlagLod)) ctx.Fail(access.inst, "compares against a color texture with gradients or an explicit LOD, which is not implemented");
@ -1484,7 +1494,7 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con
} else if (!offsetOperand) {
sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands);
} else {
const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u);
const auto selector = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), EmitShaderDataDwordLoad(state, state.program.Metadata().bindings.RuntimeStateDword() + RuntimeAbi::StateUnnormalizedSamplers), ConstantU32(state, mem.sampler)), 1u) : state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u);
const auto folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u));
const auto foldLabel = state.module.AllocateId();
const auto operandLabel = state.module.AllocateId();
@ -1644,7 +1654,7 @@ void EmitImage(SpirvValueEmitContext& ctx, const IrValue& inst) {
state.runtimeImageMetadata = 0u;
return;
}
const auto selector = base.indirectRoot == ImageResource::NoIndirectImage && !state.registrationModules ? state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ImageBase + memory.resource * PipelineSpecialization::ImageWords, 0u) : Binary(state, spv::OpShiftRightLogical, TypeU32(state), RuntimeImageDword(state, memory.resource, offsetof(RuntimeAbi::ResourceMetadata, flags) / sizeof(std::uint32_t)), ConstantU32(state, 1u));
const auto selector = base.indirectRoot == ImageResource::NoIndirectImage && !state.registrationModules ? state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ImageBase + memory.resource * PipelineSpecialization::ImageWords, 0u) : RuntimeImageMode(state, memory.resource);
const bool returnsValue = inst.Opcode() != IrOpcode::ImageWrite;
const auto resultId = returnsValue ? ctx.Result(inst) : 0u;
ctx.definitions.erase(&inst);

View File

@ -1319,7 +1319,7 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) {
if (exp.index >= 8u) throw std::runtime_error("fragment export target exceeds the runtime mapping table");
std::array<std::uint32_t, 4> components{};
for (std::uint32_t component = 0; component < components.size(); ++component) {
const auto index = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportBase + exp.index * 4u + component, component);
const auto index = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), EmitShaderDataDwordLoad(state, state.program.Metadata().bindings.RuntimeStateDword() + RuntimeAbi::StateExportMappings + exp.index / 4u), ConstantU32(state, exp.index % 4u * 8u + component * 2u)), 3u) : state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportBase + exp.index * 4u + component, component);
components[component] = state.module.AllocateId();
state.module.AddFunction(spv::OpVectorExtractDynamic, uintOutput ? TypeU32(state) : TypeF32(state), components[component], value, index);
}