perf(recompiler): read buffer V# words in the shader when modules are built at registration

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-10 10:18:14 +02:00
parent 436e310ddb
commit ddb6680667
14 changed files with 173 additions and 34 deletions

View File

@ -1010,6 +1010,8 @@ target_include_directories(agc_driver_provoking_vertex_tests PRIVATE ${agcTestSh
target_include_directories(agc_driver_prepared_exports_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_buffer_descriptor_runtime_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_prepared_publication_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

View File

@ -2926,6 +2926,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const {
target.narrowSubgroupClock = state->narrowSubgroupClock;
target.float64DenormPreserve = state->float64DenormPreserve;
target.fixedPushSlots = state->graphicsPipelineLibrary;
target.registrationModules = state->graphicsPipelineLibrary;
target.srgbDecodeFormats = state->srgbDecodeFormats;
if (state->meshShader) {
const auto& mesh = state->meshLimits;

View File

@ -1625,12 +1625,12 @@ void verifyPixelRequestSerialization() {
minimal.context.waveSize = 64;
minimal.context.pixel = ShaderPixelStageInfo{};
const auto encoded = serializer.Serialize(minimal);
require(requestPrefix(encoded, 8u) == "NVNQQRAAAAA=", "new requests did not use serialization version 16");
require(requestPrefix(encoded, 8u) == "NVNQQREAAAA=", "new requests did not use serialization version 17");
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
for (std::size_t bytes = 0; bytes < 18u; ++bytes) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source or alpha-to-coverage flag was accepted");
}
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQREAAAA="}) {
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRIAAAA="}) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
}
}
@ -1725,7 +1725,7 @@ void verifyLegacyPixelRequests() {
require(!pixel.orderedPixelShader, "a legacy request became a primitive-ordered pixel shader");
require(request.shader.stage == ShaderStage::Fragment && request.shader.code.size() == 1u && request.shader.code[0] == 0xbf810000u && !request.context.vertex.has_value() && request.context.memory.empty(), "legacy guest context was misaligned");
require(request.target.vulkanVersion == 0x00401000u && request.target.spirvVersion == 0x00010300u && request.target.subgroupSize == 64u && request.layout.firstBinding == 11u && request.layout.pushConstantSizeBytes == 128u, "legacy target or binding layout was misaligned");
require(request.useCache == (version == 1u) && request.target.nonConstantImageOffsets == (version >= 6u) && request.target.srgbDecodeFormats == 0u && !request.target.narrowSubgroupClock && !request.target.float64DenormPreserve, "legacy request trailer was misread");
require(request.useCache == (version == 1u) && request.target.nonConstantImageOffsets == (version >= 6u) && request.target.srgbDecodeFormats == 0u && !request.target.narrowSubgroupClock && !request.target.float64DenormPreserve && !request.target.registrationModules, "legacy request trailer was misread");
const auto upgraded = serializer.Deserialize(serializer.Serialize(request));
require(upgraded.request.context.pixel->targetExportMapping == pixel.targetExportMapping && RecompileCacheKey::ContextHash(upgraded.request) == RecompileCacheKey::ContextHash(request), "upgrading a legacy capture changed its pixel mapping");
}
@ -3218,6 +3218,14 @@ int main(int argc, char** argv) {
auto float64Denormals = uncached;
float64Denormals.target.float64DenormPreserve = true;
require(RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(float64Denormals)).request.target.float64DenormPreserve, "f64 denormal preservation was lost in serialization");
auto registrationModules = uncached;
registrationModules.target.registrationModules = true;
require(RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(registrationModules)).request.target.registrationModules, "registration modules were lost in serialization");
std::vector<std::uint64_t> plainKey;
std::vector<std::uint64_t> registrationKey;
RecompileCacheKey::Build(uncached, plainKey);
RecompileCacheKey::Build(registrationModules, registrationKey);
require(plainKey != registrationKey, "registration modules did not change the cache key");
auto changedLayout = request;
changedLayout.layout.pushConstantSizeBytes = 64;
require(!Recompile(changedLayout).cacheHit, "binding layout change reused an incompatible variant");

View File

@ -0,0 +1,113 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "Optimization/ResourceProgram.hpp"
#include "VulkanTestDevice.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Lanes = 32;
constexpr std::uint32_t InputBytes = 256;
constexpr std::uint32_t Fill = 0xdeadbeefu;
alignas(256) std::array<std::uint8_t, InputBytes> Input{};
alignas(256) std::array<std::uint32_t, Lanes> Output{};
alignas(256) constexpr std::array<std::uint32_t, 7> Code{
0x34020082, 0xe0301000, 0x80000201, 0xbf8c3f70, 0xe0701000, 0x80010201, 0xbf810000,
};
struct Case {
const char* name;
std::uint32_t baseOffset;
std::uint32_t records;
bool present;
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
ShaderRecompiler::RecompileRequest Request(std::span<const std::uint32_t> userData, const ShaderRecompiler::SpirvTarget& target, std::span<const ShaderRecompiler::MemoryRegion> memory) {
const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {false, false, false}, false, 1};
return {
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(Code.data()), Code, 0, {}},
{Lanes, 0, userData, compute, std::nullopt, std::nullopt, memory},
target,
{0, 0, 0, 128}
};
}
void Run(AgcDriver::VulkanDevice& device, bool registrationModules) {
const std::string mode = registrationModules ? "registration modules" : "specialized modules";
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(Code.data()), std::as_bytes(std::span(Code))}}};
std::array<std::uint32_t, 8> userData{};
auto target = device.ComputeTarget(Lanes);
target.registrationModules = registrationModules;
const auto request = Request(userData, target, memory);
const auto handle = ShaderRecompiler::PrepareShader(request);
const std::array<Case, 7> cases{{
{"aligned", 0, 128, true},
{"base +1", 1, 128, true},
{"base +2", 2, 128, true},
{"base +3", 3, 128, true},
{"records 37", 0, 37, true},
{"records 64 base +2", 2, 64, true},
{"absent", 0, 128, false},
}};
const void* first = nullptr;
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};
const auto output = BufferDescriptor(Output.data(), sizeof(Output));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
ShaderRecompiler::SrtRuntime runtime{};
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");
device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(Code.data()));
device.WaitIdle();
for (std::uint32_t lane = 0; lane < Lanes; ++lane) {
std::uint32_t expected = 0;
if (test.present && lane * 4u + 4u <= test.records) std::memcpy(&expected, Input.data() + test.baseOffset + lane * 4u, sizeof(expected));
Require(Output[lane] == expected, "buffer descriptor runtime, " + mode + ", " + test.name + ": lane " + std::to_string(lane) + " is " + Hex(Output[lane]) + ", expected " + Hex(expected));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
for (std::uint32_t index = 0; index < InputBytes; ++index) Input[index] = static_cast<std::uint8_t>(index * 41u + 3u + (index >> 3u));
Run(*device, false);
Run(*device, true);
std::puts("buffer descriptor runtime tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -240,6 +240,7 @@ private:
append(key, value.srgbDecodeFormats);
append(key, value.narrowSubgroupClock);
append(key, value.float64DenormPreserve);
append(key, value.registrationModules);
}
};

View File

@ -734,7 +734,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
std::string buffer;
Writer writer(buffer);
writer.WriteU32(0x41505335u);
writer.WriteU32(16u);
writer.WriteU32(17u);
writeShaderBinary(writer, request.shader);
writeGuestContext(writer, request.context);
writeSpirvTarget(writer, request.target);
@ -754,6 +754,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
if (request.context.compute.has_value()) writer.WriteU32(request.context.compute->scratchDwords);
writer.WriteBool(request.target.narrowSubgroupClock);
writer.WriteBool(request.target.float64DenormPreserve);
writer.WriteBool(request.target.registrationModules);
return base64Encode(buffer);
}
@ -762,7 +763,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
Reader reader(decoded);
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
const auto version = reader.ReadU32();
if (version < 1u || version > 16u) throw std::runtime_error("unsupported recompile request serialization version");
if (version < 1u || version > 17u) throw std::runtime_error("unsupported recompile request serialization version");
DeserializedRequest result{};
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
result.request.context = readGuestContext(reader, result, version);
@ -783,6 +784,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
if (version >= 10u && result.request.context.compute.has_value()) result.request.context.compute->scratchDwords = reader.ReadU32();
if (version >= 12u) result.request.target.narrowSubgroupClock = reader.ReadBool();
if (version >= 16u) result.request.target.float64DenormPreserve = reader.ReadBool();
if (version >= 17u) result.request.target.registrationModules = reader.ReadBool();
return result;
}

View File

@ -33,7 +33,7 @@ struct DescriptorBindingPlan {
class DescriptorBindingBuilder {
public:
DescriptorBindingPlan Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings = {}) const;
DescriptorBindingPlan Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings = {}, bool registrationModules = false) const;
DescriptorBindingPlan Select(const DescriptorBindingPlan& plan, std::span<const std::uint32_t> bindings) const;
void 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;
void Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot, const std::array<std::uint32_t, 3>& partialThreads, std::span<const std::uint8_t> exportMappings = {}) const;

View File

@ -250,7 +250,7 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con
allocation.specialization = std::move(plan.specialization);
}
DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings) const {
DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exportMappings, bool registrationModules) const {
if (stage == IrShaderStage::Pixel && exportMappings.size() != 8u) fail("fragment runtime export mappings are missing");
if (info.images.size() > ShaderInfo::MaxImages) fail("runtime image count exceeds the static capacity");
DescriptorBindingPlan plan;
@ -280,11 +280,13 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
auto alignment = static_cast<std::uint32_t>(buffer.typedAlignment);
if (buffer.descriptorFormatted && format != 0u) alignment = std::max(alignment, std::min(GetFormatInfo(static_cast<IrBufferFormat>(format)).byteSize, 4u));
if (alignment == 0u || misalignment % alignment != 0u) fail("is a formatted buffer access through a V# whose base is not aligned to its element");
const auto first = PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords;
plan.specialization.push_back({first, descriptor.dwords[1] & 0xffff0000u});
plan.specialization.push_back({first + 1u, descriptor.dwords[3]});
plan.specialization.push_back({first + 2u, present ? 1u : 0u});
plan.specialization.push_back({first + 3u, misalignment});
if (!registrationModules) {
const auto first = PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords;
plan.specialization.push_back({first, descriptor.dwords[1] & 0xffff0000u});
plan.specialization.push_back({first + 1u, descriptor.dwords[3]});
plan.specialization.push_back({first + 2u, present ? 1u : 0u});
plan.specialization.push_back({first + 3u, misalignment});
}
if (buffer.formattedReadMask != 0u) {
for (std::uint32_t component = 0; component < 4u; ++component) {
if ((buffer.formattedReadMask & (1u << component)) == 0u) continue;

View File

@ -418,6 +418,7 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra
targetOptions.nonConstantImageOffsets = request.target.nonConstantImageOffsets;
targetOptions.narrowSubgroupClock = request.target.narrowSubgroupClock;
targetOptions.float64DenormPreserve = request.target.float64DenormPreserve;
targetOptions.registrationModules = request.target.registrationModules;
constexpr SpirvEmitter spirvEmitter;
CompiledShaderArtifact result;
@ -601,7 +602,7 @@ struct PreparedBindingPlan {
std::map<std::vector<std::uint32_t>, std::shared_ptr<PreparedModuleEntry>> modules;
};
std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span<const std::uint8_t> exports) {
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>();
key.clear();
@ -642,7 +643,7 @@ std::shared_ptr<PreparedBindingPlan> preparedBindingPlan(const CompiledVariant&
}
std::call_once(plan->ready, [&] {
try {
plan->bindings = DescriptorBindingBuilder{}.Prepare(variant.bindings.layout, variant.info.info, variant.info.stage, snapshot, exports);
plan->bindings = DescriptorBindingBuilder{}.Prepare(variant.bindings.layout, variant.info.info, variant.info.stage, snapshot, exports, registrationModules);
} catch (...) {
plan->failure = std::current_exception();
}
@ -704,7 +705,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
.barycentricEmulation = artifact.barycentricEmulation,
.variantId = artifact.variantId
};
const auto plan = preparedBindingPlan(variant, snapshot, request.context.pixel ? std::span<const std::uint8_t>(request.context.pixel->targetExportMapping) : std::span<const std::uint8_t>{});
const auto plan = preparedBindingPlan(variant, snapshot, request.context.pixel ? std::span<const std::uint8_t>(request.context.pixel->targetExportMapping) : std::span<const std::uint8_t>{}, request.target.registrationModules);
const DescriptorBindingPlan* bindingPlan = &plan->bindings;
std::shared_ptr<PreparedModuleEntry> entry;
struct LocalModuleKeyStorage {};

View File

@ -229,6 +229,7 @@ struct SpirvTarget {
bool narrowSubgroupClock = false;
bool fixedPushSlots = false;
bool float64DenormPreserve = false;
bool registrationModules = false;
};
struct BindingLayout {

View File

@ -21,6 +21,7 @@ struct SpirvTargetOptions {
bool nonConstantImageOffsets = false;
bool narrowSubgroupClock = false;
bool float64DenormPreserve = false;
bool registrationModules = false;
};
class SpirvEmitter {

View File

@ -91,6 +91,8 @@ struct SpirvEmitterState {
bool splitSubgroup = false;
bool narrowSubgroupClock = false;
bool float64DenormPreserve = false;
bool registrationModules = false;
std::uint32_t bufferMisalignment = 0;
std::unordered_set<const IrValue*> sharedLaneValues;
std::uint32_t laneHalf = 0;
// The target's SPIR-V version and what the device accepts, for capabilities an emitter adds

View File

@ -250,6 +250,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Sh
state.nonConstantImageOffsets = target.nonConstantImageOffsets;
state.narrowSubgroupClock = target.narrowSubgroupClock;
state.float64DenormPreserve = target.float64DenormPreserve;
state.registrationModules = target.registrationModules;
state.hostSubgroupSize = target.subgroupSize;
state.splitSubgroup = program.WaveSize() == 32u && target.subgroupSize > 32u;
if (state.splitSubgroup && (state.requirements.subgroupBallot || state.requirements.subgroupShuffle)) state.requirements.subgroupLocalInvocationId = true;

View File

@ -57,9 +57,10 @@ RuntimeDescriptor descriptor(SpirvValueEmitContext& context, const IrValue& inst
const auto* handle = instruction.Argument(0)->Resolve();
if (handle->Opcode() != IrOpcode::GetBufferResource || handle->ArgumentCount() != 4u) context.Fail(instruction, "requires a runtime four-dword V#");
const auto& memory = context.Memory(instruction);
const bool runtime = memory.gpuDescriptor || state.registrationModules;
const auto first = PipelineSpecialization::BufferBase + memory.resource * PipelineSpecialization::BufferWords;
const auto word1 = memory.gpuDescriptor ? context.Arg(*handle, 1u) : state.module.SpecializationConstant(TypeU32(state), first, 0u);
const auto word3 = memory.gpuDescriptor ? context.Arg(*handle, 3u) : state.module.SpecializationConstant(TypeU32(state), first + 1u, 0u);
const auto word1 = runtime ? context.Arg(*handle, 1u) : state.module.SpecializationConstant(TypeU32(state), first, 0u);
const auto word3 = runtime ? context.Arg(*handle, 3u) : state.module.SpecializationConstant(TypeU32(state), first + 1u, 0u);
const auto u32 = TypeU32(state);
auto base = ConstantU32(state, 0u);
if (memory.gpuDescriptor || BufferAccessOf(instruction.Opcode()) == BufferAccess::Atomic) {
@ -82,7 +83,8 @@ RuntimeDescriptor descriptor(SpirvValueEmitContext& context, const IrValue& inst
}
if (memory.gpuDescriptor) faultIf(context, instruction, invalid, base, 0u, BdaAbi::FaultReason::InvalidDescriptor);
const auto stride = field(state, word1, 16u, 14u);
const auto valid = memory.gpuDescriptor ? both(state, Unary(state, spv::OpLogicalNot, TypeBool(state), invalid), Binary(state, spv::OpINotEqual, TypeBool(state), base, BdaConstant(state, 0u))) : nonzero(state, state.module.SpecializationConstant(u32, first + 2u, 0u));
const auto present = runtime ? both(state, nonzero(state, context.Arg(*handle, 2u)), Binary(state, spv::OpLogicalOr, TypeBool(state), nonzero(state, context.Arg(*handle, 0u)), nonzero(state, field(state, word1, 0u, 16u)))) : nonzero(state, state.module.SpecializationConstant(u32, first + 2u, 0u));
const auto valid = memory.gpuDescriptor ? both(state, Unary(state, spv::OpLogicalNot, TypeBool(state), invalid), Binary(state, spv::OpINotEqual, TypeBool(state), base, BdaConstant(state, 0u))) : present;
return {base, context.Arg(*handle, 2u), stride, word3, both(state, nonzero(state, stride), nonzero(state, field(state, word1, 31u, 1u))), index, valid};
}
@ -140,8 +142,10 @@ MemoryResourceAccess directAccess(SpirvValueEmitContext& context, const IrValue&
std::uint32_t baseMisalignment(SpirvValueEmitContext& context, const IrValue& instruction) {
const auto& memory = context.Memory(instruction);
if (memory.kind == ResourceKind::ScalarBuffer) return ConstantU32(context.state, 0u);
const auto first = PipelineSpecialization::BufferBase + memory.resource * PipelineSpecialization::BufferWords;
return context.state.module.SpecializationConstant(TypeU32(context.state), first + 3u, 0u);
auto& state = context.state;
if (state.bufferMisalignment != 0u) return state.bufferMisalignment;
if (state.registrationModules) return Binary(state, spv::OpBitwiseAnd, TypeU32(state), context.Arg(*instruction.Argument(0)->Resolve(), 0u), ConstantU32(state, 3u));
return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::BufferBase + memory.resource * PipelineSpecialization::BufferWords + 3u, 0u);
}
std::uint32_t directWordIndex(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t address) {
@ -401,7 +405,7 @@ std::uint32_t formattedCall(SpirvValueEmitContext& context, const IrValue& instr
auto function = state.formattedBufferFunctions.find(key);
if (function == state.formattedBufferFunctions.end()) {
const auto id = state.module.AllocateId();
const auto functionType = store ? state.module.Type(spv::OpTypeFunction, resultType, u32, u32, u32, u32, u32, valueType) : state.module.Type(spv::OpTypeFunction, resultType, u32, u32, u32, u32, u32);
const auto functionType = store ? state.module.Type(spv::OpTypeFunction, resultType, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, valueType) : state.module.Type(spv::OpTypeFunction, resultType, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32);
const auto label = state.currentLabel;
const auto byteOffset = state.memoryByteOffsets.at(element);
struct Restore {
@ -412,27 +416,23 @@ std::uint32_t formattedCall(SpirvValueEmitContext& context, const IrValue& instr
~Restore() {
state.module.EndHelperFunction();
state.memoryByteOffsets.at(element) = byteOffset;
state.bufferMisalignment = 0u;
state.currentLabel = label;
}
};
state.module.BeginHelperFunction();
const Restore restore{state, element, label, byteOffset};
state.module.AddFunction(spv::OpFunction, resultType, id, spv::FunctionControlDontInlineMask, functionType);
std::array<std::uint32_t, 6> parameters{};
for (std::uint32_t index = 0; index < (store ? 6u : 5u); ++index) {
std::array<std::uint32_t, 11> parameters{};
for (std::uint32_t index = 0; index < (store ? 11u : 10u); ++index) {
parameters[index] = state.module.AllocateId();
state.module.AddFunction(spv::OpFunctionParameter, index == 5u ? valueType : u32, parameters[index]);
state.module.AddFunction(spv::OpFunctionParameter, index == 10u ? valueType : u32, parameters[index]);
}
EmitLabel(state, state.module.AllocateId());
state.memoryByteOffsets.at(element) = parameters[4];
const auto first = PipelineSpecialization::BufferBase + memory.resource * PipelineSpecialization::BufferWords;
const auto word1 = state.module.SpecializationConstant(u32, first, 0u);
const auto word3 = state.module.SpecializationConstant(u32, first + 1u, 0u);
const auto stride = field(state, word1, 16u, 14u);
const auto swizzle = both(state, nonzero(state, stride), nonzero(state, field(state, word1, 31u, 1u)));
const auto valid = nonzero(state, state.module.SpecializationConstant(u32, first + 2u, 0u));
const RuntimeDescriptor shared{ConstantU32(state, 0u), parameters[3], stride, word3, swizzle, parameters[0], valid, parameters[1], parameters[2]};
const auto value = formatSwitch(context, instruction, shared, components, store, store ? parameters[5] : 0u);
state.bufferMisalignment = parameters[9];
const RuntimeDescriptor shared{ConstantU32(state, 0u), parameters[3], parameters[5], parameters[6], nonzero(state, parameters[7]), parameters[0], nonzero(state, parameters[8]), parameters[1], parameters[2]};
const auto value = formatSwitch(context, instruction, shared, components, store, store ? parameters[10] : 0u);
if (store) state.module.AddFunction(spv::OpReturn);
else state.module.AddFunction(spv::OpReturnValue, value);
state.module.AddFunction(spv::OpFunctionEnd);
@ -440,8 +440,12 @@ std::uint32_t formattedCall(SpirvValueEmitContext& context, const IrValue& instr
}
const auto offset = Binary(state, spv::OpIAdd, u32, context.Arg(instruction, 2u), ConstantU32(state, memory.offset));
const auto result = state.module.AllocateId();
if (store) state.module.AddFunction(spv::OpFunctionCall, resultType, result, function->second, resource.index, offset, context.Arg(instruction, 3u), resource.records, state.memoryByteOffsets.at(element), data);
else state.module.AddFunction(spv::OpFunctionCall, resultType, result, function->second, resource.index, offset, context.Arg(instruction, 3u), resource.records, state.memoryByteOffsets.at(element));
const auto flag = [&](std::uint32_t condition) { return Select(state, u32, condition, ConstantU32(state, 1u), ConstantU32(state, 0u)); };
const auto swizzle = flag(resource.swizzle);
const auto valid = flag(resource.valid);
const auto misalignment = baseMisalignment(context, instruction);
if (store) state.module.AddFunction(spv::OpFunctionCall, resultType, result, function->second, resource.index, offset, context.Arg(instruction, 3u), resource.records, state.memoryByteOffsets.at(element), resource.stride, resource.word3, swizzle, valid, misalignment, data);
else state.module.AddFunction(spv::OpFunctionCall, resultType, result, function->second, resource.index, offset, context.Arg(instruction, 3u), resource.records, state.memoryByteOffsets.at(element), resource.stride, resource.word3, swizzle, valid, misalignment);
return store ? ConstantU32(state, 0u) : result;
}