Updated spirv-cross.

This commit is contained in:
Бранимир Караџић
2026-06-14 09:25:50 -07:00
committed by Branimir Karadžić
parent 1473b05e5e
commit affc050e21
14 changed files with 2270 additions and 517 deletions
+12
View File
@@ -694,6 +694,8 @@ struct CLIArguments
uint32_t glsl_ovr_multiview_view_count = 0;
SmallVector<pair<uint32_t, uint32_t>> glsl_ext_framebuffer_fetch;
bool glsl_ext_framebuffer_fetch_noncoherent = false;
uint32_t glsl_descriptor_heap_set = UINT32_MAX;
uint32_t glsl_descriptor_heap_binding = UINT32_MAX;
bool vulkan_glsl_disable_ext_samplerless_texture_functions = false;
bool emit_line_directives = false;
bool enable_storage_image_qualifier_deduction = true;
@@ -821,6 +823,7 @@ static void print_help_glsl()
"\t\tPrimary use case is supporting external samplers in ESSL for video rendering on Android where you could remap a texture to a YUV one.\n"
"\t[--glsl-force-flattened-io-blocks]:\n\t\tAlways flatten I/O blocks and structs.\n"
"\t[--glsl-ovr-multiview-view-count count]:\n\t\tIn GL_OVR_multiview2, specify layout(num_views).\n"
"\t[--glsl-descriptor-heap-set-binding desc_set binding]:\n\t\tInstead of layout(descriptor_heap), emit layout(set = desc_set, binding = binding) instead for compatibility with mapping API.\n"
);
// clang-format on
}
@@ -1458,6 +1461,10 @@ static string compile_iteration(const CLIArguments &args, std::vector<uint32_t>
opts.force_recompile_max_debug_iterations = args.force_recompile_max_debug_iterations;
compiler->set_common_options(opts);
// This is enough for Vulkan mapping API.
if (args.glsl_descriptor_heap_set != UINT32_MAX)
compiler->remap_descriptor_heap(ResourceTypeUnknown, args.glsl_descriptor_heap_set, args.glsl_descriptor_heap_binding);
for (auto &fetch : args.glsl_ext_framebuffer_fetch)
compiler->remap_ext_framebuffer_fetch(fetch.first, fetch.second, !args.glsl_ext_framebuffer_fetch_noncoherent);
@@ -1704,6 +1711,11 @@ static int main_inner(int argc, char *argv[])
cbs.add("--glsl-ext-framebuffer-fetch-noncoherent", [&args](CLIParser &) {
args.glsl_ext_framebuffer_fetch_noncoherent = true;
});
cbs.add("--glsl-descriptor-heap-set-binding", [&args](CLIParser &parser)
{
args.glsl_descriptor_heap_set = parser.next_uint();
args.glsl_descriptor_heap_binding = parser.next_uint();
});
cbs.add("--vulkan-glsl-disable-ext-samplerless-texture-functions",
[&args](CLIParser &) { args.vulkan_glsl_disable_ext_samplerless_texture_functions = true; });
cbs.add("--disable-storage-image-qualifier-deduction",
+477 -152
View File
File diff suppressed because it is too large Load Diff
+477 -152
View File
File diff suppressed because it is too large Load Diff
+25 -1
View File
@@ -608,7 +608,8 @@ struct SPIRType : IVariant
FloatE4M3,
FloatE5M2,
Tensor
Tensor,
DescriptorHeapBuffer
};
// Scalar/vector/matrix support.
@@ -655,6 +656,11 @@ struct SPIRType : IVariant
uint32_t rank;
uint32_t shape;
} tensor;
struct
{
spv::StorageClass storage;
} descriptor_heap_buffer;
} ext;
spv::StorageClass storage = spv::StorageClassGeneric;
@@ -808,6 +814,13 @@ struct SPIRExpression : IVariant
// Whether or not gl_MeshVerticesEXT[].gl_Position (as a whole or .y) is referenced
bool access_meshlet_position_y = false;
// If this expression represents a OpBufferPointerEXT cast.
bool buffer_pointer = false;
// Temporaries which can remain forwarded as long as this variable is not modified.
// Only used for buffer pointers.
SmallVector<ID> buffer_pointer_dependees;
// A list of expressions which this expression depends on.
SmallVector<ID> expression_dependencies;
@@ -1200,6 +1213,12 @@ struct SPIRVariable : IVariant
// Used to find global LUTs
bool is_written_to = false;
// Untyped pointer. The pointer of the variable is effectively void.
// The underlying payload for allocation is in alloca_type, but may be 0 too.
// This is mostly here to support descriptor heap proxy.
bool untyped = false;
ID untyped_alloca_type = 0;
SPIRFunction::Parameter *parameter = nullptr;
SPIRV_CROSS_DECLARE_CLONE(SPIRVariable)
@@ -1535,6 +1554,9 @@ struct SPIRConstant : IVariant
// preprocessor directives before compiling the shader.
std::string specialization_constant_macro_name;
// ConstantSizeOfEXT.
ID size_of_type = 0;
SPIRV_CROSS_DECLARE_CLONE(SPIRConstant)
};
@@ -1815,10 +1837,12 @@ struct Meta
uint32_t set = 0;
uint32_t binding = 0;
uint32_t offset = 0;
uint32_t offset_id = 0;
uint32_t xfb_buffer = 0;
uint32_t xfb_stride = 0;
uint32_t stream = 0;
uint32_t array_stride = 0;
uint32_t array_stride_id = 0;
uint32_t matrix_stride = 0;
uint32_t input_attachment = 0;
uint32_t spec_id = 0;
+266 -6
View File
@@ -78,6 +78,11 @@ string Compiler::compile()
bool Compiler::variable_storage_is_aliased(const SPIRVariable &v)
{
auto &type = get<SPIRType>(v.basetype);
// Untyped pointer, assume full aliasing.
if (type.basetype == SPIRType::Void)
return true;
bool ssbo = v.storage == StorageClassStorageBuffer ||
ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock);
bool image = type.basetype == SPIRType::Image;
@@ -429,10 +434,19 @@ SPIRVariable *Compiler::maybe_get_backing_variable(uint32_t chain)
return var;
}
SPIRExpression *Compiler::maybe_get_backing_buffer_pointer(uint32_t chain)
{
auto *expr = maybe_get<SPIRExpression>(chain);
while (expr && !expr->buffer_pointer && expr->loaded_from)
expr = maybe_get<SPIRExpression>(expr->loaded_from);
return expr && expr->buffer_pointer ? expr : nullptr;
}
void Compiler::register_read(uint32_t expr, uint32_t chain, bool forwarded)
{
auto &e = get<SPIRExpression>(expr);
auto *var = maybe_get_backing_variable(chain);
auto *buffer_pointer = maybe_get_backing_buffer_pointer(chain);
if (var)
{
@@ -447,6 +461,13 @@ void Compiler::register_read(uint32_t expr, uint32_t chain, bool forwarded)
if (var && var->parameter)
var->parameter->read_count++;
}
else if (buffer_pointer)
{
e.loaded_from = buffer_pointer->self;
// If the backing variable is immutable, we do not need to depend on the variable.
if (forwarded && !is_immutable(buffer_pointer->self))
buffer_pointer->buffer_pointer_dependees.push_back(e.self);
}
}
void Compiler::register_write(uint32_t chain)
@@ -464,6 +485,8 @@ void Compiler::register_write(uint32_t chain)
var = maybe_get<SPIRVariable>(access_chain->loaded_from);
}
auto *buffer_pointer = maybe_get_backing_buffer_pointer(chain);
auto &chain_type = expression_type(chain);
if (var)
@@ -508,6 +531,10 @@ void Compiler::register_write(uint32_t chain)
force_recompile();
}
}
else if (buffer_pointer)
{
flush_dependees(*buffer_pointer);
}
else if (chain_type.pointer)
{
// If we stored through a variable pointer, then we don't know which
@@ -529,6 +556,16 @@ void Compiler::flush_dependees(SPIRVariable &var)
var.dependees.clear();
}
void Compiler::flush_dependees(SPIRExpression &expr)
{
// A little ugly to split things up like this since BufferPointerEXT is a weird case
// where it's both an expression (chain into global heap) and a memory declaration at the same time ...
assert(expr.buffer_pointer);
for (auto dep : expr.buffer_pointer_dependees)
invalid_expressions.insert(dep);
expr.buffer_pointer_dependees.clear();
}
void Compiler::flush_all_aliased_variables()
{
for (auto aliased : aliased_variables)
@@ -539,6 +576,8 @@ void Compiler::flush_all_atomic_capable_variables()
{
for (auto global : global_variables)
flush_dependees(get<SPIRVariable>(global));
for (auto global : buffer_pointer_variables)
flush_dependees(get<SPIRExpression>(global));
flush_all_aliased_variables();
}
@@ -560,6 +599,8 @@ void Compiler::flush_all_active_variables()
flush_dependees(get<SPIRVariable>(arg.id));
for (auto global : global_variables)
flush_dependees(get<SPIRVariable>(global));
for (auto global : buffer_pointer_variables)
flush_dependees(get<SPIRExpression>(global));
flush_all_aliased_variables();
}
@@ -668,8 +709,9 @@ bool Compiler::is_hidden_variable(const SPIRVariable &var, bool include_builtins
}
// In SPIR-V 1.4 and up we must also use the active variable interface to disable global variables
// which are not part of the entry point.
if (ir.get_spirv_version() >= 0x10400 && var.storage != StorageClassGeneric &&
// which are not part of the entry point. Library modules have no real entry point so the filter
// would hide every global so skip it in that case.
if (ir.get_spirv_version() >= 0x10400 && !ir.is_library_module && var.storage != StorageClassGeneric &&
var.storage != StorageClassFunction && !interface_variable_exists_in_entry_point(var.self))
{
return true;
@@ -742,17 +784,17 @@ bool Compiler::is_array(const SPIRType &type) const
bool Compiler::is_pointer(const SPIRType &type) const
{
return type.op == OpTypePointer && type.basetype != SPIRType::Unknown; // Ignore function pointers.
return (type.op == OpTypePointer || type.op == OpTypeUntypedPointerKHR) && type.basetype != SPIRType::Unknown; // Ignore function pointers.
}
bool Compiler::is_physical_pointer(const SPIRType &type) const
{
return type.op == OpTypePointer && type.storage == StorageClassPhysicalStorageBuffer;
return (type.op == OpTypePointer || type.op == OpTypeUntypedPointerKHR) && type.storage == StorageClassPhysicalStorageBuffer;
}
bool Compiler::is_physical_or_buffer_pointer(const SPIRType &type) const
{
return type.op == OpTypePointer &&
return (type.op == OpTypePointer || type.op == OpTypeUntypedPointerKHR) &&
(type.storage == StorageClassPhysicalStorageBuffer || type.storage == StorageClassUniform ||
type.storage == StorageClassStorageBuffer || type.storage == StorageClassWorkgroup ||
type.storage == StorageClassPushConstant);
@@ -2068,7 +2110,7 @@ size_t Compiler::get_declared_struct_size_runtime_array(const SPIRType &type, si
size_t size = get_declared_struct_size(type);
auto &last_type = get<SPIRType>(type.member_types.back());
if (!last_type.array.empty() && last_type.array_size_literal[0] && last_type.array[0] == 0) // Runtime array
if (!last_type.array.empty() && last_type.array_size_literal.back() && last_type.array.back() == 0) // Runtime array
size += array_size * type_struct_member_array_stride(type, uint32_t(type.member_types.size() - 1));
return size;
@@ -4799,6 +4841,18 @@ void Compiler::build_function_control_flow_graphs_and_analyze()
CFGBuilder handler(*this);
handler.function_cfgs[ir.default_entry_point].reset(new CFG(*this, get<SPIRFunction>(ir.default_entry_point)));
traverse_all_reachable_opcodes(get<SPIRFunction>(ir.default_entry_point), handler);
if (ir.is_library_module)
{
// In library mode, default_entry_point is just the first exported
// function. Build a CFG for every other exported function (and its
// callees) so per-function analyses below cover all of them.
for (auto export_id : ir.library_exported_functions)
{
auto &func = get<SPIRFunction>(export_id);
if (handler.follow_function_call(func))
traverse_all_reachable_opcodes(func, handler);
}
}
function_cfgs = std::move(handler.function_cfgs);
bool single_function = function_cfgs.size() <= 1;
@@ -5429,6 +5483,212 @@ void Compiler::analyze_non_block_pointer_types()
physical_storage_type_to_alignment = std::move(handler.physical_block_type_meta);
}
void Compiler::analyze_descriptor_heap_types()
{
struct HeapHandler : OpcodeHandler
{
bool handle(Op opcode, const uint32_t *args, uint32_t) override
{
switch (opcode)
{
case OpBufferPointerEXT:
{
auto &ptr_type = compiler.get<SPIRType>(args[0]);
// BufferPointerEXT can return untyped or typed pointers.
// If it's typed, we resolve it here.
if (ptr_type.basetype == SPIRType::Struct)
{
DescriptorHeapMeta meta = {};
meta.type = ptr_type.self;
meta.hlsl_style_stride = hlsl_style_stride_access_chains.count(args[2]);
meta.buffer_pointer_id = args[1];
meta.storage = ptr_type.storage;
meta.nonreadable = compiler.has_decoration(args[1], DecorationNonReadable);
meta.nonwritable = compiler.has_decoration(args[1], DecorationNonWritable);
meta.coherent = compiler.has_decoration(args[1], DecorationCoherent);
meta.is_restrict = compiler.has_decoration(args[1], DecorationRestrict);
meta.is_volatile = compiler.has_decoration(args[1], DecorationVolatile);
add_unique_type(meta);
}
buffer_pointers[args[1]] = { args[0], hlsl_style_stride_access_chains.count(args[2]) != 0 };
break;
}
case OpUntypedAccessChainKHR:
case OpUntypedInBoundsAccessChainKHR:
case OpUntypedArrayLengthKHR:
{
auto &data_type = compiler.get<SPIRType>(args[2]);
if (compiler.is_pointer(data_type))
SPIRV_CROSS_THROW("pointer type not allowed.");
bool hlsl_style_stride = false;
// Need to validate the array stride and types. HLLs are not flexible enough to support the full flexibility of SPIR-V.
if (BuiltIn(compiler.get_decoration(args[3], DecorationBuiltIn)) == BuiltInResourceHeapEXT)
{
if (!compiler.is_runtime_size_array(data_type))
SPIRV_CROSS_THROW("Descriptor heap must be accessed as a runtime array.");
// The only meaningful use of this is ArrayStride equal to sizeof(type) right now.
uint32_t array_stride_id = compiler.get_decoration(args[2], DecorationArrayStrideIdEXT);
if (!array_stride_id)
SPIRV_CROSS_THROW("Expected ArrayStrideIdEXT to be set for resource heap.");
auto *spec_c = compiler.maybe_get<SPIRConstantOp>(array_stride_id);
auto *c = compiler.maybe_get<SPIRConstant>(array_stride_id);
if (!spec_c && !c)
SPIRV_CROSS_THROW("Array stride must be some constant expression.");
if (spec_c)
{
// This gets potentially infinitely weird, but if we get HLSL-style shaders
// we expect the array stride to be max(buffer, image) since all descriptors have equal size in D3D12.
// We just have to be a bit loose here since it's impossible to anticipate every theoretical formulation.
// Anything non-conforming to strict GLSL is flagged in the codegen output.
if (spec_c->opcode == OpSelect)
{
auto *true_value = compiler.maybe_get<SPIRConstant>(spec_c->arguments[1]);
auto *false_value = compiler.maybe_get<SPIRConstant>(spec_c->arguments[2]);
hlsl_style_stride = true_value && true_value->size_of_type &&
false_value && false_value->size_of_type;
}
if (!hlsl_style_stride)
SPIRV_CROSS_THROW("Unusual pattern of descriptor stride detected. This probably cannot be expressed in current GLSL.");
}
if (c && !c->size_of_type)
SPIRV_CROSS_THROW("Resource heap array stride must be ConstantSizeOfEXT for high level languages.");
auto &element_type = compiler.get<SPIRType>(data_type.parent_type);
if (element_type.basetype == SPIRType::DescriptorHeapBuffer)
{
if (c && compiler.get<SPIRType>(c->size_of_type).basetype != SPIRType::DescriptorHeapBuffer)
SPIRV_CROSS_THROW("Buffer descriptors in heap must be ConstantSizeOfEXT(OpTypeBufferEXT) for GLSL.");
}
else if (data_type.basetype == SPIRType::Image)
{
if (c && compiler.get<SPIRType>(c->size_of_type).basetype != SPIRType::Image)
SPIRV_CROSS_THROW("Image descriptors in heap must be ConstantSizeOfEXT(OpTypeImage) for GLSL.");
}
else if (data_type.basetype == SPIRType::AccelerationStructure)
{
if (c && compiler.get<SPIRType>(c->size_of_type).basetype != SPIRType::AccelerationStructure)
SPIRV_CROSS_THROW("RTAS descriptors in heap must be ConstantSizeOfEXT(OpTypeAccelerationStructure) for GLSL.");
}
}
else if (BuiltIn(compiler.get_decoration(args[3], DecorationBuiltIn)) == BuiltInSamplerHeapEXT)
{
if (!compiler.is_runtime_size_array(data_type))
SPIRV_CROSS_THROW("Descriptor heap must be accessed as a runtime array.");
// The only meaningful use of this is ArrayStride equal to sizeof(sampler) right now.
uint32_t array_stride_id = compiler.get_decoration(args[2], DecorationArrayStrideIdEXT);
if (!array_stride_id)
SPIRV_CROSS_THROW("Expected ArrayStrideIdEXT to be set for sampler heap.");
auto *c = compiler.maybe_get<SPIRConstant>(array_stride_id);
if (!c || !c->size_of_type || compiler.get<SPIRType>(c->size_of_type).basetype != SPIRType::Sampler)
SPIRV_CROSS_THROW("Sampler heap array stride must be ConstantSizeOfEXT(OpTypeSampler) for high level languages.");
}
// Remember this for OpBufferPointerEXT.
if (hlsl_style_stride)
hlsl_style_stride_access_chains.insert(args[1]);
if (data_type.basetype == SPIRType::SampledImage)
{
SPIRV_CROSS_THROW("Attempting to access heap as combined sampler image. This does not make sense.");
}
else if (data_type.basetype == SPIRType::Image ||
data_type.basetype == SPIRType::AccelerationStructure ||
data_type.basetype == SPIRType::Sampler)
{
DescriptorHeapMeta meta = {};
meta.type = data_type.self;
meta.hlsl_style_stride = hlsl_style_stride;
add_unique_type(meta);
}
else if (buffer_pointers.count(args[3]) != 0)
{
if (!compiler.has_decoration(data_type.self, DecorationBlock) &&
!compiler.has_decoration(data_type.self, DecorationBufferBlock))
{
SPIRV_CROSS_THROW("BufferPointerEXT must reference a block type.");
}
auto &pointer_meta = buffer_pointers[args[3]];
auto &buffer_type = compiler.get<SPIRType>(pointer_meta.type);
if (buffer_type.basetype == SPIRType::Void)
{
// This is where the pointer becomes typed, so register it here.
DescriptorHeapMeta meta = {};
meta.type = data_type.self;
meta.hlsl_style_stride = pointer_meta.hlsl_style_stride;
meta.buffer_pointer_id = args[3];
meta.storage = buffer_type.storage;
meta.nonreadable = compiler.has_decoration(args[3], DecorationNonReadable);
meta.nonwritable = compiler.has_decoration(args[3], DecorationNonWritable);
meta.coherent = compiler.has_decoration(args[3], DecorationCoherent);
meta.is_volatile = compiler.has_decoration(args[3], DecorationVolatile);
meta.is_restrict = compiler.has_decoration(args[3], DecorationRestrict);
add_unique_type(meta);
}
}
break;
}
default:
break;
}
return true;
}
explicit HeapHandler(Compiler &compiler_) : OpcodeHandler(compiler_) {}
std::vector<DescriptorHeapMeta> heap_types;
struct BufferPointerMeta
{
TypeID type;
bool hlsl_style_stride;
};
std::unordered_map<uint32_t, BufferPointerMeta> buffer_pointers;
std::unordered_set<uint32_t> hlsl_style_stride_access_chains;
void add_unique_type(const DescriptorHeapMeta &meta)
{
assert(meta.type != 0);
for (auto &type : heap_types)
{
if (type.type == meta.type && type.storage == meta.storage &&
type.buffer_pointer_id == meta.buffer_pointer_id &&
type.nonreadable == meta.nonreadable &&
type.nonwritable == meta.nonwritable &&
type.coherent == meta.coherent &&
type.is_restrict == meta.is_restrict &&
type.hlsl_style_stride == meta.hlsl_style_stride &&
type.is_volatile == meta.is_volatile)
{
return;
}
}
heap_types.push_back(meta);
}
};
HeapHandler handler(*this);
traverse_all_reachable_opcodes(get<SPIRFunction>(ir.default_entry_point), handler);
descriptor_heap_types = std::move(handler.heap_types);
}
bool Compiler::InterlockedResourceAccessPrepassHandler::handle(Op op, const uint32_t *, uint32_t)
{
if (op == OpBeginInvocationInterlockEXT || op == OpEndInvocationInterlockEXT)
+42
View File
@@ -91,6 +91,28 @@ struct BuiltInResource
Resource resource;
};
// Needs to stay in sync 1:1 with C API.
enum ResourceType
{
ResourceTypeUnknown = 0,
ResourceTypeUniformBuffer = 1,
ResourceTypeStorageBuffer = 2,
ResourceTypeStageInput = 3,
ResourceTypeStageOutput = 4,
ResourceTypeSubpassInput = 5,
ResourceTypeStorageImage = 6,
ResourceTypeSampledImage = 7,
ResourceTypeAtomicCounter = 8,
ResourceTypePushConstant = 9,
ResourceTypeSeparateImage = 10,
ResourceTypeSeparateSamplers = 11,
ResourceTypeAccelerationStructure = 12,
ResourceTypeRayQuery = 13,
ResourceTypeShaderRecordBuffer = 14,
ResourceTypeGLPlainUniform = 15,
ResourceTypeTensor = 16
};
struct ShaderResources
{
SmallVector<Resource> uniform_buffers;
@@ -585,6 +607,7 @@ protected:
// (SSBO, image load store, etc)
SmallVector<uint32_t> global_variables;
SmallVector<uint32_t> aliased_variables;
SmallVector<uint32_t> buffer_pointer_variables;
SPIRFunction *current_function = nullptr;
SPIRBlock *current_block = nullptr;
@@ -705,6 +728,7 @@ protected:
bool expression_is_lvalue(uint32_t id) const;
bool variable_storage_is_aliased(const SPIRVariable &var);
SPIRVariable *maybe_get_backing_variable(uint32_t chain);
SPIRExpression *maybe_get_backing_buffer_pointer(uint32_t chain);
void register_read(uint32_t expr, uint32_t chain, bool forwarded);
void register_write(uint32_t chain);
@@ -739,6 +763,7 @@ protected:
// Dependency tracking for temporaries read from variables.
void flush_dependees(SPIRVariable &var);
void flush_dependees(SPIRExpression &expr);
void flush_all_active_variables();
void flush_control_dependent_expressions(uint32_t block);
void flush_all_atomic_capable_variables();
@@ -1090,6 +1115,23 @@ protected:
SmallVector<uint32_t> physical_storage_non_block_pointer_types;
std::unordered_map<uint32_t, PhysicalBlockMeta> physical_storage_type_to_alignment;
struct DescriptorHeapMeta
{
TypeID type;
bool hlsl_style_stride;
// For buffers
ID buffer_pointer_id;
StorageClass storage;
bool nonwritable;
bool nonreadable;
bool coherent;
bool is_volatile;
bool is_restrict;
};
std::vector<DescriptorHeapMeta> descriptor_heap_types;
void analyze_descriptor_heap_types();
void analyze_variable_scope(SPIRFunction &function, AnalyzeVariableScopeAccessHandler &handler);
void find_function_local_luts(SPIRFunction &function, const AnalyzeVariableScopeAccessHandler &handler,
bool single_function);
+32
View File
@@ -79,6 +79,9 @@ ParsedIR &ParsedIR::operator=(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT
memory_model = other.memory_model;
default_entry_point = other.default_entry_point;
is_library_module = other.is_library_module;
library_exports = std::move(other.library_exports);
library_exported_functions = std::move(other.library_exported_functions);
sources = std::move(other.sources);
loop_iteration_depth_hard = other.loop_iteration_depth_hard;
loop_iteration_depth_soft = other.loop_iteration_depth_soft;
@@ -111,6 +114,9 @@ ParsedIR &ParsedIR::operator=(const ParsedIR &other)
continue_block_to_loop_header = other.continue_block_to_loop_header;
entry_points = other.entry_points;
default_entry_point = other.default_entry_point;
is_library_module = other.is_library_module;
library_exports = other.library_exports;
library_exported_functions = other.library_exported_functions;
sources = other.sources;
loop_iteration_depth_hard = other.loop_iteration_depth_hard;
loop_iteration_depth_soft = other.loop_iteration_depth_soft;
@@ -404,6 +410,10 @@ void ParsedIR::set_decoration(ID id, Decoration decoration, uint32_t argument)
dec.offset = argument;
break;
case DecorationOffsetIdEXT:
dec.offset_id = argument;
break;
case DecorationXfbBuffer:
dec.xfb_buffer = argument;
break;
@@ -420,6 +430,10 @@ void ParsedIR::set_decoration(ID id, Decoration decoration, uint32_t argument)
dec.array_stride = argument;
break;
case DecorationArrayStrideIdEXT:
dec.array_stride_id = argument;
break;
case DecorationMatrixStride:
dec.matrix_stride = argument;
break;
@@ -492,6 +506,10 @@ void ParsedIR::set_member_decoration(TypeID id, uint32_t index, Decoration decor
dec.offset = argument;
break;
case DecorationOffsetIdEXT:
dec.offset_id = argument;
break;
case DecorationXfbBuffer:
dec.xfb_buffer = argument;
break;
@@ -645,6 +663,8 @@ uint32_t ParsedIR::get_decoration(ID id, Decoration decoration) const
return dec.component;
case DecorationOffset:
return dec.offset;
case DecorationOffsetIdEXT:
return dec.offset_id;
case DecorationXfbBuffer:
return dec.xfb_buffer;
case DecorationXfbStride:
@@ -661,6 +681,8 @@ uint32_t ParsedIR::get_decoration(ID id, Decoration decoration) const
return dec.spec_id;
case DecorationArrayStride:
return dec.array_stride;
case DecorationArrayStrideIdEXT:
return dec.array_stride_id;
case DecorationMatrixStride:
return dec.matrix_stride;
case DecorationIndex:
@@ -720,6 +742,10 @@ void ParsedIR::unset_decoration(ID id, Decoration decoration)
dec.offset = 0;
break;
case DecorationOffsetIdEXT:
dec.offset_id = 0;
break;
case DecorationXfbBuffer:
dec.xfb_buffer = 0;
break;
@@ -806,6 +832,8 @@ uint32_t ParsedIR::get_member_decoration(TypeID id, uint32_t index, Decoration d
return dec.binding;
case DecorationOffset:
return dec.offset;
case DecorationOffsetIdEXT:
return dec.offset_id;
case DecorationXfbBuffer:
return dec.xfb_buffer;
case DecorationXfbStride:
@@ -903,6 +931,10 @@ void ParsedIR::unset_member_decoration(TypeID id, uint32_t index, Decoration dec
dec.offset = 0;
break;
case DecorationOffsetIdEXT:
dec.offset_id = 0;
break;
case DecorationXfbBuffer:
dec.xfb_buffer = 0;
break;
+7
View File
@@ -110,6 +110,13 @@ public:
std::unordered_map<FunctionID, SPIREntryPoint> entry_points;
FunctionID default_entry_point = 0;
// A "library" module has no OpEntryPoint and instead exports symbols via
// OpDecorate ... LinkageAttributes ... Export. These vectors keep track
// of all these exports and specifically the function exports.
bool is_library_module = false;
SmallVector<uint32_t> library_exports;
SmallVector<FunctionID> library_exported_functions;
struct Source
{
SourceLanguage lang = SourceLanguageUnknown;
+690 -117
View File
File diff suppressed because it is too large Load Diff
+33 -4
View File
@@ -306,6 +306,19 @@ public:
// Returns the macro name corresponding to constant id
std::string constant_value_macro_name(uint32_t id) const;
// Rather than using layout(descriptor_heap), emit layout(set, binding).
// This intended to be compatible with descriptor buffers, legacy descriptor indexing,
// or when the heap descriptors require unusual kinds of mapping in the Vulkan API
// which is not expressible by GLSL directly.
//
// ResourceTypeUnknown can be used as a default catch-all mapping.
// dim can be used to disambiguate between texel buffers and images since they are both image types,
// but use different descriptor types in the Vulkan API.
// No distinction is made between 1D/2D/3D/Cube textures.
// The default argument of DimMax maps to both texel buffers and images.
// dim is ignored for ResourceTypeUnknown.
void remap_descriptor_heap(ResourceType type, uint32_t desc_set, uint32_t binding, Dim dim = DimMax);
protected:
struct ShaderSubgroupSupportHelper
{
@@ -670,6 +683,7 @@ protected:
bool requires_relaxed_precision_analysis = false;
bool implicit_c_integer_promotion_rules = false;
bool supports_spec_constant_array_size = true;
bool requires_phi_undef_zero_init = false;
} backend;
void emit_struct(SPIRType &type);
@@ -677,7 +691,9 @@ protected:
void emit_extension_workarounds(ExecutionModel model);
void emit_subgroup_arithmetic_workaround(const std::string &func, Op op, GroupOperation group_op);
void emit_polyfills(uint32_t polyfills, bool relaxed);
void emit_buffer_block_native(const SPIRVariable &var);
void emit_buffer_block_native(const SPIRVariable *var, const DescriptorHeapMeta *heap_meta = nullptr);
std::string to_buffer_pointer_name_prefix(uint32_t ptr_id) const;
static std::string heap_meta_to_prefix(const DescriptorHeapMeta &meta);
void emit_buffer_reference_block(uint32_t type_id, bool forward_declaration);
void emit_buffer_block_legacy(const SPIRVariable &var);
void emit_buffer_block_flattened(const SPIRVariable &type);
@@ -771,7 +787,7 @@ protected:
AccessChainFlags flags, bool &access_chain_is_arrayed, uint32_t index);
std::string access_chain_internal(uint32_t base, const uint32_t *indices, uint32_t count, AccessChainFlags flags,
AccessChainMeta *meta);
AccessChainMeta *meta, const SPIRType *untyped_data_type);
// Only meaningful on backends with physical pointer support ala MSL.
// Relevant for PtrAccessChain / BDA.
@@ -785,7 +801,8 @@ protected:
StorageClass storage, bool &is_packed);
std::string access_chain(uint32_t base, const uint32_t *indices, uint32_t count, const SPIRType &target_type,
AccessChainMeta *meta = nullptr, bool ptr_chain = false);
AccessChainMeta *meta = nullptr, bool ptr_chain = false,
const SPIRType *untyped_data_type = nullptr);
std::string flattened_access_chain(uint32_t base, const uint32_t *indices, uint32_t count,
const SPIRType &target_type, uint32_t offset, uint32_t matrix_stride,
@@ -1023,6 +1040,7 @@ protected:
std::string emit_for_loop_initializers(const SPIRBlock &block);
void emit_while_loop_initializers(const SPIRBlock &block);
std::string undef_loop_variable_initializer_suffix(const SPIRVariable &var);
bool for_loop_initializers_are_same_type(const SPIRBlock &block);
bool optimize_read_modify_write(const SPIRType &type, const std::string &lhs, const std::string &rhs);
void fixup_image_load_store_access();
@@ -1055,7 +1073,7 @@ protected:
void disallow_forwarding_in_expression_chain(const SPIRExpression &expr);
bool expression_is_constant_null(uint32_t id) const;
bool expression_is_non_value_type_array(uint32_t ptr);
bool expression_is_non_value_type_array(uint32_t value_type_id, uint32_t ptr);
virtual void emit_store_statement(uint32_t lhs_expression, uint32_t rhs_expression);
uint32_t get_integer_width_for_instruction(const Instruction &instr) const;
@@ -1089,6 +1107,17 @@ protected:
uint32_t get_fp_fast_math_flags_for_op(uint32_t result_type, uint32_t id) const;
bool has_legacy_nocontract(uint32_t result_type, uint32_t id) const;
struct DescriptorHeapMapping
{
ResourceType type;
uint32_t desc_set;
uint32_t binding;
Dim dim;
};
SmallVector<DescriptorHeapMapping> descriptor_heap_mappings;
bool is_descriptor_non_uniform(uint32_t id) const;
std::string to_descriptor_heap_layout(const SPIRType &type, StorageClass storage = StorageClassUniformConstant) const;
private:
void init();
+26 -6
View File
@@ -3117,7 +3117,12 @@ uint32_t CompilerHLSL::input_vertices_from_execution_mode(SPIREntryPoint &execut
void CompilerHLSL::emit_function_prototype(SPIRFunction &func, const Bitset &return_flags)
{
if (func.self != ir.default_entry_point)
// In library mode default_entry_point points at the first exported
// function; treat every export as a normal function rather than as the
// shader's entry point.
const bool is_entry_point = !ir.is_library_module && func.self == ir.default_entry_point;
if (!is_entry_point)
add_function_overload(func);
// Avoid shadow declarations.
@@ -3138,7 +3143,7 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, const Bitset &ret
decl = "void ";
}
if (func.self == ir.default_entry_point)
if (is_entry_point)
{
decl += get_inner_entry_point_name();
processing_entry_point = true;
@@ -5237,7 +5242,8 @@ string CompilerHLSL::write_access_chain_value(uint32_t value, const SmallVector<
{
AccessChainMeta meta;
ret = access_chain_internal(value, composite_chain.data(), uint32_t(composite_chain.size()),
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_LITERAL_MSB_FORCE_ID, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_LITERAL_MSB_FORCE_ID, &meta,
nullptr);
}
if (enclose)
@@ -7101,6 +7107,7 @@ string CompilerHLSL::compile()
backend.can_return_array = false;
backend.nonuniform_qualifier = "NonUniformResourceIndex";
backend.support_case_fallthrough = false;
backend.requires_phi_undef_zero_init = true;
backend.force_merged_mesh_block = get_execution_model() == ExecutionModelMeshEXT;
backend.force_gl_in_out_block = backend.force_merged_mesh_block;
backend.supports_empty_struct = hlsl_options.shader_model <= 30;
@@ -7146,14 +7153,27 @@ string CompilerHLSL::compile()
emit_header();
emit_resources();
emit_function(get<SPIRFunction>(ir.default_entry_point), Bitset());
emit_hlsl_entry_point();
if (ir.is_library_module)
{
// Emit each exported function as a normal free function.
// emit_function recursively emits callees, so internal helpers
// are picked up too.
for (auto export_id : ir.library_exported_functions)
emit_function(get<SPIRFunction>(export_id), Bitset());
}
else
{
emit_function(get<SPIRFunction>(ir.default_entry_point), Bitset());
emit_hlsl_entry_point();
}
pass_count++;
} while (is_forcing_recompilation());
// Entry point in HLSL is always main() for the time being.
get_entry_point().name = "main";
// Skip the rename for library modules; their exports keep their declared names.
if (!ir.is_library_module)
get_entry_point().name = "main";
return buffer.str();
}
+95 -76
View File
@@ -1087,6 +1087,18 @@ void CompilerMSL::build_implicit_builtins()
dynamic_offsets_buffer_id = var_id;
}
if (active_input_builtins.get(BuiltInDrawIndex))
{
// This is always emulated.
uint32_t var_id = build_constant_uint_array_pointer();
set_name(var_id, "spvDrawIndex");
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, ~(6u));
set_decoration(var_id, DecorationBinding, msl_options.draw_id_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary, msl_options.draw_id_buffer_index);
draw_index_buffer_id = var_id;
}
// If we're returning a struct from a vertex-like entry point, we must return a position attribute.
bool need_position = (get_execution_model() == ExecutionModelVertex || is_tese_shader()) &&
!capture_output_to_buffer && !get_is_rasterization_disabled() &&
@@ -1766,6 +1778,8 @@ string CompilerMSL::compile()
add_active_interface_variable(view_mask_buffer_id);
if (dynamic_offsets_buffer_id)
add_active_interface_variable(dynamic_offsets_buffer_id);
if (draw_index_buffer_id)
add_active_interface_variable(draw_index_buffer_id);
if (builtin_layer_id)
add_active_interface_variable(builtin_layer_id);
if (builtin_dispatch_base_id && !msl_options.supports_msl_version(1, 2))
@@ -8727,7 +8741,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
const uint32_t indices[2] = { i, interface_index };
AccessChainMeta meta;
expr += access_chain_internal(stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta, nullptr);
if (i + 1 < num_control_points)
expr += ", ";
}
@@ -8763,7 +8777,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
AccessChainMeta meta;
expr += access_chain_internal(stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT,
&meta, nullptr);
if (!is_matrix(sub_type) && sub_type.basetype != SPIRType::Struct &&
expr_type.vecsize > sub_type.vecsize)
expr += vector_swizzle(sub_type.vecsize, 0);
@@ -8821,7 +8836,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
AccessChainMeta meta;
expr += access_chain_internal(
stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT,
&meta, nullptr);
}
else
expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index);
@@ -8845,7 +8861,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
AccessChainMeta meta;
expr += access_chain_internal(
stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT,
&meta, nullptr);
}
else
expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index);
@@ -8865,7 +8882,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
AccessChainMeta meta;
expr += access_chain_internal(stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT,
&meta);
&meta, nullptr);
}
else
expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index);
@@ -8910,7 +8927,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
AccessChainMeta meta;
expr += access_chain_internal(stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT,
&meta, nullptr);
if (expr_type.vecsize > result_type.vecsize)
expr += vector_swizzle(result_type.vecsize, 0);
if (j + 1 < result_type.columns)
@@ -8955,7 +8973,8 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id
AccessChainMeta meta;
expr += access_chain_internal(stage_in_ptr_var_id, indices, 2,
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta);
ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT,
&meta, nullptr);
if (expr_type.vecsize > result_type.vecsize)
expr += vector_swizzle(result_type.vecsize, 0);
@@ -9000,7 +9019,6 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
auto *var = maybe_get_backing_variable(ops[2]);
bool patch = false;
bool flat_data = false;
bool ptr_is_chain = false;
bool flatten_composites = false;
bool is_block = false;
@@ -9021,12 +9039,6 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
// Patch inputs are treated as normal block IO variables, so they don't deal with this path at all.
if (patch && (!is_block || is_arrayed || var->storage == StorageClassInput))
flat_data = false;
// We might have a chained access chain, where
// we first take the access chain to the control point, and then we chain into a member or something similar.
// In this case, we need to skip gl_in/gl_out remapping.
// Also, skip ptr chain for patches.
ptr_is_chain = var->self != ID(ops[2]);
}
bool builtin_variable = false;
@@ -9047,10 +9059,27 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
if (variable_is_flat)
{
if (auto *ptr_expr = maybe_get<SPIRExpression>(ops[2]))
{
// Too many edge cases in incrementally resolving tessellation access chains.
// Only reasonable option is to completely rematerialize the chain from the start.
SmallVector<uint32_t> rematerialize_ops;
rematerialize_ops.push_back(ops[0]);
rematerialize_ops.push_back(ops[1]);
for (auto expr : ptr_expr->implied_read_expressions)
rematerialize_ops.push_back(expr);
for (uint32_t i = 3; i < length; i++)
rematerialize_ops.push_back(ops[i]);
return emit_tessellation_access_chain(rematerialize_ops.data(), uint32_t(rematerialize_ops.size()));
}
// If output is masked, it is emitted as a "normal" variable, just go through normal code paths.
// Only check this for the first level of access chain.
// Dealing with this for partial access chains should be possible, but awkward.
if (var->storage == StorageClassOutput && !ptr_is_chain)
if (var->storage == StorageClassOutput)
{
bool masked = false;
if (is_block)
@@ -9077,7 +9106,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
indices.reserve(length - 3 + 1);
uint32_t first_non_array_index = (ptr_is_chain ? 3 : 4) - (patch ? 1 : 0);
uint32_t first_non_array_index = 4 - (patch ? 1 : 0);
VariableID stage_var_id;
if (patch)
@@ -9085,8 +9114,9 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
else
stage_var_id = var->storage == StorageClassInput ? stage_in_ptr_var_id : stage_out_ptr_var_id;
VariableID ptr = ptr_is_chain ? VariableID(ops[2]) : stage_var_id;
if (!ptr_is_chain && !patch)
VariableID ptr = stage_var_id;
if (!patch)
{
// Index into gl_in/gl_out with first array index.
indices.push_back(ops[first_non_array_index - 1]);
@@ -9097,17 +9127,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
uint32_t const_mbr_id = next_id++;
uint32_t index = get_extended_decoration(ops[2], SPIRVCrossDecorationInterfaceMemberIndex);
// If we have a pointer chain expression, and we are no longer pointing to a composite
// object, we are in the clear. There is no longer a need to flatten anything.
bool further_access_chain_is_trivial = false;
if (ptr_is_chain && flatten_composites)
{
auto &ptr_type = expression_type(ptr);
if (!is_array(ptr_type) && !is_matrix(ptr_type) && ptr_type.basetype != SPIRType::Struct)
further_access_chain_is_trivial = true;
}
if (!further_access_chain_is_trivial && (flatten_composites || is_block))
if (flatten_composites || is_block)
{
uint32_t i = first_non_array_index;
auto *type = &get_variable_element_type(*var);
@@ -9203,42 +9223,8 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
// We use the pointer to the base of the input/output array here,
// so this is always a pointer chain.
string e;
if (!ptr_is_chain)
{
// This is the start of an access chain, use ptr_chain to index into control point array.
e = access_chain(ptr, indices.data(), uint32_t(indices.size()), result_ptr_type, &meta, !patch);
}
else
{
// If we're accessing a struct, we need to use member indices which are based on the IO block,
// not actual struct type, so we have to use a split access chain here where
// first path resolves the control point index, i.e. gl_in[index], and second half deals with
// looking up flattened member name.
// However, it is possible that we partially accessed a struct,
// by taking pointer to member inside the control-point array.
// For this case, we fall back to a natural access chain since we have already dealt with remapping struct members.
// One way to check this here is if we have 2 implied read expressions.
// First one is the gl_in/gl_out struct itself, then an index into that array.
// If we have traversed further, we use a normal access chain formulation.
auto *ptr_expr = maybe_get<SPIRExpression>(ptr);
bool split_access_chain_formulation = flatten_composites && ptr_expr &&
ptr_expr->implied_read_expressions.size() == 2 &&
!further_access_chain_is_trivial;
if (split_access_chain_formulation)
{
e = join(to_expression(ptr),
access_chain_internal(stage_var_id, indices.data(), uint32_t(indices.size()),
ACCESS_CHAIN_CHAIN_ONLY_BIT, &meta));
}
else
{
e = access_chain_internal(ptr, indices.data(), uint32_t(indices.size()), 0, &meta);
}
}
// This is the start of an access chain, use ptr_chain to index into control point array.
auto e = access_chain(ptr, indices.data(), uint32_t(indices.size()), result_ptr_type, &meta, !patch);
// Get the actual type of the object that was accessed. If it's a vector type and we changed it,
// then we'll need to add a swizzle.
@@ -9498,10 +9484,31 @@ void CompilerMSL::emit_instruction(const Instruction &instruction)
}
else
{
// Sample mask input for Metal is not an array
if (BuiltIn(get_decoration(ptr, DecorationBuiltIn)) == BuiltInSampleMask)
set_decoration(id, DecorationBuiltIn, BuiltInSampleMask);
CompilerGLSL::emit_instruction(instruction);
auto is_sample_mask = BuiltIn(get_decoration(ptr, DecorationBuiltIn)) == BuiltInSampleMask;
auto ptr_storage = get_expression_effective_storage_class(ptr);
auto *ptr_var = maybe_get_backing_variable(ptr);
// More edge cases ... Normally composite outputs are lowered at the end,
// but that's not the case for clip-cull arrays.
if (ptr_var && ptr_storage == StorageClassOutput && is_builtin_variable(*ptr_var) &&
!is_sample_mask && is_array(get<SPIRType>(ops[0])))
{
emit_uninitialized_temporary_expression(ops[0], id);
auto &type = get<SPIRType>(ops[0]);
if (type.array.size() != 1)
SPIRV_CROSS_THROW("Cannot load array of clip-cull distances from array of array.");
if (!type.array_size_literal.front())
SPIRV_CROSS_THROW("Cannot load array of clip-cull distances from spec constant array size.");
for (uint32_t i = 0; i < type.array[0]; i++)
statement(to_expression(id), "[", i, "] = ", to_expression(ptr), "[", i, "];");
}
else
{
// Sample mask input for Metal is not an array
if (is_sample_mask)
set_decoration(id, DecorationBuiltIn, BuiltInSampleMask);
CompilerGLSL::emit_instruction(instruction);
}
}
break;
}
@@ -11174,6 +11181,12 @@ bool CompilerMSL::emit_array_copy(const char *expr, uint32_t lhs_id, uint32_t rh
else if (rhs_var && rhs_storage != StorageClassGeneric && type_is_explicit_layout(get<SPIRType>(rhs_var->basetype)))
rhs_is_array_template = false;
// Special consideration for clip/culldistance. Normally composites are lowered, but clip/cull is special for reasons ...
if (lhs_var && lhs_storage == StorageClassOutput && is_builtin_variable(*lhs_var))
lhs_is_array_template = false;
if (rhs_var && rhs_storage == StorageClassOutput && is_builtin_variable(*rhs_var))
rhs_is_array_template = false;
// If threadgroup storage qualifiers are *not* used:
// Avoid spvCopy* wrapper functions; Otherwise, spvUnsafeArray<> template cannot be used with that storage qualifier.
if (lhs_is_array_template && rhs_is_array_template && !using_builtin_array())
@@ -13821,9 +13834,6 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in
return "";
return string(" [[") + builtin_qualifier(builtin) + "]]";
case BuiltInDrawIndex:
SPIRV_CROSS_THROW("DrawIndex is not supported in MSL.");
default:
return "";
}
@@ -14672,6 +14682,9 @@ bool CompilerMSL::is_direct_input_builtin(BuiltIn bi_type)
/* fallthrough */
case BuiltInSubgroupLocalInvocationId:
return !msl_options.emulate_subgroups;
case BuiltInDrawIndex:
// Emulated
return false;
default:
return true;
}
@@ -15970,6 +15983,12 @@ void CompilerMSL::fix_up_shader_inputs_outputs()
to_expression(builtin_dispatch_base_id), ".y;");
});
break;
case BuiltInDrawIndex:
entry_func.fixup_hooks_in.push_back([=]() {
statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = *",
to_expression(draw_index_buffer_id), ";");
});
break;
default:
break;
}
@@ -18120,8 +18139,9 @@ string CompilerMSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage)
{
SPIRV_CROSS_THROW("BaseInstance requires Metal 1.1 and Mac or Apple A9+ hardware.");
}
case BuiltInDrawIndex:
SPIRV_CROSS_THROW("DrawIndex is not supported in MSL.");
return "gl_DrawID";
// When used in the entry function, output builtins are qualified with output struct name.
// Test storage class as NOT Input, as output builtins might be part of generic type.
@@ -18232,8 +18252,6 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin)
return "instance_id";
case BuiltInBaseInstance:
return "base_instance";
case BuiltInDrawIndex:
SPIRV_CROSS_THROW("DrawIndex is not supported in MSL.");
// Vertex function out
case BuiltInClipDistance:
@@ -18457,7 +18475,7 @@ string CompilerMSL::builtin_type_decl(BuiltIn builtin, uint32_t id)
case BuiltInBaseInstance:
return "uint";
case BuiltInDrawIndex:
SPIRV_CROSS_THROW("DrawIndex is not supported in MSL.");
return "uint";
// Vertex function out
case BuiltInClipDistance:
@@ -19671,6 +19689,7 @@ void CompilerMSL::cast_from_variable_load(uint32_t source_id, std::string &expr,
case BuiltInSubgroupSize:
case BuiltInSubgroupLocalInvocationId:
case BuiltInViewIndex:
case BuiltInDrawIndex:
case BuiltInVertexIndex:
case BuiltInInstanceIndex:
case BuiltInBaseInstance:
+2
View File
@@ -317,6 +317,7 @@ public:
uint32_t shader_input_buffer_index = 22;
uint32_t shader_index_buffer_index = 21;
uint32_t shader_patch_input_buffer_index = 20;
uint32_t draw_id_buffer_index = 19;
uint32_t shader_input_wg_index = 0;
uint32_t device_index = 0;
uint32_t enable_frag_output_mask = 0xffffffff;
@@ -1180,6 +1181,7 @@ protected:
uint32_t swizzle_buffer_id = 0;
uint32_t buffer_size_buffer_id = 0;
uint32_t view_mask_buffer_id = 0;
uint32_t draw_index_buffer_id = 0;
uint32_t dynamic_offsets_buffer_id = 0;
uint32_t uint_type_id = 0;
uint32_t shared_uint_type_id = 0;
+86 -3
View File
@@ -151,8 +151,29 @@ void Parser::parse()
SPIRV_CROSS_THROW("Function was not terminated.");
if (current_block)
SPIRV_CROSS_THROW("Block was not terminated.");
// Now that all definitions are bound to a kind, we can filter the library
// exports and populate the exported functions.
for (uint32_t id : ir.library_exports)
{
if (ir.ids[id].get_type() == TypeFunction)
ir.library_exported_functions.push_back(id);
}
if (ir.default_entry_point == 0)
SPIRV_CROSS_THROW("There is no entry point in the SPIR-V module.");
{
if (ir.library_exported_functions.empty())
SPIRV_CROSS_THROW("There is no entry point in the SPIR-V module.");
// No OpEntryPoint, but the module exports functions. Treat as a library
// module: designate the first exported function as the default entry
// point so analyses keyed on default_entry_point can run.
ir.is_library_module = true;
ir.default_entry_point = ir.library_exported_functions.front();
auto &name = ir.get_name(ir.default_entry_point);
ir.entry_points.insert(std::make_pair(ir.default_entry_point,
SPIREntryPoint(ir.default_entry_point, ExecutionModelGLCompute, name)));
}
}
const uint32_t *Parser::stream(const Instruction &instr) const
@@ -604,6 +625,19 @@ void Parser::parse(const Instruction &instruction)
else
ir.set_decoration(id, decoration);
// Track exported functions so we can compile library modules that have no OpEntryPoint.
// LinkageAttributes layout: literal-string (variable words) followed by LinkageType.
if (decoration == DecorationLinkageAttributes && length >= 4 &&
static_cast<LinkageType>(ops[length - 1]) == LinkageTypeExport)
{
ir.library_exports.push_back(id);
// If OpName was stripped (e.g. by spirv-opt --strip-debug), fall back
// to the linkage name so the emitted function keeps its export name.
if (ir.get_name(id).empty())
ir.set_name(id, extract_string(ir.spirv, instruction.offset + 2));
}
break;
}
@@ -627,6 +661,12 @@ void Parser::parse(const Instruction &instruction)
break;
}
// MemberDecorateIdEXT only applies to OffsetIdEXT when descriptors are packed in structs.
// This is currently unsupported and will fail in compilation.
// Pass it through in case someone just needs reflection.
case OpMemberDecorateIdEXT:
break;
case OpMemberDecorateStringGOOGLE:
{
uint32_t id = ops[0];
@@ -859,6 +899,7 @@ void Parser::parse(const Instruction &instruction)
break;
}
case OpTypeUntypedPointerKHR:
case OpTypePointer:
{
uint32_t id = ops[0];
@@ -866,7 +907,7 @@ void Parser::parse(const Instruction &instruction)
// Very rarely, we might receive a FunctionPrototype here.
// We won't be able to compile it, but we shouldn't crash when parsing.
// We should be able to reflect.
auto *base = maybe_get<SPIRType>(ops[2]);
auto *base = op == OpTypePointer ? maybe_get<SPIRType>(ops[2]) : nullptr;
auto &ptrbase = set<SPIRType>(id, op);
if (base)
@@ -885,7 +926,10 @@ void Parser::parse(const Instruction &instruction)
if (base && base->forward_pointer)
forward_pointer_fixups.push_back({ id, ops[2] });
ptrbase.parent_type = ops[2];
if (op == OpTypePointer)
ptrbase.parent_type = ops[2];
else
ptrbase.basetype = SPIRType::Void;
// Do NOT set ptrbase.self!
break;
@@ -1005,6 +1049,27 @@ void Parser::parse(const Instruction &instruction)
break;
}
case OpUntypedVariableKHR:
{
uint32_t type = ops[0];
uint32_t id = ops[1];
auto storage = static_cast<StorageClass>(ops[2]);
uint32_t data_type = length >= 4 ? ops[3] : 0;
uint32_t initializer = length >= 5 ? ops[4] : 0;
if (storage == StorageClassFunction)
{
if (!current_function)
SPIRV_CROSS_THROW("No function currently in scope");
current_function->add_local_variable(id);
}
auto &v = set<SPIRVariable>(id, type, storage, initializer);
v.untyped = true;
v.untyped_alloca_type = data_type;
break;
}
// OpPhi
// OpPhi is a fairly magical opcode.
// It selects temporary variables based on which parent block we *came from*.
@@ -1135,6 +1200,24 @@ void Parser::parse(const Instruction &instruction)
break;
}
case OpConstantSizeOfEXT:
{
uint32_t id = ops[1];
uint32_t type = ops[0];
auto &c = set<SPIRConstant>(id, type);
c.size_of_type = ops[2];
break;
}
case OpTypeBufferEXT:
{
uint32_t type = ops[0];
auto &t = set<SPIRType>(type, OpTypeBufferEXT);
t.basetype = SPIRType::DescriptorHeapBuffer;
t.ext.descriptor_heap_buffer.storage = static_cast<StorageClass>(ops[1]);
break;
}
// Functions
case OpFunction:
{