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:
parent
765ea6a10f
commit
baf3437465
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue