shader: collect usage once and emit descriptor slots directly

This commit is contained in:
nmzik
2026-10-09 02:17:38 +04:00
parent b35cdc39b7
commit e1ac893a0c
28 changed files with 1068 additions and 1408 deletions
@@ -1,5 +1,6 @@
#include "common/assert.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/BufferFormat.h"
#include <fmt/format.h>
#include <map>
@@ -202,6 +203,16 @@ Value ResolveActiveU32(Value value, Value active) {
return {};
}
uint32_t StorageBufferElementBits(const Program& program, const MemoryInfo& memory) {
if (!memory.formatted) return memory.data_bits;
const auto format = memory.typed
? Format::DecodeTBufferFormat(memory.data_format, memory.number_format)
: program.info.buffers[memory.resource].descriptor_format;
const auto info = Format::GetFormatInfo(format);
return info.type == Format::ComponentType::Unknown || info.packed_bitfield
? 32u : info.component_bits[0];
}
bool HasShaderMemoryWrites(const Program& program) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
+40 -21
View File
@@ -104,6 +104,7 @@ struct BufferResource {
static constexpr uint32_t NoIndirectBuffer = UINT32_MAX;
uint32_t source = 0;
uint32_t descriptor_index = UINT32_MAX;
uint32_t first_use_pc = 0;
uint32_t max_byte_extent = 0;
uint32_t packed_stride = 0;
@@ -131,6 +132,7 @@ struct ImageResource {
static constexpr uint32_t NoIndirectImage = UINT32_MAX;
uint32_t source = 0;
uint32_t descriptor_index = UINT32_MAX;
uint32_t first_use_pc = 0;
ImageResourceClass resource_class = ImageResourceClass::None;
Prospero::TextureNumericClass numeric_class = Prospero::TextureNumericClass::Unsupported;
@@ -424,23 +426,16 @@ DescriptorBindingForImage(const ImageResource& image) {
return static_cast<DescriptorBindingKind>(base + dimension);
}
struct DescriptorBinding {
DescriptorBindingKind kind = DescriptorBindingKind::Buffers;
std::vector<uint32_t> resources;
bool operator==(const DescriptorBinding& other) const = default;
};
struct BindingLayout {
uint32_t push_data_start_dword = PushData::NoStart;
uint32_t dispatch_thread_dword = PushData::NoStart;
uint32_t memory_offset_dword = 0;
uint32_t memory_offset_count = 0;
std::vector<uint32_t> user_data_registers;
std::vector<DescriptorBinding> descriptors;
struct Bindings {
std::array<uint32_t, static_cast<size_t>(DescriptorBindingKind::Count)> descriptor_counts {};
uint64_t descriptor_mask = 0;
uint32_t push_data_start_dword = PushData::NoStart;
uint32_t dispatch_thread_dword = PushData::NoStart;
uint32_t memory_offset_dword = 0;
[[nodiscard]] uint32_t ShaderDataDwords() const {
return memory_offset_dword + (memory_offset_count + 3u) / 4u;
return memory_offset_dword +
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
}
[[nodiscard]] bool UsesPushData() const {
return push_data_start_dword != PushData::NoStart;
@@ -451,7 +446,7 @@ struct BindingLayout {
}
}
bool operator==(const BindingLayout& other) const = default;
bool operator==(const Bindings& other) const = default;
};
struct ShaderInfo {
@@ -466,11 +461,34 @@ struct ShaderInfo {
std::vector<SampledResourcePair> sampled_pairs;
std::vector<StageInput> inputs;
std::vector<StageOutput> outputs;
std::vector<uint32_t> user_data_registers;
uint64_t live_buffers = 0;
std::array<uint8_t, 32> vertex_fetch_components {};
int32_t vertex_offset_sgpr = -1;
int32_t instance_offset_sgpr = -1;
bool has_bitwise_xor = false;
bool uses_dma = false;
bool has_bitwise_xor = false;
bool uses_dma = false;
bool uses_swizzle = false;
bool uses_lds = false;
bool uses_gds = false;
bool uses_flattened_srt = false;
bool uses_dispatch_threads = false;
bool bvh = false;
bool subgroup_ballot = false;
bool subgroup_barrier = false;
bool subgroup_shuffle = false;
bool subgroup_local_invocation_id = false;
bool compute_derivatives = false;
bool image_gather_extended = false;
bool function_lds = false;
bool function_scratch = false;
bool pixel_valid_mask = false;
bool buffer_int64_atomics = false;
bool buffer_u8 = false;
bool buffer_u16 = false;
bool shared_int64_atomics = false;
bool coherent_buffers = false;
bool float64 = false;
bool operator==(const ShaderInfo& other) const = default;
};
@@ -542,7 +560,7 @@ struct CompiledShaderInfo {
uint32_t param_export_mask = 0;
bool has_address_writes = false;
ShaderInfo info;
BindingLayout bindings;
Bindings bindings;
};
struct UniformFillPlan {
@@ -622,11 +640,12 @@ struct Program: ResourcePlan {
std::vector<ExportInfo> export_info;
bool has_address_writes = false;
bool shader_info_complete = false;
BindingLayout bindings;
bool binding_layout_complete = false;
Bindings bindings;
};
uint32_t StorageBufferElementBits(const Program& program, const MemoryInfo& memory);
std::string ProgramToString(const Program& program);
bool HasShaderMemoryWrites(const Program& program);
@@ -1,197 +0,0 @@
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "common/assert.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <algorithm>
#include <array>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
[[noreturn]] void BindingFail(const char* message) {
EXIT("shader binding layout failed: %s", message);
std::abort();
}
void CollectShaderData(const Program& program, BindingLayout& layout) {
std::array<bool, NumScalarRegs> registers {};
bool uses_dispatch_threads = false;
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (!inst.HasUses()) {
continue;
}
uses_dispatch_threads |= inst.GetOpcode() == ValueOpcode::GetDispatchThreadExtent;
if (inst.GetOpcode() != ValueOpcode::GetUserData) {
continue;
}
if (inst.Arg(0).GetType() != Type::ScalarReg) {
BindingFail("typed shader contains an invalid user-data register");
}
const auto index = RegIndex(inst.Arg(0).ScalarRegister());
if (index >= NumScalarRegs) {
BindingFail("typed shader contains an invalid user-data register");
}
registers[index] = true;
}
}
for (uint32_t index = 0; index < registers.size(); index++) {
if (registers[index]) {
layout.user_data_registers.push_back(index);
}
}
layout.memory_offset_dword = static_cast<uint32_t>(layout.user_data_registers.size());
if (uses_dispatch_threads) {
layout.dispatch_thread_dword = layout.memory_offset_dword;
layout.memory_offset_dword += 3u;
}
}
void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
std::vector<uint32_t> resources = {}) {
layout.descriptors.push_back({kind, std::move(resources)});
}
} // namespace
SharedMemoryResources CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers) {
std::array<bool, ShaderInfo::MaxBuffers> live_buffers {};
SharedMemoryResources shared;
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto op = inst.GetOpcode();
if (BufferAccessOf(op) == BufferAccess::None &&
SharedAccessOf(op) == SharedAccess::None &&
AddressOpcodeInfoOf(op).access == AddressAccess::None) {
continue;
}
const auto index = inst.Flags<MemoryFlags>().index;
if (index >= program.memory_info.size()) {
BindingFail("typed shader contains invalid memory metadata");
}
const auto& memory = program.memory_info[index];
if (memory.planning_only) {
continue;
}
shared.lds |= memory.kind == ResourceKind::FlatLocal;
if (SharedAccessOf(op) != SharedAccess::None) {
if (memory.kind != ResourceKind::Lds && memory.kind != ResourceKind::Gds) {
BindingFail("typed shader contains invalid shared-memory metadata");
}
shared.gds |= memory.kind == ResourceKind::Gds;
shared.lds |= memory.kind == ResourceKind::Lds;
} else if (memory.kind == ResourceKind::Buffer || memory.kind == ResourceKind::ScalarBuffer) {
EXIT_IF(memory.resource >= program.info.buffers.size());
live_buffers.at(memory.resource) = true;
for (const auto child: program.info.buffers[memory.resource].indirect_resources) {
live_buffers.at(child) = true;
}
}
}
}
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
if (live_buffers.at(i)) {
buffers.push_back(i);
}
}
return shared;
}
bool UsesFlattenedSrt(const Program& program) {
const auto uses_mapping = [](const auto& resource) {
return resource.indirect_root != UINT32_MAX;
};
return std::ranges::any_of(program.blocks, [](const Block* block) {
return std::ranges::any_of(*block, [](const Inst& inst) {
return inst.GetOpcode() == ValueOpcode::ReadConst;
});
}) || std::ranges::any_of(program.info.buffers, uses_mapping) ||
std::ranges::any_of(program.info.images, uses_mapping);
}
void AllocateBindings(Program& program, uint32_t push_data_start_dword, bool lds_storage) {
if (!program.shader_info_complete || program.binding_layout_complete) {
EXIT("shader binding layout failed: %s", !program.shader_info_complete
? "shader info is not ready"
: "binding layout already allocated");
}
BindingLayout next;
std::vector<uint32_t> buffers;
const auto shared = CollectMemoryResources(program, buffers);
CollectShaderData(program, next);
next.memory_offset_count = static_cast<uint32_t>(buffers.size());
next.push_data_start_dword =
PushData::StartFor(push_data_start_dword, next.ShaderDataDwords());
if (!buffers.empty()) {
// Draw binding accesses this first group directly when memory_offset_count is nonzero.
AddBinding(next, DescriptorBindingKind::Buffers, std::move(buffers));
}
std::array<std::vector<uint32_t>, ImageBindingCount> image_groups;
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto kind = DescriptorBindingForImage(program.info.images[i]);
if (!kind.has_value()) {
EXIT("shader binding layout failed: image %u has an invalid binding class", i);
}
const auto group = ImageBindingIndex(*kind);
if (group >= image_groups.size()) {
EXIT("shader binding layout failed: image %u has an unmapped binding class", i);
}
auto& resources = image_groups[group];
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::Dynamic;
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i,
program.info.images[i].mip_count);
}
resources.insert(resources.end(), count, i);
}
for (uint32_t i = 0; i < image_groups.size(); i++) {
if (!image_groups[i].empty()) {
AddBinding(next, static_cast<DescriptorBindingKind>(FirstImageBinding + i),
std::move(image_groups[i]));
}
}
if (!program.info.samplers.empty()) {
std::vector<uint32_t> resources(program.info.samplers.size());
for (uint32_t i = 0; i < resources.size(); i++) {
resources[i] = i;
}
AddBinding(next, DescriptorBindingKind::Samplers, std::move(resources));
}
if (shared.gds) {
AddBinding(next, DescriptorBindingKind::Gds);
}
if (shared.lds && lds_storage) {
AddBinding(next, DescriptorBindingKind::SharedMemory);
}
if (program.info.uses_dma) {
AddBinding(next, DescriptorBindingKind::BdaPagetable);
AddBinding(next, DescriptorBindingKind::FaultBuffer);
}
if (UsesFlattenedSrt(program)) {
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
}
if (next.ShaderDataDwords() != 0 && !next.UsesPushData()) {
AddBinding(next, DescriptorBindingKind::ShaderData);
}
program.bindings = std::move(next);
program.binding_layout_complete = true;
}
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind) {
for (const auto& binding: layout.descriptors) {
if (binding.kind == kind) {
return &binding;
}
}
return nullptr;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,23 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_
#include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
void AllocateBindings(Program& program, uint32_t push_data_start_dword = 0,
bool lds_storage = false);
struct SharedMemoryResources {
bool lds = false;
bool gds = false;
};
SharedMemoryResources CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers);
bool UsesFlattenedSrt(const Program& program);
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_ */
@@ -1229,8 +1229,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
}
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete ||
program.binding_layout_complete);
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete);
EXIT_IF(program.info.buffers.size() > specialization.buffers.size() ||
program.info.images.size() > specialization.images.size());
@@ -75,128 +75,11 @@ void ValidateOptions(const Program& program, ShaderStageInputInfo input_info) {
}
}
void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_info) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case ValueOpcode::GetAttribute: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed attribute reference is not constant");
}
if ((program.stage == ShaderType::Vertex ||
program.stage == ShaderType::Local) &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >=
static_cast<uint32_t>(input_info.vertex->resources_num))) {
return Fail("vertex input reference is out of range");
}
if (program.stage == ShaderType::Pixel &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >= input_info.pixel->input_num)) {
return Fail("pixel input reference is out of range");
}
break;
}
case ValueOpcode::GetInterpolationParameter: {
if (program.stage != ShaderType::Pixel || !inst.Arg(0).IsImmediate() ||
inst.Arg(0).GetType() != Type::U32 || !inst.Arg(1).IsImmediate() ||
inst.Arg(1).GetType() != Type::U32 || !inst.Arg(2).IsImmediate() ||
inst.Arg(2).GetType() != Type::U32) {
return Fail("interpolation parameter reference is invalid");
}
if (inst.Arg(0).U32() >= input_info.pixel->input_num ||
inst.Arg(1).U32() >= 4u || inst.Arg(2).U32() >= 3u) {
return Fail("interpolation parameter reference is out of range");
}
break;
}
case ValueOpcode::GetBuiltin: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed builtin reference is not constant");
}
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
const auto component = inst.Arg(1).U32();
switch (kind) {
case StageInputKind::PackedAncillary:
return Fail("packed pixel ancillary input has an unsupported live use");
case StageInputKind::Layer:
case StageInputKind::SampleId:
if (program.stage != ShaderType::Pixel || component != 0u) {
return Fail("typed pixel scalar input is invalid");
}
break;
case StageInputKind::VertexIndex:
case StageInputKind::InvocationId:
case StageInputKind::PrimitiveId:
case StageInputKind::InstanceIndex:
case StageInputKind::FrontFacing:
case StageInputKind::LocalInvocationIndex:
if (component != 0u) {
return Fail("typed scalar builtin component is out of range");
}
break;
case StageInputKind::FragCoord:
if (component >= 4u) {
return Fail("typed fragment-coordinate component is out of range");
}
break;
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordSmoothSample:
case StageInputKind::BaryCoordSmoothCentroid:
case StageInputKind::BaryCoordNoPerspective:
if (component >= 2u) {
return Fail("typed barycentric component is out of range");
}
break;
case StageInputKind::TessCoord:
case StageInputKind::WorkgroupId:
case StageInputKind::LocalInvocationId:
case StageInputKind::GlobalInvocationId:
if (component >= 3u) {
return Fail("typed invocation builtin component is out of range");
}
break;
case StageInputKind::Parameter:
default: return Fail("typed builtin kind is invalid");
}
break;
}
case ValueOpcode::SetAttribute: {
const auto index = inst.Flags<ExportFlags>().index;
if (index >= program.export_info.size()) {
return Fail("typed export metadata index is out of range");
}
const auto& exp = program.export_info[index];
if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 &&
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::TessellationEvaluation) {
return Fail("auxiliary position export requires a vertex, mesh, or "
"tessellation evaluation shader");
}
break;
}
default: break;
}
}
}
}
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
ShaderInfo& info) {
void CollectVertexInputs(const ShaderVertexInputInfo* vertex, ShaderInfo& info,
const std::array<uint8_t, 32>& used_components) {
AddInput(info, StageInputKind::VertexIndex, 0, 1, "gl_VertexIndex");
AddInput(info, StageInputKind::InstanceIndex, 0, 1, "gl_InstanceIndex");
uint32_t used_components[ShaderVertexInputInfo::RES_MAX] = {};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
const auto attr = inst.Arg(0).U32();
const auto chan = inst.Arg(1).U32();
used_components[attr] = std::max(used_components[attr], chan + 1u);
}
}
}
for (uint32_t attr = 0; attr < static_cast<uint32_t>(vertex->resources_num); attr++) {
if (used_components[attr] != 0) {
AddInput(info, StageInputKind::Parameter, attr, used_components[attr],
@@ -205,28 +88,15 @@ void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* ve
}
}
void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixel,
ShaderInfo& info) {
void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo& info,
std::array<bool, 32>& per_vertex,
const std::array<uint8_t, 32>& used_components) {
if (pixel->HasPositionInput()) {
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
}
if (pixel->ps_front_face) {
AddInput(info, StageInputKind::FrontFacing, 0, 1, "gl_FrontFacing");
}
std::array<bool, 32> per_vertex {};
std::array<bool, 32> interpolated {};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
interpolated[inst.Arg(0).U32()] = true;
} else if (inst.GetOpcode() == ValueOpcode::GetInterpolationParameter) {
const auto input = inst.Arg(0).U32();
const auto mode = inst.Arg(2).U32();
per_vertex[input] =
per_vertex[input] || mode < 2u || !ShaderPixelParameterIsFlat(*pixel, input);
}
}
}
// Mixed interpolation modes share raw vertices at their guest export slot.
// Rectangle expansion alone supplies separate flat and smooth outputs.
for (uint32_t input = 0; input < pixel->input_num; input++) {
@@ -245,7 +115,7 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
per_vertex[input]);
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
if (interpolated[input] && per_vertex[input] &&
if (used_components[input] != 0 && per_vertex[input] &&
!ShaderPixelParameterIsFlat(*pixel, input)) {
const auto kind = pixel->ps_no_perspective ? StageInputKind::BaryCoordNoPerspective
: StageInputKind::BaryCoordSmooth;
@@ -271,147 +141,411 @@ void CollectComputeInputs(const ShaderComputeInputInfo* compute, ShaderInfo& inf
}
}
void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (program.stage == ShaderType::TessellationControl &&
inst.GetOpcode() == ValueOpcode::LaneId) {
AddInput(info, StageInputKind::InvocationId, 0, 1, "gl_InvocationID");
}
if (inst.GetOpcode() != ValueOpcode::GetBuiltin) {
continue;
}
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
switch (kind) {
case StageInputKind::VertexIndex:
AddInput(info, kind, 0, 1, "gl_VertexIndex");
break;
case StageInputKind::InstanceIndex:
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
break;
case StageInputKind::InvocationId:
AddInput(info, kind, 0, 1, "gl_InvocationID");
break;
case StageInputKind::PrimitiveId:
AddInput(info, kind, 0, 1, "gl_PrimitiveID");
break;
case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break;
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
case StageInputKind::FrontFacing:
AddInput(info, kind, 0, 1, "gl_FrontFacing");
break;
case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break;
case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break;
case StageInputKind::BaryCoordSmoothSample:
AddInput(info, StageInputKind::SampleId, 0, 1, "gl_SampleID");
[[fallthrough]];
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordSmoothCentroid:
AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR");
break;
case StageInputKind::BaryCoordNoPerspective:
AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR");
break;
case StageInputKind::WorkgroupId:
AddInput(info, kind, 0, 3, "gl_WorkGroupID");
break;
case StageInputKind::LocalInvocationId:
AddInput(info, kind, 0, 3, "gl_LocalInvocationID");
break;
case StageInputKind::LocalInvocationIndex:
AddInput(info, kind, 0, 1, "gl_LocalInvocationIndex");
break;
case StageInputKind::GlobalInvocationId:
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
break;
case StageInputKind::PackedAncillary:
case StageInputKind::Parameter: break;
}
}
void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
switch (kind) {
case StageInputKind::VertexIndex:
AddInput(info, kind, 0, 1, "gl_VertexIndex");
break;
case StageInputKind::InstanceIndex:
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
break;
case StageInputKind::InvocationId:
AddInput(info, kind, 0, 1, "gl_InvocationID");
break;
case StageInputKind::PrimitiveId:
AddInput(info, kind, 0, 1, "gl_PrimitiveID");
break;
case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break;
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
case StageInputKind::FrontFacing:
AddInput(info, kind, 0, 1, "gl_FrontFacing");
break;
case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break;
case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break;
case StageInputKind::BaryCoordSmoothSample:
AddInput(info, StageInputKind::SampleId, 0, 1, "gl_SampleID");
[[fallthrough]];
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordSmoothCentroid:
AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR");
break;
case StageInputKind::BaryCoordNoPerspective:
AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR");
break;
case StageInputKind::WorkgroupId:
AddInput(info, kind, 0, 3, "gl_WorkGroupID");
break;
case StageInputKind::LocalInvocationId:
AddInput(info, kind, 0, 3, "gl_LocalInvocationID");
break;
case StageInputKind::LocalInvocationIndex:
AddInput(info, kind, 0, 1, "gl_LocalInvocationIndex");
break;
case StageInputKind::GlobalInvocationId:
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
break;
case StageInputKind::PackedAncillary:
case StageInputKind::Parameter: break;
}
}
void CollectOutputs(const Program& program, ShaderStageInputInfo input_info, ShaderInfo& info) {
void CollectOutput(const Program& program, ShaderStageInputInfo input_info,
ShaderInfo& info, const Inst& inst) {
const bool alpha_remap = program.stage == ShaderType::Pixel && input_info.pixel != nullptr &&
input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None;
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != ValueOpcode::SetAttribute) {
continue;
const auto& export_info = program.export_info[inst.Flags<ExportFlags>().index];
if (export_info.kind == ExportTargetKind::MrtZ) {
if (program.stage == ShaderType::Pixel && (export_info.en & 0x1u) != 0 &&
input_info.pixel->ps_depth_export_enable) {
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
}
if (program.stage == ShaderType::Pixel && (export_info.en & 0x4u) != 0 &&
input_info.pixel->ps_sample_mask_export_enable) {
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
}
return;
}
if (export_info.en == 0) {
return;
}
switch (export_info.kind) {
case ExportTargetKind::Position:
if (export_info.index == 0) {
AddOutput(info, StageOutputKind::Position, 0, 0, "out_position");
break;
}
const auto& export_info = program.export_info[inst.Flags<ExportFlags>().index];
if (export_info.kind == ExportTargetKind::MrtZ) {
if (program.stage == ShaderType::Pixel && (export_info.en & 0x1u) != 0 &&
input_info.pixel->ps_depth_export_enable) {
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
}
if (program.stage == ShaderType::Pixel && (export_info.en & 0x4u) != 0 &&
input_info.pixel->ps_sample_mask_export_enable) {
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
}
continue;
if (export_info.compr) {
return Fail("compressed auxiliary position export is unsupported");
}
if (export_info.en == 0) {
continue;
}
switch (export_info.kind) {
case ExportTargetKind::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(
input_info.vertex->pa_cl_vs_out_cntl, export_info.index, component);
if (output.viewport) {
AddOutput(info, StageOutputKind::ViewportIndex, 0, 0, "gl_ViewportIndex");
}
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,
export_info.index, fmt::format("out_param_{}", export_info.index));
break;
case ExportTargetKind::Mrt: {
if (alpha_remap && export_info.index != 0) {
break;
}
const auto slot = input_info.pixel->dual_source_blending
? 0u : ShaderPixelExportTarget(input_info.pixel->target_shader_mask,
export_info.index);
if (slot >= 8) {
break;
}
AddOutput(info, StageOutputKind::Mrt, export_info.index, slot,
fmt::format("out_mrt_{}", export_info.index));
if (alpha_remap) {
AddOutput(info, StageOutputKind::Mrt, 1, 0, "out_mrt_1");
}
break;
for (uint32_t component = 0; component < 4; component++) {
if ((export_info.en & (1u << component)) == 0) {
continue;
}
const auto output = DecodePositionExportComponent(
input_info.vertex->pa_cl_vs_out_cntl, export_info.index, component);
if (output.viewport) {
AddOutput(info, StageOutputKind::ViewportIndex, 0, 0, "gl_ViewportIndex");
}
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,
export_info.index, fmt::format("out_param_{}", export_info.index));
break;
case ExportTargetKind::Mrt: {
if (alpha_remap && export_info.index != 0) {
break;
}
const auto slot = input_info.pixel->dual_source_blending
? 0u : ShaderPixelExportTarget(input_info.pixel->target_shader_mask,
export_info.index);
if (slot >= 8) {
break;
}
AddOutput(info, StageOutputKind::Mrt, export_info.index, slot,
fmt::format("out_mrt_{}", export_info.index));
if (alpha_remap) {
AddOutput(info, StageOutputKind::Mrt, 1, 0, "out_mrt_1");
}
break;
}
default: break;
}
}
struct InputUsage {
std::array<uint8_t, 32> components {};
std::array<bool, 32> per_vertex {};
std::array<bool, NumScalarRegs> user_data {};
std::array<StageInputKind, static_cast<size_t>(StageInputKind::Parameter)> builtins {};
uint32_t builtin_mask = 0;
uint32_t builtin_count = 0;
void AddBuiltin(StageInputKind kind) {
const auto bit = 1u << static_cast<uint32_t>(kind);
if ((builtin_mask & bit) == 0) {
builtin_mask |= bit;
builtins[builtin_count++] = kind;
}
}
};
void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs,
const Inst& inst) {
auto& info = program.info;
const auto op = inst.GetOpcode();
const auto buffer_access = BufferAccessOf(op);
const auto address_access = AddressOpcodeInfoOf(op).access;
const auto shared_access = SharedAccessOf(op);
info.float64 |= inst.GetType() == Type::F64;
info.buffer_int64_atomics |= buffer_access == BufferAccess::Atomic &&
inst.GetType() == Type::U64;
if (buffer_access != BufferAccess::None || address_access != AddressAccess::None ||
shared_access != SharedAccess::None) {
const auto index = inst.Flags<MemoryFlags>().index;
if (index >= program.memory_info.size()) {
return Fail("typed shader contains invalid memory metadata");
}
const auto& memory = program.memory_info[index];
const auto kind = memory.kind;
if (!memory.planning_only) {
info.uses_lds |= kind == ResourceKind::FlatLocal;
if (shared_access != SharedAccess::None) {
info.uses_lds |= kind == ResourceKind::Lds;
info.uses_gds |= kind == ResourceKind::Gds;
} else if (kind == ResourceKind::Buffer || kind == ResourceKind::ScalarBuffer) {
if (memory.resource >= info.buffers.size() || memory.resource >= ShaderInfo::MaxBuffers) {
return Fail("buffer operation has invalid resource metadata");
}
info.live_buffers |= uint64_t{1} << memory.resource;
for (const auto child: info.buffers[memory.resource].indirect_resources) {
if (child >= info.buffers.size()) {
return Fail("indirect buffer operation has invalid resource metadata");
}
info.live_buffers |= uint64_t{1} << child;
}
default: break;
}
}
if (address_access != AddressAccess::None) {
if (kind == ResourceKind::Scratch || kind == ResourceKind::FlatLocal) {
if (program.scratch_dwords == 0) {
return Fail("scratch operation has no per-thread storage");
}
info.function_scratch = true;
info.function_lds |= kind == ResourceKind::FlatLocal &&
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
} else if (address_access == AddressAccess::Write) {
return Fail("writable FLAT/GLOBAL addresses require GPU ownership tracking");
} else {
info.coherent_buffers |= memory.coherent;
}
}
if (buffer_access != BufferAccess::None) {
if (kind == ResourceKind::IndirectBuffer && op != ValueOpcode::ReadConstBuffer) {
info.subgroup_local_invocation_id = true;
}
if (kind == ResourceKind::Buffer) {
info.coherent_buffers |= memory.coherent;
if (memory.resource >= info.buffers.size()) {
return Fail("buffer operation has invalid resource metadata");
}
const auto bits = StorageBufferElementBits(program, memory);
info.buffer_u8 |= bits == 8u;
info.buffer_u16 |= bits == 16u;
if ((info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) {
if (program.stage != ShaderType::Compute) {
return Fail("buffer ADD_TID is only valid for compute shaders");
}
info.subgroup_local_invocation_id = true;
}
}
}
if (shared_access != SharedAccess::None) {
if (kind != ResourceKind::Lds && kind != ResourceKind::Gds) {
return Fail("shared operation has invalid resource kind");
}
if (shared_access == SharedAccess::Atomic && SharedComponentCount(op) == 2u) {
if (kind != ResourceKind::Lds || program.stage != ShaderType::Compute) {
return Fail("64-bit shared atomics require compute LDS");
}
info.shared_int64_atomics = true;
}
info.function_lds |= kind == ResourceKind::Lds &&
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
info.subgroup_ballot = true;
info.subgroup_shuffle = true;
info.subgroup_local_invocation_id = true;
}
}
}
switch (inst.GetOpcode()) {
case ValueOpcode::GetAttribute: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed attribute reference is not constant");
}
if ((program.stage == ShaderType::Vertex ||
program.stage == ShaderType::Local) &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >=
static_cast<uint32_t>(input_info.vertex->resources_num))) {
return Fail("vertex input reference is out of range");
}
if (program.stage == ShaderType::Pixel &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >= input_info.pixel->input_num)) {
return Fail("pixel input reference is out of range");
}
if (program.stage == ShaderType::Vertex || program.stage == ShaderType::Local ||
program.stage == ShaderType::Pixel) {
auto& components = inputs.components[inst.Arg(0).U32()];
components = std::max(components, static_cast<uint8_t>(inst.Arg(1).U32() + 1u));
}
break;
}
case ValueOpcode::GetInterpolationParameter: {
if (program.stage != ShaderType::Pixel || !inst.Arg(0).IsImmediate() ||
inst.Arg(0).GetType() != Type::U32 || !inst.Arg(1).IsImmediate() ||
inst.Arg(1).GetType() != Type::U32 || !inst.Arg(2).IsImmediate() ||
inst.Arg(2).GetType() != Type::U32) {
return Fail("interpolation parameter reference is invalid");
}
if (inst.Arg(0).U32() >= input_info.pixel->input_num ||
inst.Arg(1).U32() >= 4u || inst.Arg(2).U32() >= 3u) {
return Fail("interpolation parameter reference is out of range");
}
const auto input = inst.Arg(0).U32();
inputs.per_vertex[input] |= inst.Arg(2).U32() < 2u ||
!ShaderPixelParameterIsFlat(*input_info.pixel, input);
break;
}
case ValueOpcode::GetBuiltin: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed builtin reference is not constant");
}
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
const auto component = inst.Arg(1).U32();
switch (kind) {
case StageInputKind::PackedAncillary:
return Fail("packed pixel ancillary input has an unsupported live use");
case StageInputKind::Layer:
case StageInputKind::SampleId:
if (program.stage != ShaderType::Pixel || component != 0u) {
return Fail("typed pixel scalar input is invalid");
}
break;
case StageInputKind::VertexIndex:
case StageInputKind::InvocationId:
case StageInputKind::PrimitiveId:
case StageInputKind::InstanceIndex:
case StageInputKind::FrontFacing:
case StageInputKind::LocalInvocationIndex:
if (component != 0u) {
return Fail("typed scalar builtin component is out of range");
}
break;
case StageInputKind::FragCoord:
if (component >= 4u) {
return Fail("typed fragment-coordinate component is out of range");
}
break;
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordSmoothSample:
case StageInputKind::BaryCoordSmoothCentroid:
case StageInputKind::BaryCoordNoPerspective:
if (component >= 2u) {
return Fail("typed barycentric component is out of range");
}
break;
case StageInputKind::TessCoord:
case StageInputKind::WorkgroupId:
case StageInputKind::LocalInvocationId:
case StageInputKind::GlobalInvocationId:
if (component >= 3u) {
return Fail("typed invocation builtin component is out of range");
}
break;
case StageInputKind::Parameter:
default: return Fail("typed builtin kind is invalid");
}
inputs.AddBuiltin(kind);
break;
}
case ValueOpcode::SetAttribute: {
const auto index = inst.Flags<ExportFlags>().index;
if (index >= program.export_info.size()) {
return Fail("typed export metadata index is out of range");
}
const auto& exp = program.export_info[index];
if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 &&
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::TessellationEvaluation) {
return Fail("auxiliary position export requires a vertex, mesh, or "
"tessellation evaluation shader");
}
info.pixel_valid_mask |= program.stage == ShaderType::Pixel && exp.vm;
CollectOutput(program, input_info, info, inst);
break;
}
case ValueOpcode::GetUserData: {
if (!inst.HasUses()) break;
if (inst.Arg(0).GetType() != Type::ScalarReg) {
return Fail("typed shader contains an invalid user-data register");
}
const auto index = RegIndex(inst.Arg(0).ScalarRegister());
if (index >= NumScalarRegs) {
return Fail("typed shader contains an invalid user-data register");
}
inputs.user_data[index] = true;
break;
}
case ValueOpcode::GetDispatchThreadExtent:
info.uses_dispatch_threads |= inst.HasUses();
break;
case ValueOpcode::ReadConst: info.uses_flattened_srt = true; break;
case ValueOpcode::BitwiseXor32: info.has_bitwise_xor = true; break;
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
case ValueOpcode::BvhIntersect: info.bvh = true; break;
case ValueOpcode::ConditionRef:
info.subgroup_ballot |=
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
break;
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
case ValueOpcode::DppMoveU32:
case ValueOpcode::ReadFirstLane:
case ValueOpcode::ReadLane: {
info.subgroup_ballot = true;
info.subgroup_shuffle = true;
if (inst.GetOpcode() == ValueOpcode::DppMoveU32) {
info.subgroup_local_invocation_id = true;
}
break;
}
case ValueOpcode::DppUpdateU32:
case ValueOpcode::WriteLane: {
info.subgroup_ballot = true;
info.subgroup_local_invocation_id = true;
break;
}
case ValueOpcode::SwizzleU32:
info.uses_swizzle = true;
[[fallthrough]];
case ValueOpcode::Permlane16U32:
case ValueOpcode::PermuteU32:
case ValueOpcode::BpermuteU32: {
info.subgroup_ballot = true;
info.subgroup_shuffle = true;
info.subgroup_local_invocation_id = true;
break;
}
case ValueOpcode::LaneId:
if (program.stage == ShaderType::TessellationControl) {
inputs.AddBuiltin(StageInputKind::InvocationId);
} else {
info.subgroup_local_invocation_id = true;
}
break;
case ValueOpcode::ImageQueryLod: info.compute_derivatives = true; break;
case ValueOpcode::ImageGatherRaw:
info.image_gather_extended = true;
break;
default: break;
}
}
@@ -423,29 +557,49 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
: "shader info already collected");
}
ValidateOptions(program, input_info);
ValidateValueReferences(program, input_info);
auto& next = program.info;
next.inputs.clear();
next.outputs.clear();
next.has_bitwise_xor =
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto* block) {
return std::any_of(block->begin(), block->end(), [](const auto& inst) {
return inst.GetOpcode() == ValueOpcode::BitwiseXor32;
});
});
auto& info = program.info;
if (info.buffers.size() > ShaderInfo::MaxBuffers) {
return Fail("buffer resource limit exceeded");
}
info.inputs.clear();
info.outputs.clear();
info.user_data_registers.clear();
info.live_buffers = 0;
info.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
info.uses_dispatch_threads = info.uses_swizzle = false;
info.bvh = info.subgroup_ballot = info.subgroup_barrier = info.subgroup_shuffle =
info.subgroup_local_invocation_id = info.compute_derivatives = info.image_gather_extended =
info.function_lds = info.function_scratch = info.pixel_valid_mask = info.buffer_int64_atomics =
info.buffer_u8 = info.buffer_u16 = info.shared_int64_atomics = info.coherent_buffers = info.float64 = false;
InputUsage inputs;
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
Visit(program, input_info, inputs, inst);
}
}
for (uint32_t index = 0; index < inputs.user_data.size(); index++) {
if (inputs.user_data[index]) info.user_data_registers.push_back(index);
}
const auto uses_mapping = [](const auto& resource) {
return resource.indirect_root != UINT32_MAX;
};
info.uses_flattened_srt |= std::ranges::any_of(info.buffers, uses_mapping) ||
std::ranges::any_of(info.images, uses_mapping);
switch (program.stage) {
case ShaderType::Vertex:
case ShaderType::Local: CollectVertexInputs(program, input_info.vertex, next); break;
case ShaderType::Local: CollectVertexInputs(input_info.vertex, info, inputs.components); break;
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh: break;
case ShaderType::Pixel: CollectPixelInputs(program, input_info.pixel, next); break;
case ShaderType::Compute: CollectComputeInputs(input_info.compute, next); break;
case ShaderType::Pixel:
CollectPixelInputs(input_info.pixel, info, inputs.per_vertex, inputs.components);
break;
case ShaderType::Compute: CollectComputeInputs(input_info.compute, info); break;
default: return Fail("unsupported shader stage for info collection");
}
CollectBuiltinInputs(program, next);
CollectOutputs(program, input_info, next);
for (uint32_t i = 0; i < inputs.builtin_count; i++) {
AddBuiltinInput(info, inputs.builtins[i]);
}
program.shader_info_complete = true;
}
@@ -5,8 +5,7 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
// Completes the immutable shader interface after resource tracking. On failure Program::info and
// all completion state remain unchanged.
// Collects the shader interface, resource usage and instruction features after resource tracking.
void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info);
} // namespace Libs::Graphics::ShaderRecompiler::IR