feat(agc): add fragment shader barycentric support

This commit is contained in:
boykopovar 2026-09-22 11:43:39 +03:00
parent 5e3a13041c
commit 79c9aa587b
7 changed files with 130 additions and 15 deletions

View File

@ -60,6 +60,7 @@ struct VulkanDevice::State {
std::vector<std::string_view> spirvExtensions;
bool tessellationShader = false;
bool meshShader = false;
bool fragmentShaderBarycentric = false;
bool depthClipControl = false;
bool depthRangeUnrestricted = false;
VkPhysicalDeviceMeshShaderPropertiesEXT meshLimits{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT};
@ -306,8 +307,19 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
}
meshFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT};
meshFeatures.meshShader = state->meshShader;
VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentricFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR};
if (hasExtension(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME)) {
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &barycentricFeatures};
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
state->fragmentShaderBarycentric = barycentricFeatures.fragmentShaderBarycentric == VK_TRUE;
}
std::vector<const char*> deviceExtensions;
if (window != nullptr) deviceExtensions.assign(presentationExtensions.begin(), presentationExtensions.end());
if (state->fragmentShaderBarycentric) {
deviceExtensions.push_back(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
state->capabilities.push_back(spv::CapabilityFragmentBarycentricKHR);
state->spirvExtensions.push_back("SPV_KHR_fragment_shader_barycentric");
}
deviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
state->capabilities.push_back(spv::CapabilitySignedZeroInfNanPreserve);
state->spirvExtensions.push_back("SPV_KHR_float_controls");
@ -371,6 +383,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
deviceInfo.pNext = &depthClipFeatures;
}
byteFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
if (state->fragmentShaderBarycentric) {
barycentricFeatures.pNext = byteFeatures.pNext;
byteFeatures.pNext = &barycentricFeatures;
}
bdaFeatures.pNext = &byteFeatures;
deviceInfo.pNext = &bdaFeatures;
check(state->InstanceFunction<PFN_vkCreateDevice>("vkCreateDevice")(selected, &deviceInfo, nullptr, &state->device), "vkCreateDevice");
@ -617,7 +633,8 @@ Graphics::Context VulkanDevice::graphicsContext() const {
state->depthClipControl,
state->depthRangeUnrestricted,
true,
state->subgroup
state->subgroup,
state->fragmentShaderBarycentric
};
}

View File

@ -36,6 +36,7 @@ struct Context {
bool depthRangeUnrestricted = false;
bool bufferDeviceAddress = false;
VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
bool fragmentShaderBarycentric = false;
template<typename TFunction>
TFunction Function(const char* name) const {

View File

@ -26,7 +26,7 @@ private:
};
void ValidateShaderPair(const ShaderRecompiler::RecompileResult& vertex, const ShaderRecompiler::RecompileResult& fragment);
void ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup);
void ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric);
}

View File

@ -39,7 +39,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD
APS5_LOG_CHARS_OUT("Index buffer range OK");
Require(!state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target");
APS5_LOG_CHARS_OUT("ValidateShaders");
ValidateShaders(shaders, state, context.subgroup);
ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric);
APS5_LOG_CHARS_OUT("ValidateShaders OK");
const auto shaderStages = PipelineStages(shaders);
APS5_LOG_OUT("PipelineStages=0x%x", static_cast<unsigned>(shaderStages));

View File

