shader: fix auxiliary position exports

This commit is contained in:
nmzik
2026-08-22 02:58:35 +02:00
parent c130d74efc
commit a2baaccc6e
11 changed files with 532 additions and 10 deletions
@@ -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;
@@ -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) {
@@ -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);
@@ -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<InputBinding> inputs;
@@ -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) {
+65 -1
View File
@@ -11,6 +11,7 @@
#include "graphics/shader/shader.h"
#include <array>
#include <bit>
#include <memory>
#include <optional>
#include <string_view>
@@ -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;
@@ -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;
+4 -1
View File
@@ -783,7 +783,8 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
info = {};
info.export_count = static_cast<int>(sh.GetExportCount());
info.export_count = static_cast<int>(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];
+1
View File
@@ -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;
};
+17 -3
View File
@@ -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",
+248
View File
@@ -728,6 +728,136 @@ SpirvDecorationValueTargets(const std::vector<uint32_t> &binary,
return targets;
}
std::vector<uint32_t>
SpirvStoredBuiltInElements(const std::vector<uint32_t> &binary,
uint32_t builtin) {
struct Access {
uint32_t base = 0;
std::vector<uint32_t> indices;
};
std::unordered_map<uint32_t, uint32_t> direct_builtins;
std::unordered_map<uint64_t, uint32_t> member_builtins;
std::unordered_map<uint32_t, uint32_t> pointer_pointees;
std::unordered_map<uint32_t, uint32_t> variable_types;
std::unordered_map<uint32_t, uint32_t> constants;
std::unordered_map<uint32_t, Access> accesses;
std::vector<uint32_t> 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<uint64_t>(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<uint32_t>(binary.begin() + i + 4,
binary.begin() + i + count)};
} else if (opcode == 62u && count >= 3u) {
stores.push_back(binary[i + 1]);
}
i += count;
}
std::vector<uint32_t> 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<uint64_t>(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<uint32_t> &binary,
uint32_t builtin,
uint32_t constant) {
std::unordered_set<uint32_t> variables;
std::unordered_map<uint32_t, uint32_t> constants;
std::unordered_map<uint32_t, std::array<uint32_t, 2>> bitwise_ands;
std::unordered_map<uint32_t, uint32_t> 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<uint32_t> &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<const uint32_t> 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_t>({UINT32_MAX}),
"POS1-POS3 overwrote gl_Position");
Check(SpirvStoredBuiltInElements(all.spirv, 1u) ==
std::vector<uint32_t>({UINT32_MAX}) &&
SpirvStoredBuiltInElements(all.spirv, 9u) ==
std::vector<uint32_t>({UINT32_MAX}),
"MISC point-size/layer stores are missing");
Check(SpirvStoredBuiltInElements(all.spirv, 3u) ==
std::vector<uint32_t>({0u, 3u}) &&
SpirvStoredBuiltInElements(all.spirv, 4u) ==
std::vector<uint32_t>({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<uint32_t>({1u}) &&
SpirvStoredBuiltInElements(dense.spirv, 4u) ==
std::vector<uint32_t>({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<uint32_t>({0u}) &&
SpirvStoredBuiltInElements(shifted.spirv, 4u) ==
std::vector<uint32_t>({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();