From a2baaccc6ee3c3c5956fae512e966571009da1fc Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 22 Aug 2026 02:58:35 +0200 Subject: [PATCH] shader: fix auxiliary position exports --- .../presentation/window/vulkanWindow.cpp | 24 ++ .../backend/spirv/spirvEmitterAnalysis.cpp | 2 +- .../backend/spirv/spirvEmitterFlow.cpp | 47 ++++ .../backend/spirv/spirvEmitterInternal.h | 13 + .../backend/spirv/spirvEmitterModule.cpp | 65 +++++ src/graphics/shader/recompiler/ir/ShaderIR.h | 66 ++++- .../ir/passes/ShaderInfoCollection.cpp | 51 +++- src/graphics/shader/shader.cpp | 5 +- src/graphics/shader/shader.h | 1 + tests/ShaderRecompilerComputeTests.cpp | 20 +- tests/shaderCfgTests.cpp | 248 ++++++++++++++++++ 11 files changed, 532 insertions(+), 10 deletions(-) diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index c5e62e827..2b65278bf 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -276,6 +276,22 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa LOGF("depthBiasClamp is not supported\n"); skip_device = true; } + if (device_features2.features.shaderClipDistance != VK_TRUE) { + LOGF("shaderClipDistance is not supported\n"); + skip_device = true; + } + if (device_features2.features.shaderCullDistance != VK_TRUE) { + LOGF("shaderCullDistance is not supported\n"); + skip_device = true; + } + if (device_features2.features.largePoints != VK_TRUE) { + LOGF("largePoints is not supported\n"); + skip_device = true; + } + if (features12.shaderOutputLayer != VK_TRUE) { + LOGF("shaderOutputLayer is not supported\n"); + skip_device = true; + } if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) { LOGF("fragmentStoresAndAtomics is not supported\n"); @@ -570,10 +586,15 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V supported_features13.synchronization2 != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE); + EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE); + EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE); + EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE); + EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE); #if !defined(__APPLE__) EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE); #endif features12.timelineSemaphore = VK_TRUE; + features12.shaderOutputLayer = VK_TRUE; if (Config::GpuAssistedValidationEnabled()) { if (supported_features12.bufferDeviceAddress != VK_TRUE) { @@ -596,6 +617,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V device_features.tessellationShader = VK_TRUE; device_features.sampleRateShading = VK_TRUE; device_features.depthBiasClamp = VK_TRUE; + device_features.shaderClipDistance = VK_TRUE; + device_features.shaderCullDistance = VK_TRUE; + device_features.largePoints = VK_TRUE; graphics.sample_rate_shading_enabled = true; device_features.vertexPipelineStoresAndAtomics = supported_features2.features.vertexPipelineStoresAndAtomics; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index 026ecaa20..7bd2a92ad 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -64,7 +64,7 @@ void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program } uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) { - if (exp.kind == IR::ExportTargetKind::Position) { + if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) { return state.per_vertex_variable; } if (exp.kind == IR::ExportTargetKind::MrtZ) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index e03ae48d7..df313cd6e 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -367,6 +367,49 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo return vector; } +void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp) { + auto& state = ctx.state; + for (uint32_t component = 0; component < 4; component++) { + if ((exp.en & (1u << component)) == 0) { + continue; + } + const auto output = IR::DecodePositionExportComponent( + state.input_info.vertex->pa_cl_vs_out_cntl, exp.index, component); + if (output.layer) { + const auto raw = ExportRawComponent(ctx, data, component); + const auto layer = state.builder.AllocateId(); + state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), layer, raw, + ConstantU32(state, 0x7ffu)}); + state.builder.AddFunction({OpStore, state.layer_variable, layer}); + continue; + } + if (!output.point_size && output.clip_distance == UINT32_MAX && + output.cull_distance == UINT32_MAX) { + continue; + } + + const auto raw = ExportRawComponent(ctx, data, component); + const auto f32 = state.builder.AllocateId(); + state.builder.AddFunction({OpBitcast, TypeF32(state), f32, raw}); + if (output.point_size) { + state.builder.AddFunction({OpStore, state.point_size_variable, f32}); + continue; + } + auto StoreDistance = [&](uint32_t variable, uint32_t index) { + if (index == UINT32_MAX) { + return; + } + const auto pointer = state.builder.AllocateId(); + state.builder.AddFunction( + {OpAccessChain, TypePointer(state, StorageClassOutput, TypeF32(state)), pointer, + variable, ConstantU32(state, index)}); + state.builder.AddFunction({OpStore, pointer, f32}); + }; + StoreDistance(state.clip_distance_variable, output.clip_distance); + StoreDistance(state.cull_distance_variable, output.cull_distance); + } +} + void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) { auto& state = ctx.state; const auto& exp = ctx.Export(inst); @@ -384,6 +427,10 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) { } EmitIfCondition(state, exec, [&]() { const auto data = ctx.Arg(inst, 0); + if (exp.kind == IR::ExportTargetKind::Position && exp.index != 0) { + EmitAuxPositionExport(ctx, data, exp); + return; + } if (exp.kind == IR::ExportTargetKind::MrtZ) { if ((exp.en & 1u) != 0u && state.depth_variable != 0) { const auto raw = ExportRawComponent(ctx, data, 0); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index e33106b2e..dfece133c 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -39,6 +39,8 @@ enum : uint32_t { MemoryModelGLSL450 = 1, CapabilityShader = 1, CapabilityImageGatherExtended = 25, + CapabilityClipDistance = 32, + CapabilityCullDistance = 33, CapabilitySampled1D = 43, CapabilityImage1D = 44, CapabilityImageQuery = 50, @@ -47,6 +49,7 @@ enum : uint32_t { CapabilityGroupNonUniformBallot = 64, CapabilityGroupNonUniformShuffle = 65, CapabilitySignedZeroInfNanPreserve = 4466, + CapabilityShaderViewportIndexLayerEXT = 5254, CapabilityFragmentBarycentricKHR = 5284, CapabilityComputeDerivativeGroupQuadsKHR = 5288, StorageClassUniformConstant = 0, @@ -77,6 +80,10 @@ enum : uint32_t { enum : uint32_t { BuiltInPosition = 0, + BuiltInPointSize = 1, + BuiltInClipDistance = 3, + BuiltInCullDistance = 4, + BuiltInLayer = 9, BuiltInFragCoord = 15, BuiltInFrontFacing = 17, BuiltInSampleMask = 20, @@ -359,6 +366,12 @@ struct EmitterState { uint32_t pixel_valid_mask_variable = 0; uint32_t subgroup_local_invocation_id_variable = 0; uint32_t per_vertex_variable = 0; + uint32_t point_size_variable = 0; + uint32_t clip_distance_variable = 0; + uint32_t cull_distance_variable = 0; + uint32_t layer_variable = 0; + uint32_t clip_distance_count = 0; + uint32_t cull_distance_count = 0; uint32_t depth_variable = 0; uint32_t sample_mask_variable = 0; std::vector inputs; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 6f1e1f17e..263179d88 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -130,6 +130,10 @@ uint32_t SampleMaskArrayType(EmitterState& state) { return state.builder.Type(OpTypeArray, {TypeI32(state), ConstantU32(state, 1)}); } +uint32_t F32ArrayType(EmitterState& state, uint32_t count) { + return state.builder.Type(OpTypeArray, {TypeF32(state), ConstantU32(state, count)}); +} + void DefineDescriptorVariables(EmitterState& state) { if (DescriptorBinding(state, IR::DescriptorBindingKind::Buffers) != nullptr) { const auto count = @@ -420,6 +424,23 @@ void AllocateOutputVariables(EmitterState& state) { binding.variable_id = AllocateSharedOutputVariable(state, state.per_vertex_variable); break; + case IR::StageOutputKind::PointSize: + binding.variable_id = + AllocateSharedOutputVariable(state, state.point_size_variable); + break; + case IR::StageOutputKind::ClipDistance: + binding.variable_id = + AllocateSharedOutputVariable(state, state.clip_distance_variable); + state.clip_distance_count = std::max(state.clip_distance_count, binding.index + 1); + break; + case IR::StageOutputKind::CullDistance: + binding.variable_id = + AllocateSharedOutputVariable(state, state.cull_distance_variable); + state.cull_distance_count = std::max(state.cull_distance_count, binding.index + 1); + break; + case IR::StageOutputKind::Layer: + binding.variable_id = AllocateSharedOutputVariable(state, state.layer_variable); + break; case IR::StageOutputKind::Depth: binding.variable_id = AllocateSharedOutputVariable(state, state.depth_variable); break; @@ -495,6 +516,16 @@ void AddOutputAnnotationsAndNames(EmitterState& state) { state.builder.AddName(PerVertexType(state), "gl_PerVertex"); state.builder.AddName(state.per_vertex_variable, "outPerVertex"); } + auto AddBuiltIn = [&](uint32_t variable, const char* name, uint32_t builtin) { + if (variable != 0) { + state.builder.AddName(variable, name); + state.builder.AddAnnotation({OpDecorate, variable, DecorationBuiltIn, builtin}); + } + }; + AddBuiltIn(state.point_size_variable, "gl_PointSize", BuiltInPointSize); + AddBuiltIn(state.clip_distance_variable, "gl_ClipDistance", BuiltInClipDistance); + AddBuiltIn(state.cull_distance_variable, "gl_CullDistance", BuiltInCullDistance); + AddBuiltIn(state.layer_variable, "gl_Layer", BuiltInLayer); if (state.depth_variable != 0) { state.builder.AddName(state.depth_variable, "gl_FragDepth"); state.builder.AddAnnotation( @@ -613,6 +644,16 @@ void DefineModule(EmitterState& state) { state.builder.RequireCapability(CapabilityShader); state.builder.RequireCapability(CapabilitySignedZeroInfNanPreserve); + if (state.clip_distance_variable != 0) { + state.builder.RequireCapability(CapabilityClipDistance); + } + if (state.cull_distance_variable != 0) { + state.builder.RequireCapability(CapabilityCullDistance); + } + if (state.layer_variable != 0) { + state.builder.RequireCapability(CapabilityShaderViewportIndexLayerEXT); + state.builder.RequireExtension("SPV_EXT_shader_viewport_index_layer"); + } if (state.requirements.image_gather_extended) { state.builder.RequireCapability(CapabilityImageGatherExtended); } @@ -733,6 +774,30 @@ void DefineModule(EmitterState& state) { state.per_vertex_variable, TypePointer(state, StorageClassOutput, PerVertexType(state)), StorageClassOutput); } + if (state.point_size_variable != 0) { + state.builder.DefineGlobalVariable( + state.point_size_variable, TypePointer(state, StorageClassOutput, TypeF32(state)), + StorageClassOutput); + } + if (state.clip_distance_variable != 0) { + state.builder.DefineGlobalVariable( + state.clip_distance_variable, + TypePointer(state, StorageClassOutput, + F32ArrayType(state, state.clip_distance_count)), + StorageClassOutput); + } + if (state.cull_distance_variable != 0) { + state.builder.DefineGlobalVariable( + state.cull_distance_variable, + TypePointer(state, StorageClassOutput, + F32ArrayType(state, state.cull_distance_count)), + StorageClassOutput); + } + if (state.layer_variable != 0) { + state.builder.DefineGlobalVariable( + state.layer_variable, TypePointer(state, StorageClassOutput, TypeU32(state)), + StorageClassOutput); + } for (const auto& binding: state.outputs) { if (binding.kind == IR::StageOutputKind::Parameter || binding.kind == IR::StageOutputKind::Mrt) { diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 01b754b20..8f2312766 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -11,6 +11,7 @@ #include "graphics/shader/shader.h" #include +#include #include #include #include @@ -282,7 +283,70 @@ enum class StageInputKind { Parameter, }; -enum class StageOutputKind { Position, Parameter, Mrt, Depth, SampleMask }; +enum class StageOutputKind { + Position, + Parameter, + Mrt, + Depth, + SampleMask, + PointSize, + ClipDistance, + CullDistance, + Layer +}; + +struct PositionExportComponent { + uint32_t clip_distance = UINT32_MAX; + uint32_t cull_distance = UINT32_MAX; + bool valid = false; + bool point_size = false; + bool layer = false; + bool viewport = false; +}; + +inline PositionExportComponent DecodePositionExportComponent(uint32_t control, + uint32_t pos_index, + uint32_t component) { + PositionExportComponent result; + if (pos_index == 0 || component >= 4) { + return result; + } + + uint32_t slot = pos_index - 1; + uint32_t vector = 3; + for (uint32_t i = 0; i < 3; i++) { + if ((control & (1u << (21u + i))) != 0) { + if (slot == 0) { + vector = i; + break; + } + slot--; + } + } + if (vector == 3) { + return result; + } + + result.valid = true; + if (vector == 0) { + result.point_size = component == 0 && (control & (1u << 16u)) != 0; + result.layer = component == 2 && (control & (1u << 18u)) != 0; + result.viewport = component == 2 && (control & (1u << 19u)) != 0; + return result; + } + + const auto scalar = (vector - 1) * 4 + component; + const auto lower = (1u << scalar) - 1u; + const auto clip = control & 0xffu; + const auto cull = (control >> 8u) & 0xffu; + if ((clip & (1u << scalar)) != 0) { + result.clip_distance = std::popcount(clip & lower); + } + if ((cull & (1u << scalar)) != 0) { + result.cull_distance = std::popcount(cull & lower); + } + return result; +} struct StageInput { StageInputKind kind = StageInputKind::VertexIndex; diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index e8a805328..7224a3070 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -269,7 +269,14 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { } } -void CollectOutputs(const Program& program, const ShaderPixelInputInfo* pixel, ShaderInfo& info) { +bool CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex, + const ShaderPixelInputInfo* pixel, ShaderInfo& info, std::string* error) { + auto Fail = [&](std::string message) { + if (error != nullptr) { + *error = std::move(message); + } + return false; + }; for (const auto* block: program.values->blocks) { for (const auto& inst: *block) { if (inst.GetOpcode() != ValueOpcode::SetAttribute) { @@ -292,8 +299,41 @@ void CollectOutputs(const Program& program, const ShaderPixelInputInfo* pixel, S } switch (export_info.kind) { case ExportTargetKind::Position: - AddOutput(info, StageOutputKind::Position, export_info.index, 0, - "out_position"); + if (export_info.index == 0) { + AddOutput(info, StageOutputKind::Position, 0, 0, "out_position"); + break; + } + if (export_info.compr) { + return Fail("compressed auxiliary position export is unsupported"); + } + for (uint32_t component = 0; component < 4; component++) { + if ((export_info.en & (1u << component)) == 0) { + continue; + } + const auto output = DecodePositionExportComponent( + vertex->pa_cl_vs_out_cntl, export_info.index, component); + if (!output.valid) { + return Fail(fmt::format("unsupported POS{} auxiliary/stereo export", + export_info.index)); + } + if (output.viewport) { + return Fail("vertex viewport-index export is unsupported"); + } + if (output.point_size) { + AddOutput(info, StageOutputKind::PointSize, 0, 0, "gl_PointSize"); + } + if (output.layer) { + AddOutput(info, StageOutputKind::Layer, 0, 0, "gl_Layer"); + } + if (output.clip_distance != UINT32_MAX) { + AddOutput(info, StageOutputKind::ClipDistance, output.clip_distance, 0, + "gl_ClipDistance"); + } + if (output.cull_distance != UINT32_MAX) { + AddOutput(info, StageOutputKind::CullDistance, output.cull_distance, 0, + "gl_CullDistance"); + } + } break; case ExportTargetKind::Parameter: AddOutput(info, StageOutputKind::Parameter, export_info.index, @@ -307,6 +347,7 @@ void CollectOutputs(const Program& program, const ShaderPixelInputInfo* pixel, S } } } + return true; } } // namespace @@ -348,7 +389,9 @@ bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std:: default: return false; } CollectBuiltinInputs(program, next); - CollectOutputs(program, options.pixel, next); + if (!CollectOutputs(program, options.vertex, options.pixel, next, error)) { + return false; + } program.info = std::move(next); program.shader_info_complete = true; return true; diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 5fe8bebcf..92c50d136 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -783,7 +783,8 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs, info = {}; - info.export_count = static_cast(sh.GetExportCount()); + info.export_count = static_cast(sh.GetExportCount()); + info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl; EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0 || regs.gs_regs.chksum == 0); @@ -1452,6 +1453,7 @@ bool ShaderCompileSpirvVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegi KYTY_PROFILER_FUNCTION(profiler::colors::Amber300); EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0 || regs.gs_regs.chksum == 0); + input_info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl; const uint64_t shader_addr = regs.es_regs.data_addr; const auto code = ShaderGetMappedCode(shader_addr, "ShaderRecompiler VS", regs.gs_regs.chksum); @@ -1619,6 +1621,7 @@ ShaderId ShaderGetIdVS(const HW::VertexShaderInfo& regs, const ShaderVertexInput ret.ids.push_back(input_info.resources_num); ret.ids.push_back(input_info.export_count); ret.ids.push_back(input_info.scratch_size_dwords); + ret.ids.push_back(input_info.pa_cl_vs_out_cntl); for (int i = 0; i < input_info.resources_num; i++) { const auto& r = input_info.resources[i]; diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 7314c1dd8..d715c9d13 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -95,6 +95,7 @@ struct ShaderVertexInputInfo { int export_count = 0; uint32_t scratch_size_dwords = 0; uint32_t param_export_mask = 0; + uint32_t pa_cl_vs_out_cntl = 0; bool fetch_external = false; bool fetch_embedded = false; }; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 08a2b03b7..06a45edd1 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10624,6 +10624,15 @@ private: if ((queues[i].queueFlags & (vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) == (vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) { + vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{}; + barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR; + vk::PhysicalDeviceFeatures2 features{}; + features.sType = vk::StructureType::ePhysicalDeviceFeatures2; + features.pNext = &barycentric; + physical.getFeatures2(&features); + if (barycentric.fragmentShaderBarycentric != true) { + continue; + } m_physical_device = physical; m_queue_family = i; break; @@ -10634,7 +10643,7 @@ private: } } Require("VulkanHarness", "dispatch", m_physical_device != nullptr, - "no Vulkan graphics+compute queue family"); + "no Vulkan graphics+compute device with fragment barycentrics"); m_physical_device.getMemoryProperties(&m_memory_properties); vk::PhysicalDeviceFeatures available_features{}; @@ -10681,10 +10690,14 @@ private: device_features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features; device_features12.timelineSemaphore = true; + vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{}; + barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR; + barycentric.pNext = &device_features12; + barycentric.fragmentShaderBarycentric = true; vk::PhysicalDeviceVulkan13Features device_features13{}; device_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features; - device_features13.pNext = &device_features12; + device_features13.pNext = &barycentric; device_features13.dynamicRendering = true; device_features13.synchronization2 = true; device_info.pNext = &device_features13; @@ -10693,7 +10706,8 @@ private: device_features.sampleRateShading = true; device_info.pEnabledFeatures = &device_features; constexpr const char *device_extensions[] = { - VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME}; + VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, + VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME}; device_info.enabledExtensionCount = std::size(device_extensions); device_info.ppEnabledExtensionNames = device_extensions; RequireVk("VulkanHarness", "dispatch", diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 09f164ce1..af048ab6c 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -728,6 +728,136 @@ SpirvDecorationValueTargets(const std::vector &binary, return targets; } +std::vector +SpirvStoredBuiltInElements(const std::vector &binary, + uint32_t builtin) { + struct Access { + uint32_t base = 0; + std::vector indices; + }; + std::unordered_map direct_builtins; + std::unordered_map member_builtins; + std::unordered_map pointer_pointees; + std::unordered_map variable_types; + std::unordered_map constants; + std::unordered_map accesses; + std::vector stores; + + for (size_t i = 5; i < binary.size();) { + const uint32_t opcode = binary[i] & 0xffffu; + const uint32_t count = binary[i] >> 16u; + if (count == 0 || i + count > binary.size()) { + return {}; + } + if (opcode == 71u && count >= 4u && binary[i + 2] == 11u) { + direct_builtins[binary[i + 1]] = binary[i + 3]; + } else if (opcode == 72u && count >= 5u && binary[i + 3] == 11u) { + const uint64_t key = (static_cast(binary[i + 1]) << 32u) | + binary[i + 2]; + member_builtins[key] = binary[i + 4]; + } else if (opcode == 32u && count == 4u) { + pointer_pointees[binary[i + 1]] = binary[i + 3]; + } else if (opcode == 43u && count >= 4u) { + constants[binary[i + 2]] = binary[i + 3]; + } else if (opcode == 59u && count >= 4u) { + variable_types[binary[i + 2]] = binary[i + 1]; + } else if (opcode == 65u && count >= 5u) { + accesses[binary[i + 2]] = + {binary[i + 3], std::vector(binary.begin() + i + 4, + binary.begin() + i + count)}; + } else if (opcode == 62u && count >= 3u) { + stores.push_back(binary[i + 1]); + } + i += count; + } + + std::vector elements; + for (const auto pointer : stores) { + if (const auto decorated = direct_builtins.find(pointer); + decorated != direct_builtins.end()) { + if (decorated->second == builtin) { + elements.push_back(UINT32_MAX); + } + continue; + } + const auto access = accesses.find(pointer); + if (access == accesses.end()) { + continue; + } + if (const auto decorated = direct_builtins.find(access->second.base); + decorated != direct_builtins.end()) { + if (decorated->second == builtin && !access->second.indices.empty()) { + const auto index = constants.find(access->second.indices.back()); + if (index != constants.end()) { + elements.push_back(index->second); + } + } + continue; + } + const auto variable = variable_types.find(access->second.base); + if (variable == variable_types.end() || access->second.indices.empty()) { + continue; + } + const auto pointee = pointer_pointees.find(variable->second); + const auto member = constants.find(access->second.indices.front()); + if (pointee == pointer_pointees.end() || member == constants.end()) { + continue; + } + const uint64_t key = (static_cast(pointee->second) << 32u) | + member->second; + if (const auto decorated = member_builtins.find(key); + decorated != member_builtins.end() && decorated->second == builtin) { + elements.push_back(UINT32_MAX); + } + } + std::sort(elements.begin(), elements.end()); + return elements; +} + +bool SpirvBuiltInStoreUsesAndConstant(const std::vector &binary, + uint32_t builtin, + uint32_t constant) { + std::unordered_set variables; + std::unordered_map constants; + std::unordered_map> bitwise_ands; + std::unordered_map stores; + for (size_t i = 5; i < binary.size();) { + const uint32_t opcode = binary[i] & 0xffffu; + const uint32_t count = binary[i] >> 16u; + if (count == 0 || i + count > binary.size()) { + return false; + } + if (opcode == 71u && count >= 4u && binary[i + 2] == 11u && + binary[i + 3] == builtin) { + variables.insert(binary[i + 1]); + } else if (opcode == 43u && count >= 4u) { + constants[binary[i + 2]] = binary[i + 3]; + } else if (opcode == 199u && count == 5u) { + bitwise_ands[binary[i + 2]] = {binary[i + 3], binary[i + 4]}; + } else if (opcode == 62u && count >= 3u) { + stores[binary[i + 1]] = binary[i + 2]; + } + i += count; + } + for (const auto variable : variables) { + const auto store = stores.find(variable); + if (store == stores.end()) { + continue; + } + const auto value = bitwise_ands.find(store->second); + if (value == bitwise_ands.end()) { + continue; + } + for (const auto operand : value->second) { + const auto literal = constants.find(operand); + if (literal != constants.end() && literal->second == constant) { + return true; + } + } + } + return false; +} + bool SpirvTargetHasDecoration(const std::vector &binary, uint32_t target, uint32_t decoration) { for (size_t i = 5; i < binary.size();) { @@ -8605,6 +8735,123 @@ void TestNewShaderRecompilerExpVertexOutputs() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerAuxPositionExports() { + const auto compile = [](std::span shader, uint32_t control) { + ShaderVertexInputInfo vertex{}; + vertex.pa_cl_vs_out_cntl = control; + auto options = MakeCompileOptions(ShaderType::Vertex); + options.input_info.vertex = &vertex; + + ShaderRecompiler::CompileResult result; + std::string error; + Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), + error.c_str()); + return result; + }; + + const uint32_t all_outputs[] = { + EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3), + EncodeExp0(0x0d, 0x5, false), EncodeExp1(4, 5, 6, 7), + EncodeExp0(0x0e, 0xb, false), EncodeExp1(8, 9, 10, 11), + EncodeExp0(0x0f, 0x6), EncodeExp1(12, 13, 14, 15), + 0xbf810000u, + }; + const auto all = compile(all_outputs, 0x00e5aa55u); + CheckSpirvBinaryValidates(all.spirv); + Check(SpirvStoredBuiltInElements(all.spirv, 0u) == + std::vector({UINT32_MAX}), + "POS1-POS3 overwrote gl_Position"); + Check(SpirvStoredBuiltInElements(all.spirv, 1u) == + std::vector({UINT32_MAX}) && + SpirvStoredBuiltInElements(all.spirv, 9u) == + std::vector({UINT32_MAX}), + "MISC point-size/layer stores are missing"); + Check(SpirvStoredBuiltInElements(all.spirv, 3u) == + std::vector({0u, 3u}) && + SpirvStoredBuiltInElements(all.spirv, 4u) == + std::vector({0u, 1u, 2u}), + "partial clip/cull exports used the wrong dense elements"); + Check(SpirvHasDecorationValue(all.spirv, 11u, 1u) && + SpirvHasDecorationValue(all.spirv, 11u, 3u) && + SpirvHasDecorationValue(all.spirv, 11u, 4u) && + SpirvHasDecorationValue(all.spirv, 11u, 9u) && + SpirvContainsCapability(all.spirv, 32u) && + SpirvContainsCapability(all.spirv, 33u) && + SpirvContainsCapability(all.spirv, 5254u), + "auxiliary vertex BuiltIns or capabilities are missing"); + const auto all_source = DisassembleSpirvBinary(all.spirv); + Check(Common::ContainsStr(all_source, "SPV_EXT_shader_viewport_index_layer") && + SpirvBuiltInStoreUsesAndConstant(all.spirv, 9u, 0x7ffu), + "layer export extension or GFX10 layer mask is missing"); + const auto positions = std::count_if( + all.program.info.outputs.begin(), all.program.info.outputs.end(), + [](const auto &output) { + return output.kind == ShaderRecompiler::IR::StageOutputKind::Position; + }); + Check(positions == 1 && + std::ranges::find_if(all.program.info.outputs, [](const auto &output) { + return output.kind == ShaderRecompiler::IR::StageOutputKind::Position; + })->index == 0, + "shader info did not reserve Position exclusively for POS0"); + + const uint32_t cc1_only[] = { + EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3), + EncodeExp0(0x0d, 0x6), EncodeExp1(4, 5, 6, 7), + 0xbf810000u, + }; + const auto dense = compile(cc1_only, 0x0080a050u); + CheckSpirvBinaryValidates(dense.spirv); + Check(SpirvStoredBuiltInElements(dense.spirv, 0u).size() == 1u && + SpirvStoredBuiltInElements(dense.spirv, 3u) == + std::vector({1u}) && + SpirvStoredBuiltInElements(dense.spirv, 4u) == + std::vector({0u}), + "CCDIST1 was not densely packed into POS1"); + + const uint32_t middle_hole[] = { + EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3), + EncodeExp0(0x0d, 0x1, false), EncodeExp1(4, 5, 6, 7), + EncodeExp0(0x0e, 0x3), EncodeExp1(8, 9, 10, 11), + 0xbf810000u, + }; + const auto shifted = compile(middle_hole, 0x00a12010u); + CheckSpirvBinaryValidates(shifted.spirv); + Check(SpirvStoredBuiltInElements(shifted.spirv, 1u).size() == 1u && + SpirvStoredBuiltInElements(shifted.spirv, 3u) == + std::vector({0u}) && + SpirvStoredBuiltInElements(shifted.spirv, 4u) == + std::vector({0u}), + "CCDIST1 did not shift across a disabled CCDIST0 vector"); + + const auto rejects = [](uint32_t control, uint32_t en, const char *message) { + const uint32_t shader[] = { + EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3), + EncodeExp0(0x0d, en), EncodeExp1(4, 5, 6, 7), + 0xbf810000u, + }; + ShaderVertexInputInfo vertex{}; + vertex.pa_cl_vs_out_cntl = control; + auto options = MakeCompileOptions(ShaderType::Vertex); + options.input_info.vertex = &vertex; + ShaderRecompiler::CompileResult result; + std::string error; + Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) && + Common::ContainsStr(error, message), + "unsupported auxiliary position export did not fail explicitly"); + }; + rejects(0x00280000u, 0x4u, "viewport-index"); + rejects(0u, 0x1u, "auxiliary/stereo"); + + HW::VertexShaderInfo regs{}; + regs.es_regs.data_addr = 1; + regs.gs_regs.chksum = 1; + ShaderVertexInputInfo key0{}; + ShaderVertexInputInfo key1{}; + key1.pa_cl_vs_out_cntl = 1; + Check(ShaderGetIdVS(regs, key0, false) != ShaderGetIdVS(regs, key1, false), + "PA_CL_VS_OUT_CNTL is absent from the vertex shader cache key"); +} + void TestDemandDrivenSpirvDeclarations() { using ShaderRecompiler::Spirv::Builder; @@ -11661,6 +11908,7 @@ int main() { TestNewShaderRecompilerSpirvSizeBaselines(); TestDemandDrivenSpirvDeclarations(); TestNewShaderRecompilerSMovB32(); + TestNewShaderRecompilerAuxPositionExports(); TestNewShaderRecompilerNativeWideScalarMemoryIr(); TestNewShaderRecompilerNativeWideBufferIr(); TestNewShaderRecompilerScalarB64LaneLowering();