@ -10,7 +10,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const RenderTarge
Require((target != nullptr) == state.hasColorTarget, "render target does not match decoded color state");
Require(state.renderExtent.width != 0 && state.renderExtent.height != 0 && state.renderExtent.width <= context.limits.maxFramebufferWidth && state.renderExtent.height <= context.limits.maxFramebufferHeight, "framebuffer extent exceeds device limits");
Require(state.hasColorTarget || (context.limits.framebufferNoAttachmentsSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0, "device does not support single-sample rendering without attachments");
ValidateShaders(shaders, state, context.subgroup);
ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric);
Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled");
if (state.stages.tessellation) {
Require(context.tessellationShader, "device does not support tessellation shaders");

View File

@ -21,6 +21,7 @@ struct Decoration {
bool block = false;
bool patch = false;
bool perPrimitive = false;
bool perVertex = false;
};
struct Variable {
@ -40,6 +41,7 @@ struct Module {
std::map<std::uint32_t, std::string> outputs;
bool position = false;
bool primitiveIndices = false;
bool fragmentBarycentric = false;
std::map<std::uint32_t, std::vector<std::uint32_t>> modes;
const std::vector<std::uint32_t>& Type(std::uint32_t id) const {
@ -78,6 +80,10 @@ struct Module {
const bool vertex = stage == Stage::Vertex || stage == Stage::Local;
const bool input = storage == spv::StorageClassInput;
const auto& raw = Type(type);
if (value == spv::BuiltInBaryCoordKHR || value == spv::BuiltInBaryCoordNoPerspKHR) {
Require(fragmentBarycentric && stage == Stage::Fragment && input && Signature(type) == "f32x3", "invalid barycentric built-in: expected fragment Float32 vec3 input with FragmentBarycentricKHR");
return;
}
if (value == spv::BuiltInSubgroupLocalInvocationId) {
Require(input && Signature(type) == "u32", "invalid subgroup local invocation ID input");
Require((subgroup.supportedStages & VulkanStage(stage)) != 0 && (subgroup.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0, "subgroup local invocation ID is unsupported for this shader stage");
@ -117,7 +123,7 @@ struct Module {
}
};
Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup) {
Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric) {
using Stage = ShaderRecompiler::ShaderStage;
Require(compiled.program != nullptr, "missing compiled shader");
const auto& shader = *compiled.program;
@ -146,6 +152,12 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
Require(count == 2, "invalid OpCapability instruction");
const auto capability = static_cast<spv::Capability>(instruction[1]);
const bool isBarycentricCapability = capability == spv::CapabilityFragmentBarycentricKHR;
if (isBarycentricCapability) {
Require(fragment, "FragmentBarycentricKHR requires a fragment shader");
Require(fragmentShaderBarycentric, "device does not support enabled VK_KHR_fragment_shader_barycentric with fragmentShaderBarycentric");
module.fragmentBarycentric = true;
}
VkSubgroupFeatureFlags subgroupOperations = 0;
switch (capability) {
case spv::CapabilityGroupNonUniform: subgroupOperations = VK_SUBGROUP_FEATURE_BASIC_BIT; break;
@ -179,6 +191,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
Require(
isBaseCapability ||
isBarycentricCapability ||
isSubgroupCapability ||
isBdaCapability ||
isTessellationCapability ||
@ -195,6 +208,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
const auto end = std::find(text, text + bytes.size(), '\0');
Require(end != text + bytes.size(), "unterminated SPIR-V extension");
const std::string_view extension(text, static_cast<std::size_t>(end - text));
if (extension == "SPV_KHR_fragment_shader_barycentric") {
Require(fragment && fragmentShaderBarycentric, "SPV_KHR_fragment_shader_barycentric requires enabled fragmentShaderBarycentric in a fragment shader");
break;
}
Require(extension == "SPV_KHR_float_controls" || (mesh && extension == "SPV_EXT_mesh_shader") || (shader.bdaAbiVersion == ShaderRecompiler::BdaAbi::Version && (extension == "SPV_KHR_physical_storage_buffer" || extension == "SPV_KHR_8bit_storage")), "unsupported SPIR-V extension");
break;
}
@ -238,6 +255,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
Require(kind != spv::DecorationComponent && kind != spv::DecorationIndex && kind != spv::DecorationStream && kind != spv::DecorationXfbBuffer && kind != spv::DecorationXfbStride, "unsupported shader interface packing or transform feedback");
if (kind == spv::DecorationPatch) decoration.patch = true;
if (kind == spv::DecorationPerPrimitiveEXT) decoration.perPrimitive = true;
if (kind == spv::DecorationPerVertexKHR) {
Require(count == 3, "malformed PerVertexKHR decoration");
decoration.perVertex = true;
}
if (kind == spv::DecorationBlock) decoration.block = true;
break;
}
@ -314,6 +335,13 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
const bool tessellationLevels = decoration.builtin && (*decoration.builtin == spv::BuiltInTessLevelOuter || *decoration.builtin == spv::BuiltInTessLevelInner);
const bool vertexArray = !tessellationLevels && ((control && (input || output)) || (evaluation && input) || (mesh && output)) && !decoration.patch;
const auto& outer = module.Type(typeId);
if (decoration.perVertex) {
Require(fragment && input && module.fragmentBarycentric && !decoration.patch && !decoration.perPrimitive && !decoration.builtin && decoration.location.has_value(), "PerVertexKHR requires a fragment location input with FragmentBarycentricKHR");
Require(outer.size() == 4 && (outer[0] & 0xffffu) == spv::OpTypeArray, "PerVertexKHR input must be an array");
const auto length = module.constants.find(outer[3]);
Require(length != module.constants.end() && length->second == 3, "PerVertexKHR input must contain three vertices");
typeId = outer[2];
}
if (vertexArray && (outer[0] & 0xffffu) == spv::OpTypeArray) {
Require(outer.size() == 4, "malformed per-vertex interface array");
const auto length = module.constants.find(outer[3]);
@ -408,7 +436,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
}
void ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup) {
void ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric) {
using Stage = ShaderRecompiler::ShaderStage;
const bool tessellation = state.stages.path == ShaderPath::Tessellation;
const bool mesh = state.stages.path == ShaderPath::Geometry;
@ -423,7 +451,7 @@ void ValidateShaders(std::span<const CompiledShader> shaders, const State& state
Require(shaders[i].program != nullptr, "missing compiled shader");
Require(shaders[i].stage == expected, "graphics stage order disagrees");
for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == 0, "graphics resource uses a descriptor set other than zero");
const auto current = Inspect(shaders[i], state, subgroup);
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric);
if (i != 0) {
for (const auto& [location, signature] : current.inputs) {
const auto output = previous.outputs.find(location);
@ -439,7 +467,7 @@ void ValidateShaderPair(const ShaderRecompiler::RecompileResult& vertex, const S
const std::array<CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, static_cast<std::uint32_t>(vertex.pushConstants.size())}}};
State state{};
state.stages.path = ShaderPath::Vertex;
ValidateShaders(shaders, state, VkPhysicalDeviceSubgroupProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES});
ValidateShaders(shaders, state, VkPhysicalDeviceSubgroupProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}, false);
}
}

View File

@ -629,6 +629,12 @@ struct ModuleShape {
bool plainBuffer = false;
bool shaderData = false;
bool vertexInput = false;
bool barycentric = false;
bool barycentricNoPerspective = false;
std::uint32_t barycentricComponents = 3;
bool perVertex = false;
std::uint32_t perVertexLength = 3;
bool parameterOutput = false;
};
void emit(std::vector<std::uint32_t>& out, spv::Op op, std::initializer_list<std::uint32_t> operands) {
@ -653,6 +659,7 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
const auto label = id();
const auto inputPointer = id();
const auto input = id();
std::vector<std::uint32_t> extraInterface;
emit(declarations, spv::OpTypeVoid, {voidType});
emit(declarations, spv::OpTypeFunction, {functionType, voidType});
emit(declarations, spv::OpTypeFloat, {floatType, 32});
@ -660,6 +667,35 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
emit(declarations, spv::OpTypeInt, {uintType, 32, 0});
emit(declarations, spv::OpTypePointer, {outputPointer, spv::StorageClassOutput, vectorType});
emit(declarations, spv::OpVariable, {outputPointer, output, spv::StorageClassOutput});
if (shape.parameterOutput) {
const auto parameter = id();
emit(declarations, spv::OpVariable, {outputPointer, parameter, spv::StorageClassOutput});
emit(annotations, spv::OpDecorate, {parameter, spv::DecorationLocation, 0});
extraInterface.push_back(parameter);
}
if (shape.barycentric) {
const auto vector = id();
const auto pointer = id();
const auto variable = id();
emit(declarations, spv::OpTypeVector, {vector, floatType, shape.barycentricComponents});
emit(declarations, spv::OpTypePointer, {pointer, spv::StorageClassInput, vector});
emit(declarations, spv::OpVariable, {pointer, variable, spv::StorageClassInput});
emit(annotations, spv::OpDecorate, {variable, spv::DecorationBuiltIn, shape.barycentricNoPerspective ? spv::BuiltInBaryCoordNoPerspKHR : spv::BuiltInBaryCoordKHR});
extraInterface.push_back(variable);
}
if (shape.perVertex) {
const auto length = id();
const auto array = id();
const auto pointer = id();
const auto variable = id();
emit(declarations, spv::OpConstant, {uintType, length, shape.perVertexLength});
emit(declarations, spv::OpTypeArray, {array, vectorType, length});
emit(declarations, spv::OpTypePointer, {pointer, spv::StorageClassInput, array});
emit(declarations, spv::OpVariable, {pointer, variable, spv::StorageClassInput});
emit(annotations, spv::OpDecorate, {variable, spv::DecorationLocation, 0});
emit(annotations, spv::OpDecorate, {variable, spv::DecorationPerVertexKHR});
extraInterface.push_back(variable);
}
if (shape.vertexInput) {
emit(declarations, spv::OpTypePointer, {inputPointer, spv::StorageClassInput, vectorType});
emit(declarations, spv::OpVariable, {inputPointer, input, spv::StorageClassInput});
@ -714,9 +750,21 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
emit(function, spv::OpFunctionEnd, {});
std::vector<std::uint32_t> words{spv::MagicNumber, 0x10300, 0, next, 0};
emit(words, spv::OpCapability, {spv::CapabilityShader});
if (shape.barycentric) {
emit(words, spv::OpCapability, {spv::CapabilityFragmentBarycentricKHR});
const std::string extension = "SPV_KHR_fragment_shader_barycentric";
const auto count = (extension.size() + 4) / 4;
words.push_back((static_cast<std::uint32_t>(count + 1) << 16u) | spv::OpExtension);
const auto start = words.size();
words.resize(start + count, 0);
for (std::size_t i = 0; i < extension.size(); ++i) words[start + i / 4] |= static_cast<std::uint32_t>(static_cast<unsigned char>(extension[i])) << ((i % 4) * 8);
}
emit(words, spv::OpMemoryModel, {spv::AddressingModelLogical, spv::MemoryModelGLSL450});
const auto entryPointOffset = words.size();
if (shape.vertexInput) emit(words, spv::OpEntryPoint, {spv::ExecutionModelVertex, main, 0x6e69616du, 0, output, input});
else emit(words, spv::OpEntryPoint, {shape.fragment ? spv::ExecutionModelFragment : spv::ExecutionModelVertex, main, 0x6e69616du, 0, output});
words[entryPointOffset] += static_cast<std::uint32_t>(extraInterface.size()) << 16u;
words.insert(words.end(), extraInterface.begin(), extraInterface.end());
if (shape.fragment) emit(words, spv::OpExecutionMode, {main, spv::ExecutionModeOriginUpperLeft});
words.insert(words.end(), annotations.begin(), annotations.end());
words.insert(words.end(), declarations.begin(), declarations.end());
@ -729,17 +777,38 @@ void validationTests() {
state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex;
ShaderRecompiler::RecompileResult fragment;
fragment.spirv = makeModule({.fragment = true});
{
ShaderRecompiler::RecompileResult vertex;
vertex.spirv = makeModule({.parameterOutput = true});
ShaderRecompiler::RecompileResult pixel;
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &pixel, 0}}};
const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
for (const auto noPerspective : {false, true}) {
pixel.spirv = makeModule({.fragment = true, .barycentric = true, .barycentricNoPerspective = noPerspective, .perVertex = true});
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, true);
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "fragmentShaderBarycentric");
}
pixel.spirv = makeModule({.fragment = true, .barycentric = true, .barycentricComponents = 4});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, true); }, "invalid barycentric built-in");
pixel.spirv = makeModule({.fragment = true, .barycentric = true, .perVertex = true, .perVertexLength = 2});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, true); }, "three vertices");
pixel.spirv = makeModule({.fragment = true, .perVertex = true});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, true); }, "PerVertexKHR requires");
vertex.spirv = makeModule({.barycentric = true});
pixel.spirv = makeModule({.fragment = true});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, true); }, "requires a fragment shader");
}
{
ShaderRecompiler::RecompileResult vertex;
vertex.spirv = makeModule({.vertexInput = true});
ShaderRecompiler::VertexAttribute attribute{0, 4, {{0x1000, 32u << 16u, 3, 77u << 12u}}, 0};
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}}};
const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup); }, "missing attribute metadata");
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "missing attribute metadata");
vertex.vertexAttributes.push_back(attribute);
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup);
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false);
vertex.vertexAttributes[0].components = 2;
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup); }, "metadata disagrees");
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "metadata disagrees");
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 2, 1) == 80, "incorrect strided vertex range");
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 3, 1); }, "record count");
attribute.fetchIndex = 1;
@ -779,17 +848,17 @@ void validationTests() {
VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
subgroup.supportedStages = VK_SHADER_STAGE_VERTEX_BIT;
subgroup.supportedOperations = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT;
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup);
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false);
subgroup.supportedStages = VK_SHADER_STAGE_FRAGMENT_BIT;
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup); }, "unsupported for shader stage");
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "unsupported for shader stage");
subgroup.supportedStages = VK_SHADER_STAGE_VERTEX_BIT;
subgroup.supportedOperations = capability == spv::CapabilityGroupNonUniform ? 0u : VK_SUBGROUP_FEATURE_BASIC_BIT;
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup); }, "device lacks operations");
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "device lacks operations");
}
const std::vector<std::uint32_t> words(8, 0);
const auto validate = [&](const ShaderRecompiler::RecompileResult& vertex, std::uint32_t fragmentOffset) {
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, fragmentOffset}}};
AgcDriver::Graphics::ValidateShaders(shaders, state, VkPhysicalDeviceSubgroupProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES});
AgcDriver::Graphics::ValidateShaders(shaders, state, VkPhysicalDeviceSubgroupProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}, false);
};
const auto pushed = [&](const ModuleShape& shape) {
ShaderRecompiler::RecompileResult vertex;