diff --git a/src/libraries/spirv_cross/GLSL.std.450.h b/src/libraries/spirv_cross/GLSL.std.450.h index 54cc00e9a..2686fc4ea 100644 --- a/src/libraries/spirv_cross/GLSL.std.450.h +++ b/src/libraries/spirv_cross/GLSL.std.450.h @@ -1,27 +1,10 @@ /* -** Copyright (c) 2014-2016 The Khronos Group Inc. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and/or associated documentation files (the "Materials"), -** to deal in the Materials without restriction, including without limitation -** the rights to use, copy, modify, merge, publish, distribute, sublicense, -** and/or sell copies of the Materials, and to permit persons to whom the -** Materials are furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Materials. -** -** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS -** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND -** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ -** -** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS -** IN THE MATERIALS. + * Copyright 2014-2016,2021 The Khronos Group, Inc. + * SPDX-License-Identifier: MIT + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS + * STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND + * HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ */ #ifndef GLSLstd450_H diff --git a/src/libraries/spirv_cross/include/spirv_cross/barrier.hpp b/src/libraries/spirv_cross/include/spirv_cross/barrier.hpp index bfcd22843..4ca7f4d77 100644 --- a/src/libraries/spirv_cross/include/spirv_cross/barrier.hpp +++ b/src/libraries/spirv_cross/include/spirv_cross/barrier.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/libraries/spirv_cross/include/spirv_cross/external_interface.h b/src/libraries/spirv_cross/include/spirv_cross/external_interface.h index 1d26f1e1e..949654f5b 100644 --- a/src/libraries/spirv_cross/include/spirv_cross/external_interface.h +++ b/src/libraries/spirv_cross/include/spirv_cross/external_interface.h @@ -1,5 +1,6 @@ /* * Copyright 2015-2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/libraries/spirv_cross/include/spirv_cross/image.hpp b/src/libraries/spirv_cross/include/spirv_cross/image.hpp index 73de894f8..a41ccdfbb 100644 --- a/src/libraries/spirv_cross/include/spirv_cross/image.hpp +++ b/src/libraries/spirv_cross/include/spirv_cross/image.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/libraries/spirv_cross/include/spirv_cross/internal_interface.hpp b/src/libraries/spirv_cross/include/spirv_cross/internal_interface.hpp index e56223dfd..3ff7f8e25 100644 --- a/src/libraries/spirv_cross/include/spirv_cross/internal_interface.hpp +++ b/src/libraries/spirv_cross/include/spirv_cross/internal_interface.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/libraries/spirv_cross/include/spirv_cross/sampler.hpp b/src/libraries/spirv_cross/include/spirv_cross/sampler.hpp index 542e11b44..020848095 100644 --- a/src/libraries/spirv_cross/include/spirv_cross/sampler.hpp +++ b/src/libraries/spirv_cross/include/spirv_cross/sampler.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/libraries/spirv_cross/include/spirv_cross/thread_group.hpp b/src/libraries/spirv_cross/include/spirv_cross/thread_group.hpp index 377f098b4..b21558156 100644 --- a/src/libraries/spirv_cross/include/spirv_cross/thread_group.hpp +++ b/src/libraries/spirv_cross/include/spirv_cross/thread_group.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/libraries/spirv_cross/spirv_cfg.cpp b/src/libraries/spirv_cross/spirv_cfg.cpp index 0a66d60bd..a938634e2 100644 --- a/src/libraries/spirv_cross/spirv_cfg.cpp +++ b/src/libraries/spirv_cross/spirv_cfg.cpp @@ -1,5 +1,6 @@ /* * Copyright 2016-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_cfg.hpp" @@ -135,7 +135,9 @@ bool CFG::post_order_visit(uint32_t block_id) break; case SPIRBlock::MultiSelect: - for (auto &target : block.cases) + { + const auto &cases = compiler.get_case_list(block); + for (const auto &target : cases) { if (post_order_visit(target.block)) add_branch(block_id, target.block); @@ -143,7 +145,7 @@ bool CFG::post_order_visit(uint32_t block_id) if (block.default_block && post_order_visit(block.default_block)) add_branch(block_id, block.default_block); break; - + } default: break; } @@ -385,7 +387,9 @@ void DominatorBuilder::lift_continue_block_dominator() break; case SPIRBlock::MultiSelect: - for (auto &target : block.cases) + { + auto &cases = cfg.get_compiler().get_case_list(block); + for (auto &target : cases) { if (cfg.get_visit_order(target.block) > post_order) back_edge_dominator = true; @@ -393,6 +397,7 @@ void DominatorBuilder::lift_continue_block_dominator() if (block.default_block && cfg.get_visit_order(block.default_block) > post_order) back_edge_dominator = true; break; + } default: break; diff --git a/src/libraries/spirv_cross/spirv_cfg.hpp b/src/libraries/spirv_cross/spirv_cfg.hpp index 041526d65..90973b567 100644 --- a/src/libraries/spirv_cross/spirv_cfg.hpp +++ b/src/libraries/spirv_cross/spirv_cfg.hpp @@ -1,5 +1,6 @@ /* * Copyright 2016-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_CFG_HPP diff --git a/src/libraries/spirv_cross/spirv_common.hpp b/src/libraries/spirv_cross/spirv_common.hpp index a7a84188f..bb2260e4d 100644 --- a/src/libraries/spirv_cross/spirv_common.hpp +++ b/src/libraries/spirv_cross/spirv_common.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_COMMON_HPP @@ -211,6 +211,28 @@ inline std::string convert_to_string(const T &t) return std::to_string(t); } +static inline std::string convert_to_string(int32_t value) +{ + // INT_MIN is ... special on some backends. If we use a decimal literal, and negate it, we + // could accidentally promote the literal to long first, then negate. + // To workaround it, emit int(0x80000000) instead. + if (value == std::numeric_limits::min()) + return "int(0x80000000)"; + else + return std::to_string(value); +} + +static inline std::string convert_to_string(int64_t value, const std::string &int64_type, bool long_long_literal_suffix) +{ + // INT64_MIN is ... special on some backends. + // If we use a decimal literal, and negate it, we might overflow the representable numbers. + // To workaround it, emit int(0x80000000) instead. + if (value == std::numeric_limits::min()) + return join(int64_type, "(0x8000000000000000u", (long_long_literal_suffix ? "ll" : "l"), ")"); + else + return std::to_string(value) + (long_long_literal_suffix ? "ll" : "l"); +} + // Allow implementations to set a convenient standard precision #ifndef SPIRV_CROSS_FLT_FMT #define SPIRV_CROSS_FLT_FMT "%.32g" @@ -417,6 +439,11 @@ struct IVariant virtual ~IVariant() = default; virtual IVariant *clone(ObjectPoolBase *pool) = 0; ID self = 0; + +protected: + IVariant() = default; + IVariant(const IVariant&) = default; + IVariant &operator=(const IVariant&) = default; }; #define SPIRV_CROSS_DECLARE_CLONE(T) \ @@ -827,10 +854,11 @@ struct SPIRBlock : IVariant struct Case { - uint32_t value; + uint64_t value; BlockID block; }; - SmallVector cases; + SmallVector cases_32bit; + SmallVector cases_64bit; // If we have tried to optimize code for this block but failed, // keep track of this. @@ -1372,7 +1400,7 @@ public: ~Variant() { if (holder) - group->pools[type]->free_opaque(holder); + group->pools[type]->deallocate_opaque(holder); } // Marking custom move constructor as noexcept is important. @@ -1391,7 +1419,7 @@ public: if (this != &other) { if (holder) - group->pools[type]->free_opaque(holder); + group->pools[type]->deallocate_opaque(holder); holder = other.holder; group = other.group; type = other.type; @@ -1415,7 +1443,7 @@ public: if (this != &other) { if (holder) - group->pools[type]->free_opaque(holder); + group->pools[type]->deallocate_opaque(holder); if (other.holder) holder = other.holder->clone(group->pools[other.type].get()); @@ -1431,13 +1459,13 @@ public: void set(IVariant *val, Types new_type) { if (holder) - group->pools[type]->free_opaque(holder); + group->pools[type]->deallocate_opaque(holder); holder = nullptr; if (!allow_type_rewrite && type != TypeNone && type != new_type) { if (val) - group->pools[new_type]->free_opaque(val); + group->pools[new_type]->deallocate_opaque(val); SPIRV_CROSS_THROW("Overwriting a variant with new type."); } @@ -1492,7 +1520,7 @@ public: void reset() { if (holder) - group->pools[type]->free_opaque(holder); + group->pools[type]->deallocate_opaque(holder); holder = nullptr; type = TypeNone; } @@ -1763,6 +1791,22 @@ struct SetBindingPair } }; +struct LocationComponentPair +{ + uint32_t location; + uint32_t component; + + inline bool operator==(const LocationComponentPair &other) const + { + return location == other.location && component == other.component; + } + + inline bool operator<(const LocationComponentPair &other) const + { + return location < other.location || (location == other.location && component < other.component); + } +}; + struct StageSetBinding { spv::ExecutionModel model; @@ -1785,6 +1829,14 @@ struct InternalHasher return (hash_set * 0x10001b31) ^ hash_binding; } + inline size_t operator()(const LocationComponentPair &value) const + { + // Quality of hash doesn't really matter here. + auto hash_set = std::hash()(value.location); + auto hash_binding = std::hash()(value.component); + return (hash_set * 0x10001b31) ^ hash_binding; + } + inline size_t operator()(const StageSetBinding &value) const { // Quality of hash doesn't really matter here. diff --git a/src/libraries/spirv_cross/spirv_cpp.cpp b/src/libraries/spirv_cross/spirv_cpp.cpp index e95fe00ee..b7946bf33 100644 --- a/src/libraries/spirv_cross/spirv_cpp.cpp +++ b/src/libraries/spirv_cross/spirv_cpp.cpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_cpp.hpp" diff --git a/src/libraries/spirv_cross/spirv_cpp.hpp b/src/libraries/spirv_cross/spirv_cpp.hpp index a3bf0edb3..c76629cdc 100644 --- a/src/libraries/spirv_cross/spirv_cpp.hpp +++ b/src/libraries/spirv_cross/spirv_cpp.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_CPP_HPP diff --git a/src/libraries/spirv_cross/spirv_cross.cpp b/src/libraries/spirv_cross/spirv_cross.cpp index f024fa78b..dc8360663 100644 --- a/src/libraries/spirv_cross/spirv_cross.cpp +++ b/src/libraries/spirv_cross/spirv_cross.cpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_cross.hpp" @@ -167,6 +167,12 @@ bool Compiler::block_is_pure(const SPIRBlock &block) case OpTraceRayKHR: case OpExecuteCallableNV: case OpExecuteCallableKHR: + case OpRayQueryInitializeKHR: + case OpRayQueryTerminateKHR: + case OpRayQueryGenerateIntersectionKHR: + case OpRayQueryConfirmIntersectionKHR: + case OpRayQueryProceedKHR: + // There are various getters in ray query, but they are considered pure. return false; // OpExtInst is potentially impure depending on extension, but GLSL builtins are at least pure. @@ -175,6 +181,30 @@ bool Compiler::block_is_pure(const SPIRBlock &block) // This is a global side effect of the function. return false; + case OpExtInst: + { + uint32_t extension_set = ops[2]; + if (get(extension_set).ext == SPIRExtension::GLSL) + { + auto op_450 = static_cast(ops[3]); + switch (op_450) + { + case GLSLstd450Modf: + case GLSLstd450Frexp: + { + auto &type = expression_type(ops[5]); + if (type.storage != StorageClassFunction) + return false; + break; + } + + default: + break; + } + } + break; + } + default: break; } @@ -284,31 +314,6 @@ SPIRVariable *Compiler::maybe_get_backing_variable(uint32_t chain) return var; } -StorageClass Compiler::get_expression_effective_storage_class(uint32_t ptr) -{ - auto *var = maybe_get_backing_variable(ptr); - - // If the expression has been lowered to a temporary, we need to use the Generic storage class. - // We're looking for the effective storage class of a given expression. - // An access chain or forwarded OpLoads from such access chains - // will generally have the storage class of the underlying variable, but if the load was not forwarded - // we have lost any address space qualifiers. - bool forced_temporary = ir.ids[ptr].get_type() == TypeExpression && !get(ptr).access_chain && - (forced_temporaries.count(ptr) != 0 || forwarded_temporaries.count(ptr) == 0); - - if (var && !forced_temporary) - { - // Normalize SSBOs to StorageBuffer here. - if (var->storage == StorageClassUniform && - has_decoration(get(var->basetype).self, DecorationBufferBlock)) - return StorageClassStorageBuffer; - else - return var->storage; - } - else - return expression_type(ptr).storage; -} - void Compiler::register_read(uint32_t expr, uint32_t chain, bool forwarded) { auto &e = get(expr); @@ -735,6 +740,15 @@ bool Compiler::InterfaceVariableAccessHandler::handle(Op opcode, const uint32_t break; } + case GLSLstd450Modf: + case GLSLstd450Fract: + { + auto *var = compiler.maybe_get(args[5]); + if (var && storage_class_is_interface(var->storage)) + variables.insert(args[5]); + break; + } + default: break; } @@ -853,19 +867,79 @@ ShaderResources Compiler::get_shader_resources(const unordered_set * // It is possible for uniform storage classes to be passed as function parameters, so detect // that. To detect function parameters, check of StorageClass of variable is function scope. - if (var.storage == StorageClassFunction || !type.pointer || is_builtin_variable(var)) + if (var.storage == StorageClassFunction || !type.pointer) return; if (active_variables && active_variables->find(var.self) == end(*active_variables)) return; + // In SPIR-V 1.4 and up, every global must be present in the entry point interface list, + // not just IO variables. + bool active_in_entry_point = true; + if (ir.get_spirv_version() < 0x10400) + { + if (var.storage == StorageClassInput || var.storage == StorageClassOutput) + active_in_entry_point = interface_variable_exists_in_entry_point(var.self); + } + else + active_in_entry_point = interface_variable_exists_in_entry_point(var.self); + + if (!active_in_entry_point) + return; + + bool is_builtin = is_builtin_variable(var); + + if (is_builtin) + { + if (var.storage != StorageClassInput && var.storage != StorageClassOutput) + return; + + auto &list = var.storage == StorageClassInput ? res.builtin_inputs : res.builtin_outputs; + BuiltInResource resource; + + if (has_decoration(type.self, DecorationBlock)) + { + resource.resource = { var.self, var.basetype, type.self, + get_remapped_declared_block_name(var.self, false) }; + + for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) + { + resource.value_type_id = type.member_types[i]; + resource.builtin = BuiltIn(get_member_decoration(type.self, i, DecorationBuiltIn)); + list.push_back(resource); + } + } + else + { + bool strip_array = + !has_decoration(var.self, DecorationPatch) && ( + get_execution_model() == ExecutionModelTessellationControl || + (get_execution_model() == ExecutionModelTessellationEvaluation && + var.storage == StorageClassInput)); + + resource.resource = { var.self, var.basetype, type.self, get_name(var.self) }; + + if (strip_array && !type.array.empty()) + resource.value_type_id = get_variable_data_type(var).parent_type; + else + resource.value_type_id = get_variable_data_type_id(var); + + assert(resource.value_type_id); + + resource.builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn)); + list.push_back(std::move(resource)); + } + return; + } + // Input - if (var.storage == StorageClassInput && interface_variable_exists_in_entry_point(var.self)) + if (var.storage == StorageClassInput) { if (has_decoration(type.self, DecorationBlock)) { res.stage_inputs.push_back( - { var.self, var.basetype, type.self, get_remapped_declared_block_name(var.self, false) }); + { var.self, var.basetype, type.self, + get_remapped_declared_block_name(var.self, false) }); } else res.stage_inputs.push_back({ var.self, var.basetype, type.self, get_name(var.self) }); @@ -876,12 +950,12 @@ ShaderResources Compiler::get_shader_resources(const unordered_set * res.subpass_inputs.push_back({ var.self, var.basetype, type.self, get_name(var.self) }); } // Outputs - else if (var.storage == StorageClassOutput && interface_variable_exists_in_entry_point(var.self)) + else if (var.storage == StorageClassOutput) { if (has_decoration(type.self, DecorationBlock)) { res.stage_outputs.push_back( - { var.self, var.basetype, type.self, get_remapped_declared_block_name(var.self, false) }); + { var.self, var.basetype, type.self, get_remapped_declared_block_name(var.self, false) }); } else res.stage_outputs.push_back({ var.self, var.basetype, type.self, get_name(var.self) }); @@ -1585,6 +1659,39 @@ SPIRBlock::ContinueBlockType Compiler::continue_block_type(const SPIRBlock &bloc } } +const SmallVector &Compiler::get_case_list(const SPIRBlock &block) const +{ + uint32_t width = 0; + + // First we check if we can get the type directly from the block.condition + // since it can be a SPIRConstant or a SPIRVariable. + if (const auto *constant = maybe_get(block.condition)) + { + const auto &type = get(constant->constant_type); + width = type.width; + } + else if (const auto *var = maybe_get(block.condition)) + { + const auto &type = get(var->basetype); + width = type.width; + } + else + { + auto search = ir.load_type_width.find(block.condition); + if (search == ir.load_type_width.end()) + { + SPIRV_CROSS_THROW("Use of undeclared variable on a switch statement."); + } + + width = search->second; + } + + if (width > 32) + return block.cases_64bit; + + return block.cases_32bit; +} + bool Compiler::traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const { handler.set_current_block(block); @@ -1618,6 +1725,9 @@ bool Compiler::traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHand } } + if (!handler.handle_terminator(block)) + return false; + return true; } @@ -1685,10 +1795,22 @@ size_t Compiler::get_declared_struct_size(const SPIRType &type) const if (type.member_types.empty()) SPIRV_CROSS_THROW("Declared struct in block cannot be empty."); - uint32_t last = uint32_t(type.member_types.size() - 1); - size_t offset = type_struct_member_offset(type, last); - size_t size = get_declared_struct_member_size(type, last); - return offset + size; + // Offsets can be declared out of order, so we need to deduce the actual size + // based on last member instead. + uint32_t member_index = 0; + size_t highest_offset = 0; + for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) + { + size_t offset = type_struct_member_offset(type, i); + if (offset > highest_offset) + { + highest_offset = offset; + member_index = i; + } + } + + size_t size = get_declared_struct_member_size(type, member_index); + return highest_offset + size; } size_t Compiler::get_declared_struct_size_runtime_array(const SPIRType &type, size_t array_size) const @@ -2968,12 +3090,15 @@ void Compiler::AnalyzeVariableScopeAccessHandler::set_current_block(const SPIRBl break; case SPIRBlock::MultiSelect: + { notify_variable_access(block.condition, block.self); - for (auto &target : block.cases) + auto &cases = compiler.get_case_list(block); + for (auto &target : cases) test_phi(target.block); if (block.default_block) test_phi(block.default_block); break; + } default: break; @@ -3014,6 +3139,27 @@ bool Compiler::AnalyzeVariableScopeAccessHandler::id_is_potential_temporary(uint return compiler.ir.ids[id].empty() || (compiler.ir.ids[id].get_type() == TypeExpression); } +bool Compiler::AnalyzeVariableScopeAccessHandler::handle_terminator(const SPIRBlock &block) +{ + switch (block.terminator) + { + case SPIRBlock::Return: + if (block.return_value) + notify_variable_access(block.return_value, block.self); + break; + + case SPIRBlock::Select: + case SPIRBlock::MultiSelect: + notify_variable_access(block.condition, block.self); + break; + + default: + break; + } + + return true; +} + bool Compiler::AnalyzeVariableScopeAccessHandler::handle(spv::Op op, const uint32_t *args, uint32_t length) { // Keep track of the types of temporaries, so we can hoist them out as necessary. @@ -3185,15 +3331,69 @@ bool Compiler::AnalyzeVariableScopeAccessHandler::handle(spv::Op op, const uint3 break; } + case OpSelect: + { + // In case of variable pointers, we might access a variable here. + // We cannot prove anything about these accesses however. + for (uint32_t i = 1; i < length; i++) + { + if (i >= 3) + { + auto *var = compiler.maybe_get_backing_variable(args[i]); + if (var) + { + accessed_variables_to_block[var->self].insert(current_block->self); + // Assume we can get partial writes to this variable. + partial_write_variables_to_block[var->self].insert(current_block->self); + } + } + + // Might try to copy a Phi variable here. + notify_variable_access(args[i], current_block->self); + } + break; + } + case OpExtInst: { for (uint32_t i = 4; i < length; i++) notify_variable_access(args[i], current_block->self); notify_variable_access(args[1], current_block->self); + + uint32_t extension_set = args[2]; + if (compiler.get(extension_set).ext == SPIRExtension::GLSL) + { + auto op_450 = static_cast(args[3]); + switch (op_450) + { + case GLSLstd450Modf: + case GLSLstd450Frexp: + { + uint32_t ptr = args[5]; + auto *var = compiler.maybe_get_backing_variable(ptr); + if (var) + { + accessed_variables_to_block[var->self].insert(current_block->self); + if (var->self == ptr) + complete_write_variables_to_block[var->self].insert(current_block->self); + else + partial_write_variables_to_block[var->self].insert(current_block->self); + } + break; + } + + default: + break; + } + } break; } case OpArrayLength: + // Only result is a temporary. + notify_variable_access(args[1], current_block->self); + break; + case OpLine: case OpNoLine: // Uses literals, but cannot be a phi variable or temporary, so ignore. @@ -4071,7 +4271,7 @@ void Compiler::update_active_builtins() } // Returns whether this shader uses a builtin of the storage class -bool Compiler::has_active_builtin(BuiltIn builtin, StorageClass storage) +bool Compiler::has_active_builtin(BuiltIn builtin, StorageClass storage) const { const Bitset *flags; switch (storage) @@ -4284,16 +4484,13 @@ bool Compiler::CombinedImageSamplerUsageHandler::handle(Op opcode, const uint32_ if (length < 4) return false; - uint32_t result_type = args[0]; - uint32_t result_id = args[1]; - auto &type = compiler.get(result_type); - // If the underlying resource has been used for comparison then duplicate loads of that resource must be too. // This image must be a depth image. + uint32_t result_id = args[1]; uint32_t image = args[2]; uint32_t sampler = args[3]; - if (type.image.depth || dref_combined_samplers.count(result_id) != 0) + if (dref_combined_samplers.count(result_id) != 0) { add_hierarchy_to_comparison_ids(image); @@ -4553,9 +4750,11 @@ bool Compiler::is_desktop_only_format(spv::ImageFormat format) return false; } -bool Compiler::image_is_comparison(const SPIRType &type, uint32_t id) const +// An image is determined to be a depth image if it is marked as a depth image and is not also +// explicitly marked with a color format, or if there are any sample/gather compare operations on it. +bool Compiler::is_depth_image(const SPIRType &type, uint32_t id) const { - return type.image.depth || (comparison_ids.count(id) != 0); + return (type.image.depth && type.image.format == ImageFormatUnknown) || comparison_ids.count(id); } bool Compiler::type_is_opaque_value(const SPIRType &type) const @@ -4585,31 +4784,181 @@ Compiler::PhysicalStorageBufferPointerHandler::PhysicalStorageBufferPointerHandl { } -bool Compiler::PhysicalStorageBufferPointerHandler::handle(Op op, const uint32_t *args, uint32_t) +Compiler::PhysicalBlockMeta *Compiler::PhysicalStorageBufferPointerHandler::find_block_meta(uint32_t id) const { - if (op == OpConvertUToPtr || op == OpBitcast) + auto chain_itr = access_chain_to_physical_block.find(id); + if (chain_itr != access_chain_to_physical_block.end()) + return chain_itr->second; + else + return nullptr; +} + +void Compiler::PhysicalStorageBufferPointerHandler::mark_aligned_access(uint32_t id, const uint32_t *args, uint32_t length) +{ + uint32_t mask = *args; + args++; + length--; + if (length && (mask & MemoryAccessVolatileMask) != 0) { - auto &type = compiler.get(args[0]); - if (type.storage == StorageClassPhysicalStorageBufferEXT && type.pointer && type.pointer_depth == 1) + args++; + length--; + } + + if (length && (mask & MemoryAccessAlignedMask) != 0) + { + uint32_t alignment = *args; + auto *meta = find_block_meta(id); + + // This makes the assumption that the application does not rely on insane edge cases like: + // Bind buffer with ADDR = 8, use block offset of 8 bytes, load/store with 16 byte alignment. + // If we emit the buffer with alignment = 16 here, the first element at offset = 0 should + // actually have alignment of 8 bytes, but this is too theoretical and awkward to support. + // We could potentially keep track of any offset in the access chain, but it's + // practically impossible for high level compilers to emit code like that, + // so deducing overall alignment requirement based on maximum observed Alignment value is probably fine. + if (meta && alignment > meta->alignment) + meta->alignment = alignment; + } +} + +bool Compiler::PhysicalStorageBufferPointerHandler::type_is_bda_block_entry(uint32_t type_id) const +{ + auto &type = compiler.get(type_id); + return type.storage == StorageClassPhysicalStorageBufferEXT && type.pointer && + type.pointer_depth == 1 && !compiler.type_is_array_of_pointers(type); +} + +uint32_t Compiler::PhysicalStorageBufferPointerHandler::get_minimum_scalar_alignment(const SPIRType &type) const +{ + if (type.storage == spv::StorageClassPhysicalStorageBufferEXT) + return 8; + else if (type.basetype == SPIRType::Struct) + { + uint32_t alignment = 0; + for (auto &member_type : type.member_types) { - // If we need to cast to a pointer type which is not a block, we might need to synthesize ourselves - // a block type which wraps this POD type. - if (type.basetype != SPIRType::Struct) - types.insert(args[0]); + uint32_t member_align = get_minimum_scalar_alignment(compiler.get(member_type)); + if (member_align > alignment) + alignment = member_align; } + return alignment; + } + else + return type.width / 8; +} + +void Compiler::PhysicalStorageBufferPointerHandler::setup_meta_chain(uint32_t type_id, uint32_t var_id) +{ + if (type_is_bda_block_entry(type_id)) + { + auto &meta = physical_block_type_meta[type_id]; + access_chain_to_physical_block[var_id] = &meta; + + auto &type = compiler.get(type_id); + if (type.basetype != SPIRType::Struct) + non_block_types.insert(type_id); + + if (meta.alignment == 0) + meta.alignment = get_minimum_scalar_alignment(compiler.get_pointee_type(type)); + } +} + +bool Compiler::PhysicalStorageBufferPointerHandler::handle(Op op, const uint32_t *args, uint32_t length) +{ + // When a BDA pointer comes to life, we need to keep a mapping of SSA ID -> type ID for the pointer type. + // For every load and store, we'll need to be able to look up the type ID being accessed and mark any alignment + // requirements. + switch (op) + { + case OpConvertUToPtr: + case OpBitcast: + case OpCompositeExtract: + // Extract can begin a new chain if we had a struct or array of pointers as input. + // We don't begin chains before we have a pure scalar pointer. + setup_meta_chain(args[0], args[1]); + break; + + case OpAccessChain: + case OpInBoundsAccessChain: + case OpPtrAccessChain: + case OpCopyObject: + { + auto itr = access_chain_to_physical_block.find(args[2]); + if (itr != access_chain_to_physical_block.end()) + access_chain_to_physical_block[args[1]] = itr->second; + break; + } + + case OpLoad: + { + setup_meta_chain(args[0], args[1]); + if (length >= 4) + mark_aligned_access(args[2], args + 3, length - 3); + break; + } + + case OpStore: + { + if (length >= 3) + mark_aligned_access(args[0], args + 2, length - 2); + break; + } + + default: + break; } return true; } +uint32_t Compiler::PhysicalStorageBufferPointerHandler::get_base_non_block_type_id(uint32_t type_id) const +{ + auto *type = &compiler.get(type_id); + while (type->pointer && + type->storage == StorageClassPhysicalStorageBufferEXT && + !type_is_bda_block_entry(type_id)) + { + type_id = type->parent_type; + type = &compiler.get(type_id); + } + + assert(type_is_bda_block_entry(type_id)); + return type_id; +} + +void Compiler::PhysicalStorageBufferPointerHandler::analyze_non_block_types_from_block(const SPIRType &type) +{ + for (auto &member : type.member_types) + { + auto &subtype = compiler.get(member); + if (subtype.basetype != SPIRType::Struct && subtype.pointer && + subtype.storage == spv::StorageClassPhysicalStorageBufferEXT) + { + non_block_types.insert(get_base_non_block_type_id(member)); + } + else if (subtype.basetype == SPIRType::Struct && !subtype.pointer) + analyze_non_block_types_from_block(subtype); + } +} + void Compiler::analyze_non_block_pointer_types() { PhysicalStorageBufferPointerHandler handler(*this); traverse_all_reachable_opcodes(get(ir.default_entry_point), handler); - physical_storage_non_block_pointer_types.reserve(handler.types.size()); - for (auto type : handler.types) + + // Analyze any block declaration we have to make. It might contain + // physical pointers to POD types which we never used, and thus never added to the list. + // We'll need to add those pointer types to the set of types we declare. + ir.for_each_typed_id([&](uint32_t, SPIRType &type) { + if (has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock)) + handler.analyze_non_block_types_from_block(type); + }); + + physical_storage_non_block_pointer_types.reserve(handler.non_block_types.size()); + for (auto type : handler.non_block_types) physical_storage_non_block_pointer_types.push_back(type); sort(begin(physical_storage_non_block_pointer_types), end(physical_storage_non_block_pointer_types)); + physical_storage_type_to_alignment = move(handler.physical_block_type_meta); } bool Compiler::InterlockedResourceAccessPrepassHandler::handle(Op op, const uint32_t *, uint32_t) diff --git a/src/libraries/spirv_cross/spirv_cross.hpp b/src/libraries/spirv_cross/spirv_cross.hpp index 84e23ca30..c945401d8 100644 --- a/src/libraries/spirv_cross/spirv_cross.hpp +++ b/src/libraries/spirv_cross/spirv_cross.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_HPP @@ -59,6 +59,27 @@ struct Resource std::string name; }; +struct BuiltInResource +{ + // This is mostly here to support reflection of builtins such as Position/PointSize/CullDistance/ClipDistance. + // This needs to be different from Resource since we can collect builtins from blocks. + // A builtin present here does not necessarily mean it's considered an active builtin, + // since variable ID "activeness" is only tracked on OpVariable level, not Block members. + // For that, update_active_builtins() -> has_active_builtin() can be used to further refine the reflection. + spv::BuiltIn builtin; + + // This is the actual value type of the builtin. + // Typically float4, float, array for the gl_PerVertex builtins. + // If the builtin is a control point, the control point array type will be stripped away here as appropriate. + TypeID value_type_id; + + // This refers to the base resource which contains the builtin. + // If resource is a Block, it can hold multiple builtins, or it might not be a block. + // For advanced reflection scenarios, all information in builtin/value_type_id can be deduced, + // it's just more convenient this way. + Resource resource; +}; + struct ShaderResources { SmallVector uniform_buffers; @@ -79,6 +100,9 @@ struct ShaderResources // these correspond to separate texture2D and samplers respectively. SmallVector separate_images; SmallVector separate_samplers; + + SmallVector builtin_inputs; + SmallVector builtin_outputs; }; struct CombinedImageSampler @@ -324,7 +348,7 @@ public: // Traverses all reachable opcodes and sets active_builtins to a bitmask of all builtin variables which are accessed in the shader. void update_active_builtins(); - bool has_active_builtin(spv::BuiltIn builtin, spv::StorageClass storage); + bool has_active_builtin(spv::BuiltIn builtin, spv::StorageClass storage) const; // Query and modify OpExecutionMode. const Bitset &get_execution_mode_bitset() const; @@ -647,7 +671,6 @@ 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); - spv::StorageClass get_expression_effective_storage_class(uint32_t ptr); void register_read(uint32_t expr, uint32_t chain, bool forwarded); void register_write(uint32_t chain); @@ -742,6 +765,10 @@ protected: // Return true if traversal should continue. // If false, traversal will end immediately. virtual bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) = 0; + virtual bool handle_terminator(const SPIRBlock &) + { + return true; + } virtual bool follow_function_call(const SPIRFunction &) { @@ -956,6 +983,7 @@ protected: bool id_is_phi_variable(uint32_t id) const; bool id_is_potential_temporary(uint32_t id) const; bool handle(spv::Op op, const uint32_t *args, uint32_t length) override; + bool handle_terminator(const SPIRBlock &block) override; Compiler &compiler; SPIRFunction &entry; @@ -982,15 +1010,32 @@ protected: uint32_t write_count = 0; }; + struct PhysicalBlockMeta + { + uint32_t alignment = 0; + }; + struct PhysicalStorageBufferPointerHandler : OpcodeHandler { explicit PhysicalStorageBufferPointerHandler(Compiler &compiler_); bool handle(spv::Op op, const uint32_t *args, uint32_t length) override; Compiler &compiler; - std::unordered_set types; + + std::unordered_set non_block_types; + std::unordered_map physical_block_type_meta; + std::unordered_map access_chain_to_physical_block; + + void mark_aligned_access(uint32_t id, const uint32_t *args, uint32_t length); + PhysicalBlockMeta *find_block_meta(uint32_t id) const; + bool type_is_bda_block_entry(uint32_t type_id) const; + void setup_meta_chain(uint32_t type_id, uint32_t var_id); + uint32_t get_minimum_scalar_alignment(const SPIRType &type) const; + void analyze_non_block_types_from_block(const SPIRType &type); + uint32_t get_base_non_block_type_id(uint32_t type_id) const; }; void analyze_non_block_pointer_types(); SmallVector physical_storage_non_block_pointer_types; + std::unordered_map physical_storage_type_to_alignment; void analyze_variable_scope(SPIRFunction &function, AnalyzeVariableScopeAccessHandler &handler); void find_function_local_luts(SPIRFunction &function, const AnalyzeVariableScopeAccessHandler &handler, @@ -1062,7 +1107,7 @@ protected: Bitset combined_decoration_for_member(const SPIRType &type, uint32_t index) const; static bool is_desktop_only_format(spv::ImageFormat format); - bool image_is_comparison(const SPIRType &type, uint32_t id) const; + bool is_depth_image(const SPIRType &type, uint32_t id) const; void set_extended_decoration(uint32_t id, ExtendedDecorations decoration, uint32_t value = 0); uint32_t get_extended_decoration(uint32_t id, ExtendedDecorations decoration) const; @@ -1090,6 +1135,11 @@ protected: bool is_vertex_like_shader() const; + // Get the correct case list for the OpSwitch, since it can be either a + // 32 bit wide condition or a 64 bit, but the type is not embedded in the + // instruction itself. + const SmallVector &get_case_list(const SPIRBlock &block) const; + private: // Used only to implement the old deprecated get_entry_point() interface. const SPIREntryPoint &get_first_entry_point(const std::string &name) const; diff --git a/src/libraries/spirv_cross/spirv_cross_c.cpp b/src/libraries/spirv_cross/spirv_cross_c.cpp index ace5376bb..4d5615404 100644 --- a/src/libraries/spirv_cross/spirv_cross_c.cpp +++ b/src/libraries/spirv_cross/spirv_cross_c.cpp @@ -1,5 +1,6 @@ /* * Copyright 2019-2021 Hans-Kristian Arntzen + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_cross_c.h" @@ -197,8 +197,11 @@ struct spvc_resources_s : ScratchMemoryAllocation SmallVector separate_images; SmallVector separate_samplers; SmallVector acceleration_structures; + SmallVector builtin_inputs; + SmallVector builtin_outputs; bool copy_resources(SmallVector &outputs, const SmallVector &inputs); + bool copy_resources(SmallVector &outputs, const SmallVector &inputs); bool copy_resources(const ShaderResources &resources); }; @@ -469,6 +472,9 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c case SPVC_COMPILER_OPTION_GLSL_FORCE_FLATTENED_IO_BLOCKS: options->glsl.force_flattened_io_blocks = value != 0; break; + case SPVC_COMPILER_OPTION_GLSL_OVR_MULTIVIEW_VIEW_COUNT: + options->glsl.ovr_multiview_view_count = value; + break; #endif #if SPIRV_CROSS_C_API_HLSL @@ -818,6 +824,44 @@ spvc_bool spvc_compiler_variable_is_depth_or_compare(spvc_compiler compiler, spv #endif } +spvc_result spvc_compiler_mask_stage_output_by_location(spvc_compiler compiler, + unsigned location, unsigned component) +{ +#if SPIRV_CROSS_C_API_GLSL + if (compiler->backend == SPVC_BACKEND_NONE) + { + compiler->context->report_error("Cross-compilation related option used on NONE backend which only supports reflection."); + return SPVC_ERROR_INVALID_ARGUMENT; + } + + static_cast(compiler->compiler.get())->mask_stage_output_by_location(location, component); + return SPVC_SUCCESS; +#else + (void)location; + (void)component; + compiler->context->report_error("Cross-compilation related option used on NONE backend which only supports reflection."); + return SPVC_ERROR_INVALID_ARGUMENT; +#endif +} + +spvc_result spvc_compiler_mask_stage_output_by_builtin(spvc_compiler compiler, SpvBuiltIn builtin) +{ +#if SPIRV_CROSS_C_API_GLSL + if (compiler->backend == SPVC_BACKEND_NONE) + { + compiler->context->report_error("Cross-compilation related option used on NONE backend which only supports reflection."); + return SPVC_ERROR_INVALID_ARGUMENT; + } + + static_cast(compiler->compiler.get())->mask_stage_output_by_builtin(spv::BuiltIn(builtin)); + return SPVC_SUCCESS; +#else + (void)builtin; + compiler->context->report_error("Cross-compilation related option used on NONE backend which only supports reflection."); + return SPVC_ERROR_INVALID_ARGUMENT; +#endif +} + spvc_result spvc_compiler_hlsl_set_root_constants_layout(spvc_compiler compiler, const spvc_hlsl_root_constants *constant_info, size_t count) @@ -1551,6 +1595,30 @@ bool spvc_resources_s::copy_resources(SmallVector &outp return true; } +bool spvc_resources_s::copy_resources(SmallVector &outputs, + const SmallVector &inputs) +{ + for (auto &i : inputs) + { + spvc_reflected_builtin_resource br; + + br.value_type_id = i.value_type_id; + br.builtin = SpvBuiltIn(i.builtin); + + auto &r = br.resource; + r.base_type_id = i.resource.base_type_id; + r.type_id = i.resource.type_id; + r.id = i.resource.id; + r.name = context->allocate_name(i.resource.name); + if (!r.name) + return false; + + outputs.push_back(br); + } + + return true; +} + bool spvc_resources_s::copy_resources(const ShaderResources &resources) { if (!copy_resources(uniform_buffers, resources.uniform_buffers)) @@ -1577,6 +1645,10 @@ bool spvc_resources_s::copy_resources(const ShaderResources &resources) return false; if (!copy_resources(acceleration_structures, resources.acceleration_structures)) return false; + if (!copy_resources(builtin_inputs, resources.builtin_inputs)) + return false; + if (!copy_resources(builtin_outputs, resources.builtin_outputs)) + return false; return true; } @@ -1735,6 +1807,37 @@ spvc_result spvc_resources_get_resource_list_for_type(spvc_resources resources, return SPVC_SUCCESS; } +spvc_result spvc_resources_get_builtin_resource_list_for_type( + spvc_resources resources, spvc_builtin_resource_type type, + const spvc_reflected_builtin_resource **resource_list, + size_t *resource_size) +{ + const SmallVector *list = nullptr; + switch (type) + { + case SPVC_BUILTIN_RESOURCE_TYPE_STAGE_INPUT: + list = &resources->builtin_inputs; + break; + + case SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT: + list = &resources->builtin_outputs; + break; + + default: + break; + } + + if (!list) + { + resources->context->report_error("Invalid argument."); + return SPVC_ERROR_INVALID_ARGUMENT; + } + + *resource_size = list->size(); + *resource_list = list->data(); + return SPVC_SUCCESS; +} + void spvc_compiler_set_decoration(spvc_compiler compiler, SpvId id, SpvDecoration decoration, unsigned argument) { compiler->compiler->set_decoration(id, static_cast(decoration), argument); @@ -1937,6 +2040,18 @@ SpvExecutionModel spvc_compiler_get_execution_model(spvc_compiler compiler) return static_cast(compiler->compiler->get_execution_model()); } +void spvc_compiler_update_active_builtins(spvc_compiler compiler) +{ + compiler->compiler->update_active_builtins(); +} + +spvc_bool spvc_compiler_has_active_builtin(spvc_compiler compiler, SpvBuiltIn builtin, SpvStorageClass storage) +{ + return compiler->compiler->has_active_builtin(static_cast(builtin), static_cast(storage)) ? + SPVC_TRUE : + SPVC_FALSE; +} + spvc_type spvc_compiler_get_type_handle(spvc_compiler compiler, spvc_type_id id) { // Should only throw if an intentionally garbage ID is passed, but the IDs are not type-safe. diff --git a/src/libraries/spirv_cross/spirv_cross_c.h b/src/libraries/spirv_cross/spirv_cross_c.h index 1511a1dbd..a590c805a 100644 --- a/src/libraries/spirv_cross/spirv_cross_c.h +++ b/src/libraries/spirv_cross/spirv_cross_c.h @@ -1,5 +1,6 @@ /* * Copyright 2019-2021 Hans-Kristian Arntzen + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_C_API_H @@ -40,7 +40,7 @@ extern "C" { /* Bumped if ABI or API breaks backwards compatibility. */ #define SPVC_C_API_VERSION_MAJOR 0 /* Bumped if APIs or enumerations are added in a backwards compatible way. */ -#define SPVC_C_API_VERSION_MINOR 45 +#define SPVC_C_API_VERSION_MINOR 48 /* Bumped if internal implementation details change. */ #define SPVC_C_API_VERSION_PATCH 0 @@ -99,6 +99,13 @@ typedef struct spvc_reflected_resource const char *name; } spvc_reflected_resource; +typedef struct spvc_reflected_builtin_resource +{ + SpvBuiltIn builtin; + spvc_type_id value_type_id; + spvc_reflected_resource resource; +} spvc_reflected_builtin_resource; + /* See C++ API. */ typedef struct spvc_entry_point { @@ -221,6 +228,14 @@ typedef enum spvc_resource_type SPVC_RESOURCE_TYPE_INT_MAX = 0x7fffffff } spvc_resource_type; +typedef enum spvc_builtin_resource_type +{ + SPVC_BUILTIN_RESOURCE_TYPE_UNKNOWN = 0, + SPVC_BUILTIN_RESOURCE_TYPE_STAGE_INPUT = 1, + SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT = 2, + SPVC_BUILTIN_RESOURCE_TYPE_INT_MAX = 0x7fffffff +} spvc_builtin_resource_type; + /* Maps to spirv_cross::SPIRType::BaseType. */ typedef enum spvc_basetype { @@ -660,6 +675,8 @@ typedef enum spvc_compiler_option SPVC_COMPILER_OPTION_MSL_FORCE_SAMPLE_RATE_SHADING = 75 | SPVC_COMPILER_OPTION_MSL_BIT, SPVC_COMPILER_OPTION_MSL_IOS_SUPPORT_BASE_VERTEX_INSTANCE = 76 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_GLSL_OVR_MULTIVIEW_VIEW_COUNT = 77 | SPVC_COMPILER_OPTION_GLSL_BIT, + SPVC_COMPILER_OPTION_INT_MAX = 0x7fffffff } spvc_compiler_option; @@ -722,6 +739,10 @@ SPVC_PUBLIC_API spvc_result spvc_compiler_flatten_buffer_block(spvc_compiler com SPVC_PUBLIC_API spvc_bool spvc_compiler_variable_is_depth_or_compare(spvc_compiler compiler, spvc_variable_id id); +SPVC_PUBLIC_API spvc_result spvc_compiler_mask_stage_output_by_location(spvc_compiler compiler, + unsigned location, unsigned component); +SPVC_PUBLIC_API spvc_result spvc_compiler_mask_stage_output_by_builtin(spvc_compiler compiler, SpvBuiltIn builtin); + /* * HLSL specifics. * Maps to C++ API. @@ -805,6 +826,11 @@ SPVC_PUBLIC_API spvc_result spvc_resources_get_resource_list_for_type(spvc_resou const spvc_reflected_resource **resource_list, size_t *resource_size); +SPVC_PUBLIC_API spvc_result spvc_resources_get_builtin_resource_list_for_type( + spvc_resources resources, spvc_builtin_resource_type type, + const spvc_reflected_builtin_resource **resource_list, + size_t *resource_size); + /* * Decorations. * Maps to C++ API. @@ -861,6 +887,8 @@ SPVC_PUBLIC_API unsigned spvc_compiler_get_execution_mode_argument(spvc_compiler SPVC_PUBLIC_API unsigned spvc_compiler_get_execution_mode_argument_by_index(spvc_compiler compiler, SpvExecutionMode mode, unsigned index); SPVC_PUBLIC_API SpvExecutionModel spvc_compiler_get_execution_model(spvc_compiler compiler); +SPVC_PUBLIC_API void spvc_compiler_update_active_builtins(spvc_compiler compiler); +SPVC_PUBLIC_API spvc_bool spvc_compiler_has_active_builtin(spvc_compiler compiler, SpvBuiltIn builtin, SpvStorageClass storage); /* * Type query interface. diff --git a/src/libraries/spirv_cross/spirv_cross_containers.hpp b/src/libraries/spirv_cross/spirv_cross_containers.hpp index b9c3737c9..506b069c7 100644 --- a/src/libraries/spirv_cross/spirv_cross_containers.hpp +++ b/src/libraries/spirv_cross/spirv_cross_containers.hpp @@ -1,5 +1,6 @@ /* * Copyright 2019-2021 Hans-Kristian Arntzen + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_CONTAINERS_HPP @@ -327,8 +327,8 @@ public: void reserve(size_t count) SPIRV_CROSS_NOEXCEPT { - if ((count > std::numeric_limits::max() / sizeof(T)) || - (count > std::numeric_limits::max() / 2)) + if ((count > (std::numeric_limits::max)() / sizeof(T)) || + (count > (std::numeric_limits::max)() / 2)) { // Only way this should ever happen is with garbage input, terminate. std::terminate(); @@ -546,7 +546,7 @@ class ObjectPoolBase { public: virtual ~ObjectPoolBase() = default; - virtual void free_opaque(void *ptr) = 0; + virtual void deallocate_opaque(void *ptr) = 0; }; template @@ -580,15 +580,15 @@ public: return ptr; } - void free(T *ptr) + void deallocate(T *ptr) { ptr->~T(); vacants.push_back(ptr); } - void free_opaque(void *ptr) override + void deallocate_opaque(void *ptr) override { - free(static_cast(ptr)); + deallocate(static_cast(ptr)); } void clear() diff --git a/src/libraries/spirv_cross/spirv_cross_error_handling.hpp b/src/libraries/spirv_cross/spirv_cross_error_handling.hpp index 32af107eb..e96ebb9a7 100644 --- a/src/libraries/spirv_cross/spirv_cross_error_handling.hpp +++ b/src/libraries/spirv_cross/spirv_cross_error_handling.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_ERROR_HANDLING diff --git a/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp b/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp index e1a13c5e0..e7fcdff04 100644 --- a/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp +++ b/src/libraries/spirv_cross/spirv_cross_parsed_ir.cpp @@ -1,5 +1,6 @@ /* * Copyright 2018-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_cross_parsed_ir.hpp" @@ -83,6 +83,7 @@ ParsedIR &ParsedIR::operator=(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT loop_iteration_depth_soft = other.loop_iteration_depth_soft; meta_needing_name_fixup = std::move(other.meta_needing_name_fixup); + load_type_width = std::move(other.load_type_width); } return *this; } @@ -115,7 +116,9 @@ ParsedIR &ParsedIR::operator=(const ParsedIR &other) addressing_model = other.addressing_model; memory_model = other.memory_model; + meta_needing_name_fixup = other.meta_needing_name_fixup; + load_type_width = other.load_type_width; // Very deliberate copying of IDs. There is no default copy constructor, nor a simple default constructor. // Construct object first so we have the correct allocator set-up, then we can copy object into our new pool group. diff --git a/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp b/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp index 88a069196..138d9dd43 100644 --- a/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp +++ b/src/libraries/spirv_cross/spirv_cross_parsed_ir.hpp @@ -1,5 +1,6 @@ /* * Copyright 2018-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_PARSED_IR_HPP @@ -78,6 +78,13 @@ public: SmallVector ids_for_constant_or_type; SmallVector ids_for_constant_or_variable; + // We need to keep track of the width the Ops that contains a type for the + // OpSwitch instruction, since this one doesn't contains the type in the + // instruction itself. And in some case we need to cast the condition to + // wider types. We only need the width to do the branch fixup since the + // type check itself can be done at runtime + std::unordered_map load_type_width; + // Declared capabilities and extensions in the SPIR-V module. // Not really used except for reflection at the moment. SmallVector declared_capabilities; diff --git a/src/libraries/spirv_cross/spirv_cross_util.cpp b/src/libraries/spirv_cross/spirv_cross_util.cpp index 9ce6d4fb7..7cff010d1 100644 --- a/src/libraries/spirv_cross/spirv_cross_util.cpp +++ b/src/libraries/spirv_cross/spirv_cross_util.cpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_cross_util.hpp" diff --git a/src/libraries/spirv_cross/spirv_cross_util.hpp b/src/libraries/spirv_cross/spirv_cross_util.hpp index daa4a7900..e6e3fcdb6 100644 --- a/src/libraries/spirv_cross/spirv_cross_util.hpp +++ b/src/libraries/spirv_cross/spirv_cross_util.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_UTIL_HPP diff --git a/src/libraries/spirv_cross/spirv_glsl.cpp b/src/libraries/spirv_cross/spirv_glsl.cpp index 31ff85042..cdc1c6b6e 100644 --- a/src/libraries/spirv_cross/spirv_glsl.cpp +++ b/src/libraries/spirv_cross/spirv_glsl.cpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_glsl.hpp" @@ -359,10 +359,26 @@ void CompilerGLSL::remap_pls_variables() } } -void CompilerGLSL::remap_ext_framebuffer_fetch(uint32_t input_attachment_index, uint32_t color_location) +void CompilerGLSL::remap_ext_framebuffer_fetch(uint32_t input_attachment_index, uint32_t color_location, bool coherent) { subpass_to_framebuffer_fetch_attachment.push_back({ input_attachment_index, color_location }); - inout_color_attachments.insert(color_location); + inout_color_attachments.push_back({ color_location, coherent }); +} + +bool CompilerGLSL::location_is_framebuffer_fetch(uint32_t location) const +{ + return std::find_if(begin(inout_color_attachments), end(inout_color_attachments), + [&](const std::pair &elem) { + return elem.first == location; + }) != end(inout_color_attachments); +} + +bool CompilerGLSL::location_is_non_coherent_framebuffer_fetch(uint32_t location) const +{ + return std::find_if(begin(inout_color_attachments), end(inout_color_attachments), + [&](const std::pair &elem) { + return elem.first == location && !elem.second; + }) != end(inout_color_attachments); } void CompilerGLSL::find_static_extensions() @@ -449,7 +465,8 @@ void CompilerGLSL::find_static_extensions() // Need to figure out if we should target KHR or NV extension based on capabilities. for (auto &cap : ir.declared_capabilities) { - if (cap == CapabilityRayTracingKHR || cap == CapabilityRayQueryKHR) + if (cap == CapabilityRayTracingKHR || cap == CapabilityRayQueryKHR || + cap == CapabilityRayTraversalPrimitiveCullingKHR) { ray_tracing_is_khr = true; break; @@ -484,7 +501,22 @@ void CompilerGLSL::find_static_extensions() SPIRV_CROSS_THROW("Can only use GL_EXT_shader_framebuffer_fetch in fragment shaders."); if (options.vulkan_semantics) SPIRV_CROSS_THROW("Cannot use EXT_shader_framebuffer_fetch in Vulkan GLSL."); - require_extension_internal("GL_EXT_shader_framebuffer_fetch"); + + bool has_coherent = false; + bool has_incoherent = false; + + for (auto &att : inout_color_attachments) + { + if (att.second) + has_coherent = true; + else + has_incoherent = true; + } + + if (has_coherent) + require_extension_internal("GL_EXT_shader_framebuffer_fetch"); + if (has_incoherent) + require_extension_internal("GL_EXT_shader_framebuffer_fetch_non_coherent"); } if (options.separate_shader_objects && !options.es && options.version < 410) @@ -535,10 +567,46 @@ void CompilerGLSL::find_static_extensions() case CapabilityVariablePointersStorageBuffer: SPIRV_CROSS_THROW("VariablePointers capability is not supported in GLSL."); + case CapabilityMultiView: + if (options.vulkan_semantics) + require_extension_internal("GL_EXT_multiview"); + else + { + require_extension_internal("GL_OVR_multiview2"); + if (options.ovr_multiview_view_count == 0) + SPIRV_CROSS_THROW("ovr_multiview_view_count must be non-zero when using GL_OVR_multiview2."); + if (get_execution_model() != ExecutionModelVertex) + SPIRV_CROSS_THROW("OVR_multiview2 can only be used with Vertex shaders."); + } + break; + + case CapabilityRayQueryKHR: + if (options.es || options.version < 460 || !options.vulkan_semantics) + SPIRV_CROSS_THROW("RayQuery requires Vulkan GLSL 460."); + require_extension_internal("GL_EXT_ray_query"); + ray_tracing_is_khr = true; + break; + + case CapabilityRayTraversalPrimitiveCullingKHR: + if (options.es || options.version < 460 || !options.vulkan_semantics) + SPIRV_CROSS_THROW("RayQuery requires Vulkan GLSL 460."); + require_extension_internal("GL_EXT_ray_flags_primitive_culling"); + ray_tracing_is_khr = true; + break; + default: break; } } + + if (options.ovr_multiview_view_count) + { + if (options.vulkan_semantics) + SPIRV_CROSS_THROW("OVR_multiview2 cannot be used with Vulkan semantics."); + if (get_execution_model() != ExecutionModelVertex) + SPIRV_CROSS_THROW("OVR_multiview2 can only be used with Vertex shaders."); + require_extension_internal("GL_OVR_multiview2"); + } } void CompilerGLSL::ray_tracing_khr_fixup_locations() @@ -559,18 +627,19 @@ string CompilerGLSL::compile() { ir.fixup_reserved_names(); - if (options.vulkan_semantics) - backend.allow_precision_qualifiers = true; - else + if (!options.vulkan_semantics) { // only NV_gpu_shader5 supports divergent indexing on OpenGL, and it does so without extra qualifiers backend.nonuniform_qualifier = ""; backend.needs_row_major_load_workaround = true; } + backend.allow_precision_qualifiers = options.vulkan_semantics || options.es; backend.force_gl_in_out_block = true; backend.supports_extensions = true; backend.use_array_constructor = true; + backend.support_precise_qualifier = (!options.es && options.version >= 400) || (options.es && options.version >= 320); + if (is_legacy_es()) backend.support_case_fallthrough = false; @@ -617,15 +686,9 @@ string CompilerGLSL::compile() statement("void main()"); begin_scope(); statement("// Interlocks were used in a way not compatible with GLSL, this is very slow."); - if (options.es) - statement("beginInvocationInterlockNV();"); - else - statement("beginInvocationInterlockARB();"); + statement("SPIRV_Cross_beginInvocationInterlock();"); statement("spvMainInterlockedBody();"); - if (options.es) - statement("endInvocationInterlockNV();"); - else - statement("endInvocationInterlockARB();"); + statement("SPIRV_Cross_endInvocationInterlock();"); end_scope(); } @@ -715,10 +778,12 @@ void CompilerGLSL::emit_header() require_extension_internal("GL_ARB_post_depth_coverage"); // Needed for: layout({pixel,sample}_interlock_[un]ordered) in; - if (execution.flags.get(ExecutionModePixelInterlockOrderedEXT) || - execution.flags.get(ExecutionModePixelInterlockUnorderedEXT) || - execution.flags.get(ExecutionModeSampleInterlockOrderedEXT) || - execution.flags.get(ExecutionModeSampleInterlockUnorderedEXT)) + bool interlock_used = execution.flags.get(ExecutionModePixelInterlockOrderedEXT) || + execution.flags.get(ExecutionModePixelInterlockUnorderedEXT) || + execution.flags.get(ExecutionModeSampleInterlockOrderedEXT) || + execution.flags.get(ExecutionModeSampleInterlockUnorderedEXT); + + if (interlock_used) { if (options.es) { @@ -764,6 +829,8 @@ void CompilerGLSL::emit_header() { statement("#if defined(GL_AMD_gpu_shader_int16)"); statement("#extension GL_AMD_gpu_shader_int16 : require"); + statement("#elif defined(GL_NV_gpu_shader5)"); + statement("#extension GL_NV_gpu_shader5 : require"); statement("#else"); statement("#error No extension available for Int16."); statement("#endif"); @@ -789,6 +856,40 @@ void CompilerGLSL::emit_header() statement("#extension ", ext, " : enable"); statement("#endif"); } + else if (ext == "GL_EXT_control_flow_attributes") + { + // These are just hints so we can conditionally enable and fallback in the shader. + statement("#if defined(GL_EXT_control_flow_attributes)"); + statement("#extension GL_EXT_control_flow_attributes : require"); + statement("#define SPIRV_CROSS_FLATTEN [[flatten]]"); + statement("#define SPIRV_CROSS_BRANCH [[dont_flatten]]"); + statement("#define SPIRV_CROSS_UNROLL [[unroll]]"); + statement("#define SPIRV_CROSS_LOOP [[dont_unroll]]"); + statement("#else"); + statement("#define SPIRV_CROSS_FLATTEN"); + statement("#define SPIRV_CROSS_BRANCH"); + statement("#define SPIRV_CROSS_UNROLL"); + statement("#define SPIRV_CROSS_LOOP"); + statement("#endif"); + } + else if (ext == "GL_NV_fragment_shader_interlock") + { + statement("#extension GL_NV_fragment_shader_interlock : require"); + statement("#define SPIRV_Cross_beginInvocationInterlock() beginInvocationInterlockNV()"); + statement("#define SPIRV_Cross_endInvocationInterlock() endInvocationInterlockNV()"); + } + else if (ext == "GL_ARB_fragment_shader_interlock") + { + statement("#ifdef GL_ARB_fragment_shader_interlock"); + statement("#extension GL_ARB_fragment_shader_interlock : enable"); + statement("#define SPIRV_Cross_beginInvocationInterlock() beginInvocationInterlockARB()"); + statement("#define SPIRV_Cross_endInvocationInterlock() endInvocationInterlockARB()"); + statement("#elif defined(GL_INTEL_fragment_shader_ordering)"); + statement("#extension GL_INTEL_fragment_shader_ordering : enable"); + statement("#define SPIRV_Cross_beginInvocationInterlock() beginFragmentShaderOrderingINTEL()"); + statement("#define SPIRV_Cross_endInvocationInterlock()"); + statement("#endif"); + } else statement("#extension ", ext, " : require"); } @@ -840,6 +941,10 @@ void CompilerGLSL::emit_header() switch (execution.model) { + case ExecutionModelVertex: + if (options.ovr_multiview_view_count) + inputs.push_back(join("num_views = ", options.ovr_multiview_view_count)); + break; case ExecutionModelGeometry: if ((execution.flags.get(ExecutionModeInvocations)) && execution.invocations != 1) inputs.push_back(join("invocations = ", execution.invocations)); @@ -965,14 +1070,24 @@ void CompilerGLSL::emit_header() if (execution.flags.get(ExecutionModePostDepthCoverage)) inputs.push_back("post_depth_coverage"); + if (interlock_used) + statement("#if defined(GL_ARB_fragment_shader_interlock)"); + if (execution.flags.get(ExecutionModePixelInterlockOrderedEXT)) - inputs.push_back("pixel_interlock_ordered"); + statement("layout(pixel_interlock_ordered) in;"); else if (execution.flags.get(ExecutionModePixelInterlockUnorderedEXT)) - inputs.push_back("pixel_interlock_unordered"); + statement("layout(pixel_interlock_unordered) in;"); else if (execution.flags.get(ExecutionModeSampleInterlockOrderedEXT)) - inputs.push_back("sample_interlock_ordered"); + statement("layout(sample_interlock_ordered) in;"); else if (execution.flags.get(ExecutionModeSampleInterlockUnorderedEXT)) - inputs.push_back("sample_interlock_unordered"); + statement("layout(sample_interlock_unordered) in;"); + + if (interlock_used) + { + statement("#elif !defined(GL_INTEL_fragment_shader_ordering)"); + statement("#error Fragment Shader Interlock/Ordering extension missing!"); + statement("#endif"); + } if (!options.es && execution.flags.get(ExecutionModeDepthGreater)) statement("layout(depth_greater) out float gl_FragDepth;"); @@ -985,6 +1100,10 @@ void CompilerGLSL::emit_header() break; } + for (auto &cap : ir.declared_capabilities) + if (cap == CapabilityRayTraversalPrimitiveCullingKHR) + statement("layout(primitive_culling);"); + if (!inputs.empty()) statement("layout(", merge(inputs), ") in;"); if (!outputs.empty()) @@ -1060,8 +1179,22 @@ string CompilerGLSL::to_interpolation_qualifiers(const Bitset &flags) res += "sample "; if (flags.get(DecorationInvariant)) res += "invariant "; + if (flags.get(DecorationExplicitInterpAMD)) + { + require_extension_internal("GL_AMD_shader_explicit_vertex_parameter"); res += "__explicitInterpAMD "; + } + + if (flags.get(DecorationPerVertexNV)) + { + if (options.es && options.version < 320) + SPIRV_CROSS_THROW("pervertexNV requires ESSL 320."); + else if (!options.es && options.version < 450) + SPIRV_CROSS_THROW("pervertexNV requires GLSL 450."); + require_extension_internal("GL_NV_fragment_shader_barycentric"); + res += "pervertexNV "; + } return res; } @@ -1071,8 +1204,7 @@ string CompilerGLSL::layout_for_member(const SPIRType &type, uint32_t index) if (is_legacy()) return ""; - bool is_block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock) || - ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); + bool is_block = has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock); if (!is_block) return ""; @@ -1690,6 +1822,12 @@ string CompilerGLSL::layout_for_variable(const SPIRVariable &var) attr.push_back(join("location = ", get_decoration(var.self, DecorationLocation))); } + if (get_execution_model() == ExecutionModelFragment && var.storage == StorageClassOutput && + location_is_non_coherent_framebuffer_fetch(get_decoration(var.self, DecorationLocation))) + { + attr.push_back("noncoherent"); + } + // Transform feedback bool uses_enhanced_layouts = false; if (is_block && var.storage == StorageClassOutput) @@ -2033,8 +2171,9 @@ void CompilerGLSL::emit_buffer_block_legacy(const SPIRVariable &var) statement(""); } -void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_declaration) +void CompilerGLSL::emit_buffer_reference_block(uint32_t type_id, bool forward_declaration) { + auto &type = get(type_id); string buffer_name; if (forward_declaration) @@ -2077,8 +2216,19 @@ void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_decl if (!forward_declaration) { + auto itr = physical_storage_type_to_alignment.find(type_id); + uint32_t alignment = 0; + if (itr != physical_storage_type_to_alignment.end()) + alignment = itr->second.alignment; + if (type.basetype == SPIRType::Struct) { + SmallVector attributes; + attributes.push_back("buffer_reference"); + if (alignment) + attributes.push_back(join("buffer_reference_align = ", alignment)); + attributes.push_back(buffer_to_packing_standard(type, true)); + auto flags = ir.get_buffer_block_type_flags(type); string decorations; if (flags.get(DecorationRestrict)) @@ -2089,9 +2239,11 @@ void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_decl decorations += " writeonly"; if (flags.get(DecorationNonWritable)) decorations += " readonly"; - statement("layout(buffer_reference, ", buffer_to_packing_standard(type, true), - ")", decorations, " buffer ", buffer_name); + + statement("layout(", merge(attributes), ")", decorations, " buffer ", buffer_name); } + else if (alignment) + statement("layout(buffer_reference, buffer_reference_align = ", alignment, ") buffer ", buffer_name); else statement("layout(buffer_reference) buffer ", buffer_name); @@ -2231,7 +2383,9 @@ const char *CompilerGLSL::to_storage_qualifiers_glsl(const SPIRVariable &var) return "varying "; // Fragment outputs are renamed so they never hit this case. else if (execution.model == ExecutionModelFragment && var.storage == StorageClassOutput) { - if (inout_color_attachments.count(get_decoration(var.self, DecorationLocation)) != 0) + uint32_t loc = get_decoration(var.self, DecorationLocation); + bool is_inout = location_is_framebuffer_fetch(loc); + if (is_inout) return "inout "; else return "out "; @@ -3307,28 +3461,28 @@ void CompilerGLSL::emit_resources() { for (auto type : physical_storage_non_block_pointer_types) { - emit_buffer_reference_block(get(type), false); + emit_buffer_reference_block(type, false); } // Output buffer reference blocks. // Do this in two stages, one with forward declaration, // and one without. Buffer reference blocks can reference themselves // to support things like linked lists. - ir.for_each_typed_id([&](uint32_t, SPIRType &type) { - bool has_block_flags = has_decoration(type.self, DecorationBlock); - if (has_block_flags && type.pointer && type.pointer_depth == 1 && !type_is_array_of_pointers(type) && + ir.for_each_typed_id([&](uint32_t self, SPIRType &type) { + if (type.basetype == SPIRType::Struct && type.pointer && + type.pointer_depth == 1 && !type_is_array_of_pointers(type) && type.storage == StorageClassPhysicalStorageBufferEXT) { - emit_buffer_reference_block(type, true); + emit_buffer_reference_block(self, true); } }); - ir.for_each_typed_id([&](uint32_t, SPIRType &type) { - bool has_block_flags = has_decoration(type.self, DecorationBlock); - if (has_block_flags && type.pointer && type.pointer_depth == 1 && !type_is_array_of_pointers(type) && + ir.for_each_typed_id([&](uint32_t self, SPIRType &type) { + if (type.basetype == SPIRType::Struct && + type.pointer && type.pointer_depth == 1 && !type_is_array_of_pointers(type) && type.storage == StorageClassPhysicalStorageBufferEXT) { - emit_buffer_reference_block(type, false); + emit_buffer_reference_block(self, false); } }); } @@ -3402,7 +3556,7 @@ void CompilerGLSL::emit_resources() // Unused output I/O variables might still be required to implement framebuffer fetch. if (var.storage == StorageClassOutput && !is_legacy() && - inout_color_attachments.count(get_decoration(var.self, DecorationLocation)) != 0) + location_is_framebuffer_fetch(get_decoration(var.self, DecorationLocation)) != 0) { is_hidden = false; } @@ -3411,6 +3565,11 @@ void CompilerGLSL::emit_resources() (var.storage == StorageClassInput || var.storage == StorageClassOutput) && interface_variable_exists_in_entry_point(var.self) && !is_hidden) { + if (options.es && get_execution_model() == ExecutionModelVertex && var.storage == StorageClassInput && + type.array.size() == 1) + { + SPIRV_CROSS_THROW("OpenGL ES doesn't support array input variables in vertex shader."); + } emit_interface_block(var); emitted = true; } @@ -4259,19 +4418,7 @@ string CompilerGLSL::to_unpacked_expression(uint32_t id, bool register_expressio string CompilerGLSL::to_enclosed_unpacked_expression(uint32_t id, bool register_expression_read) { - // If we need to transpose, it will also take care of unpacking rules. - auto *e = maybe_get(id); - bool need_transpose = e && e->need_transpose; - bool is_remapped = has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID); - bool is_packed = has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked); - if (!need_transpose && (is_remapped || is_packed)) - { - return unpack_expression_type(to_expression(id, register_expression_read), expression_type(id), - get_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID), - has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked), false); - } - else - return to_enclosed_expression(id, register_expression_read); + return enclose_expression(to_unpacked_expression(id, register_expression_read)); } string CompilerGLSL::to_dereferenced_expression(uint32_t id, bool register_expression_read) @@ -4395,6 +4542,16 @@ string CompilerGLSL::to_composite_constructor_expression(uint32_t id, bool uses_ return to_unpacked_expression(id); } +string CompilerGLSL::to_non_uniform_aware_expression(uint32_t id) +{ + string expr = to_expression(id); + + if (has_decoration(id, DecorationNonUniform)) + convert_non_uniform_expression(expr, id); + + return expr; +} + string CompilerGLSL::to_expression(uint32_t id, bool register_expression_read) { auto itr = invalid_expressions.find(id); @@ -4586,6 +4743,14 @@ string CompilerGLSL::constant_op_expression(const SPIRConstantOp &cop) GLSL_BOP(UGreaterThanEqual, ">="); GLSL_BOP(SGreaterThanEqual, ">="); + case OpSRem: + { + uint32_t op0 = cop.arguments[0]; + uint32_t op1 = cop.arguments[1]; + return join(to_enclosed_expression(op0), " - ", to_enclosed_expression(op1), " * ", "(", + to_enclosed_expression(op0), " / ", to_enclosed_expression(op1), ")"); + } + case OpSelect: { if (cop.arguments.size() < 3) @@ -4896,7 +5061,13 @@ string CompilerGLSL::convert_float_to_string(const SPIRConstant &c, uint32_t col char print_buffer[32]; sprintf(print_buffer, "0x%xu", c.scalar(col, row)); - res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, ")"); + + const char *comment = "inf"; + if (float_value == -numeric_limits::infinity()) + comment = "-inf"; + else if (std::isnan(float_value)) + comment = "nan"; + res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, " /* ", comment, " */)"); } else { @@ -4963,7 +5134,13 @@ std::string CompilerGLSL::convert_double_to_string(const SPIRConstant &c, uint32 char print_buffer[64]; sprintf(print_buffer, "0x%llx%s", static_cast(u64_value), backend.long_long_literal_suffix ? "ull" : "ul"); - res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, ")"); + + const char *comment = "inf"; + if (double_value == -numeric_limits::infinity()) + comment = "-inf"; + else if (std::isnan(double_value)) + comment = "nan"; + res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, " /* ", comment, " */)"); } else { @@ -5144,13 +5321,15 @@ string CompilerGLSL::constant_expression_vector(const SPIRConstant &c, uint32_t break; case SPIRType::Int64: + { + auto tmp = type; + tmp.vecsize = 1; + tmp.columns = 1; + auto int64_type = type_to_glsl(tmp); + if (splat) { - res += convert_to_string(c.scalar_i64(vector, 0)); - if (backend.long_long_literal_suffix) - res += "ll"; - else - res += "l"; + res += convert_to_string(c.scalar_i64(vector, 0), int64_type, backend.long_long_literal_suffix); } else { @@ -5159,19 +5338,14 @@ string CompilerGLSL::constant_expression_vector(const SPIRConstant &c, uint32_t if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0) res += to_name(c.specialization_constant_id(vector, i)); else - { - res += convert_to_string(c.scalar_i64(vector, i)); - if (backend.long_long_literal_suffix) - res += "ll"; - else - res += "l"; - } + res += convert_to_string(c.scalar_i64(vector, i), int64_type, backend.long_long_literal_suffix); if (i + 1 < c.vector_size()) res += ", "; } } break; + } case SPIRType::UInt64: if (splat) @@ -5533,7 +5707,12 @@ void CompilerGLSL::emit_unary_op(uint32_t result_type, uint32_t result_id, uint3 void CompilerGLSL::emit_binary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op) { - bool forward = should_forward(op0) && should_forward(op1); + // Various FP arithmetic opcodes such as add, sub, mul will hit this. + bool force_temporary_precise = backend.support_precise_qualifier && + has_decoration(result_id, DecorationNoContraction) && + type_is_floating_point(get(result_type)); + bool forward = should_forward(op0) && should_forward(op1) && !force_temporary_precise; + emit_op(result_type, result_id, join(to_enclosed_unpacked_expression(op0), " ", op, " ", to_enclosed_unpacked_expression(op1)), forward); @@ -5712,6 +5891,27 @@ void CompilerGLSL::emit_binary_func_op(uint32_t result_type, uint32_t result_id, inherit_expression_dependencies(result_id, op1); } +void CompilerGLSL::emit_atomic_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, + const char *op) +{ + forced_temporaries.insert(result_id); + emit_op(result_type, result_id, + join(op, "(", to_non_uniform_aware_expression(op0), ", ", + to_unpacked_expression(op1), ")"), false); + flush_all_atomic_capable_variables(); +} + +void CompilerGLSL::emit_atomic_func_op(uint32_t result_type, uint32_t result_id, + uint32_t op0, uint32_t op1, uint32_t op2, + const char *op) +{ + forced_temporaries.insert(result_id); + emit_op(result_type, result_id, + join(op, "(", to_non_uniform_aware_expression(op0), ", ", + to_unpacked_expression(op1), ", ", to_unpacked_expression(op2), ")"), false); + flush_all_atomic_capable_variables(); +} + void CompilerGLSL::emit_unary_func_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op, SPIRType::BaseType input_type, SPIRType::BaseType expected_result_type) { @@ -6020,7 +6220,7 @@ string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtyp // GLES has very limited support for shadow samplers. // Basically shadow2D and shadow2DProj work through EXT_shadow_samplers, // everything else can just throw - bool is_comparison = image_is_comparison(imgtype, tex); + bool is_comparison = is_depth_image(imgtype, tex); if (is_comparison && is_legacy_es()) { if (op == "texture" || op == "textureProj") @@ -6085,44 +6285,58 @@ bool CompilerGLSL::to_trivial_mix_op(const SPIRType &type, string &op, uint32_t if (cleft->specialization || cright->specialization) return false; - // We can only use trivial construction if we have a scalar - // (should be possible to do it for vectors as well, but that is overkill for now). - if (lerptype.basetype != SPIRType::Boolean || lerptype.vecsize > 1) + auto &value_type = get(cleft->constant_type); + + if (lerptype.basetype != SPIRType::Boolean) + return false; + if (value_type.basetype == SPIRType::Struct || is_array(value_type)) + return false; + if (!backend.use_constructor_splatting && value_type.vecsize != lerptype.vecsize) + return false; + + // Only valid way in SPIR-V 1.4 to use matrices in select is a scalar select. + // matrix(scalar) constructor fills in diagnonals, so gets messy very quickly. + // Just avoid this case. + if (value_type.columns > 1) return false; // If our bool selects between 0 and 1, we can cast from bool instead, making our trivial constructor. - bool ret = false; - switch (type.basetype) + bool ret = true; + for (uint32_t row = 0; ret && row < value_type.vecsize; row++) { - case SPIRType::Short: - case SPIRType::UShort: - ret = cleft->scalar_u16() == 0 && cright->scalar_u16() == 1; - break; + switch (type.basetype) + { + case SPIRType::Short: + case SPIRType::UShort: + ret = cleft->scalar_u16(0, row) == 0 && cright->scalar_u16(0, row) == 1; + break; - case SPIRType::Int: - case SPIRType::UInt: - ret = cleft->scalar() == 0 && cright->scalar() == 1; - break; + case SPIRType::Int: + case SPIRType::UInt: + ret = cleft->scalar(0, row) == 0 && cright->scalar(0, row) == 1; + break; - case SPIRType::Half: - ret = cleft->scalar_f16() == 0.0f && cright->scalar_f16() == 1.0f; - break; + case SPIRType::Half: + ret = cleft->scalar_f16(0, row) == 0.0f && cright->scalar_f16(0, row) == 1.0f; + break; - case SPIRType::Float: - ret = cleft->scalar_f32() == 0.0f && cright->scalar_f32() == 1.0f; - break; + case SPIRType::Float: + ret = cleft->scalar_f32(0, row) == 0.0f && cright->scalar_f32(0, row) == 1.0f; + break; - case SPIRType::Double: - ret = cleft->scalar_f64() == 0.0 && cright->scalar_f64() == 1.0; - break; + case SPIRType::Double: + ret = cleft->scalar_f64(0, row) == 0.0 && cright->scalar_f64(0, row) == 1.0; + break; - case SPIRType::Int64: - case SPIRType::UInt64: - ret = cleft->scalar_u64() == 0 && cright->scalar_u64() == 1; - break; + case SPIRType::Int64: + case SPIRType::UInt64: + ret = cleft->scalar_u64(0, row) == 0 && cright->scalar_u64(0, row) == 1; + break; - default: - break; + default: + ret = false; + break; + } } if (ret) @@ -6214,7 +6428,7 @@ string CompilerGLSL::to_combined_image_sampler(VariableID image_id, VariableID s { // Keep track of the array indices we have used to load the image. // We'll need to use the same array index into the combined image sampler array. - auto image_expr = to_expression(image_id); + auto image_expr = to_non_uniform_aware_expression(image_id); string array_expr; auto array_index = image_expr.find_first_of('['); if (array_index != string::npos) @@ -6442,20 +6656,8 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool sparse, bool auto &result_type = get(result_type_id); inherited_expressions.push_back(coord); - - // Make sure non-uniform decoration is back-propagated to where it needs to be. - if (has_decoration(img, DecorationNonUniformEXT)) - { - // In Vulkan GLSL, we cannot back-propgate nonuniform qualifiers if we - // use a combined image sampler constructor. - // We're only interested in back-propagating if we can trace back through access chains. - // If not, we will apply nonuniform to the sampled image expression itself. - auto *backing = maybe_get_backing_variable(img); - if (backing) - propagate_nonuniform_qualifier(img); - else - nonuniform_expression = true; - } + if (has_decoration(img, DecorationNonUniform) && !maybe_get_backing_variable(img)) + nonuniform_expression = true; switch (op) { @@ -6645,7 +6847,7 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool sparse, bool expr += ")"; // texture(samplerXShadow) returns float. shadowX() returns vec4. Swizzle here. - if (is_legacy() && image_is_comparison(imgtype, img)) + if (is_legacy() && is_depth_image(imgtype, img)) expr += ".r"; // Sampling from a texture which was deduced to be a depth image, might actually return 1 component here. @@ -6656,16 +6858,16 @@ std::string CompilerGLSL::to_texture_op(const Instruction &i, bool sparse, bool const auto *combined = maybe_get(img); VariableID image_id = combined ? combined->image : img; - if (combined && image_is_comparison(imgtype, combined->image)) + if (combined && is_depth_image(imgtype, combined->image)) image_is_depth = true; - else if (image_is_comparison(imgtype, img)) + else if (is_depth_image(imgtype, img)) image_is_depth = true; // We must also check the backing variable for the image. // We might have loaded an OpImage, and used that handle for two different purposes. // Once with comparison, once without. auto *image_variable = maybe_get_backing_variable(image_id); - if (image_variable && image_is_comparison(get(image_variable->basetype), image_variable->self)) + if (image_variable && is_depth_image(get(image_variable->basetype), image_variable->self)) image_is_depth = true; if (image_is_depth) @@ -6733,7 +6935,7 @@ string CompilerGLSL::to_function_name(const TextureFunctionNameArguments &args) // This happens for HLSL SampleCmpLevelZero on Texture2DArray and TextureCube. bool workaround_lod_array_shadow_as_grad = false; if (((imgtype.image.arrayed && imgtype.image.dim == Dim2D) || imgtype.image.dim == DimCube) && - image_is_comparison(imgtype, tex) && args.lod) + is_depth_image(imgtype, tex) && args.lod) { if (!expression_is_constant_null(args.lod)) { @@ -6794,7 +6996,7 @@ std::string CompilerGLSL::convert_separate_image_to_expression(uint32_t id) // Don't need to consider Shadow state since the dummy sampler is always non-shadow. auto sampled_type = type; sampled_type.basetype = SPIRType::SampledImage; - return join(type_to_glsl(sampled_type), "(", to_expression(id), ", ", + return join(type_to_glsl(sampled_type), "(", to_non_uniform_aware_expression(id), ", ", to_expression(dummy_sampler_id), ")"); } else @@ -6814,7 +7016,7 @@ std::string CompilerGLSL::convert_separate_image_to_expression(uint32_t id) } } - return to_expression(id); + return to_non_uniform_aware_expression(id); } // Returns the function args for a texture sampling function for the specified image and sampling characteristics. @@ -6827,7 +7029,7 @@ string CompilerGLSL::to_function_args(const TextureFunctionArguments &args, bool if (args.base.is_fetch) farg_str = convert_separate_image_to_expression(img); else - farg_str = to_expression(img); + farg_str = to_non_uniform_aware_expression(img); if (args.nonuniform_expression && farg_str.find_first_of('[') != string::npos) { @@ -6877,7 +7079,7 @@ string CompilerGLSL::to_function_args(const TextureFunctionArguments &args, bool // This happens for HLSL SampleCmpLevelZero on Texture2DArray and TextureCube. bool workaround_lod_array_shadow_as_grad = ((imgtype.image.arrayed && imgtype.image.dim == Dim2D) || imgtype.image.dim == DimCube) && - image_is_comparison(imgtype, img) && args.lod != 0; + is_depth_image(imgtype, img) && args.lod != 0; if (args.dref) { @@ -7030,7 +7232,11 @@ string CompilerGLSL::to_function_args(const TextureFunctionArguments &args, bool { forward = forward && should_forward(args.component); farg_str += ", "; - farg_str += to_expression(args.component); + auto &component_type = expression_type(args.component); + if (component_type.basetype == SPIRType::Int) + farg_str += to_expression(args.component); + else + farg_str += join("int(", to_expression(args.component), ")"); } *p_forward = forward; @@ -7878,7 +8084,11 @@ string CompilerGLSL::bitcast_glsl_op(const SPIRType &out_type, const SPIRType &i { // OpBitcast can deal with pointers. if (out_type.pointer || in_type.pointer) + { + if (out_type.vecsize == 2 || in_type.vecsize == 2) + require_extension_internal("GL_EXT_buffer_reference_uvec2"); return type_to_glsl(out_type); + } if (out_type.basetype == in_type.basetype) return ""; @@ -8133,13 +8343,9 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) } return "gl_BaseVertex"; } - else - { - // On regular GL, this is soft-enabled and we emit ifdefs in code. - require_extension_internal("GL_ARB_shader_draw_parameters"); - return "SPIRV_Cross_BaseVertex"; - } - break; + // On regular GL, this is soft-enabled and we emit ifdefs in code. + require_extension_internal("GL_ARB_shader_draw_parameters"); + return "SPIRV_Cross_BaseVertex"; case BuiltInBaseInstance: if (options.es) @@ -8154,13 +8360,9 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) } return "gl_BaseInstance"; } - else - { - // On regular GL, this is soft-enabled and we emit ifdefs in code. - require_extension_internal("GL_ARB_shader_draw_parameters"); - return "SPIRV_Cross_BaseInstance"; - } - break; + // On regular GL, this is soft-enabled and we emit ifdefs in code. + require_extension_internal("GL_ARB_shader_draw_parameters"); + return "SPIRV_Cross_BaseInstance"; case BuiltInDrawIndex: if (options.es) @@ -8175,13 +8377,9 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) } return "gl_DrawID"; } - else - { - // On regular GL, this is soft-enabled and we emit ifdefs in code. - require_extension_internal("GL_ARB_shader_draw_parameters"); - return "gl_DrawIDARB"; - } - break; + // On regular GL, this is soft-enabled and we emit ifdefs in code. + require_extension_internal("GL_ARB_shader_draw_parameters"); + return "gl_DrawIDARB"; case BuiltInSampleId: if (options.es && options.version < 320) @@ -8210,15 +8408,9 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) case BuiltInViewIndex: if (options.vulkan_semantics) - { - require_extension_internal("GL_EXT_multiview"); return "gl_ViewIndex"; - } else - { - require_extension_internal("GL_OVR_multiview2"); return "gl_ViewID_OVR"; - } case BuiltInNumSubgroups: request_subgroup_feature(ShaderSubgroupSupportHelper::NumSubgroups); @@ -8317,12 +8509,35 @@ string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) SPIRV_CROSS_THROW("Stencil export not supported in GLES."); } + case BuiltInPrimitiveShadingRateKHR: + { + if (!options.vulkan_semantics) + SPIRV_CROSS_THROW("Can only use PrimitiveShadingRateKHR in Vulkan GLSL."); + require_extension_internal("GL_EXT_fragment_shading_rate"); + return "gl_PrimitiveShadingRateEXT"; + } + + case BuiltInShadingRateKHR: + { + if (!options.vulkan_semantics) + SPIRV_CROSS_THROW("Can only use ShadingRateKHR in Vulkan GLSL."); + require_extension_internal("GL_EXT_fragment_shading_rate"); + return "gl_ShadingRateEXT"; + } + case BuiltInDeviceIndex: if (!options.vulkan_semantics) SPIRV_CROSS_THROW("Need Vulkan semantics for device group support."); require_extension_internal("GL_EXT_device_group"); return "gl_DeviceIndex"; + case BuiltInFullyCoveredEXT: + if (!options.es) + require_extension_internal("GL_NV_conservative_raster_underestimation"); + else + SPIRV_CROSS_THROW("Need desktop GL to use GL_NV_conservative_raster_underestimation."); + return "gl_FragFullyCoveredNV"; + default: return join("gl_BuiltIn_", convert_to_string(builtin)); } @@ -8341,11 +8556,11 @@ const char *CompilerGLSL::index_to_swizzle(uint32_t index) case 3: return "w"; default: - SPIRV_CROSS_THROW("Swizzle index out of range"); + return "x"; // Don't crash, but engage the "undefined behavior" described for out-of-bounds logical addressing in spec. } } -void CompilerGLSL::access_chain_internal_append_index(std::string &expr, uint32_t /*base*/, const SPIRType *type, +void CompilerGLSL::access_chain_internal_append_index(std::string &expr, uint32_t /*base*/, const SPIRType * /*type*/, AccessChainFlags flags, bool & /*access_chain_is_arrayed*/, uint32_t index) { @@ -8354,27 +8569,19 @@ void CompilerGLSL::access_chain_internal_append_index(std::string &expr, uint32_ expr += "["; - // If we are indexing into an array of SSBOs or UBOs, we need to index it with a non-uniform qualifier. - bool nonuniform_index = - has_decoration(index, DecorationNonUniformEXT) && - (has_decoration(type->self, DecorationBlock) || has_decoration(type->self, DecorationBufferBlock)); - if (nonuniform_index) - { - expr += backend.nonuniform_qualifier; - expr += "("; - } - if (index_is_literal) expr += convert_to_string(index); else - expr += to_expression(index, register_expression_read); - - if (nonuniform_index) - expr += ")"; + expr += to_unpacked_expression(index, register_expression_read); expr += "]"; } +bool CompilerGLSL::access_chain_needs_stage_io_builtin_translation(uint32_t) +{ + return true; +} + string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indices, uint32_t count, AccessChainFlags flags, AccessChainMeta *meta) { @@ -8573,18 +8780,21 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice access_chain_is_arrayed = true; } - // For structs, the index refers to a constant, which indexes into the members. + // For structs, the index refers to a constant, which indexes into the members, possibly through a redirection mapping. // We also check if this member is a builtin, since we then replace the entire expression with the builtin one. else if (type->basetype == SPIRType::Struct) { if (!is_literal) index = evaluate_constant_u32(index); + if (index < uint32_t(type->member_type_index_redirection.size())) + index = type->member_type_index_redirection[index]; + if (index >= type->member_types.size()) SPIRV_CROSS_THROW("Member index is out of bounds!"); BuiltIn builtin; - if (is_member_builtin(*type, index, &builtin)) + if (is_member_builtin(*type, index, &builtin) && access_chain_needs_stage_io_builtin_translation(base)) { if (access_chain_is_arrayed) { @@ -8629,7 +8839,7 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice if (is_literal) expr += convert_to_string(index); else - expr += to_expression(index, register_expression_read); + expr += to_unpacked_expression(index, register_expression_read); expr += "]"; type_id = type->parent_type; @@ -8652,13 +8862,30 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice // Internally, access chain implementation can also be used on composites, // ignore scalar access workarounds in this case. - StorageClass effective_storage; - if (expression_type(base).pointer) - effective_storage = get_expression_effective_storage_class(base); - else - effective_storage = StorageClassGeneric; + StorageClass effective_storage = StorageClassGeneric; + bool ignore_potential_sliced_writes = false; + if ((flags & ACCESS_CHAIN_FORCE_COMPOSITE_BIT) == 0) + { + if (expression_type(base).pointer) + effective_storage = get_expression_effective_storage_class(base); - if (!row_major_matrix_needs_conversion) + // Special consideration for control points. + // Control points can only be written by InvocationID, so there is no need + // to consider scalar access chains here. + // Cleans up some cases where it's very painful to determine the accurate storage class + // since blocks can be partially masked ... + auto *var = maybe_get_backing_variable(base); + if (var && var->storage == StorageClassOutput && + get_execution_model() == ExecutionModelTessellationControl && + !has_decoration(var->self, DecorationPatch)) + { + ignore_potential_sliced_writes = true; + } + } + else + ignore_potential_sliced_writes = true; + + if (!row_major_matrix_needs_conversion && !ignore_potential_sliced_writes) { // On some backends, we might not be able to safely access individual scalars in a vector. // To work around this, we might have to cast the access chain reference to something which can, @@ -8694,11 +8921,11 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice else { expr += "["; - expr += to_expression(index, register_expression_read); + expr += to_unpacked_expression(index, register_expression_read); expr += "]"; } - if (row_major_matrix_needs_conversion) + if (row_major_matrix_needs_conversion && !ignore_potential_sliced_writes) { prepare_access_chain_for_scalar_access(expr, get(type->parent_type), effective_storage, is_packed); @@ -9682,9 +9909,11 @@ void CompilerGLSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_ex if (!unroll_array_to_complex_store(lhs_expression, rhs_expression)) { auto lhs = to_dereferenced_expression(lhs_expression); + if (has_decoration(lhs_expression, DecorationNonUniform)) + convert_non_uniform_expression(lhs, lhs_expression); // We might need to cast in order to store to a builtin. - cast_to_builtin_store(lhs_expression, rhs, expression_type(rhs_expression)); + cast_to_variable_store(lhs_expression, rhs, expression_type(rhs_expression)); // Tries to optimize assignments like " = op expr". // While this is purely cosmetic, this is important for legacy ESSL where loop @@ -9849,7 +10078,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) expr = enclose_expression(expr + vector_swizzle(type.vecsize, 0)); // We might need to cast in order to load from a builtin. - cast_from_builtin_load(ptr, expr, type); + cast_from_variable_load(ptr, expr, type); // We might be trying to load a gl_Position[N], where we should be // doing float4[](gl_in[i].gl_Position, ...) instead. @@ -9857,12 +10086,10 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // Also, loading from gl_SampleMask array needs special unroll. unroll_array_from_complex_load(id, ptr, expr); - // Shouldn't need to check for ID, but current glslang codegen requires it in some cases - // when loading Image/Sampler descriptors. It does not hurt to check ID as well. - if (has_decoration(id, DecorationNonUniformEXT) || has_decoration(ptr, DecorationNonUniformEXT)) + if (!type_is_opaque_value(type) && has_decoration(ptr, DecorationNonUniform)) { - propagate_nonuniform_qualifier(ptr); - convert_non_uniform_expression(type, expr); + // If we're loading something non-opaque, we need to handle non-uniform descriptor access. + convert_non_uniform_expression(expr, ptr); } if (forward && ptr_expression) @@ -9885,7 +10112,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // it is an array, and our backend does not support arrays as value types. // Emit the temporary, and copy it explicitly. e = &emit_uninitialized_temporary_expression(result_type, id); - emit_array_copy(to_expression(id), ptr, StorageClassFunction, get_expression_effective_storage_class(ptr)); + emit_array_copy(to_expression(id), id, ptr, StorageClassFunction, get_expression_effective_storage_class(ptr)); } else e = &emit_op(result_type, id, expr, forward, !usage_tracking); @@ -9966,9 +10193,6 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (expr.expression_dependencies.empty()) forwarded_temporaries.erase(ops[1]); - if (has_decoration(ops[1], DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(ops[1]); - break; } @@ -10006,6 +10230,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) uint32_t result_type = ops[0]; uint32_t id = ops[1]; auto e = access_chain_internal(ops[2], &ops[3], length - 3, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, nullptr); + if (has_decoration(ops[2], DecorationNonUniform)) + convert_non_uniform_expression(e, ops[2]); set(id, join(type_to_glsl(get(result_type)), "(", e, ".length())"), result_type, true); break; @@ -10307,14 +10533,16 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // Including the base will prevent this and would trigger multiple reads // from expression causing it to be forced to an actual temporary in GLSL. auto expr = access_chain_internal(ops[2], &ops[3], length, - ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_CHAIN_ONLY_BIT, &meta); + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_CHAIN_ONLY_BIT | + ACCESS_CHAIN_FORCE_COMPOSITE_BIT, &meta); e = &emit_op(result_type, id, expr, true, should_suppress_usage_tracking(ops[2])); inherit_expression_dependencies(id, ops[2]); e->base_expression = ops[2]; } else { - auto expr = access_chain_internal(ops[2], &ops[3], length, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &meta); + auto expr = access_chain_internal(ops[2], &ops[3], length, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_FORCE_COMPOSITE_BIT, &meta); e = &emit_op(result_type, id, expr, should_forward(ops[2]), should_suppress_usage_tracking(ops[2])); inherit_expression_dependencies(id, ops[2]); } @@ -11221,9 +11449,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // Ignore semantics for now, probably only relevant to CL. uint32_t val = ops[5]; const char *op = check_atomic_image(ptr) ? "imageAtomicExchange" : "atomicExchange"; - forced_temporaries.insert(id); - emit_binary_func_op(result_type, id, ptr, val, op); - flush_all_atomic_capable_variables(); + + emit_atomic_func_op(result_type, id, ptr, val, op); break; } @@ -11236,9 +11463,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) uint32_t comp = ops[7]; const char *op = check_atomic_image(ptr) ? "imageAtomicCompSwap" : "atomicCompSwap"; - forced_temporaries.insert(id); - emit_trinary_func_op(result_type, id, ptr, comp, val, op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(result_type, id, ptr, comp, val, op); break; } @@ -11253,7 +11478,9 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) (atomic_image && get(type.image.type).basetype == SPIRType::UInt); const char *op = atomic_image ? "imageAtomicAdd" : "atomicAdd"; const char *increment = unsigned_type ? "0u" : "0"; - emit_op(ops[0], ops[1], join(op, "(", to_expression(ops[2]), ", ", increment, ")"), false); + emit_op(ops[0], ops[1], + join(op, "(", + to_non_uniform_aware_expression(ops[2]), ", ", increment, ")"), false); flush_all_atomic_capable_variables(); break; } @@ -11266,7 +11493,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // Ignore semantics for now, probably only relevant to CL. uint32_t val = ops[3]; const char *op = check_atomic_image(ptr) ? "imageAtomicExchange" : "atomicExchange"; - statement(op, "(", to_expression(ptr), ", ", to_expression(val), ");"); + statement(op, "(", to_non_uniform_aware_expression(ptr), ", ", to_expression(val), ");"); flush_all_atomic_capable_variables(); break; } @@ -11301,7 +11528,8 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) else increment = "-1"; - emit_op(ops[0], ops[1], join(op, "(", to_expression(ops[2]), ", ", increment, ")"), false); + emit_op(ops[0], ops[1], + join(op, "(", to_non_uniform_aware_expression(ops[2]), ", ", increment, ")"), false); } flush_all_atomic_capable_variables(); @@ -11311,9 +11539,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpAtomicIAdd: { const char *op = check_atomic_image(ops[2]) ? "imageAtomicAdd" : "atomicAdd"; - forced_temporaries.insert(ops[1]); - emit_binary_func_op(ops[0], ops[1], ops[2], ops[5], op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op); break; } @@ -11321,7 +11547,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) { const char *op = check_atomic_image(ops[2]) ? "imageAtomicAdd" : "atomicAdd"; forced_temporaries.insert(ops[1]); - auto expr = join(op, "(", to_expression(ops[2]), ", -", to_enclosed_expression(ops[5]), ")"); + auto expr = join(op, "(", to_non_uniform_aware_expression(ops[2]), ", -", to_enclosed_expression(ops[5]), ")"); emit_op(ops[0], ops[1], expr, should_forward(ops[2]) && should_forward(ops[5])); flush_all_atomic_capable_variables(); break; @@ -11331,9 +11557,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpAtomicUMin: { const char *op = check_atomic_image(ops[2]) ? "imageAtomicMin" : "atomicMin"; - forced_temporaries.insert(ops[1]); - emit_binary_func_op(ops[0], ops[1], ops[2], ops[5], op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op); break; } @@ -11341,36 +11565,28 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpAtomicUMax: { const char *op = check_atomic_image(ops[2]) ? "imageAtomicMax" : "atomicMax"; - forced_temporaries.insert(ops[1]); - emit_binary_func_op(ops[0], ops[1], ops[2], ops[5], op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op); break; } case OpAtomicAnd: { const char *op = check_atomic_image(ops[2]) ? "imageAtomicAnd" : "atomicAnd"; - forced_temporaries.insert(ops[1]); - emit_binary_func_op(ops[0], ops[1], ops[2], ops[5], op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op); break; } case OpAtomicOr: { const char *op = check_atomic_image(ops[2]) ? "imageAtomicOr" : "atomicOr"; - forced_temporaries.insert(ops[1]); - emit_binary_func_op(ops[0], ops[1], ops[2], ops[5], op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op); break; } case OpAtomicXor: { const char *op = check_atomic_image(ops[2]) ? "imageAtomicXor" : "atomicXor"; - forced_temporaries.insert(ops[1]); - emit_binary_func_op(ops[0], ops[1], ops[2], ops[5], op); - flush_all_atomic_capable_variables(); + emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op); break; } @@ -11465,16 +11681,33 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpImageQueryLod: { + const char *op = nullptr; if (!options.es && options.version < 400) { require_extension_internal("GL_ARB_texture_query_lod"); // For some reason, the ARB spec is all-caps. - GLSL_BFOP(textureQueryLOD); + op = "textureQueryLOD"; } else if (options.es) SPIRV_CROSS_THROW("textureQueryLod not supported in ES profile."); else - GLSL_BFOP(textureQueryLod); + op = "textureQueryLod"; + + auto sampler_expr = to_expression(ops[2]); + if (has_decoration(ops[2], DecorationNonUniform)) + { + if (maybe_get_backing_variable(ops[2])) + convert_non_uniform_expression(sampler_expr, ops[2]); + else if (*backend.nonuniform_qualifier != '\0') + sampler_expr = join(backend.nonuniform_qualifier, "(", sampler_expr, ")"); + } + + bool forward = should_forward(ops[3]); + emit_op(ops[0], ops[1], + join(op, "(", sampler_expr, ", ", to_unpacked_expression(ops[3]), ")"), + forward); + inherit_expression_dependencies(ops[1], ops[2]); + inherit_expression_dependencies(ops[1], ops[3]); register_control_dependent_expression(ops[1]); break; } @@ -11504,7 +11737,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) string expr; if (type.image.sampled == 2) - expr = join("imageSamples(", to_expression(ops[2]), ")"); + expr = join("imageSamples(", to_non_uniform_aware_expression(ops[2]), ")"); else expr = join("textureSamples(", convert_separate_image_to_expression(ops[2]), ")"); @@ -11615,10 +11848,10 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) "operand mask was used."); uint32_t samples = ops[5]; - imgexpr = join("subpassLoad(", to_expression(ops[2]), ", ", to_expression(samples), ")"); + imgexpr = join("subpassLoad(", to_non_uniform_aware_expression(ops[2]), ", ", to_expression(samples), ")"); } else - imgexpr = join("subpassLoad(", to_expression(ops[2]), ")"); + imgexpr = join("subpassLoad(", to_non_uniform_aware_expression(ops[2]), ")"); } else { @@ -11630,13 +11863,13 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) "operand mask was used."); uint32_t samples = ops[5]; - imgexpr = join("texelFetch(", to_expression(ops[2]), ", ivec2(gl_FragCoord.xy), ", + imgexpr = join("texelFetch(", to_non_uniform_aware_expression(ops[2]), ", ivec2(gl_FragCoord.xy), ", to_expression(samples), ")"); } else { // Implement subpass loads via texture barrier style sampling. - imgexpr = join("texelFetch(", to_expression(ops[2]), ", ivec2(gl_FragCoord.xy), 0)"); + imgexpr = join("texelFetch(", to_non_uniform_aware_expression(ops[2]), ", ivec2(gl_FragCoord.xy), 0)"); } } imgexpr = remap_swizzle(get(result_type), 4, imgexpr); @@ -11667,12 +11900,12 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) "operand mask was used."); uint32_t samples = ops[5]; - statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_expression(ops[2]), ", ", + statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_non_uniform_aware_expression(ops[2]), ", ", coord_expr, ", ", to_expression(samples), ", ", to_expression(sparse_texel_id), ");"); } else { - statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_expression(ops[2]), ", ", + statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_non_uniform_aware_expression(ops[2]), ", ", coord_expr, ", ", to_expression(sparse_texel_id), ");"); } imgexpr = join(type_to_glsl(get(result_type)), "(", to_expression(sparse_code_id), ", ", @@ -11689,10 +11922,10 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) uint32_t samples = ops[5]; imgexpr = - join("imageLoad(", to_expression(ops[2]), ", ", coord_expr, ", ", to_expression(samples), ")"); + join("imageLoad(", to_non_uniform_aware_expression(ops[2]), ", ", coord_expr, ", ", to_expression(samples), ")"); } else - imgexpr = join("imageLoad(", to_expression(ops[2]), ", ", coord_expr, ")"); + imgexpr = join("imageLoad(", to_non_uniform_aware_expression(ops[2]), ", ", coord_expr, ")"); } if (!sparse) @@ -11733,9 +11966,6 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) coord_expr = bitcast_expression(target_coord_type, expression_type(ops[3]).basetype, coord_expr); auto expr = join(to_expression(ops[2]), ", ", coord_expr); - if (has_decoration(id, DecorationNonUniformEXT) || has_decoration(ops[2], DecorationNonUniformEXT)) - convert_non_uniform_expression(expression_type(ops[2]), expr); - auto &e = set(id, expr, result_type, true); // When using the pointer, we need to know which variable it is actually loaded from. @@ -11778,11 +12008,11 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (operands != ImageOperandsSampleMask || length != 5) SPIRV_CROSS_THROW("Multisampled image used in OpImageWrite, but unexpected operand mask was used."); uint32_t samples = ops[4]; - statement("imageStore(", to_expression(ops[0]), ", ", coord_expr, ", ", to_expression(samples), ", ", + statement("imageStore(", to_non_uniform_aware_expression(ops[0]), ", ", coord_expr, ", ", to_expression(samples), ", ", remap_swizzle(store_type, value_type.vecsize, to_expression(ops[2])), ");"); } else - statement("imageStore(", to_expression(ops[0]), ", ", coord_expr, ", ", + statement("imageStore(", to_non_uniform_aware_expression(ops[0]), ", ", coord_expr, ", ", remap_swizzle(store_type, value_type.vecsize, to_expression(ops[2])), ");"); if (var && variable_storage_is_aliased(*var)) @@ -11807,7 +12037,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) SPIRV_CROSS_THROW("At least ESSL 3.10 required for imageSize."); // The size of an image is always constant. - expr = join("imageSize(", to_expression(ops[2]), ")"); + expr = join("imageSize(", to_non_uniform_aware_expression(ops[2]), ")"); } else { @@ -12332,9 +12562,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) flush_control_dependent_expressions(current_emitting_block->self); break; case OpTraceNV: - if (has_decoration(ops[0], DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(ops[0]); - statement("traceNV(", to_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", + statement("traceNV(", to_non_uniform_aware_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ", ", to_expression(ops[6]), ", ", to_expression(ops[7]), ", ", to_expression(ops[8]), ", ", to_expression(ops[9]), ", ", to_expression(ops[10]), ");"); @@ -12343,9 +12571,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpTraceRayKHR: if (!has_decoration(ops[10], DecorationLocation)) SPIRV_CROSS_THROW("A memory declaration object must be used in TraceRayKHR."); - if (has_decoration(ops[0], DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(ops[0]); - statement("traceRayEXT(", to_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", + statement("traceRayEXT(", to_non_uniform_aware_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ", ", to_expression(ops[6]), ", ", to_expression(ops[7]), ", ", to_expression(ops[8]), ", ", to_expression(ops[9]), ", ", get_decoration(ops[10], DecorationLocation), ");"); @@ -12362,7 +12588,64 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) flush_control_dependent_expressions(current_emitting_block->self); break; + // Don't bother forwarding temporaries. Avoids having to test expression invalidation with ray query objects. + case OpRayQueryInitializeKHR: + flush_variable_declaration(ops[0]); + statement("rayQueryInitializeEXT(", + to_expression(ops[0]), ", ", to_expression(ops[1]), ", ", + to_expression(ops[2]), ", ", to_expression(ops[3]), ", ", + to_expression(ops[4]), ", ", to_expression(ops[5]), ", ", + to_expression(ops[6]), ", ", to_expression(ops[7]), ");"); + break; + case OpRayQueryProceedKHR: + flush_variable_declaration(ops[0]); + emit_op(ops[0], ops[1], join("rayQueryProceedEXT(", to_expression(ops[2]), ")"), false); + break; + case OpRayQueryTerminateKHR: + flush_variable_declaration(ops[0]); + statement("rayQueryTerminateEXT(", to_expression(ops[0]), ");"); + break; + case OpRayQueryGenerateIntersectionKHR: + flush_variable_declaration(ops[0]); + statement("rayQueryGenerateIntersectionEXT(", to_expression(ops[0]), ", ", to_expression(ops[1]), ");"); + break; + case OpRayQueryConfirmIntersectionKHR: + flush_variable_declaration(ops[0]); + statement("rayQueryConfirmIntersectionEXT(", to_expression(ops[0]), ");"); + break; +#define GLSL_RAY_QUERY_GET_OP(op) \ + case OpRayQueryGet##op##KHR: \ + flush_variable_declaration(ops[2]); \ + emit_op(ops[0], ops[1], join("rayQueryGet" #op "EXT(", to_expression(ops[2]), ")"), false); \ + break +#define GLSL_RAY_QUERY_GET_OP2(op) \ + case OpRayQueryGet##op##KHR: \ + flush_variable_declaration(ops[2]); \ + emit_op(ops[0], ops[1], join("rayQueryGet" #op "EXT(", to_expression(ops[2]), ", ", "bool(", to_expression(ops[3]), "))"), false); \ + break + GLSL_RAY_QUERY_GET_OP(RayTMin); + GLSL_RAY_QUERY_GET_OP(RayFlags); + GLSL_RAY_QUERY_GET_OP(WorldRayOrigin); + GLSL_RAY_QUERY_GET_OP(WorldRayDirection); + GLSL_RAY_QUERY_GET_OP(IntersectionCandidateAABBOpaque); + GLSL_RAY_QUERY_GET_OP2(IntersectionType); + GLSL_RAY_QUERY_GET_OP2(IntersectionT); + GLSL_RAY_QUERY_GET_OP2(IntersectionInstanceCustomIndex); + GLSL_RAY_QUERY_GET_OP2(IntersectionInstanceId); + GLSL_RAY_QUERY_GET_OP2(IntersectionInstanceShaderBindingTableRecordOffset); + GLSL_RAY_QUERY_GET_OP2(IntersectionGeometryIndex); + GLSL_RAY_QUERY_GET_OP2(IntersectionPrimitiveIndex); + GLSL_RAY_QUERY_GET_OP2(IntersectionBarycentrics); + GLSL_RAY_QUERY_GET_OP2(IntersectionFrontFace); + GLSL_RAY_QUERY_GET_OP2(IntersectionObjectRayDirection); + GLSL_RAY_QUERY_GET_OP2(IntersectionObjectRayOrigin); + GLSL_RAY_QUERY_GET_OP2(IntersectionObjectToWorld); + GLSL_RAY_QUERY_GET_OP2(IntersectionWorldToObject); +#undef GLSL_RAY_QUERY_GET_OP +#undef GLSL_RAY_QUERY_GET_OP2 + case OpConvertUToAccelerationStructureKHR: + require_extension_internal("GL_EXT_ray_tracing"); GLSL_UFOP(accelerationStructureEXT); break; @@ -12372,6 +12655,10 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (type.storage != StorageClassPhysicalStorageBufferEXT) SPIRV_CROSS_THROW("Only StorageClassPhysicalStorageBufferEXT is supported by OpConvertUToPtr."); + auto &in_type = expression_type(ops[2]); + if (in_type.vecsize == 2) + require_extension_internal("GL_EXT_buffer_reference_uvec2"); + auto op = type_to_glsl(type); emit_unary_func_op(ops[0], ops[1], ops[2], op.c_str()); break; @@ -12384,6 +12671,9 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) if (ptr_type.storage != StorageClassPhysicalStorageBufferEXT) SPIRV_CROSS_THROW("Only StorageClassPhysicalStorageBufferEXT is supported by OpConvertPtrToU."); + if (type.vecsize == 2) + require_extension_internal("GL_EXT_buffer_reference_uvec2"); + auto op = type_to_glsl(type); emit_unary_func_op(ops[0], ops[1], ops[2], op.c_str()); break; @@ -12420,11 +12710,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // If the interlock is complex, we emit this elsewhere. if (!interlocked_is_complex) { - if (options.es) - statement("beginInvocationInterlockNV();"); - else - statement("beginInvocationInterlockARB();"); - + statement("SPIRV_Cross_beginInvocationInterlock();"); flush_all_active_variables(); // Make sure forwarding doesn't propagate outside interlock region. } @@ -12434,11 +12720,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // If the interlock is complex, we emit this elsewhere. if (!interlocked_is_complex) { - if (options.es) - statement("endInvocationInterlockNV();"); - else - statement("endInvocationInterlockARB();"); - + statement("SPIRV_Cross_endInvocationInterlock();"); flush_all_active_variables(); // Make sure forwarding doesn't propagate outside interlock region. } @@ -12644,6 +12926,11 @@ string CompilerGLSL::variable_decl(const SPIRType &type, const string &name, uin return join(type_name, " ", name, type_to_array_glsl(type)); } +bool CompilerGLSL::variable_decl_is_remapped_storage(const SPIRVariable &var, StorageClass storage) const +{ + return var.storage == storage; +} + // Emit a structure member. Subclasses may override to modify output, // or to dynamically add a padding member if needed. void CompilerGLSL::emit_struct_member(const SPIRType &type, uint32_t member_type_id, uint32_t index, @@ -12671,17 +12958,25 @@ void CompilerGLSL::emit_struct_padding_target(const SPIRType &) { } -const char *CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const Bitset &flags) +string CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const Bitset &flags) { // GL_EXT_buffer_reference variables can be marked as restrict. if (flags.get(DecorationRestrictPointerEXT)) return "restrict "; + string qual; + + if (type_is_floating_point(type) && flags.get(DecorationNoContraction) && backend.support_precise_qualifier) + qual = "precise "; + // Structs do not have precision qualifiers, neither do doubles (desktop only anyways, so no mediump/highp). - if (type.basetype != SPIRType::Float && type.basetype != SPIRType::Int && type.basetype != SPIRType::UInt && - type.basetype != SPIRType::Image && type.basetype != SPIRType::SampledImage && - type.basetype != SPIRType::Sampler) - return ""; + bool type_supports_precision = + type.basetype == SPIRType::Float || type.basetype == SPIRType::Int || type.basetype == SPIRType::UInt || + type.basetype == SPIRType::Image || type.basetype == SPIRType::SampledImage || + type.basetype == SPIRType::Sampler; + + if (!type_supports_precision) + return qual; if (options.es) { @@ -12697,7 +12992,7 @@ const char *CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const B options.fragment.default_int_precision == Options::Mediump && execution.model == ExecutionModelFragment; - return implied_fmediump || implied_imediump ? "" : "mediump "; + qual += (implied_fmediump || implied_imediump) ? "" : "mediump "; } else { @@ -12711,7 +13006,7 @@ const char *CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const B execution.model == ExecutionModelFragment) || (execution.model != ExecutionModelFragment)); - return implied_fhighp || implied_ihighp ? "" : "highp "; + qual += (implied_fhighp || implied_ihighp) ? "" : "highp "; } } else if (backend.allow_precision_qualifiers) @@ -12719,18 +13014,16 @@ const char *CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, const B // Vulkan GLSL supports precision qualifiers, even in desktop profiles, which is convenient. // The default is highp however, so only emit mediump in the rare case that a shader has these. if (flags.get(DecorationRelaxedPrecision)) - return "mediump "; - else - return ""; + qual += "mediump "; } - else - return ""; + + return qual; } -const char *CompilerGLSL::to_precision_qualifiers_glsl(uint32_t id) +string CompilerGLSL::to_precision_qualifiers_glsl(uint32_t id) { auto &type = expression_type(id); - bool use_precision_qualifiers = backend.allow_precision_qualifiers || options.es; + bool use_precision_qualifiers = backend.allow_precision_qualifiers; if (use_precision_qualifiers && (type.basetype == SPIRType::Image || type.basetype == SPIRType::SampledImage)) { // Force mediump for the sampler type. We cannot declare 16-bit or smaller image types. @@ -12787,10 +13080,24 @@ string CompilerGLSL::to_qualifiers_glsl(uint32_t id) res += "coherent "; if (flags.get(DecorationRestrict)) res += "restrict "; + if (flags.get(DecorationNonWritable)) res += "readonly "; + + bool formatted_load = type.image.format == ImageFormatUnknown; if (flags.get(DecorationNonReadable)) + { res += "writeonly "; + formatted_load = false; + } + + if (formatted_load) + { + if (!options.es) + require_extension_internal("GL_EXT_shader_image_load_formatted"); + else + SPIRV_CROSS_THROW("Cannot use GL_EXT_shader_image_load_formatted in ESSL."); + } } res += to_precision_qualifiers_glsl(id); @@ -12817,7 +13124,7 @@ string CompilerGLSL::argument_decl(const SPIRFunction::Parameter &arg) string CompilerGLSL::to_initializer_expression(const SPIRVariable &var) { - return to_expression(var.initializer); + return to_unpacked_expression(var.initializer); } string CompilerGLSL::to_zero_initialized_expression(uint32_t type_id) @@ -12865,11 +13172,11 @@ string CompilerGLSL::variable_decl(const SPIRVariable &variable) { uint32_t expr = variable.static_expression; if (ir.ids[expr].get_type() != TypeUndef) - res += join(" = ", to_expression(variable.static_expression)); + res += join(" = ", to_unpacked_expression(variable.static_expression)); else if (options.force_zero_initialized_variables && type_can_zero_initialize(type)) res += join(" = ", to_zero_initialized_expression(get_variable_data_type_id(variable))); } - else if (variable.initializer) + else if (variable.initializer && !variable_decl_is_remapped_storage(variable, StorageClassWorkgroup)) { uint32_t expr = variable.initializer; if (ir.ids[expr].get_type() != TypeUndef) @@ -13090,7 +13397,7 @@ string CompilerGLSL::image_type_glsl(const SPIRType &type, uint32_t id) // "Shadow" state in GLSL only exists for samplers and combined image samplers. if (((type.basetype == SPIRType::SampledImage) || (type.basetype == SPIRType::Sampler)) && - image_is_comparison(type, id)) + is_depth_image(type, id)) { res += "Shadow"; } @@ -13161,6 +13468,9 @@ string CompilerGLSL::type_to_glsl(const SPIRType &type, uint32_t id) case SPIRType::AccelerationStructure: return ray_tracing_is_khr ? "accelerationStructureEXT" : "accelerationStructureNV"; + case SPIRType::RayQuery: + return "rayQueryEXT"; + case SPIRType::Void: return "void"; @@ -13559,7 +13869,7 @@ void CompilerGLSL::emit_function(SPIRFunction &func, const Bitset &return_flags) auto &var = get(v); var.deferred_declaration = false; - if (var.storage == StorageClassWorkgroup) + if (variable_decl_is_remapped_storage(var, StorageClassWorkgroup)) { // Special variable type which cannot have initializer, // need to be declared as standalone variables. @@ -13869,7 +14179,10 @@ void CompilerGLSL::branch(BlockID from, uint32_t cond, BlockID true_block, Block if (!true_block_needs_code && !false_block_needs_code) return; - emit_block_hints(get(from)); + // We might have a loop merge here. Only consider selection flattening constructs. + // Loop hints are handled explicitly elsewhere. + if (from_block.hint == SPIRBlock::HintFlatten || from_block.hint == SPIRBlock::HintDontFlatten) + emit_block_hints(from_block); if (true_block_needs_code) { @@ -14375,6 +14688,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) // for (;;) { create-temporary; break; } consume-temporary; // so force-declare temporaries here. emit_hoisted_temporaries(block.potential_declare_temporary); + emit_block_hints(block); statement("for (;;)"); begin_scope(); @@ -14459,13 +14773,13 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) case SPIRBlock::MultiSelect: { auto &type = expression_type(block.condition); - bool unsigned_case = - type.basetype == SPIRType::UInt || type.basetype == SPIRType::UShort || type.basetype == SPIRType::UByte; + bool unsigned_case = type.basetype == SPIRType::UInt || type.basetype == SPIRType::UShort || + type.basetype == SPIRType::UByte || type.basetype == SPIRType::UInt64; if (block.merge == SPIRBlock::MergeNone) SPIRV_CROSS_THROW("Switch statement is not structured"); - if (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64) + if (!backend.support_64bit_switch && (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64)) { // SPIR-V spec suggests this is allowed, but we cannot support it in higher level languages. SPIRV_CROSS_THROW("Cannot use 64-bit switch selectors."); @@ -14474,6 +14788,10 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) const char *label_suffix = ""; if (type.basetype == SPIRType::UInt && backend.uint32_t_literal_suffix) label_suffix = "u"; + else if (type.basetype == SPIRType::Int64 && backend.support_64bit_switch) + label_suffix = "l"; + else if (type.basetype == SPIRType::UInt64 && backend.support_64bit_switch) + label_suffix = "ul"; else if (type.basetype == SPIRType::UShort) label_suffix = backend.uint16_t_literal_suffix; else if (type.basetype == SPIRType::Short) @@ -14486,15 +14804,16 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) statement("bool _", block.self, "_ladder_break = false;"); // Find all unique case constructs. - unordered_map> case_constructs; + unordered_map> case_constructs; SmallVector block_declaration_order; - SmallVector literals_to_merge; + SmallVector literals_to_merge; // If a switch case branches to the default block for some reason, we can just remove that literal from consideration // and let the default: block handle it. // 2.11 in SPIR-V spec states that for fall-through cases, there is a very strict declaration order which we can take advantage of here. // We only need to consider possible fallthrough if order[i] branches to order[i + 1]. - for (auto &c : block.cases) + auto &cases = get_case_list(block); + for (auto &c : cases) { if (c.block != block.next_block && c.block != block.default_block) { @@ -14551,11 +14870,22 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) size_t num_blocks = block_declaration_order.size(); - const auto to_case_label = [](uint32_t literal, bool is_unsigned_case) -> string { - return is_unsigned_case ? convert_to_string(literal) : convert_to_string(int32_t(literal)); + const auto to_case_label = [](uint64_t literal, uint32_t width, bool is_unsigned_case) -> string + { + if (is_unsigned_case) + return convert_to_string(literal); + + // For smaller cases, the literals are compiled as 32 bit wide + // literals so we don't need to care for all sizes specifically. + if (width <= 32) + { + return convert_to_string(int64_t(int32_t(literal))); + } + + return convert_to_string(int64_t(literal)); }; - const auto to_legacy_case_label = [&](uint32_t condition, const SmallVector &labels, + const auto to_legacy_case_label = [&](uint32_t condition, const SmallVector &labels, const char *suffix) -> string { string ret; size_t count = labels.size(); @@ -14597,7 +14927,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) auto &negative_literals = case_constructs[block_declaration_order[j]]; for (auto &case_label : negative_literals) conditions.push_back(join(to_enclosed_expression(block.condition), - " != ", to_case_label(case_label, unsigned_case))); + " != ", to_case_label(case_label, type.width, unsigned_case))); } statement("if (", merge(conditions, " && "), ")"); @@ -14611,7 +14941,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) conditions.reserve(literals.size()); for (auto &case_label : literals) conditions.push_back(join(to_enclosed_expression(block.condition), - " == ", to_case_label(case_label, unsigned_case))); + " == ", to_case_label(case_label, type.width, unsigned_case))); statement("if (", merge(conditions, " || "), ")"); begin_scope(); flush_phi(block.self, target_block); @@ -14626,7 +14956,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) // If there is only one default block, and no cases, this is a case where SPIRV-opt decided to emulate // non-structured exits with the help of a switch block. // This is buggy on FXC, so just emit the logical equivalent of a do { } while(false), which is more idiomatic. - bool degenerate_switch = block.default_block != block.merge_block && block.cases.empty(); + bool degenerate_switch = block.default_block != block.merge_block && cases.empty(); if (degenerate_switch || is_legacy_es()) { @@ -14643,7 +14973,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) else { emit_block_hints(block); - statement("switch (", to_expression(block.condition), ")"); + statement("switch (", to_unpacked_expression(block.condition), ")"); } begin_scope(); @@ -14675,7 +15005,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) for (auto &case_literal : literals) { // The case label value must be sign-extended properly in SPIR-V, so we can assume 32-bit values here. - statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":"); + statement("case ", to_case_label(case_literal, type.width, unsigned_case), label_suffix, ":"); } } } @@ -14703,26 +15033,30 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) } // Might still have to flush phi variables if we branch from loop header directly to merge target. - if (flush_phi_required(block.self, block.next_block)) + // This is supposed to emit all cases where we branch from header to merge block directly. + // There are two main scenarios where cannot rely on default fallthrough. + // - There is an explicit default: label already. + // In this case, literals_to_merge need to form their own "default" case, so that we avoid executing that block. + // - Header -> Merge requires flushing PHI. In this case, we need to collect all cases and flush PHI there. + bool header_merge_requires_phi = flush_phi_required(block.self, block.next_block); + bool need_fallthrough_block = block.default_block == block.next_block || !literals_to_merge.empty(); + if ((header_merge_requires_phi && need_fallthrough_block) || !literals_to_merge.empty()) { - if (block.default_block == block.next_block || !literals_to_merge.empty()) + for (auto &case_literal : literals_to_merge) + statement("case ", to_case_label(case_literal, type.width, unsigned_case), label_suffix, ":"); + + if (block.default_block == block.next_block) { - for (auto &case_literal : literals_to_merge) - statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":"); - - if (block.default_block == block.next_block) - { - if (is_legacy_es()) - statement("else"); - else - statement("default:"); - } - - begin_scope(); - flush_phi(block.self, block.next_block); - statement("break;"); - end_scope(); + if (is_legacy_es()) + statement("else"); + else + statement("default:"); } + + begin_scope(); + flush_phi(block.self, block.next_block); + statement("break;"); + end_scope(); } if (degenerate_switch && !is_legacy_es()) @@ -14761,7 +15095,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) // The backend is responsible for setting this up, and redirection the return values as appropriate. if (ir.ids[block.return_value].get_type() != TypeUndef) { - emit_array_copy("spvReturnValue", block.return_value, StorageClassFunction, + emit_array_copy("spvReturnValue", 0, block.return_value, StorageClassFunction, get_expression_effective_storage_class(block.return_value)); } @@ -14775,7 +15109,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) { // OpReturnValue can return Undef, so don't emit anything for this case. if (ir.ids[block.return_value].get_type() != TypeUndef) - statement("return ", to_expression(block.return_value), ";"); + statement("return ", to_unpacked_expression(block.return_value), ";"); } } else if (!cfg.node_terminates_control_flow_in_sub_graph(current_function->entry_block, block.self) || @@ -14790,8 +15124,11 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block) break; } + // If the Kill is terminating a block with a (probably synthetic) return value, emit a return value statement. case SPIRBlock::Kill: statement(backend.discard_literal, ";"); + if (block.return_value) + statement("return ", to_unpacked_expression(block.return_value), ";"); break; case SPIRBlock::Unreachable: @@ -14980,7 +15317,7 @@ uint32_t CompilerGLSL::mask_relevant_memory_semantics(uint32_t semantics) MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask); } -void CompilerGLSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageClass, StorageClass) +void CompilerGLSL::emit_array_copy(const string &lhs, uint32_t, uint32_t rhs_id, StorageClass, StorageClass) { statement(lhs, " = ", to_expression(rhs_id), ";"); } @@ -15086,7 +15423,7 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s } } -void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) +void CompilerGLSL::cast_from_variable_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) { // We will handle array cases elsewhere. if (!expr_type.array.empty()) @@ -15120,6 +15457,8 @@ void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, case BuiltInFragStencilRefEXT: case BuiltInInstanceCustomIndexNV: case BuiltInSampleMask: + case BuiltInPrimitiveShadingRateKHR: + case BuiltInShadingRateKHR: expected_type = SPIRType::Int; break; @@ -15143,7 +15482,7 @@ void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, expr = bitcast_expression(expr_type, expected_type, expr); } -void CompilerGLSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) +void CompilerGLSL::cast_to_variable_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) { auto *var = maybe_get_backing_variable(target_id); if (var) @@ -15164,6 +15503,8 @@ void CompilerGLSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, case BuiltInViewportIndex: case BuiltInFragStencilRefEXT: case BuiltInSampleMask: + case BuiltInPrimitiveShadingRateKHR: + case BuiltInShadingRateKHR: expected_type = SPIRType::Int; break; @@ -15179,68 +15520,90 @@ void CompilerGLSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, } } -void CompilerGLSL::convert_non_uniform_expression(const SPIRType &type, std::string &expr) +void CompilerGLSL::convert_non_uniform_expression(string &expr, uint32_t ptr_id) { if (*backend.nonuniform_qualifier == '\0') return; - // Handle SPV_EXT_descriptor_indexing. - if (type.basetype == SPIRType::Sampler || type.basetype == SPIRType::SampledImage || - type.basetype == SPIRType::Image || type.basetype == SPIRType::AccelerationStructure) + auto *var = maybe_get_backing_variable(ptr_id); + if (!var) + return; + + if (var->storage != StorageClassUniformConstant && + var->storage != StorageClassStorageBuffer && + var->storage != StorageClassUniform) + return; + + auto &backing_type = get(var->basetype); + if (backing_type.array.empty()) + return; + + // If we get here, we know we're accessing an arrayed resource which + // might require nonuniform qualifier. + + auto start_array_index = expr.find_first_of('['); + + if (start_array_index == string::npos) + return; + + // We've opened a bracket, track expressions until we can close the bracket. + // This must be our resource index. + size_t end_array_index = string::npos; + unsigned bracket_count = 1; + for (size_t index = start_array_index + 1; index < expr.size(); index++) { - // The image/sampler ID must be declared as non-uniform. - // However, it is not legal GLSL to have - // nonuniformEXT(samplers[index]), so we must move the nonuniform qualifier - // to the array indexing, like - // samplers[nonuniformEXT(index)]. - // While the access chain will generally be nonuniformEXT, it's not necessarily so, - // so we might have to fixup the OpLoad-ed expression late. - - auto start_array_index = expr.find_first_of('['); - - if (start_array_index == string::npos) - return; - - // Check for the edge case that a non-arrayed resource was marked to be nonuniform, - // and the bracket we found is actually part of non-resource related data. - if (expr.find_first_of(',') < start_array_index) - return; - - // We've opened a bracket, track expressions until we can close the bracket. - // This must be our image index. - size_t end_array_index = string::npos; - unsigned bracket_count = 1; - for (size_t index = start_array_index + 1; index < expr.size(); index++) + if (expr[index] == ']') { - if (expr[index] == ']') + if (--bracket_count == 0) { - if (--bracket_count == 0) - { - end_array_index = index; - break; - } + end_array_index = index; + break; } - else if (expr[index] == '[') - bracket_count++; } - - assert(bracket_count == 0); - - // Doesn't really make sense to declare a non-arrayed image with nonuniformEXT, but there's - // nothing we can do here to express that. - if (start_array_index == string::npos || end_array_index == string::npos || end_array_index < start_array_index) - return; - - start_array_index++; - - expr = join(expr.substr(0, start_array_index), backend.nonuniform_qualifier, "(", - expr.substr(start_array_index, end_array_index - start_array_index), ")", - expr.substr(end_array_index, string::npos)); + else if (expr[index] == '[') + bracket_count++; } + + assert(bracket_count == 0); + + // Doesn't really make sense to declare a non-arrayed image with nonuniformEXT, but there's + // nothing we can do here to express that. + if (start_array_index == string::npos || end_array_index == string::npos || end_array_index < start_array_index) + return; + + start_array_index++; + + expr = join(expr.substr(0, start_array_index), backend.nonuniform_qualifier, "(", + expr.substr(start_array_index, end_array_index - start_array_index), ")", + expr.substr(end_array_index, string::npos)); } -void CompilerGLSL::emit_block_hints(const SPIRBlock &) +void CompilerGLSL::emit_block_hints(const SPIRBlock &block) { + if ((options.es && options.version < 310) || (!options.es && options.version < 140)) + return; + + switch (block.hint) + { + case SPIRBlock::HintFlatten: + require_extension_internal("GL_EXT_control_flow_attributes"); + statement("SPIRV_CROSS_FLATTEN"); + break; + case SPIRBlock::HintDontFlatten: + require_extension_internal("GL_EXT_control_flow_attributes"); + statement("SPIRV_CROSS_BRANCH"); + break; + case SPIRBlock::HintUnroll: + require_extension_internal("GL_EXT_control_flow_attributes"); + statement("SPIRV_CROSS_UNROLL"); + break; + case SPIRBlock::HintDontUnroll: + require_extension_internal("GL_EXT_control_flow_attributes"); + statement("SPIRV_CROSS_LOOP"); + break; + default: + break; + } } void CompilerGLSL::preserve_alias_on_reset(uint32_t id) @@ -15342,40 +15705,6 @@ void CompilerGLSL::emit_line_directive(uint32_t file_id, uint32_t line_literal) } } -void CompilerGLSL::propagate_nonuniform_qualifier(uint32_t id) -{ - // SPIR-V might only tag the very last ID with NonUniformEXT, but for codegen, - // we need to know NonUniformEXT a little earlier, when the resource is actually loaded. - // Back-propagate the qualifier based on the expression dependency chain. - - if (!has_decoration(id, DecorationNonUniformEXT)) - { - set_decoration(id, DecorationNonUniformEXT); - force_recompile(); - } - - auto *e = maybe_get(id); - auto *combined = maybe_get(id); - auto *chain = maybe_get(id); - if (e) - { - for (auto &expr : e->expression_dependencies) - propagate_nonuniform_qualifier(expr); - for (auto &expr : e->implied_read_expressions) - propagate_nonuniform_qualifier(expr); - } - else if (combined) - { - propagate_nonuniform_qualifier(combined->image); - propagate_nonuniform_qualifier(combined->sampler); - } - else if (chain) - { - for (auto &expr : chain->implied_read_expressions) - propagate_nonuniform_qualifier(expr); - } -} - void CompilerGLSL::emit_copy_logical_type(uint32_t lhs_id, uint32_t lhs_type_id, uint32_t rhs_id, uint32_t rhs_type_id, SmallVector chain) { @@ -15523,7 +15852,7 @@ void CompilerGLSL::emit_inout_fragment_outputs_copy_to_subpass_inputs() bool CompilerGLSL::variable_is_depth_or_compare(VariableID id) const { - return image_is_comparison(get(get(id).basetype), id); + return is_depth_image(get(get(id).basetype), id); } const char *CompilerGLSL::ShaderSubgroupSupportHelper::get_extension_name(Candidate c) @@ -15788,3 +16117,149 @@ void CompilerGLSL::rewrite_load_for_wrapped_row_major(std::string &expr, TypeID expr = join("spvWorkaroundRowMajor(", expr, ")"); } } + +void CompilerGLSL::mask_stage_output_by_location(uint32_t location, uint32_t component) +{ + masked_output_locations.insert({ location, component }); +} + +void CompilerGLSL::mask_stage_output_by_builtin(BuiltIn builtin) +{ + masked_output_builtins.insert(builtin); +} + +bool CompilerGLSL::is_stage_output_variable_masked(const SPIRVariable &var) const +{ + auto &type = get(var.basetype); + bool is_block = has_decoration(type.self, DecorationBlock); + // Blocks by themselves are never masked. Must be masked per-member. + if (is_block) + return false; + + bool is_builtin = has_decoration(var.self, DecorationBuiltIn); + + if (is_builtin) + { + return is_stage_output_builtin_masked(BuiltIn(get_decoration(var.self, DecorationBuiltIn))); + } + else + { + if (!has_decoration(var.self, DecorationLocation)) + return false; + + return is_stage_output_location_masked( + get_decoration(var.self, DecorationLocation), + get_decoration(var.self, DecorationComponent)); + } +} + +bool CompilerGLSL::is_stage_output_block_member_masked(const SPIRVariable &var, uint32_t index, bool strip_array) const +{ + auto &type = get(var.basetype); + bool is_block = has_decoration(type.self, DecorationBlock); + if (!is_block) + return false; + + BuiltIn builtin = BuiltInMax; + if (is_member_builtin(type, index, &builtin)) + { + return is_stage_output_builtin_masked(builtin); + } + else + { + uint32_t location = get_declared_member_location(var, index, strip_array); + uint32_t component = get_member_decoration(type.self, index, DecorationComponent); + return is_stage_output_location_masked(location, component); + } +} + +bool CompilerGLSL::is_stage_output_location_masked(uint32_t location, uint32_t component) const +{ + return masked_output_locations.count({ location, component }) != 0; +} + +bool CompilerGLSL::is_stage_output_builtin_masked(spv::BuiltIn builtin) const +{ + return masked_output_builtins.count(builtin) != 0; +} + +uint32_t CompilerGLSL::get_declared_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) const +{ + auto &block_type = get(var.basetype); + if (has_member_decoration(block_type.self, mbr_idx, DecorationLocation)) + return get_member_decoration(block_type.self, mbr_idx, DecorationLocation); + else + return get_accumulated_member_location(var, mbr_idx, strip_array); +} + +uint32_t CompilerGLSL::get_accumulated_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) const +{ + auto &type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var); + uint32_t location = get_decoration(var.self, DecorationLocation); + + for (uint32_t i = 0; i < mbr_idx; i++) + { + auto &mbr_type = get(type.member_types[i]); + + // Start counting from any place we have a new location decoration. + if (has_member_decoration(type.self, mbr_idx, DecorationLocation)) + location = get_member_decoration(type.self, mbr_idx, DecorationLocation); + + uint32_t location_count = type_to_location_count(mbr_type); + location += location_count; + } + + return location; +} + +StorageClass CompilerGLSL::get_expression_effective_storage_class(uint32_t ptr) +{ + auto *var = maybe_get_backing_variable(ptr); + + // If the expression has been lowered to a temporary, we need to use the Generic storage class. + // We're looking for the effective storage class of a given expression. + // An access chain or forwarded OpLoads from such access chains + // will generally have the storage class of the underlying variable, but if the load was not forwarded + // we have lost any address space qualifiers. + bool forced_temporary = ir.ids[ptr].get_type() == TypeExpression && !get(ptr).access_chain && + (forced_temporaries.count(ptr) != 0 || forwarded_temporaries.count(ptr) == 0); + + if (var && !forced_temporary) + { + if (variable_decl_is_remapped_storage(*var, StorageClassWorkgroup)) + return StorageClassWorkgroup; + if (variable_decl_is_remapped_storage(*var, StorageClassStorageBuffer)) + return StorageClassStorageBuffer; + + // Normalize SSBOs to StorageBuffer here. + if (var->storage == StorageClassUniform && + has_decoration(get(var->basetype).self, DecorationBufferBlock)) + return StorageClassStorageBuffer; + else + return var->storage; + } + else + return expression_type(ptr).storage; +} + +uint32_t CompilerGLSL::type_to_location_count(const SPIRType &type) const +{ + uint32_t count; + if (type.basetype == SPIRType::Struct) + { + uint32_t mbr_count = uint32_t(type.member_types.size()); + count = 0; + for (uint32_t i = 0; i < mbr_count; i++) + count += type_to_location_count(get(type.member_types[i])); + } + else + { + count = type.columns > 1 ? type.columns : 1; + } + + uint32_t dim_count = uint32_t(type.array.size()); + for (uint32_t i = 0; i < dim_count; i++) + count *= to_array_size_literal(type, i); + + return count; +} diff --git a/src/libraries/spirv_cross/spirv_glsl.hpp b/src/libraries/spirv_cross/spirv_glsl.hpp index 0e0303824..b34ed9930 100644 --- a/src/libraries/spirv_cross/spirv_glsl.hpp +++ b/src/libraries/spirv_cross/spirv_glsl.hpp @@ -1,5 +1,6 @@ /* * Copyright 2015-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_GLSL_HPP @@ -65,7 +65,8 @@ enum AccessChainFlagBits ACCESS_CHAIN_PTR_CHAIN_BIT = 1 << 2, ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT = 1 << 3, ACCESS_CHAIN_LITERAL_MSB_FORCE_ID = 1 << 4, - ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT = 1 << 5 + ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT = 1 << 5, + ACCESS_CHAIN_FORCE_COMPOSITE_BIT = 1 << 6 }; typedef uint32_t AccessChainFlags; @@ -132,6 +133,9 @@ public: // what happens on legacy GLSL targets for blocks and structs. bool force_flattened_io_blocks = false; + // If non-zero, controls layout(num_views = N) in; in GL_OVR_multiview2. + uint32_t ovr_multiview_view_count = 0; + enum Precision { DontCare, @@ -177,7 +181,8 @@ public: // Redirect a subpassInput reading from input_attachment_index to instead load its value from // the color attachment at location = color_location. Requires ESSL. - void remap_ext_framebuffer_fetch(uint32_t input_attachment_index, uint32_t color_location); + // If coherent, uses GL_EXT_shader_framebuffer_fetch, if not, uses noncoherent variant. + void remap_ext_framebuffer_fetch(uint32_t input_attachment_index, uint32_t color_location, bool coherent); explicit CompilerGLSL(std::vector spirv_) : Compiler(std::move(spirv_)) @@ -250,6 +255,16 @@ public: // - Images which are statically used at least once with Dref opcodes. bool variable_is_depth_or_compare(VariableID id) const; + // If a shader output is active in this stage, but inactive in a subsequent stage, + // this can be signalled here. This can be used to work around certain cross-stage matching problems + // which plagues MSL and HLSL in certain scenarios. + // An output which matches one of these will not be emitted in stage output interfaces, but rather treated as a private + // variable. + // This option is only meaningful for MSL and HLSL, since GLSL matches by location directly. + // Masking builtins only takes effect if the builtin in question is part of the stage output interface. + void mask_stage_output_by_location(uint32_t location, uint32_t component); + void mask_stage_output_by_builtin(spv::BuiltIn builtin); + protected: struct ShaderSubgroupSupportHelper { @@ -371,10 +386,11 @@ protected: virtual void emit_struct_padding_target(const SPIRType &type); virtual std::string image_type_glsl(const SPIRType &type, uint32_t id = 0); std::string constant_expression(const SPIRConstant &c); - std::string constant_op_expression(const SPIRConstantOp &cop); + virtual std::string constant_op_expression(const SPIRConstantOp &cop); virtual std::string constant_expression_vector(const SPIRConstant &c, uint32_t vector); virtual void emit_fixup(); virtual std::string variable_decl(const SPIRType &type, const std::string &name, uint32_t id = 0); + virtual bool variable_decl_is_remapped_storage(const SPIRVariable &var, spv::StorageClass storage) const; virtual std::string to_func_call_arg(const SPIRFunction::Parameter &arg, uint32_t id); struct TextureFunctionBaseArguments @@ -569,13 +585,15 @@ protected: bool use_array_constructor = false; bool needs_row_major_load_workaround = false; bool support_pointer_to_pointer = false; + bool support_precise_qualifier = false; + bool support_64bit_switch = false; } backend; void emit_struct(SPIRType &type); void emit_resources(); void emit_extension_workarounds(spv::ExecutionModel model); void emit_buffer_block_native(const SPIRVariable &var); - void emit_buffer_reference_block(SPIRType &type, bool forward_declaration); + 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); void fixup_implicit_builtin_block_names(); @@ -616,6 +634,8 @@ protected: void emit_trinary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, uint32_t op2, const char *op); void emit_binary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op); + void emit_atomic_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op); + void emit_atomic_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, uint32_t op2, const char *op); void emit_unary_func_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op, SPIRType::BaseType input_type, SPIRType::BaseType expected_result_type); @@ -661,6 +681,9 @@ protected: std::string access_chain_internal(uint32_t base, const uint32_t *indices, uint32_t count, AccessChainFlags flags, AccessChainMeta *meta); + spv::StorageClass get_expression_effective_storage_class(uint32_t ptr); + virtual bool access_chain_needs_stage_io_builtin_translation(uint32_t base); + virtual void prepare_access_chain_for_scalar_access(std::string &expr, const SPIRType &type, spv::StorageClass storage, bool &is_packed); @@ -691,6 +714,7 @@ protected: void emit_uninitialized_temporary(uint32_t type, uint32_t id); SPIRExpression &emit_uninitialized_temporary_expression(uint32_t type, uint32_t id); void append_global_func_args(const SPIRFunction &func, uint32_t index, SmallVector &arglist); + std::string to_non_uniform_aware_expression(uint32_t id); std::string to_expression(uint32_t id, bool register_expression_read = true); std::string to_composite_constructor_expression(uint32_t id, bool uses_buffer_offset); std::string to_rerolled_array_expression(const std::string &expr, const SPIRType &type); @@ -716,17 +740,17 @@ protected: virtual std::string to_qualifiers_glsl(uint32_t id); void fixup_io_block_patch_qualifiers(const SPIRVariable &var); void emit_output_variable_initializer(const SPIRVariable &var); - const char *to_precision_qualifiers_glsl(uint32_t id); + std::string to_precision_qualifiers_glsl(uint32_t id); virtual const char *to_storage_qualifiers_glsl(const SPIRVariable &var); - const char *flags_to_qualifiers_glsl(const SPIRType &type, const Bitset &flags); + std::string flags_to_qualifiers_glsl(const SPIRType &type, const Bitset &flags); const char *format_to_glsl(spv::ImageFormat format); virtual std::string layout_for_member(const SPIRType &type, uint32_t index); virtual std::string to_interpolation_qualifiers(const Bitset &flags); std::string layout_for_variable(const SPIRVariable &variable); std::string to_combined_image_sampler(VariableID image_id, VariableID samp_id); virtual bool skip_argument(uint32_t id) const; - virtual void emit_array_copy(const std::string &lhs, uint32_t rhs_id, spv::StorageClass lhs_storage, - spv::StorageClass rhs_storage); + virtual void emit_array_copy(const std::string &lhs, uint32_t lhs_id, uint32_t rhs_id, + spv::StorageClass lhs_storage, spv::StorageClass rhs_storage); virtual void emit_block_hints(const SPIRBlock &block); virtual std::string to_initializer_expression(const SPIRVariable &var); virtual std::string to_zero_initialized_expression(uint32_t type_id); @@ -741,6 +765,7 @@ protected: uint32_t type_to_packed_alignment(const SPIRType &type, const Bitset &flags, BufferPackingStandard packing); uint32_t type_to_packed_array_stride(const SPIRType &type, const Bitset &flags, BufferPackingStandard packing); uint32_t type_to_packed_size(const SPIRType &type, const Bitset &flags, BufferPackingStandard packing); + uint32_t type_to_location_count(const SPIRType &type) const; std::string bitcast_glsl(const SPIRType &result_type, uint32_t arg); virtual std::string bitcast_glsl_op(const SPIRType &result_type, const SPIRType &argument_type); @@ -838,7 +863,9 @@ protected: // GL_EXT_shader_framebuffer_fetch support. std::vector> subpass_to_framebuffer_fetch_attachment; - std::unordered_set inout_color_attachments; + std::vector> inout_color_attachments; + bool location_is_framebuffer_fetch(uint32_t location) const; + bool location_is_non_coherent_framebuffer_fetch(uint32_t location) const; bool subpass_input_is_framebuffer_fetch(uint32_t id) const; void emit_inout_fragment_outputs_copy_to_subpass_inputs(); const SPIRVariable *find_subpass_input_by_attachment_index(uint32_t index) const; @@ -877,11 +904,11 @@ protected: // Builtins in GLSL are always specific signedness, but the SPIR-V can declare them // as either unsigned or signed. // Sometimes we will need to automatically perform casts on load and store to make this work. - virtual void cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type); - virtual void cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type); + virtual void cast_to_variable_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type); + virtual void cast_from_variable_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type); void unroll_array_from_complex_load(uint32_t target_id, uint32_t source_id, std::string &expr); bool unroll_array_to_complex_store(uint32_t target_id, uint32_t source_id); - void convert_non_uniform_expression(const SPIRType &type, std::string &expr); + void convert_non_uniform_expression(std::string &expr, uint32_t ptr_id); void handle_store_to_invariant_variable(uint32_t store_id, uint32_t value_id); void disallow_forwarding_in_expression_chain(const SPIRExpression &expr); @@ -900,10 +927,17 @@ protected: void fixup_type_alias(); void reorder_type_alias(); - void propagate_nonuniform_qualifier(uint32_t id); - static const char *vector_swizzle(int vecsize, int index); + bool is_stage_output_location_masked(uint32_t location, uint32_t component) const; + bool is_stage_output_builtin_masked(spv::BuiltIn builtin) const; + bool is_stage_output_variable_masked(const SPIRVariable &var) const; + bool is_stage_output_block_member_masked(const SPIRVariable &var, uint32_t index, bool strip_array) const; + uint32_t get_accumulated_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) const; + uint32_t get_declared_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) const; + std::unordered_set masked_output_locations; + std::unordered_set masked_output_builtins; + private: void init(); }; diff --git a/src/libraries/spirv_cross/spirv_hlsl.cpp b/src/libraries/spirv_cross/spirv_hlsl.cpp index c0b6d1d9a..bdcb6dd37 100644 --- a/src/libraries/spirv_cross/spirv_hlsl.cpp +++ b/src/libraries/spirv_cross/spirv_hlsl.cpp @@ -1,5 +1,6 @@ /* * Copyright 2016-2021 Robert Konrad + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +20,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_hlsl.hpp" @@ -570,7 +570,7 @@ void CompilerHLSL::emit_builtin_outputs_in_struct() switch (builtin) { case BuiltInPosition: - type = "float4"; + type = is_position_invariant() && backend.support_precise_qualifier ? "precise float4" : "float4"; semantic = legacy ? "POSITION" : "SV_Position"; break; @@ -641,7 +641,6 @@ void CompilerHLSL::emit_builtin_outputs_in_struct() default: SPIRV_CROSS_THROW("Unsupported builtin in HLSL."); - break; } if (type && semantic) @@ -770,7 +769,6 @@ void CompilerHLSL::emit_builtin_inputs_in_struct() default: SPIRV_CROSS_THROW("Unsupported builtin in HLSL."); - break; } if (type && semantic) @@ -818,8 +816,8 @@ string CompilerHLSL::to_interpolation_qualifiers(const Bitset &flags) res += "patch "; // Seems to be different in actual HLSL. if (flags.get(DecorationSample)) res += "sample "; - if (flags.get(DecorationInvariant)) - res += "invariant "; // Not supported? + if (flags.get(DecorationInvariant) && backend.support_precise_qualifier) + res += "precise "; // Not supported? return res; } @@ -854,48 +852,25 @@ std::string CompilerHLSL::to_initializer_expression(const SPIRVariable &var) return CompilerGLSL::to_initializer_expression(var); } -void CompilerHLSL::emit_io_block(const SPIRVariable &var) +void CompilerHLSL::emit_interface_block_member_in_struct(const SPIRVariable &var, uint32_t member_index, + uint32_t location, + std::unordered_set &active_locations) { auto &execution = get_entry_point(); + auto type = get(var.basetype); + auto semantic = to_semantic(location, execution.model, var.storage); + auto mbr_name = join(to_name(type.self), "_", to_member_name(type, member_index)); + auto &mbr_type = get(type.member_types[member_index]); - auto &type = get(var.basetype); - add_resource_name(type.self); + statement(to_interpolation_qualifiers(get_member_decoration_bitset(type.self, member_index)), + type_to_glsl(mbr_type), + " ", mbr_name, type_to_array_glsl(mbr_type), + " : ", semantic, ";"); - statement("struct ", to_name(type.self)); - begin_scope(); - type.member_name_cache.clear(); - - uint32_t base_location = get_decoration(var.self, DecorationLocation); - - for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) - { - string semantic; - if (has_member_decoration(type.self, i, DecorationLocation)) - { - uint32_t location = get_member_decoration(type.self, i, DecorationLocation); - semantic = join(" : ", to_semantic(location, execution.model, var.storage)); - } - else - { - // If the block itself has a location, but not its members, use the implicit location. - // There could be a conflict if the block members partially specialize the locations. - // It is unclear how SPIR-V deals with this. Assume this does not happen for now. - uint32_t location = base_location + i; - semantic = join(" : ", to_semantic(location, execution.model, var.storage)); - } - - add_member_name(type, i); - - auto &membertype = get(type.member_types[i]); - statement(to_interpolation_qualifiers(get_member_decoration_bitset(type.self, i)), - variable_decl(membertype, to_member_name(type, i)), semantic, ";"); - } - - end_scope_decl(); - statement(""); - - statement("static ", variable_decl(var), ";"); - statement(""); + // Structs and arrays should consume more locations. + uint32_t consumed_locations = type_to_consumed_locations(mbr_type); + for (uint32_t i = 0; i < consumed_locations; i++) + active_locations.insert(location + i); } void CompilerHLSL::emit_interface_block_in_struct(const SPIRVariable &var, unordered_set &active_locations) @@ -930,7 +905,6 @@ void CompilerHLSL::emit_interface_block_in_struct(const SPIRVariable &var, unord bool need_matrix_unroll = var.storage == StorageClassInput && execution.model == ExecutionModelVertex; - auto &m = ir.meta[var.self].decoration; auto name = to_name(var.self); if (use_location_number) { @@ -938,8 +912,8 @@ void CompilerHLSL::emit_interface_block_in_struct(const SPIRVariable &var, unord // If an explicit location exists, use it with TEXCOORD[N] semantic. // Otherwise, pick a vacant location. - if (m.decoration_flags.get(DecorationLocation)) - location_number = m.location; + if (has_decoration(var.self, DecorationLocation)) + location_number = get_decoration(var.self, DecorationLocation); else location_number = get_vacant_location(); @@ -1188,10 +1162,10 @@ void CompilerHLSL::emit_composite_constants() auto &type = this->get(c.constant_type); - // Cannot declare block type constants here. - // We do not have the struct type yet. - bool is_block = has_decoration(type.self, DecorationBlock); - if (!is_block && (type.basetype == SPIRType::Struct || !type.array.empty())) + if (type.basetype == SPIRType::Struct && is_builtin_type(type)) + return; + + if (type.basetype == SPIRType::Struct || !type.array.empty()) { auto name = to_name(c.self); statement("static const ", variable_decl(type, name), " = ", constant_expression(c), ";"); @@ -1209,6 +1183,18 @@ void CompilerHLSL::emit_specialization_constants_and_structs() SpecializationConstant wg_x, wg_y, wg_z; ID workgroup_size_id = get_work_group_size_specialization_constants(wg_x, wg_y, wg_z); + std::unordered_set io_block_types; + ir.for_each_typed_id([&](uint32_t, const SPIRVariable &var) { + auto &type = this->get(var.basetype); + if ((var.storage == StorageClassInput || var.storage == StorageClassOutput) && + !var.remapped_variable && type.pointer && !is_builtin_variable(var) && + interface_variable_exists_in_entry_point(var.self) && + has_decoration(type.self, DecorationBlock)) + { + io_block_types.insert(type.self); + } + }); + auto loop_lock = ir.create_loop_hard_lock(); for (auto &id_ : ir.ids_for_constant_or_type) { @@ -1251,9 +1237,11 @@ void CompilerHLSL::emit_specialization_constants_and_structs() else if (id.get_type() == TypeType) { auto &type = id.get(); - if (type.basetype == SPIRType::Struct && type.array.empty() && !type.pointer && - (!ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock) && - !ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock))) + bool is_non_io_block = has_decoration(type.self, DecorationBlock) && + io_block_types.count(type.self) == 0; + bool is_buffer_block = has_decoration(type.self, DecorationBufferBlock); + if (type.basetype == SPIRType::Struct && type.array.empty() && + !type.pointer && !is_non_io_block && !is_buffer_block) { if (emitted) statement(""); @@ -1379,16 +1367,12 @@ void CompilerHLSL::emit_resources() ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { auto &type = this->get(var.basetype); - bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); - // Do not emit I/O blocks here. - // I/O blocks can be arrayed, so we must deal with them separately to support geometry shaders - // and tessellation down the line. - if (!block && var.storage != StorageClassFunction && !var.remapped_variable && type.pointer && + if (var.storage != StorageClassFunction && !var.remapped_variable && type.pointer && (var.storage == StorageClassInput || var.storage == StorageClassOutput) && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) { - // Only emit non-builtins which are not blocks here. Builtin variables are handled separately. + // Builtin variables are handled separately. emit_interface_block_globally(var); emitted = true; } @@ -1402,69 +1386,72 @@ void CompilerHLSL::emit_resources() require_output = false; unordered_set active_inputs; unordered_set active_outputs; - SmallVector input_variables; - SmallVector output_variables; + + struct IOVariable + { + const SPIRVariable *var; + uint32_t location; + uint32_t block_member_index; + bool block; + }; + + SmallVector input_variables; + SmallVector output_variables; + ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { auto &type = this->get(var.basetype); - bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); + bool block = has_decoration(type.self, DecorationBlock); if (var.storage != StorageClassInput && var.storage != StorageClassOutput) return; - // Do not emit I/O blocks here. - // I/O blocks can be arrayed, so we must deal with them separately to support geometry shaders - // and tessellation down the line. - if (!block && !var.remapped_variable && type.pointer && !is_builtin_variable(var) && + if (!var.remapped_variable && type.pointer && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) { - if (var.storage == StorageClassInput) - input_variables.push_back(&var); - else - output_variables.push_back(&var); - } - - // Reserve input and output locations for block variables as necessary. - if (block && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) - { - auto &active = var.storage == StorageClassInput ? active_inputs : active_outputs; - for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) + if (block) { - if (has_member_decoration(type.self, i, DecorationLocation)) + for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) { - uint32_t location = get_member_decoration(type.self, i, DecorationLocation); - active.insert(location); + uint32_t location = get_declared_member_location(var, i, false); + if (var.storage == StorageClassInput) + input_variables.push_back({ &var, location, i, true }); + else + output_variables.push_back({ &var, location, i, true }); } } - - // Emit the block struct and a global variable here. - emit_io_block(var); + else + { + uint32_t location = get_decoration(var.self, DecorationLocation); + if (var.storage == StorageClassInput) + input_variables.push_back({ &var, location, 0, false }); + else + output_variables.push_back({ &var, location, 0, false }); + } } }); - const auto variable_compare = [&](const SPIRVariable *a, const SPIRVariable *b) -> bool { + const auto variable_compare = [&](const IOVariable &a, const IOVariable &b) -> bool { // Sort input and output variables based on, from more robust to less robust: // - Location // - Variable has a location // - Name comparison // - Variable has a name // - Fallback: ID - bool has_location_a = has_decoration(a->self, DecorationLocation); - bool has_location_b = has_decoration(b->self, DecorationLocation); + bool has_location_a = a.block || has_decoration(a.var->self, DecorationLocation); + bool has_location_b = b.block || has_decoration(b.var->self, DecorationLocation); if (has_location_a && has_location_b) - { - return get_decoration(a->self, DecorationLocation) < get_decoration(b->self, DecorationLocation); - } + return a.location < b.location; else if (has_location_a && !has_location_b) return true; else if (!has_location_a && has_location_b) return false; - const auto &name1 = to_name(a->self); - const auto &name2 = to_name(b->self); + const auto &name1 = to_name(a.var->self); + const auto &name2 = to_name(b.var->self); if (name1.empty() && name2.empty()) - return a->self < b->self; + return a.var->self < b.var->self; else if (name1.empty()) return true; else if (name2.empty()) @@ -1491,8 +1478,13 @@ void CompilerHLSL::emit_resources() begin_scope(); sort(input_variables.begin(), input_variables.end(), variable_compare); - for (auto var : input_variables) - emit_interface_block_in_struct(*var, active_inputs); + for (auto &var : input_variables) + { + if (var.block) + emit_interface_block_member_in_struct(*var.var, var.block_member_index, var.location, active_inputs); + else + emit_interface_block_in_struct(*var.var, active_inputs); + } emit_builtin_inputs_in_struct(); end_scope_decl(); statement(""); @@ -1504,10 +1496,14 @@ void CompilerHLSL::emit_resources() statement("struct SPIRV_Cross_Output"); begin_scope(); - // FIXME: Use locations properly if they exist. sort(output_variables.begin(), output_variables.end(), variable_compare); - for (auto var : output_variables) - emit_interface_block_in_struct(*var, active_outputs); + for (auto &var : output_variables) + { + if (var.block) + emit_interface_block_member_in_struct(*var.var, var.block_member_index, var.location, active_outputs); + else + emit_interface_block_in_struct(*var.var, active_outputs); + } emit_builtin_outputs_in_struct(); end_scope_decl(); statement(""); @@ -1944,6 +1940,28 @@ void CompilerHLSL::emit_resources() end_scope(); statement(""); } + + for (TypeID type_id : composite_selection_workaround_types) + { + // Need out variable since HLSL does not support returning arrays. + auto &type = get(type_id); + auto type_str = type_to_glsl(type); + auto type_arr_str = type_to_array_glsl(type); + statement("void spvSelectComposite(out ", type_str, " out_value", type_arr_str, ", bool cond, ", + type_str, " true_val", type_arr_str, ", ", + type_str, " false_val", type_arr_str, ")"); + begin_scope(); + statement("if (cond)"); + begin_scope(); + statement("out_value = true_val;"); + end_scope(); + statement("else"); + begin_scope(); + statement("out_value = false_val;"); + end_scope(); + end_scope(); + statement(""); + } } void CompilerHLSL::emit_texture_size_variants(uint64_t variant_mask, const char *vecsize_qualifier, bool uav, @@ -2051,13 +2069,6 @@ void CompilerHLSL::emit_struct_member(const SPIRType &type, uint32_t member_type if (index < memb.size()) memberflags = memb[index].decoration_flags; - string qualifiers; - bool is_block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock) || - ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock); - - if (is_block) - qualifiers = to_interpolation_qualifiers(memberflags); - string packing_offset; bool is_push_constant = type.storage == StorageClassPushConstant; @@ -2072,7 +2083,7 @@ void CompilerHLSL::emit_struct_member(const SPIRType &type, uint32_t member_type packing_offset = join(" : packoffset(c", offset / 16, packing_swizzle[(offset & 15) >> 2], ")"); } - statement(layout_for_member(type, index), qualifiers, qualifier, + statement(layout_for_member(type, index), qualifier, variable_decl(membertype, to_member_name(type, index)), packing_offset, ";"); } @@ -2249,7 +2260,7 @@ void CompilerHLSL::emit_push_constant_block(const SPIRVariable &var) string CompilerHLSL::to_sampler_expression(uint32_t id) { - auto expr = join("_", to_expression(id)); + auto expr = join("_", to_non_uniform_aware_expression(id)); auto index = expr.find_first_of('['); if (index == string::npos) { @@ -2369,7 +2380,7 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, const Bitset &ret arg_type.image.dim != DimBuffer) { // Manufacture automatic sampler arg for SampledImage texture - arglist.push_back(join(image_is_comparison(arg_type, arg.id) ? "SamplerComparisonState " : "SamplerState ", + arglist.push_back(join(is_depth_image(arg_type, arg.id) ? "SamplerComparisonState " : "SamplerState ", to_sampler_expression(arg.id), type_to_array_glsl(arg_type))); } @@ -2407,27 +2418,6 @@ void CompilerHLSL::emit_hlsl_entry_point() if (require_input) arguments.push_back("SPIRV_Cross_Input stage_input"); - // Add I/O blocks as separate arguments with appropriate storage qualifier. - ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { - auto &type = this->get(var.basetype); - bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); - - if (var.storage != StorageClassInput && var.storage != StorageClassOutput) - return; - - if (block && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) - { - if (var.storage == StorageClassInput) - { - arguments.push_back(join("in ", variable_decl(type, join("stage_input", to_name(var.self))))); - } - else if (var.storage == StorageClassOutput) - { - arguments.push_back(join("out ", variable_decl(type, join("stage_output", to_name(var.self))))); - } - } - }); - auto &execution = get_entry_point(); switch (execution.model) @@ -2588,36 +2578,43 @@ void CompilerHLSL::emit_hlsl_entry_point() // Copy from stage input struct to globals. ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { auto &type = this->get(var.basetype); - bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); + bool block = has_decoration(type.self, DecorationBlock); if (var.storage != StorageClassInput) return; bool need_matrix_unroll = var.storage == StorageClassInput && execution.model == ExecutionModelVertex; - if (!block && !var.remapped_variable && type.pointer && !is_builtin_variable(var) && + if (!var.remapped_variable && type.pointer && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) { - auto name = to_name(var.self); - auto &mtype = this->get(var.basetype); - if (need_matrix_unroll && mtype.columns > 1) + if (block) { - // Unroll matrices. - for (uint32_t col = 0; col < mtype.columns; col++) - statement(name, "[", col, "] = stage_input.", name, "_", col, ";"); + auto type_name = to_name(type.self); + auto var_name = to_name(var.self); + for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(type.member_types.size()); mbr_idx++) + { + auto mbr_name = to_member_name(type, mbr_idx); + auto flat_name = join(type_name, "_", mbr_name); + statement(var_name, ".", mbr_name, " = stage_input.", flat_name, ";"); + } } else { - statement(name, " = stage_input.", name, ";"); + auto name = to_name(var.self); + auto &mtype = this->get(var.basetype); + if (need_matrix_unroll && mtype.columns > 1) + { + // Unroll matrices. + for (uint32_t col = 0; col < mtype.columns; col++) + statement(name, "[", col, "] = stage_input.", name, "_", col, ";"); + } + else + { + statement(name, " = stage_input.", name, ";"); + } } } - - // I/O blocks don't use the common stage input/output struct, but separate outputs. - if (block && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) - { - auto name = to_name(var.self); - statement(name, " = stage_input", name, ";"); - } }); // Run the shader. @@ -2630,22 +2627,6 @@ void CompilerHLSL::emit_hlsl_entry_point() else SPIRV_CROSS_THROW("Unsupported shader stage."); - // Copy block outputs. - ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { - auto &type = this->get(var.basetype); - bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); - - if (var.storage != StorageClassOutput) - return; - - // I/O blocks don't use the common stage input/output struct, but separate outputs. - if (block && !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) - { - auto name = to_name(var.self); - statement("stage_output", name, " = ", name, ";"); - } - }); - // Copy stage outputs. if (require_output) { @@ -2682,27 +2663,43 @@ void CompilerHLSL::emit_hlsl_entry_point() ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { auto &type = this->get(var.basetype); - bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock); + bool block = has_decoration(type.self, DecorationBlock); if (var.storage != StorageClassOutput) return; - if (!block && var.storage != StorageClassFunction && !var.remapped_variable && type.pointer && - !is_builtin_variable(var) && interface_variable_exists_in_entry_point(var.self)) + if (!var.remapped_variable && type.pointer && + !is_builtin_variable(var) && + interface_variable_exists_in_entry_point(var.self)) { - auto name = to_name(var.self); - - if (legacy && execution.model == ExecutionModelFragment) + if (block) { - string output_filler; - for (uint32_t size = type.vecsize; size < 4; ++size) - output_filler += ", 0.0"; - - statement("stage_output.", name, " = float4(", name, output_filler, ");"); + // I/O blocks need to flatten output. + auto type_name = to_name(type.self); + auto var_name = to_name(var.self); + for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(type.member_types.size()); mbr_idx++) + { + auto mbr_name = to_member_name(type, mbr_idx); + auto flat_name = join(type_name, "_", mbr_name); + statement("stage_output.", flat_name, " = ", var_name, ".", mbr_name, ";"); + } } else { - statement("stage_output.", name, " = ", name, ";"); + auto name = to_name(var.self); + + if (legacy && execution.model == ExecutionModelFragment) + { + string output_filler; + for (uint32_t size = type.vecsize; size < 4; ++size) + output_filler += ", 0.0"; + + statement("stage_output.", name, " = float4(", name, output_filler, ");"); + } + else + { + statement("stage_output.", name, " = ", name, ";"); + } } } }); @@ -2754,14 +2751,17 @@ void CompilerHLSL::emit_texture_op(const Instruction &i, bool sparse) bool proj = false; const uint32_t *opt = nullptr; auto *combined_image = maybe_get(img); - auto img_expr = to_expression(combined_image ? combined_image->image : img); + + if (combined_image && has_decoration(img, DecorationNonUniform)) + { + set_decoration(combined_image->image, DecorationNonUniform); + set_decoration(combined_image->sampler, DecorationNonUniform); + } + + auto img_expr = to_non_uniform_aware_expression(combined_image ? combined_image->image : img); inherited_expressions.push_back(coord); - // Make sure non-uniform decoration is back-propagated to where it needs to be. - if (has_decoration(img, DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(img); - switch (op) { case OpImageSampleDrefImplicitLod: @@ -2910,7 +2910,7 @@ void CompilerHLSL::emit_texture_op(const Instruction &i, bool sparse) { texop += img_expr; - if (image_is_comparison(imgtype, img)) + if (is_depth_image(imgtype, img)) { if (gather) { @@ -3016,7 +3016,7 @@ void CompilerHLSL::emit_texture_op(const Instruction &i, bool sparse) { string sampler_expr; if (combined_image) - sampler_expr = to_expression(combined_image->sampler); + sampler_expr = to_non_uniform_aware_expression(combined_image->sampler); else sampler_expr = to_sampler_expression(img); expr += sampler_expr; @@ -3386,7 +3386,7 @@ void CompilerHLSL::emit_modern_uniform(const SPIRVariable &var) if (type.basetype == SPIRType::SampledImage && type.image.dim != DimBuffer) { // For combined image samplers, also emit a combined image sampler. - if (image_is_comparison(type, var.self)) + if (is_depth_image(type, var.self)) statement("SamplerComparisonState ", to_sampler_expression(var.self), type_to_array_glsl(type), to_resource_binding_sampler(var), ";"); else @@ -3812,6 +3812,10 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR SPIRV_CROSS_THROW("Reading types other than 32-bit from ByteAddressBuffer not yet supported, unless SM 6.2 and " "native 16-bit types are enabled."); + string base = chain.base; + if (has_decoration(chain.self, DecorationNonUniform)) + convert_non_uniform_expression(base, chain.self); + bool templated_load = hlsl_options.shader_model >= 62; string load_expr; @@ -3844,7 +3848,7 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR if (templated_load) load_op = "Load"; - load_expr = join(chain.base, ".", load_op, template_expr, "(", chain.dynamic_index, chain.static_index, ")"); + load_expr = join(base, ".", load_op, template_expr, "(", chain.dynamic_index, chain.static_index, ")"); } else if (type.columns == 1) { @@ -3866,7 +3870,7 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR for (uint32_t r = 0; r < type.vecsize; r++) { - load_expr += join(chain.base, ".Load", template_expr, "(", chain.dynamic_index, + load_expr += join(base, ".Load", template_expr, "(", chain.dynamic_index, chain.static_index + r * chain.matrix_stride, ")"); if (r + 1 < type.vecsize) load_expr += ", "; @@ -3915,7 +3919,7 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR for (uint32_t c = 0; c < type.columns; c++) { - load_expr += join(chain.base, ".", load_op, template_expr, "(", chain.dynamic_index, + load_expr += join(base, ".", load_op, template_expr, "(", chain.dynamic_index, chain.static_index + c * chain.matrix_stride, ")"); if (c + 1 < type.columns) load_expr += ", "; @@ -3944,7 +3948,7 @@ void CompilerHLSL::read_access_chain(string *expr, const string &lhs, const SPIR { for (uint32_t r = 0; r < type.vecsize; r++) { - load_expr += join(chain.base, ".Load", template_expr, "(", chain.dynamic_index, + load_expr += join(base, ".Load", template_expr, "(", chain.dynamic_index, chain.static_index + c * (type.width / 8) + r * chain.matrix_stride, ")"); if ((r + 1 < type.vecsize) || (c + 1 < type.columns)) @@ -3981,9 +3985,6 @@ void CompilerHLSL::emit_load(const Instruction &instruction) uint32_t id = ops[1]; uint32_t ptr = ops[2]; - if (has_decoration(ptr, DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(ptr); - auto &type = get(result_type); bool composite_load = !type.array.empty() || type.basetype == SPIRType::Struct; @@ -4122,9 +4123,6 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val // Make sure we trigger a read of the constituents in the access chain. track_expression_read(chain.self); - if (has_decoration(chain.self, DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(chain.self); - SPIRType target_type; target_type.basetype = SPIRType::UInt; target_type.vecsize = type.vecsize; @@ -4148,6 +4146,10 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val bool templated_store = hlsl_options.shader_model >= 62; + auto base = chain.base; + if (has_decoration(chain.self, DecorationNonUniform)) + convert_non_uniform_expression(base, chain.self); + string template_expr; if (templated_store) template_expr = join("<", type_to_glsl(type), ">"); @@ -4183,7 +4185,7 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val } else store_op = "Store"; - statement(chain.base, ".", store_op, template_expr, "(", chain.dynamic_index, chain.static_index, ", ", + statement(base, ".", store_op, template_expr, "(", chain.dynamic_index, chain.static_index, ", ", store_expr, ");"); } else if (type.columns == 1) @@ -4214,7 +4216,7 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val store_expr = join(bitcast_op, "(", store_expr, ")"); } - statement(chain.base, ".Store", template_expr, "(", chain.dynamic_index, + statement(base, ".Store", template_expr, "(", chain.dynamic_index, chain.static_index + chain.matrix_stride * r, ", ", store_expr, ");"); } } @@ -4258,7 +4260,7 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val store_expr = join(bitcast_op, "(", store_expr, ")"); } - statement(chain.base, ".", store_op, template_expr, "(", chain.dynamic_index, + statement(base, ".", store_op, template_expr, "(", chain.dynamic_index, chain.static_index + c * chain.matrix_stride, ", ", store_expr, ");"); } } @@ -4282,7 +4284,7 @@ void CompilerHLSL::write_access_chain(const SPIRAccessChain &chain, uint32_t val auto bitcast_op = bitcast_glsl_op(target_type, type); if (!bitcast_op.empty()) store_expr = join(bitcast_op, "(", store_expr, ")"); - statement(chain.base, ".Store", template_expr, "(", chain.dynamic_index, + statement(base, ".Store", template_expr, "(", chain.dynamic_index, chain.static_index + c * (type.width / 8) + r * chain.matrix_stride, ", ", store_expr, ");"); } } @@ -4384,9 +4386,6 @@ void CompilerHLSL::emit_access_chain(const Instruction &instruction) inherit_expression_dependencies(ops[1], ops[i]); add_implied_read_expression(e, ops[i]); } - - if (has_decoration(ops[1], DecorationNonUniformEXT)) - propagate_nonuniform_qualifier(ops[1]); } else { @@ -4486,13 +4485,16 @@ void CompilerHLSL::emit_atomic(const uint32_t *ops, uint32_t length, spv::Op op) if (data_type.storage == StorageClassImage || !chain) { - statement(atomic_op, "(", to_expression(ops[0]), ", ", to_expression(ops[3]), ", ", to_expression(tmp_id), - ");"); + statement(atomic_op, "(", to_non_uniform_aware_expression(ops[0]), ", ", + to_expression(ops[3]), ", ", to_expression(tmp_id), ");"); } else { + string base = chain->base; + if (has_decoration(chain->self, DecorationNonUniform)) + convert_non_uniform_expression(base, chain->self); // RWByteAddress buffer is always uint in its underlying type. - statement(chain->base, ".", atomic_op, "(", chain->dynamic_index, chain->static_index, ", ", + statement(base, ".", atomic_op, "(", chain->dynamic_index, chain->static_index, ", ", to_expression(ops[3]), ", ", to_expression(tmp_id), ");"); } } @@ -4510,14 +4512,17 @@ void CompilerHLSL::emit_atomic(const uint32_t *ops, uint32_t length, spv::Op op) SPIRType::BaseType expr_type; if (data_type.storage == StorageClassImage || !chain) { - statement(atomic_op, "(", to_expression(ops[2]), ", ", value_expr, ", ", to_name(id), ");"); + statement(atomic_op, "(", to_non_uniform_aware_expression(ops[2]), ", ", value_expr, ", ", to_name(id), ");"); expr_type = data_type.basetype; } else { // RWByteAddress buffer is always uint in its underlying type. + string base = chain->base; + if (has_decoration(chain->self, DecorationNonUniform)) + convert_non_uniform_expression(base, chain->self); expr_type = SPIRType::UInt; - statement(chain->base, ".", atomic_op, "(", chain->dynamic_index, chain->static_index, ", ", value_expr, + statement(base, ".", atomic_op, "(", chain->dynamic_index, chain->static_index, ", ", value_expr, ", ", to_name(id), ");"); } @@ -4581,19 +4586,15 @@ void CompilerHLSL::emit_subgroup_op(const Instruction &i) case OpGroupNonUniformInverseBallot: SPIRV_CROSS_THROW("Cannot trivially implement InverseBallot in HLSL."); - break; case OpGroupNonUniformBallotBitExtract: SPIRV_CROSS_THROW("Cannot trivially implement BallotBitExtract in HLSL."); - break; case OpGroupNonUniformBallotFindLSB: SPIRV_CROSS_THROW("Cannot trivially implement BallotFindLSB in HLSL."); - break; case OpGroupNonUniformBallotFindMSB: SPIRV_CROSS_THROW("Cannot trivially implement BallotFindMSB in HLSL."); - break; case OpGroupNonUniformBallotBitCount: { @@ -4618,13 +4619,35 @@ void CompilerHLSL::emit_subgroup_op(const Instruction &i) } case OpGroupNonUniformShuffle: - SPIRV_CROSS_THROW("Cannot trivially implement Shuffle in HLSL."); + emit_binary_func_op(result_type, id, ops[3], ops[4], "WaveReadLaneAt"); + break; case OpGroupNonUniformShuffleXor: - SPIRV_CROSS_THROW("Cannot trivially implement ShuffleXor in HLSL."); + { + bool forward = should_forward(ops[3]); + emit_op(ops[0], ops[1], + join("WaveReadLaneAt(", to_unpacked_expression(ops[3]), ", ", + "WaveGetLaneIndex() ^ ", to_enclosed_expression(ops[4]), ")"), forward); + inherit_expression_dependencies(ops[1], ops[3]); + break; + } case OpGroupNonUniformShuffleUp: - SPIRV_CROSS_THROW("Cannot trivially implement ShuffleUp in HLSL."); + { + bool forward = should_forward(ops[3]); + emit_op(ops[0], ops[1], + join("WaveReadLaneAt(", to_unpacked_expression(ops[3]), ", ", + "WaveGetLaneIndex() - ", to_enclosed_expression(ops[4]), ")"), forward); + inherit_expression_dependencies(ops[1], ops[3]); + break; + } case OpGroupNonUniformShuffleDown: - SPIRV_CROSS_THROW("Cannot trivially implement ShuffleDown in HLSL."); + { + bool forward = should_forward(ops[3]); + emit_op(ops[0], ops[1], + join("WaveReadLaneAt(", to_unpacked_expression(ops[3]), ", ", + "WaveGetLaneIndex() + ", to_enclosed_expression(ops[4]), ")"), forward); + inherit_expression_dependencies(ops[1], ops[3]); + break; + } case OpGroupNonUniformAll: emit_unary_func_op(result_type, id, ops[3], "WaveActiveAllTrue"); @@ -4771,6 +4794,34 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) break; } + case OpSelect: + { + auto &value_type = expression_type(ops[3]); + if (value_type.basetype == SPIRType::Struct || is_array(value_type)) + { + // HLSL does not support ternary expressions on composites. + // Cannot use branches, since we might be in a continue block + // where explicit control flow is prohibited. + // Emit a helper function where we can use control flow. + TypeID value_type_id = expression_type_id(ops[3]); + auto itr = std::find(composite_selection_workaround_types.begin(), + composite_selection_workaround_types.end(), + value_type_id); + if (itr == composite_selection_workaround_types.end()) + { + composite_selection_workaround_types.push_back(value_type_id); + force_recompile(); + } + emit_uninitialized_temporary_expression(ops[0], ops[1]); + statement("spvSelectComposite(", + to_expression(ops[1]), ", ", to_expression(ops[2]), ", ", + to_expression(ops[3]), ", ", to_expression(ops[4]), ");"); + } + else + CompilerGLSL::emit_instruction(instruction); + break; + } + case OpStore: { emit_store(instruction); @@ -5150,7 +5201,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) auto dummy_samples_levels = join(get_fallback_name(id), "_dummy_parameter"); statement("uint ", dummy_samples_levels, ";"); - auto expr = join("spvTextureSize(", to_expression(ops[2]), ", ", + auto expr = join("spvTextureSize(", to_non_uniform_aware_expression(ops[2]), ", ", bitcast_expression(SPIRType::UInt, ops[3]), ", ", dummy_samples_levels, ")"); auto &restype = get(ops[0]); @@ -5176,9 +5227,9 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) string expr; if (uav) - expr = join("spvImageSize(", to_expression(ops[2]), ", ", dummy_samples_levels, ")"); + expr = join("spvImageSize(", to_non_uniform_aware_expression(ops[2]), ", ", dummy_samples_levels, ")"); else - expr = join("spvTextureSize(", to_expression(ops[2]), ", 0u, ", dummy_samples_levels, ")"); + expr = join("spvTextureSize(", to_non_uniform_aware_expression(ops[2]), ", 0u, ", dummy_samples_levels, ")"); auto &restype = get(ops[0]); expr = bitcast_expression(restype, SPIRType::UInt, expr); @@ -5208,9 +5259,9 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) statement(variable_decl(type, to_name(id)), ";"); if (uav) - statement("spvImageSize(", to_expression(ops[2]), ", ", to_name(id), ");"); + statement("spvImageSize(", to_non_uniform_aware_expression(ops[2]), ", ", to_name(id), ");"); else - statement("spvTextureSize(", to_expression(ops[2]), ", 0u, ", to_name(id), ");"); + statement("spvTextureSize(", to_non_uniform_aware_expression(ops[2]), ", 0u, ", to_name(id), ");"); auto &restype = get(ops[0]); auto expr = bitcast_expression(restype, SPIRType::UInt, to_name(id)); @@ -5241,16 +5292,16 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) if (operands != ImageOperandsSampleMask || instruction.length != 6) SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected operand mask was used."); uint32_t sample = ops[5]; - imgexpr = join(to_expression(ops[2]), ".Load(int2(gl_FragCoord.xy), ", to_expression(sample), ")"); + imgexpr = join(to_non_uniform_aware_expression(ops[2]), ".Load(int2(gl_FragCoord.xy), ", to_expression(sample), ")"); } else - imgexpr = join(to_expression(ops[2]), ".Load(int3(int2(gl_FragCoord.xy), 0))"); + imgexpr = join(to_non_uniform_aware_expression(ops[2]), ".Load(int3(int2(gl_FragCoord.xy), 0))"); pure = true; } else { - imgexpr = join(to_expression(ops[2]), "[", to_expression(ops[3]), "]"); + imgexpr = join(to_non_uniform_aware_expression(ops[2]), "[", to_expression(ops[3]), "]"); // The underlying image type in HLSL depends on the image format, unlike GLSL, where all images are "vec4", // except that the underlying type changes how the data is interpreted. @@ -5299,7 +5350,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) value_expr = remap_swizzle(narrowed_type, expression_type(ops[2]).vecsize, value_expr); } - statement(to_expression(ops[0]), "[", to_expression(ops[1]), "] = ", value_expr, ";"); + statement(to_non_uniform_aware_expression(ops[0]), "[", to_expression(ops[1]), "] = ", value_expr, ";"); if (var && variable_storage_is_aliased(*var)) flush_all_aliased_variables(); break; @@ -5311,10 +5362,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) uint32_t id = ops[1]; auto expr = to_expression(ops[2]); - if (has_decoration(id, DecorationNonUniformEXT) || has_decoration(ops[2], DecorationNonUniformEXT)) - convert_non_uniform_expression(expression_type(ops[2]), expr); expr += join("[", to_expression(ops[3]), "]"); - auto &e = set(id, expr, result_type, true); // When using the pointer, we need to know which variable it is actually loaded from. @@ -5492,7 +5540,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) case OpArrayLength: { - auto *var = maybe_get(ops[2]); + auto *var = maybe_get_backing_variable(ops[2]); if (!var) SPIRV_CROSS_THROW("Array length must point directly to an SSBO block."); @@ -5502,7 +5550,7 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) // This must be 32-bit uint, so we're good to go. emit_uninitialized_temporary_expression(ops[0], ops[1]); - statement(to_expression(ops[2]), ".GetDimensions(", to_expression(ops[1]), ");"); + statement(to_non_uniform_aware_expression(ops[2]), ".GetDimensions(", to_expression(ops[1]), ");"); uint32_t offset = type_struct_member_offset(type, ops[3]); uint32_t stride = type_struct_member_array_stride(type, ops[3]); statement(to_expression(ops[1]), " = (", to_expression(ops[1]), " - ", offset, ") / ", stride, ";"); @@ -5648,6 +5696,7 @@ VariableID CompilerHLSL::remap_num_workgroups_builtin() ir.meta[variable_id].decoration.alias = "SPIRV_Cross_NumWorkgroups"; num_workgroups_builtin = variable_id; + get_entry_point().interface_variables.push_back(num_workgroups_builtin); return variable_id; } @@ -5718,6 +5767,9 @@ string CompilerHLSL::compile() backend.nonuniform_qualifier = "NonUniformResourceIndex"; backend.support_case_fallthrough = false; + // SM 4.1 does not support precise for some reason. + backend.support_precise_qualifier = hlsl_options.shader_model >= 50 || hlsl_options.shader_model == 40; + fixup_type_alias(); reorder_type_alias(); build_function_control_flow_graphs_and_analyze(); diff --git a/src/libraries/spirv_cross/spirv_hlsl.hpp b/src/libraries/spirv_cross/spirv_hlsl.hpp index f46b1868a..54a49dafa 100644 --- a/src/libraries/spirv_cross/spirv_hlsl.hpp +++ b/src/libraries/spirv_cross/spirv_hlsl.hpp @@ -1,5 +1,6 @@ /* * Copyright 2016-2021 Robert Konrad + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_HLSL_HPP @@ -219,7 +219,10 @@ private: void emit_resources(); void declare_undefined_values() override; void emit_interface_block_globally(const SPIRVariable &type); - void emit_interface_block_in_struct(const SPIRVariable &type, std::unordered_set &active_locations); + void emit_interface_block_in_struct(const SPIRVariable &var, std::unordered_set &active_locations); + void emit_interface_block_member_in_struct(const SPIRVariable &var, uint32_t member_index, + uint32_t location, + std::unordered_set &active_locations); void emit_builtin_inputs_in_struct(); void emit_builtin_outputs_in_struct(); void emit_texture_op(const Instruction &i, bool sparse) override; @@ -347,7 +350,6 @@ private: uint32_t type_to_consumed_locations(const SPIRType &type) const; - void emit_io_block(const SPIRVariable &var); std::string to_semantic(uint32_t location, spv::ExecutionModel em, spv::StorageClass sc); uint32_t num_workgroups_builtin = 0; @@ -369,6 +371,8 @@ private: // Returns true for BuiltInSampleMask because gl_SampleMask[] is an array in SPIR-V, but SV_Coverage is a scalar in HLSL. bool builtin_translates_to_nonarray(spv::BuiltIn builtin) const override; + + std::vector composite_selection_workaround_types; }; } // namespace SPIRV_CROSS_NAMESPACE diff --git a/src/libraries/spirv_cross/spirv_msl.cpp b/src/libraries/spirv_cross/spirv_msl.cpp index 1d19e6df3..ffa69bfe7 100644 --- a/src/libraries/spirv_cross/spirv_msl.cpp +++ b/src/libraries/spirv_cross/spirv_msl.cpp @@ -1,5 +1,6 @@ /* * Copyright 2016-2021 The Brenwill Workshop Ltd. + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_msl.hpp" @@ -58,7 +58,7 @@ CompilerMSL::CompilerMSL(ParsedIR &&ir_) void CompilerMSL::add_msl_shader_input(const MSLShaderInput &si) { - inputs_by_location[si.location] = si; + inputs_by_location[{si.location, si.component}] = si; if (si.builtin != BuiltInMax && !inputs_by_builtin.count(si.builtin)) inputs_by_builtin[si.builtin] = si; } @@ -67,6 +67,51 @@ void CompilerMSL::add_msl_resource_binding(const MSLResourceBinding &binding) { StageSetBinding tuple = { binding.stage, binding.desc_set, binding.binding }; resource_bindings[tuple] = { binding, false }; + + // If we might need to pad argument buffer members to positionally align + // arg buffer indexes, also maintain a lookup by argument buffer index. + if (msl_options.pad_argument_buffer_resources) + { + StageSetBinding arg_idx_tuple = { binding.stage, binding.desc_set, k_unknown_component }; + +#define ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(rez) \ + arg_idx_tuple.binding = binding.msl_##rez; \ + resource_arg_buff_idx_to_binding_number[arg_idx_tuple] = binding.binding + + switch (binding.basetype) + { + case SPIRType::Void: + case SPIRType::Boolean: + case SPIRType::SByte: + case SPIRType::UByte: + case SPIRType::Short: + case SPIRType::UShort: + case SPIRType::Int: + case SPIRType::UInt: + case SPIRType::Int64: + case SPIRType::UInt64: + case SPIRType::AtomicCounter: + case SPIRType::Half: + case SPIRType::Float: + case SPIRType::Double: + ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(buffer); + break; + case SPIRType::Image: + ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(texture); + break; + case SPIRType::Sampler: + ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(sampler); + break; + case SPIRType::SampledImage: + ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(texture); + ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(sampler); + break; + default: + SPIRV_CROSS_THROW("Unexpected argument buffer resource base type. When padding argument buffer elements, " + "all descriptor set resources must be supplied with a base type by the app."); + } +#undef ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP + } } void CompilerMSL::add_dynamic_buffer(uint32_t desc_set, uint32_t binding, uint32_t index) @@ -100,7 +145,9 @@ void CompilerMSL::set_argument_buffer_device_address_space(uint32_t desc_set, bo bool CompilerMSL::is_msl_shader_input_used(uint32_t location) { - return location_inputs_in_use.count(location) != 0; + // Don't report internal location allocations to app. + return location_inputs_in_use.count(location) != 0 && + location_inputs_in_use_fallback.count(location) == 0; } uint32_t CompilerMSL::get_automatic_builtin_input_location(spv::BuiltIn builtin) const @@ -183,13 +230,12 @@ void CompilerMSL::build_implicit_builtins() (active_input_builtins.get(BuiltInVertexId) || active_input_builtins.get(BuiltInVertexIndex) || active_input_builtins.get(BuiltInBaseVertex) || active_input_builtins.get(BuiltInInstanceId) || active_input_builtins.get(BuiltInInstanceIndex) || active_input_builtins.get(BuiltInBaseInstance)); - bool need_sample_mask = msl_options.additional_fixed_sample_mask != 0xffffffff; bool need_local_invocation_index = msl_options.emulate_subgroups && active_input_builtins.get(BuiltInSubgroupId); bool need_workgroup_size = msl_options.emulate_subgroups && active_input_builtins.get(BuiltInNumSubgroups); if (need_subpass_input || need_sample_pos || need_subgroup_mask || need_vertex_params || need_tesc_params || need_multiview || need_dispatch_base || need_vertex_base_params || need_grid_params || needs_sample_id || - needs_subgroup_invocation_id || needs_subgroup_size || need_sample_mask || need_local_invocation_index || + needs_subgroup_invocation_id || needs_subgroup_size || has_additional_fixed_sample_mask() || need_local_invocation_index || need_workgroup_size) { bool has_frag_coord = false; @@ -220,7 +266,7 @@ void CompilerMSL::build_implicit_builtins() if (var.storage == StorageClassOutput) { - if (need_sample_mask && builtin == BuiltInSampleMask) + if (has_additional_fixed_sample_mask() && builtin == BuiltInSampleMask) { builtin_sample_mask_id = var.self; mark_implicit_builtin(StorageClassOutput, BuiltInSampleMask, var.self); @@ -710,7 +756,7 @@ void CompilerMSL::build_implicit_builtins() builtin_dispatch_base_id = var_id; } - if (need_sample_mask && !does_shader_write_sample_mask) + if (has_additional_fixed_sample_mask() && !does_shader_write_sample_mask) { uint32_t offset = ir.increase_bound_by(2); uint32_t var_id = offset + 1; @@ -831,12 +877,36 @@ void CompilerMSL::build_implicit_builtins() if (need_position) { // If we can get away with returning void from entry point, we don't need to care. - // If there is at least one other stage output, we need to return [[position]]. - need_position = false; + // If there is at least one other stage output, we need to return [[position]], + // so we need to create one if it doesn't appear in the SPIR-V. Before adding the + // implicit variable, check if it actually exists already, but just has not been used + // or initialized, and if so, mark it as active, and do not create the implicit variable. + bool has_output = false; ir.for_each_typed_id([&](uint32_t, SPIRVariable &var) { if (var.storage == StorageClassOutput && interface_variable_exists_in_entry_point(var.self)) - need_position = true; + { + has_output = true; + + // Check if the var is the Position builtin + if (has_decoration(var.self, DecorationBuiltIn) && get_decoration(var.self, DecorationBuiltIn) == BuiltInPosition) + active_output_builtins.set(BuiltInPosition); + + // If the var is a struct, check if any members is the Position builtin + auto &var_type = get_variable_element_type(var); + if (var_type.basetype == SPIRType::Struct) + { + auto mbr_cnt = var_type.member_types.size(); + for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++) + { + auto builtin = BuiltInMax; + bool is_builtin = is_member_builtin(var_type, mbr_idx, &builtin); + if (is_builtin && builtin == BuiltInPosition) + active_output_builtins.set(BuiltInPosition); + } + } + } }); + need_position = has_output && !active_output_builtins.get(BuiltInPosition); } if (need_position) @@ -1285,6 +1355,7 @@ string CompilerMSL::compile() backend.nonuniform_qualifier = ""; backend.support_small_type_sampling_result = true; backend.supports_empty_struct = true; + backend.support_64bit_switch = true; // Allow Metal to use the array template unless we force it off. backend.can_return_array = !msl_options.force_native_arrays; @@ -1392,9 +1463,9 @@ string CompilerMSL::compile() emit_header(); emit_custom_templates(); + emit_custom_functions(); emit_specialization_constants_and_structs(); emit_resources(); - emit_custom_functions(); emit_function(get(ir.default_entry_point), Bitset()); pass_count++; @@ -1442,6 +1513,14 @@ void CompilerMSL::preprocess_op_codes() (is_sample_rate() && (active_input_builtins.get(BuiltInFragCoord) || (need_subpass_input && !msl_options.use_framebuffer_fetch_subpasses)))) needs_sample_id = true; + + if (is_intersection_query()) + { + add_header_line("#if __METAL_VERSION__ >= 230"); + add_header_line("#include "); + add_header_line("using namespace metal::raytracing;"); + add_header_line("#endif"); + } } // Move the Private and Workgroup global variables to the entry function. @@ -1632,6 +1711,16 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: added_arg_ids.insert(stage_in_var_id); break; } + + case GLSLstd450Modf: + case GLSLstd450Frexp: + { + uint32_t base_id = ops[5]; + if (global_var_ids.find(base_id) != global_var_ids.end()) + added_arg_ids.insert(base_id); + break; + } + default: break; } @@ -1685,8 +1774,11 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: // Add the global variables as arguments to the function if (func_id != ir.default_entry_point) { - bool added_in = false; - bool added_out = false; + bool control_point_added_in = false; + bool control_point_added_out = false; + bool patch_added_in = false; + bool patch_added_out = false; + for (uint32_t arg_id : added_arg_ids) { auto &var = get(arg_id); @@ -1694,34 +1786,72 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: auto *p_type = &get(type_id); BuiltIn bi_type = BuiltIn(get_decoration(arg_id, DecorationBuiltIn)); - if (((is_tessellation_shader() && var.storage == StorageClassInput) || - (get_execution_model() == ExecutionModelTessellationControl && var.storage == StorageClassOutput)) && - !(has_decoration(arg_id, DecorationPatch) || is_patch_block(*p_type)) && - (!is_builtin_variable(var) || bi_type == BuiltInPosition || bi_type == BuiltInPointSize || - bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance || - p_type->basetype == SPIRType::Struct)) + bool is_patch = has_decoration(arg_id, DecorationPatch) || is_patch_block(*p_type); + bool is_block = has_decoration(p_type->self, DecorationBlock); + bool is_control_point_storage = + !is_patch && + ((is_tessellation_shader() && var.storage == StorageClassInput) || + (get_execution_model() == ExecutionModelTessellationControl && var.storage == StorageClassOutput)); + bool is_patch_block_storage = is_patch && is_block && var.storage == StorageClassOutput; + bool is_builtin = is_builtin_variable(var); + bool variable_is_stage_io = + !is_builtin || bi_type == BuiltInPosition || bi_type == BuiltInPointSize || + bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance || + p_type->basetype == SPIRType::Struct; + bool is_redirected_to_global_stage_io = (is_control_point_storage || is_patch_block_storage) && + variable_is_stage_io; + + // If output is masked it is not considered part of the global stage IO interface. + if (is_redirected_to_global_stage_io && var.storage == StorageClassOutput) + is_redirected_to_global_stage_io = !is_stage_output_variable_masked(var); + + if (is_redirected_to_global_stage_io) { // Tessellation control shaders see inputs and per-vertex outputs as arrays. // Similarly, tessellation evaluation shaders see per-vertex inputs as arrays. // We collected them into a structure; we must pass the array of this // structure to the function. std::string name; + if (is_patch) + name = var.storage == StorageClassInput ? patch_stage_in_var_name : patch_stage_out_var_name; + else + name = var.storage == StorageClassInput ? "gl_in" : "gl_out"; + + if (var.storage == StorageClassOutput && has_decoration(p_type->self, DecorationBlock)) + { + // If we're redirecting a block, we might still need to access the original block + // variable if we're masking some members. + for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(p_type->member_types.size()); mbr_idx++) + { + if (is_stage_output_block_member_masked(var, mbr_idx, true)) + { + func.add_parameter(var.basetype, var.self, true); + break; + } + } + } + + // Tessellation control shaders see inputs and per-vertex outputs as arrays. + // Similarly, tessellation evaluation shaders see per-vertex inputs as arrays. + // We collected them into a structure; we must pass the array of this + // structure to the function. if (var.storage == StorageClassInput) { + auto &added_in = is_patch ? patch_added_in : control_point_added_in; if (added_in) continue; - name = "gl_in"; - arg_id = stage_in_ptr_var_id; + arg_id = is_patch ? patch_stage_in_var_id : stage_in_ptr_var_id; added_in = true; } else if (var.storage == StorageClassOutput) { + auto &added_out = is_patch ? patch_added_out : control_point_added_out; if (added_out) continue; - name = "gl_out"; - arg_id = stage_out_ptr_var_id; + arg_id = is_patch ? patch_stage_out_var_id : stage_out_ptr_var_id; added_out = true; } + type_id = get(arg_id).basetype; uint32_t next_id = ir.increase_bound_by(1); func.add_parameter(type_id, next_id, true); @@ -1729,7 +1859,7 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: set_name(next_id, name); } - else if (is_builtin_variable(var) && p_type->basetype == SPIRType::Struct) + else if (is_builtin && has_decoration(p_type->self, DecorationBlock)) { // Get the pointee type type_id = get_pointee_type_id(type_id); @@ -1739,7 +1869,7 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: for (auto &mbr_type_id : p_type->member_types) { BuiltIn builtin = BuiltInMax; - bool is_builtin = is_member_builtin(*p_type, mbr_idx, &builtin); + is_builtin = is_member_builtin(*p_type, mbr_idx, &builtin); if (is_builtin && has_active_builtin(builtin, var.storage)) { // Add a arg variable with the same type and decorations as the member @@ -1825,28 +1955,20 @@ void CompilerMSL::mark_as_packable(SPIRType &type) } } -uint32_t CompilerMSL::type_to_location_count(const SPIRType &type) const -{ - // In MSL, we cannot place structs in any context where we need locations. - assert(type.basetype != SPIRType::Struct); - - uint32_t dim = 1; - for (uint32_t i = 0; i < type.array.size(); i++) - dim *= to_array_size_literal(type, i); - - uint32_t count = dim * type.columns; - return count; -} - // If a shader input exists at the location, it is marked as being used by this shader -void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, StorageClass storage) +void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, + StorageClass storage, bool fallback) { if (storage != StorageClassInput) return; uint32_t count = type_to_location_count(type); for (uint32_t i = 0; i < count; i++) + { location_inputs_in_use.insert(location + i); + if (fallback) + location_inputs_in_use_fallback.insert(location + i); + } } uint32_t CompilerMSL::get_target_components_for_fragment_location(uint32_t location) const @@ -1910,6 +2032,92 @@ uint32_t CompilerMSL::build_msl_interpolant_type(uint32_t type_id, bool is_noper return new_type_id; } +bool CompilerMSL::add_component_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, + SPIRVariable &var, + const SPIRType &type, + InterfaceBlockMeta &meta) +{ + // Deal with Component decorations. + const InterfaceBlockMeta::LocationMeta *location_meta = nullptr; + uint32_t location = ~0u; + if (has_decoration(var.self, DecorationLocation)) + { + location = get_decoration(var.self, DecorationLocation); + auto location_meta_itr = meta.location_meta.find(location); + if (location_meta_itr != end(meta.location_meta)) + location_meta = &location_meta_itr->second; + } + + // Check if we need to pad fragment output to match a certain number of components. + if (location_meta) + { + bool pad_fragment_output = has_decoration(var.self, DecorationLocation) && + msl_options.pad_fragment_output_components && + get_entry_point().model == ExecutionModelFragment && storage == StorageClassOutput; + + auto &entry_func = get(ir.default_entry_point); + uint32_t start_component = get_decoration(var.self, DecorationComponent); + uint32_t type_components = type.vecsize; + uint32_t num_components = location_meta->num_components; + + if (pad_fragment_output) + { + uint32_t locn = get_decoration(var.self, DecorationLocation); + num_components = std::max(num_components, get_target_components_for_fragment_location(locn)); + } + + // We have already declared an IO block member as m_location_N. + // Just emit an early-declared variable and fixup as needed. + // Arrays need to be unrolled here since each location might need a different number of components. + entry_func.add_local_variable(var.self); + vars_needing_early_declaration.push_back(var.self); + + if (var.storage == StorageClassInput) + { + entry_func.fixup_hooks_in.push_back([=, &type, &var]() { + if (!type.array.empty()) + { + uint32_t array_size = to_array_size_literal(type); + for (uint32_t loc_off = 0; loc_off < array_size; loc_off++) + { + statement(to_name(var.self), "[", loc_off, "]", " = ", ib_var_ref, + ".m_location_", location + loc_off, + vector_swizzle(type_components, start_component), ";"); + } + } + else + { + statement(to_name(var.self), " = ", ib_var_ref, ".m_location_", location, + vector_swizzle(type_components, start_component), ";"); + } + }); + } + else + { + entry_func.fixup_hooks_out.push_back([=, &type, &var]() { + if (!type.array.empty()) + { + uint32_t array_size = to_array_size_literal(type); + for (uint32_t loc_off = 0; loc_off < array_size; loc_off++) + { + statement(ib_var_ref, ".m_location_", location + loc_off, + vector_swizzle(type_components, start_component), " = ", + to_name(var.self), "[", loc_off, "];"); + } + } + else + { + statement(ib_var_ref, ".m_location_", location, + vector_swizzle(type_components, start_component), " = ", to_name(var.self), ";"); + } + }); + } + return true; + } + else + return false; +} + void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, const string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, InterfaceBlockMeta &meta) { @@ -1938,65 +2146,14 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co auto &entry_func = get(ir.default_entry_point); - // Deal with Component decorations. - InterfaceBlockMeta::LocationMeta *location_meta = nullptr; - if (has_decoration(var.self, DecorationLocation)) - { - auto location_meta_itr = meta.location_meta.find(get_decoration(var.self, DecorationLocation)); - if (location_meta_itr != end(meta.location_meta)) - location_meta = &location_meta_itr->second; - } + if (add_component_variable_to_interface_block(storage, ib_var_ref, var, type, meta)) + return; bool pad_fragment_output = has_decoration(var.self, DecorationLocation) && msl_options.pad_fragment_output_components && get_entry_point().model == ExecutionModelFragment && storage == StorageClassOutput; - // Check if we need to pad fragment output to match a certain number of components. - if (location_meta) - { - start_component = get_decoration(var.self, DecorationComponent); - uint32_t num_components = location_meta->num_components; - if (pad_fragment_output) - { - uint32_t locn = get_decoration(var.self, DecorationLocation); - num_components = std::max(num_components, get_target_components_for_fragment_location(locn)); - } - - if (location_meta->ib_index != ~0u) - { - // We have already declared the variable. Just emit an early-declared variable and fixup as needed. - entry_func.add_local_variable(var.self); - vars_needing_early_declaration.push_back(var.self); - - if (var.storage == StorageClassInput) - { - uint32_t ib_index = location_meta->ib_index; - entry_func.fixup_hooks_in.push_back([=, &var]() { - statement(to_name(var.self), " = ", ib_var_ref, ".", to_member_name(ib_type, ib_index), - vector_swizzle(type_components, start_component), ";"); - }); - } - else - { - uint32_t ib_index = location_meta->ib_index; - entry_func.fixup_hooks_out.push_back([=, &var]() { - statement(ib_var_ref, ".", to_member_name(ib_type, ib_index), - vector_swizzle(type_components, start_component), " = ", to_name(var.self), ";"); - }); - } - return; - } - else - { - location_meta->ib_index = uint32_t(ib_type.member_types.size()); - type_id = build_extended_vector_type(type_id, num_components); - if (var.storage == StorageClassInput) - padded_input = true; - else - padded_output = true; - } - } - else if (pad_fragment_output) + if (pad_fragment_output) { uint32_t locn = get_decoration(var.self, DecorationLocation); target_components = get_target_components_for_fragment_location(locn); @@ -2066,10 +2223,11 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co { entry_func.fixup_hooks_in.push_back([=, &var]() { uint32_t index = get_extended_decoration(var.self, SPIRVCrossDecorationInterfaceMemberIndex); + auto invocation = to_tesc_invocation_id(); statement(to_expression(stage_out_ptr_var_id), "[", - builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "].", + invocation, "].", to_member_name(ib_type, index), " = ", to_expression(var.initializer), "[", - builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "];"); + invocation, "];"); }); } else @@ -2085,11 +2243,11 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation); + uint32_t comp = get_decoration(var.self, DecorationComponent); if (storage == StorageClassInput) { - type_id = ensure_correct_input_type(var.basetype, locn, location_meta ? location_meta->num_components : 0); - if (!location_meta) - var.basetype = type_id; + type_id = ensure_correct_input_type(var.basetype, locn, comp, 0, meta.strip_array); + var.basetype = type_id; type_id = get_pointee_type_id(type_id); if (meta.strip_array && is_array(get(type_id))) @@ -2100,22 +2258,21 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co ib_type.member_types[ib_mbr_idx] = type_id; } set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); + if (comp) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp); mark_location_as_used_by_shader(locn, get(type_id), storage); } - else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin)) { uint32_t locn = inputs_by_builtin[builtin].location; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); mark_location_as_used_by_shader(locn, type, storage); } - if (!location_meta) + if (get_decoration_bitset(var.self).get(DecorationComponent)) { - if (get_decoration_bitset(var.self).get(DecorationComponent)) - { - uint32_t component = get_decoration(var.self, DecorationComponent); - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, component); - } + uint32_t component = get_decoration(var.self, DecorationComponent); + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, component); } if (get_decoration_bitset(var.self).get(DecorationIndex)) @@ -2145,10 +2302,7 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); } - // If we have location meta, there is no unique OrigID. We won't need it, since we flatten/unflatten - // the variable to stack anyways here. - if (!location_meta) - set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self); + set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self); } void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage, const string &ib_var_ref, @@ -2159,6 +2313,9 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage auto &var_type = meta.strip_array ? get_variable_element_type(var) : get_variable_data_type(var); uint32_t elem_cnt = 0; + if (add_component_variable_to_interface_block(storage, ib_var_ref, var, var_type, meta)) + return; + if (is_matrix(var_type)) { if (is_array(var_type)) @@ -2255,29 +2412,32 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation) + i; + uint32_t comp = get_decoration(var.self, DecorationComponent); if (storage == StorageClassInput) { - var.basetype = ensure_correct_input_type(var.basetype, locn); - uint32_t mbr_type_id = ensure_correct_input_type(usable_type->self, locn); + var.basetype = ensure_correct_input_type(var.basetype, locn, comp, 0, meta.strip_array); + uint32_t mbr_type_id = ensure_correct_input_type(usable_type->self, locn, comp, 0, meta.strip_array); if (storage == StorageClassInput && pull_model_inputs.count(var.self)) ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective); else ib_type.member_types[ib_mbr_idx] = mbr_type_id; } set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); + if (comp) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp); mark_location_as_used_by_shader(locn, *usable_type, storage); } - else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin)) { uint32_t locn = inputs_by_builtin[builtin].location + i; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); mark_location_as_used_by_shader(locn, *usable_type, storage); } - else if (is_builtin && builtin == BuiltInClipDistance) + else if (is_builtin && (builtin == BuiltInClipDistance || builtin == BuiltInCullDistance)) { - // Declare the ClipDistance as [[user(clipN)]]. - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, BuiltInClipDistance); - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, i); + // Declare the Clip/CullDistance as [[user(clip/cullN)]]. + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationIndex, i); } if (get_decoration_bitset(var.self).get(DecorationIndex)) @@ -2351,34 +2511,6 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage } } -uint32_t CompilerMSL::get_accumulated_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) -{ - auto &type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var); - uint32_t location = get_decoration(var.self, DecorationLocation); - - for (uint32_t i = 0; i < mbr_idx; i++) - { - auto &mbr_type = get(type.member_types[i]); - - // Start counting from any place we have a new location decoration. - if (has_member_decoration(type.self, mbr_idx, DecorationLocation)) - location = get_member_decoration(type.self, mbr_idx, DecorationLocation); - - uint32_t location_count = 1; - - if (mbr_type.columns > 1) - location_count = mbr_type.columns; - - if (!mbr_type.array.empty()) - for (uint32_t j = 0; j < uint32_t(mbr_type.array.size()); j++) - location_count *= to_array_size_literal(mbr_type, j); - - location += location_count; - } - - return location; -} - void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass storage, const string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, uint32_t mbr_idx, InterfaceBlockMeta &meta) @@ -2468,17 +2600,17 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); mark_location_as_used_by_shader(locn, *usable_type, storage); } - else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin)) { uint32_t locn = inputs_by_builtin[builtin].location + i; set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); mark_location_as_used_by_shader(locn, *usable_type, storage); } - else if (is_builtin && builtin == BuiltInClipDistance) + else if (is_builtin && (builtin == BuiltInClipDistance || builtin == BuiltInCullDistance)) { - // Declare the ClipDistance as [[user(clipN)]]. - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, BuiltInClipDistance); - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, i); + // Declare the Clip/CullDistance as [[user(clip/cullN)]]. + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationIndex, i); } if (has_member_decoration(var_type.self, mbr_idx, DecorationComponent)) @@ -2501,7 +2633,7 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, mbr_idx); // Unflatten or flatten from [[stage_in]] or [[stage_out]] as appropriate. - if (!meta.strip_array) + if (!meta.strip_array && meta.allow_local_declaration) { switch (storage) { @@ -2602,7 +2734,7 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor // so redirect to qualified name. set_member_qualified_name(var_type.self, mbr_idx, qual_var_name); } - else if (!meta.strip_array) + else if (!meta.strip_array && meta.allow_local_declaration) { // Unflatten or flatten from [[stage_in]] or [[stage_out]] as appropriate. switch (storage) @@ -2629,9 +2761,10 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor if (has_member_decoration(var_type.self, mbr_idx, DecorationLocation)) { uint32_t locn = get_member_decoration(var_type.self, mbr_idx, DecorationLocation); + uint32_t comp = get_member_decoration(var_type.self, mbr_idx, DecorationComponent); if (storage == StorageClassInput) { - mbr_type_id = ensure_correct_input_type(mbr_type_id, locn); + mbr_type_id = ensure_correct_input_type(mbr_type_id, locn, comp, 0, meta.strip_array); var_type.member_types[mbr_idx] = mbr_type_id; if (storage == StorageClassInput && pull_model_inputs.count(var.self)) ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective); @@ -2648,7 +2781,7 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor uint32_t locn = get_accumulated_member_location(var, mbr_idx, meta.strip_array); if (storage == StorageClassInput) { - mbr_type_id = ensure_correct_input_type(mbr_type_id, locn); + mbr_type_id = ensure_correct_input_type(mbr_type_id, locn, 0, 0, meta.strip_array); var_type.member_types[mbr_idx] = mbr_type_id; if (storage == StorageClassInput && pull_model_inputs.count(var.self)) ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective); @@ -2658,7 +2791,7 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); mark_location_as_used_by_shader(locn, get(mbr_type_id), storage); } - else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin)) + else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin)) { uint32_t locn = 0; auto builtin_itr = inputs_by_builtin.find(builtin); @@ -2691,14 +2824,12 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor { entry_func.fixup_hooks_in.push_back([=, &var]() { auto &type = this->get(var.basetype); - uint32_t index = get_extended_decoration(var.self, SPIRVCrossDecorationInterfaceMemberIndex); - index += mbr_idx; - - AccessChainMeta chain_meta; - auto constant_chain = access_chain_internal(var.initializer, &builtin_invocation_id_id, 1, 0, &chain_meta); + uint32_t index = get_extended_member_decoration(var.self, mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex); + auto invocation = to_tesc_invocation_id(); + auto constant_chain = join(to_expression(var.initializer), "[", invocation, "]"); statement(to_expression(stage_out_ptr_var_id), "[", - builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "].", + invocation, "].", to_member_name(ib_type, index), " = ", constant_chain, ".", to_member_name(type, mbr_idx), ";"); }); @@ -2743,100 +2874,19 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ BuiltIn builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn)); // Force the variable to have the proper name. - set_name(var.self, builtin_to_glsl(builtin, StorageClassFunction)); + string var_name = builtin_to_glsl(builtin, StorageClassFunction); + set_name(var.self, var_name); - if (get_execution_mode_bitset().get(ExecutionModeTriangles)) - { - // Triangles are tricky, because we want only one member in the struct. + // We need to declare the variable early and at entry-point scope. + entry_func.add_local_variable(var.self); + vars_needing_early_declaration.push_back(var.self); + bool triangles = get_execution_mode_bitset().get(ExecutionModeTriangles); + string mbr_name; - // We need to declare the variable early and at entry-point scope. - entry_func.add_local_variable(var.self); - vars_needing_early_declaration.push_back(var.self); - - string mbr_name = "gl_TessLevel"; - - // If we already added the other one, we can skip this step. - if (!added_builtin_tess_level) - { - // Add a reference to the variable type to the interface struct. - uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size()); - - uint32_t type_id = build_extended_vector_type(var_type.self, 4); - - ib_type.member_types.push_back(type_id); - - // Give the member a name - set_member_name(ib_type.self, ib_mbr_idx, mbr_name); - - // We cannot decorate both, but the important part is that - // it's marked as builtin so we can get automatic attribute assignment if needed. - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); - - // There is no qualified alias since we need to flatten the internal array on return. - if (get_decoration_bitset(var.self).get(DecorationLocation)) - { - uint32_t locn = get_decoration(var.self, DecorationLocation); - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, var_type, StorageClassInput); - } - else if (inputs_by_builtin.count(builtin)) - { - uint32_t locn = inputs_by_builtin[builtin].location; - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); - mark_location_as_used_by_shader(locn, var_type, StorageClassInput); - } - - added_builtin_tess_level = true; - } - - switch (builtin) - { - case BuiltInTessLevelOuter: - entry_func.fixup_hooks_in.push_back([=, &var]() { - statement(to_name(var.self), "[0] = ", ib_var_ref, ".", mbr_name, ".x;"); - statement(to_name(var.self), "[1] = ", ib_var_ref, ".", mbr_name, ".y;"); - statement(to_name(var.self), "[2] = ", ib_var_ref, ".", mbr_name, ".z;"); - }); - break; - - case BuiltInTessLevelInner: - entry_func.fixup_hooks_in.push_back( - [=, &var]() { statement(to_name(var.self), "[0] = ", ib_var_ref, ".", mbr_name, ".w;"); }); - break; - - default: - assert(false); - break; - } - } - else - { - // Add a reference to the variable type to the interface struct. - uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size()); - - uint32_t type_id = build_extended_vector_type(var_type.self, builtin == BuiltInTessLevelOuter ? 4 : 2); - // Change the type of the variable, too. - uint32_t ptr_type_id = ir.increase_bound_by(1); - auto &new_var_type = set(ptr_type_id, get(type_id)); - new_var_type.pointer = true; - new_var_type.pointer_depth++; - new_var_type.storage = StorageClassInput; - new_var_type.parent_type = type_id; - var.basetype = ptr_type_id; - - ib_type.member_types.push_back(type_id); - - // Give the member a name - string mbr_name = to_expression(var.self); - set_member_name(ib_type.self, ib_mbr_idx, mbr_name); - - // Since vectors can be indexed like arrays, there is no need to unpack this. We can - // just refer to the vector directly. So give it a qualified alias. - string qual_var_name = ib_var_ref + "." + mbr_name; - ir.meta[var.self].decoration.qualified_alias = qual_var_name; - - set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + // Add a reference to the variable type to the interface struct. + uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size()); + const auto mark_locations = [&](const SPIRType &new_var_type) { if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation); @@ -2849,6 +2899,170 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn); mark_location_as_used_by_shader(locn, new_var_type, StorageClassInput); } + }; + + if (triangles) + { + // Triangles are tricky, because we want only one member in the struct. + mbr_name = "gl_TessLevel"; + + // If we already added the other one, we can skip this step. + if (!added_builtin_tess_level) + { + uint32_t type_id = build_extended_vector_type(var_type.self, 4); + + ib_type.member_types.push_back(type_id); + + // Give the member a name + set_member_name(ib_type.self, ib_mbr_idx, mbr_name); + + // We cannot decorate both, but the important part is that + // it's marked as builtin so we can get automatic attribute assignment if needed. + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + + mark_locations(var_type); + added_builtin_tess_level = true; + } + } + else + { + mbr_name = var_name; + + uint32_t type_id = build_extended_vector_type(var_type.self, builtin == BuiltInTessLevelOuter ? 4 : 2); + + uint32_t ptr_type_id = ir.increase_bound_by(1); + auto &new_var_type = set(ptr_type_id, get(type_id)); + new_var_type.pointer = true; + new_var_type.pointer_depth++; + new_var_type.storage = StorageClassInput; + new_var_type.parent_type = type_id; + + ib_type.member_types.push_back(type_id); + + // Give the member a name + set_member_name(ib_type.self, ib_mbr_idx, mbr_name); + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + + mark_locations(new_var_type); + } + + if (builtin == BuiltInTessLevelOuter) + { + entry_func.fixup_hooks_in.push_back([=]() { + statement(var_name, "[0] = ", ib_var_ref, ".", mbr_name, ".x;"); + statement(var_name, "[1] = ", ib_var_ref, ".", mbr_name, ".y;"); + statement(var_name, "[2] = ", ib_var_ref, ".", mbr_name, ".z;"); + if (!triangles) + statement(var_name, "[3] = ", ib_var_ref, ".", mbr_name, ".w;"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([=]() { + if (triangles) + { + statement(var_name, "[0] = ", ib_var_ref, ".", mbr_name, ".w;"); + } + else + { + statement(var_name, "[0] = ", ib_var_ref, ".", mbr_name, ".x;"); + statement(var_name, "[1] = ", ib_var_ref, ".", mbr_name, ".y;"); + } + }); + } +} + +bool CompilerMSL::variable_storage_requires_stage_io(spv::StorageClass storage) const +{ + if (storage == StorageClassOutput) + return !capture_output_to_buffer; + else if (storage == StorageClassInput) + return !(get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup); + else + return false; +} + +string CompilerMSL::to_tesc_invocation_id() +{ + if (msl_options.multi_patch_workgroup) + { + // n.b. builtin_invocation_id_id here is the dispatch global invocation ID, + // not the TC invocation ID. + return join(to_expression(builtin_invocation_id_id), ".x % ", get_entry_point().output_vertices); + } + else + return builtin_to_glsl(BuiltInInvocationId, StorageClassInput); +} + +void CompilerMSL::emit_local_masked_variable(const SPIRVariable &masked_var, bool strip_array) +{ + auto &entry_func = get(ir.default_entry_point); + bool threadgroup_storage = variable_decl_is_remapped_storage(masked_var, StorageClassWorkgroup); + + if (threadgroup_storage && msl_options.multi_patch_workgroup) + { + // We need one threadgroup block per patch, so fake this. + entry_func.fixup_hooks_in.push_back([this, &masked_var]() { + auto &type = get_variable_data_type(masked_var); + add_local_variable_name(masked_var.self); + + bool old_is_builtin = is_using_builtin_array; + is_using_builtin_array = true; + + const uint32_t max_control_points_per_patch = 32u; + uint32_t max_num_instances = + (max_control_points_per_patch + get_entry_point().output_vertices - 1u) / + get_entry_point().output_vertices; + statement("threadgroup ", type_to_glsl(type), " ", + "spvStorage", to_name(masked_var.self), "[", max_num_instances, "]", + type_to_array_glsl(type), ";"); + + // Assign a threadgroup slice to each PrimitiveID. + // We assume here that workgroup size is rounded to 32, + // since that's the maximum number of control points per patch. + // We cannot size the array based on fixed dispatch parameters, + // since Metal does not allow that. :( + // FIXME: We will likely need an option to support passing down target workgroup size, + // so we can emit appropriate size here. + statement("threadgroup ", type_to_glsl(type), " ", + "(&", to_name(masked_var.self), ")", + type_to_array_glsl(type), " = spvStorage", to_name(masked_var.self), "[", + "(", to_expression(builtin_invocation_id_id), ".x / ", + get_entry_point().output_vertices, ") % ", + max_num_instances, "];"); + + is_using_builtin_array = old_is_builtin; + }); + } + else + { + entry_func.add_local_variable(masked_var.self); + } + + if (!threadgroup_storage) + { + vars_needing_early_declaration.push_back(masked_var.self); + } + else if (masked_var.initializer) + { + // Cannot directly initialize threadgroup variables. Need fixup hooks. + ID initializer = masked_var.initializer; + if (strip_array) + { + entry_func.fixup_hooks_in.push_back([this, &masked_var, initializer]() { + auto invocation = to_tesc_invocation_id(); + statement(to_expression(masked_var.self), "[", + invocation, "] = ", + to_expression(initializer), "[", + invocation, "];"); + }); + } + else + { + entry_func.fixup_hooks_in.push_back([this, &masked_var, initializer]() { + statement(to_expression(masked_var.self), " = ", to_expression(initializer), ";"); + }); + } } } @@ -2862,11 +3076,25 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st auto &var_type = meta.strip_array ? get_variable_element_type(var) : get_variable_data_type(var); bool is_builtin = is_builtin_variable(var); auto builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn)); + bool is_block = has_decoration(var_type.self, DecorationBlock); + + // If stage variables are masked out, emit them as plain variables instead. + // For builtins, we query them one by one later. + // IO blocks are not masked here, we need to mask them per-member instead. + if (storage == StorageClassOutput && is_stage_output_variable_masked(var)) + { + // If we ignore an output, we must still emit it, since it might be used by app. + // Instead, just emit it as early declaration. + emit_local_masked_variable(var, meta.strip_array); + return; + } if (var_type.basetype == SPIRType::Struct) { - if (!is_builtin_type(var_type) && (!capture_output_to_buffer || storage == StorageClassInput) && - !meta.strip_array) + bool block_requires_flattening = variable_storage_requires_stage_io(storage) || is_block; + bool needs_local_declaration = !is_builtin && block_requires_flattening && meta.allow_local_declaration; + + if (needs_local_declaration) { // For I/O blocks or structs, we will need to pass the block itself around // to functions if they are used globally in leaf functions. @@ -2874,11 +3102,10 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st // we unflatten I/O blocks while running the shader, // and pass the actual struct type down to leaf functions. // We then unflatten inputs, and flatten outputs in the "fixup" stages. - entry_func.add_local_variable(var.self); - vars_needing_early_declaration.push_back(var.self); + emit_local_masked_variable(var, meta.strip_array); } - if (capture_output_to_buffer && storage != StorageClassInput && !has_decoration(var_type.self, DecorationBlock)) + if (!block_requires_flattening) { // In Metal tessellation shaders, the interface block itself is arrayed. This makes things // very complicated, since stage-in structures in MSL don't support nested structures. @@ -2889,6 +3116,8 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st } else { + bool masked_block = false; + // Flatten the struct members into the interface struct for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(var_type.member_types.size()); mbr_idx++) { @@ -2896,18 +3125,48 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st is_builtin = is_member_builtin(var_type, mbr_idx, &builtin); auto &mbr_type = get(var_type.member_types[mbr_idx]); - if (!is_builtin || has_active_builtin(builtin, storage)) + if (storage == StorageClassOutput && is_stage_output_block_member_masked(var, mbr_idx, meta.strip_array)) + { + if (is_block) + masked_block = true; + + // Non-builtin block output variables are just ignored, since they will still access + // the block variable as-is. They're just not flattened. + if (is_builtin && !meta.strip_array) + { + // Emit a fake variable instead. + uint32_t ids = ir.increase_bound_by(2); + uint32_t ptr_type_id = ids + 0; + uint32_t var_id = ids + 1; + + auto ptr_type = mbr_type; + ptr_type.pointer = true; + ptr_type.pointer_depth++; + ptr_type.parent_type = var_type.member_types[mbr_idx]; + ptr_type.storage = StorageClassOutput; + + uint32_t initializer = 0; + if (var.initializer) + if (auto *c = maybe_get(var.initializer)) + initializer = c->subconstants[mbr_idx]; + + set(ptr_type_id, ptr_type); + set(var_id, ptr_type_id, StorageClassOutput, initializer); + entry_func.add_local_variable(var_id); + vars_needing_early_declaration.push_back(var_id); + set_name(var_id, builtin_to_glsl(builtin, StorageClassOutput)); + set_decoration(var_id, DecorationBuiltIn, builtin); + } + } + else if (!is_builtin || has_active_builtin(builtin, storage)) { bool is_composite_type = is_matrix(mbr_type) || is_array(mbr_type); bool attribute_load_store = storage == StorageClassInput && get_execution_model() != ExecutionModelFragment; - bool storage_is_stage_io = - (storage == StorageClassInput && !(get_execution_model() == ExecutionModelTessellationControl && - msl_options.multi_patch_workgroup)) || - storage == StorageClassOutput; + bool storage_is_stage_io = variable_storage_requires_stage_io(storage); - // ClipDistance always needs to be declared as user attributes. - if (builtin == BuiltInClipDistance) + // Clip/CullDistance always need to be declared as user attributes. + if (builtin == BuiltInClipDistance || builtin == BuiltInCullDistance) is_builtin = false; if ((!is_builtin || attribute_load_store) && storage_is_stage_io && is_composite_type) @@ -2921,6 +3180,29 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st } } } + + // If we're redirecting a block, we might still need to access the original block + // variable if we're masking some members. + if (masked_block && !needs_local_declaration && + (!is_builtin_variable(var) || get_execution_model() == ExecutionModelTessellationControl)) + { + if (is_builtin_variable(var)) + { + // Ensure correct names for the block members if we're actually going to + // declare gl_PerVertex. + for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(var_type.member_types.size()); mbr_idx++) + { + set_member_name(var_type.self, mbr_idx, builtin_to_glsl( + BuiltIn(get_member_decoration(var_type.self, mbr_idx, DecorationBuiltIn)), + StorageClassOutput)); + } + + set_name(var_type.self, "gl_PerVertex"); + set_name(var.self, "gl_out_masked"); + stage_out_masked_builtin_type_id = var_type.self; + } + emit_local_masked_variable(var, meta.strip_array); + } } } else if (get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput && @@ -2934,14 +3216,11 @@ void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const st if (!is_builtin || has_active_builtin(builtin, storage)) { bool is_composite_type = is_matrix(var_type) || is_array(var_type); - bool storage_is_stage_io = - (storage == StorageClassInput && - !(get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup)) || - (storage == StorageClassOutput && !capture_output_to_buffer); + bool storage_is_stage_io = variable_storage_requires_stage_io(storage); bool attribute_load_store = storage == StorageClassInput && get_execution_model() != ExecutionModelFragment; - // ClipDistance always needs to be declared as user attributes. - if (builtin == BuiltInClipDistance) + // Clip/CullDistance always needs to be declared as user attributes. + if (builtin == BuiltInClipDistance || builtin == BuiltInCullDistance) is_builtin = false; // MSL does not allow matrices or arrays in input or output variables, so need to handle it specially. @@ -2979,10 +3258,16 @@ void CompilerMSL::fix_up_interface_member_indices(StorageClass storage, uint32_t auto &var = get(var_id); auto &type = get_variable_element_type(var); - if (storage == StorageClassInput && type.basetype == SPIRType::Struct) - { - uint32_t mbr_idx = get_extended_member_decoration(ib_type_id, i, SPIRVCrossDecorationInterfaceMemberIndex); + bool flatten_composites = variable_storage_requires_stage_io(var.storage); + bool is_block = has_decoration(type.self, DecorationBlock); + + uint32_t mbr_idx = uint32_t(-1); + if (type.basetype == SPIRType::Struct && (flatten_composites || is_block)) + mbr_idx = get_extended_member_decoration(ib_type_id, i, SPIRVCrossDecorationInterfaceMemberIndex); + + if (mbr_idx != uint32_t(-1)) + { // Only set the lowest InterfaceMemberIndex for each variable member. // IB struct members will be emitted in-order w.r.t. interface member index. if (!has_extended_member_decoration(var_id, mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex)) @@ -3024,23 +3309,49 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) auto &type = this->get(var.basetype); bool is_builtin = is_builtin_variable(var); - auto bi_type = BuiltIn(get_decoration(var_id, DecorationBuiltIn)); + bool is_block = has_decoration(type.self, DecorationBlock); + + auto bi_type = BuiltInMax; + bool builtin_is_gl_in_out = false; + if (is_builtin && !is_block) + { + bi_type = BuiltIn(get_decoration(var_id, DecorationBuiltIn)); + builtin_is_gl_in_out = bi_type == BuiltInPosition || bi_type == BuiltInPointSize || + bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance; + } + + if (is_builtin && is_block) + builtin_is_gl_in_out = true; + uint32_t location = get_decoration(var_id, DecorationLocation); + bool builtin_is_stage_in_out = builtin_is_gl_in_out || + bi_type == BuiltInLayer || bi_type == BuiltInViewportIndex || + bi_type == BuiltInBaryCoordNV || bi_type == BuiltInBaryCoordNoPerspNV || + bi_type == BuiltInFragDepth || + bi_type == BuiltInFragStencilRefEXT || bi_type == BuiltInSampleMask; + // These builtins are part of the stage in/out structs. bool is_interface_block_builtin = - (bi_type == BuiltInPosition || bi_type == BuiltInPointSize || bi_type == BuiltInClipDistance || - bi_type == BuiltInCullDistance || bi_type == BuiltInLayer || bi_type == BuiltInViewportIndex || - bi_type == BuiltInBaryCoordNV || bi_type == BuiltInBaryCoordNoPerspNV || bi_type == BuiltInFragDepth || - bi_type == BuiltInFragStencilRefEXT || bi_type == BuiltInSampleMask) || - (get_execution_model() == ExecutionModelTessellationEvaluation && - (bi_type == BuiltInTessLevelOuter || bi_type == BuiltInTessLevelInner)); + builtin_is_stage_in_out || + (get_execution_model() == ExecutionModelTessellationEvaluation && + (bi_type == BuiltInTessLevelOuter || bi_type == BuiltInTessLevelInner)); bool is_active = interface_variable_exists_in_entry_point(var.self); if (is_builtin && is_active) { // Only emit the builtin if it's active in this entry point. Interface variable list might lie. - is_active = has_active_builtin(bi_type, storage); + if (is_block) + { + // If any builtin is active, the block is active. + uint32_t mbr_cnt = uint32_t(type.member_types.size()); + for (uint32_t i = 0; !is_active && i < mbr_cnt; i++) + is_active = has_active_builtin(BuiltIn(get_member_decoration(type.self, i, DecorationBuiltIn)), storage); + } + else + { + is_active = has_active_builtin(bi_type, storage); + } } bool filter_patch_decoration = (has_decoration(var_id, DecorationPatch) || is_patch_block(type)) == patch; @@ -3048,22 +3359,28 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) bool hidden = is_hidden_variable(var, incl_builtins); // ClipDistance is never hidden, we need to emulate it when used as an input. - if (bi_type == BuiltInClipDistance) + if (bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance) hidden = false; // It's not enough to simply avoid marking fragment outputs if the pipeline won't // accept them. We can't put them in the struct at all, or otherwise the compiler // complains that the outputs weren't explicitly marked. + // Frag depth and stencil outputs are incompatible with explicit early fragment tests. + // In GLSL, depth and stencil outputs are just ignored when explicit early fragment tests are required. + // In Metal, it's a compilation error, so we need to exclude them from the output struct. if (get_execution_model() == ExecutionModelFragment && storage == StorageClassOutput && !patch && - ((is_builtin && ((bi_type == BuiltInFragDepth && !msl_options.enable_frag_depth_builtin) || - (bi_type == BuiltInFragStencilRefEXT && !msl_options.enable_frag_stencil_ref_builtin))) || + ((is_builtin && ((bi_type == BuiltInFragDepth && (!msl_options.enable_frag_depth_builtin || uses_explicit_early_fragment_test())) || + (bi_type == BuiltInFragStencilRefEXT && (!msl_options.enable_frag_stencil_ref_builtin || uses_explicit_early_fragment_test())))) || (!is_builtin && !(msl_options.enable_frag_output_mask & (1 << location))))) { hidden = true; disabled_frag_outputs.push_back(var_id); - // If a builtin, force it to have the proper name. + // If a builtin, force it to have the proper name, and mark it as not part of the output struct. if (is_builtin) + { set_name(var_id, builtin_to_glsl(bi_type, StorageClassFunction)); + mask_stage_output_by_builtin(bi_type); + } } // Barycentric inputs must be emitted in stage-in, because they can have interpolation arguments. @@ -3085,7 +3402,6 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) // Need to deal specially with DecorationComponent. // Multiple variables can alias the same Location, and try to make sure each location is declared only once. // We will swizzle data in and out to make this work. - // We only need to consider plain variables here, not composites. // This is only relevant for vertex inputs and fragment outputs. // Technically tessellation as well, but it is too complicated to support. uint32_t component = get_decoration(var_id, DecorationComponent); @@ -3095,8 +3411,22 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) SPIRV_CROSS_THROW("Component decoration is not supported in tessellation shaders."); else if (pack_components) { - auto &location_meta = meta.location_meta[location]; - location_meta.num_components = std::max(location_meta.num_components, component + type.vecsize); + uint32_t array_size = 1; + if (!type.array.empty()) + array_size = to_array_size_literal(type); + + for (uint32_t location_offset = 0; location_offset < array_size; location_offset++) + { + auto &location_meta = meta.location_meta[location + location_offset]; + location_meta.num_components = std::max(location_meta.num_components, component + type.vecsize); + + // For variables sharing location, decorations and base type must match. + location_meta.base_type_id = type.self; + location_meta.flat = has_decoration(var.self, DecorationFlat); + location_meta.noperspective = has_decoration(var.self, DecorationNoPerspective); + location_meta.centroid = has_decoration(var.self, DecorationCentroid); + location_meta.sample = has_decoration(var.self, DecorationSample); + } } } } @@ -3166,6 +3496,9 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) // Add the output interface struct as a local variable to the entry function. // If the entry point should return the output struct, set the entry function // to return the output interface struct, otherwise to return nothing. + // Watch out for the rare case where the terminator of the last entry point block is a + // Kill, instead of a Return. Based on SPIR-V's block-domination rules, we assume that + // any block that has a Kill will also have a terminating Return, except the last block. // Indicate the output var requires early initialization. bool ep_should_return_output = !get_is_rasterization_disabled(); uint32_t rtn_id = ep_should_return_output ? ib_var_id : 0; @@ -3175,7 +3508,7 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) for (auto &blk_id : entry_func.blocks) { auto &blk = get(blk_id); - if (blk.terminator == SPIRBlock::Return) + if (blk.terminator == SPIRBlock::Return || (blk.terminator == SPIRBlock::Kill && blk_id == entry_func.blocks.back())) blk.return_value = rtn_id; } vars_needing_early_declaration.push_back(ib_var_id); @@ -3283,7 +3616,11 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) (get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput)) && !patch; + // Fixing up flattened stores in TESC is impossible since the memory is group shared either via + // device (not masked) or threadgroup (masked) storage classes and it's race condition city. meta.strip_array = strip_array; + meta.allow_local_declaration = !strip_array && !(get_execution_model() == ExecutionModelTessellationControl && + storage == StorageClassOutput); add_variable_to_interface_block(storage, ib_var_ref, ib_type, *p_var, meta); } @@ -3294,7 +3631,7 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) // the struct containing them is the correct size and layout. for (auto &input : inputs_by_location) { - if (is_msl_shader_input_used(input.first)) + if (location_inputs_in_use.count(input.first.location) != 0) continue; // Create a fake variable to put at the location. @@ -3334,20 +3671,48 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) ptr_type.self = array_type_id; auto &fake_var = set(var_id, ptr_type_id, storage); - set_decoration(var_id, DecorationLocation, input.first); + set_decoration(var_id, DecorationLocation, input.first.location); + if (input.first.component) + set_decoration(var_id, DecorationComponent, input.first.component); + meta.strip_array = true; + meta.allow_local_declaration = false; add_variable_to_interface_block(storage, ib_var_ref, ib_type, fake_var, meta); } } + // When multiple variables need to access same location, + // unroll locations one by one and we will flatten output or input as necessary. + for (auto &loc : meta.location_meta) + { + uint32_t location = loc.first; + auto &location_meta = loc.second; + + uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size()); + uint32_t type_id = build_extended_vector_type(location_meta.base_type_id, location_meta.num_components); + ib_type.member_types.push_back(type_id); + + set_member_name(ib_type.self, ib_mbr_idx, join("m_location_", location)); + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, location); + mark_location_as_used_by_shader(location, get(type_id), storage); + + if (location_meta.flat) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat); + if (location_meta.noperspective) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective); + if (location_meta.centroid) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid); + if (location_meta.sample) + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample); + } + // Sort the members of the structure by their locations. - MemberSorter member_sorter(ib_type, ir.meta[ib_type_id], MemberSorter::Location); + MemberSorter member_sorter(ib_type, ir.meta[ib_type_id], MemberSorter::LocationThenBuiltInType); member_sorter.sort(); // The member indices were saved to the original variables, but after the members // were sorted, those indices are now likely incorrect. Fix those up now. - if (!patch) - fix_up_interface_member_indices(storage, ib_type_id); + fix_up_interface_member_indices(storage, ib_type_id); // For patch inputs, add one more member, holding the array of control point data. if (get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput && patch && @@ -3461,11 +3826,17 @@ uint32_t CompilerMSL::ensure_correct_builtin_type(uint32_t type_id, BuiltIn buil // Ensure that the type is compatible with the shader input. // If it is, simply return the given type ID. // Otherwise, create a new type, and return its ID. -uint32_t CompilerMSL::ensure_correct_input_type(uint32_t type_id, uint32_t location, uint32_t num_components) +uint32_t CompilerMSL::ensure_correct_input_type(uint32_t type_id, uint32_t location, uint32_t component, uint32_t num_components, bool strip_array) { auto &type = get(type_id); - auto p_va = inputs_by_location.find(location); + uint32_t max_array_dimensions = strip_array ? 1 : 0; + + // Struct and array types must match exactly. + if (type.basetype == SPIRType::Struct || type.array.size() > max_array_dimensions) + return type_id; + + auto p_va = inputs_by_location.find({location, component}); if (p_va == end(inputs_by_location)) { if (num_components > type.vecsize) @@ -3817,11 +4188,7 @@ void CompilerMSL::ensure_member_packing_rules_msl(SPIRType &ib_type, uint32_t in set_decoration(type_id, DecorationArrayStride, array_stride); // Remove packed_ for vectors of size 1, 2 and 4. - if (has_extended_decoration(ib_type.self, SPIRVCrossDecorationPhysicalTypePacked)) - SPIRV_CROSS_THROW("Unable to remove packed decoration as entire struct must be fully packed. Do not mix " - "scalar and std140 layout rules."); - else - unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked); + unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked); } else if (is_matrix(mbr_type)) { @@ -3848,11 +4215,7 @@ void CompilerMSL::ensure_member_packing_rules_msl(SPIRType &ib_type, uint32_t in set_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypeID, type_id); // Remove packed_ for vectors of size 1, 2 and 4. - if (has_extended_decoration(ib_type.self, SPIRVCrossDecorationPhysicalTypePacked)) - SPIRV_CROSS_THROW("Unable to remove packed decoration as entire struct must be fully packed. Do not mix " - "scalar and std140 layout rules."); - else - unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked); + unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked); } else SPIRV_CROSS_THROW("Found a buffer packing case which we cannot represent in MSL."); @@ -3950,9 +4313,6 @@ void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_exp // In this case, we just flip transpose states, and emit the store, a transpose must be in the RHS expression, if any. if (is_matrix(type) && lhs_e && lhs_e->need_transpose) { - if (!rhs_e) - SPIRV_CROSS_THROW("Need to transpose right-side expression of a store to row-major matrix, but it is " - "not a SPIRExpression."); lhs_e->need_transpose = false; if (rhs_e && rhs_e->need_transpose) @@ -4592,24 +4952,34 @@ void CompilerMSL::emit_custom_functions() // "fadd" intrinsic support case SPVFuncImplFAdd: statement("template"); - statement("T spvFAdd(T l, T r)"); + statement("[[clang::optnone]] T spvFAdd(T l, T r)"); begin_scope(); statement("return fma(T(1), l, r);"); end_scope(); statement(""); break; + // "fsub" intrinsic support + case SPVFuncImplFSub: + statement("template"); + statement("[[clang::optnone]] T spvFSub(T l, T r)"); + begin_scope(); + statement("return fma(T(-1), r, l);"); + end_scope(); + statement(""); + break; + // "fmul' intrinsic support case SPVFuncImplFMul: statement("template"); - statement("T spvFMul(T l, T r)"); + statement("[[clang::optnone]] T spvFMul(T l, T r)"); begin_scope(); statement("return fma(l, r, T(0));"); end_scope(); statement(""); statement("template"); - statement("vec spvFMulVectorMatrix(vec v, matrix m)"); + statement("[[clang::optnone]] vec spvFMulVectorMatrix(vec v, matrix m)"); begin_scope(); statement("vec res = vec(0);"); statement("for (uint i = Rows; i > 0; --i)"); @@ -4626,7 +4996,7 @@ void CompilerMSL::emit_custom_functions() statement(""); statement("template"); - statement("vec spvFMulMatrixVector(matrix m, vec v)"); + statement("[[clang::optnone]] vec spvFMulMatrixVector(matrix m, vec v)"); begin_scope(); statement("vec res = vec(0);"); statement("for (uint i = Cols; i > 0; --i)"); @@ -4638,8 +5008,7 @@ void CompilerMSL::emit_custom_functions() statement(""); statement("template"); - statement( - "matrix spvFMulMatrixMatrix(matrix l, matrix r)"); + statement("[[clang::optnone]] matrix spvFMulMatrixMatrix(matrix l, matrix r)"); begin_scope(); statement("matrix res;"); statement("for (uint i = 0; i < RCols; i++)"); @@ -4656,6 +5025,24 @@ void CompilerMSL::emit_custom_functions() statement(""); break; + case SPVFuncImplQuantizeToF16: + // Ensure fast-math is disabled to match Vulkan results. + // SpvHalfTypeSelector is used to match the half* template type to the float* template type. + // Depending on GPU, MSL does not always flush converted subnormal halfs to zero, + // as required by OpQuantizeToF16, so check for subnormals and flush them to zero. + statement("template struct SpvHalfTypeSelector;"); + statement("template <> struct SpvHalfTypeSelector { public: using H = half; };"); + statement("template struct SpvHalfTypeSelector> { using H = vec; };"); + statement("template::H>"); + statement("[[clang::optnone]] F spvQuantizeToF16(F fval)"); + begin_scope(); + statement("H hval = H(fval);"); + statement("hval = select(copysign(H(0), hval), hval, isnormal(hval) || isinf(hval) || isnan(hval));"); + statement("return F(hval);"); + end_scope(); + statement(""); + break; + // Emulate texturecube_array with texture2d_array for iOS where this type is not available case SPVFuncImplCubemapTo2DArrayFace: statement(force_inline); @@ -5061,9 +5448,7 @@ void CompilerMSL::emit_custom_functions() statement("// SPIR-V callers expect a uint4. We must convert."); statement("// FIXME: This won't include higher bits if Apple ever supports"); statement("// 128 lanes in an SIMD-group."); - statement( - "return uint4((uint)((simd_vote::vote_t)vote & 0xFFFFFFFF), (uint)(((simd_vote::vote_t)vote >> " - "32) & 0xFFFFFFFF), 0, 0);"); + statement("return uint4(as_type((simd_vote::vote_t)vote), 0, 0);"); } end_scope(); statement(""); @@ -5375,8 +5760,9 @@ void CompilerMSL::emit_custom_functions() case SPVFuncImplReflectScalar: // Metal does not support scalar versions of these functions. + // Ensure fast-math is disabled to match Vulkan results. statement("template"); - statement("inline T spvReflect(T i, T n)"); + statement("[[clang::optnone]] T spvReflect(T i, T n)"); begin_scope(); statement("return i - T(2) * i * n * n;"); end_scope(); @@ -6361,6 +6747,10 @@ void CompilerMSL::emit_specialization_constants_and_structs() if (patch_stage_in_var_id && get_patch_stage_in_struct_type().self == type_id) is_declarable_struct = false; + // Special case. Declare builtin struct anyways if we need to emit a threadgroup version of it. + if (stage_out_masked_builtin_type_id == type_id) + is_declarable_struct = true; + // Align and emit declarable structs...but avoid declaring each more than once. if (is_declarable_struct && declared_structs.count(type_id) == 0) { @@ -6406,16 +6796,22 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id if (ptr_type.storage == StorageClassOutput && get_execution_model() == ExecutionModelTessellationEvaluation) return false; - bool multi_patch_tess_ctl = get_execution_model() == ExecutionModelTessellationControl && - msl_options.multi_patch_workgroup && ptr_type.storage == StorageClassInput; - bool flat_matrix = is_matrix(result_type) && ptr_type.storage == StorageClassInput && !multi_patch_tess_ctl; - bool flat_struct = result_type.basetype == SPIRType::Struct && ptr_type.storage == StorageClassInput; - bool flat_data_type = flat_matrix || is_array(result_type) || flat_struct; - if (!flat_data_type) - return false; - if (has_decoration(ptr, DecorationPatch)) return false; + bool ptr_is_io_variable = ir.ids[ptr].get_type() == TypeVariable; + + bool flattened_io = variable_storage_requires_stage_io(ptr_type.storage); + + bool flat_data_type = flattened_io && + (is_matrix(result_type) || is_array(result_type) || result_type.basetype == SPIRType::Struct); + + // Edge case, even with multi-patch workgroups, we still need to unroll load + // if we're loading control points directly. + if (ptr_is_io_variable && is_array(result_type)) + flat_data_type = true; + + if (!flat_data_type) + return false; // Now, we must unflatten a composite type and take care of interleaving array access with gl_in/gl_out. // Lots of painful code duplication since we *really* should not unroll these kinds of loads in entry point fixup @@ -6424,12 +6820,31 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id uint32_t interface_index = get_extended_decoration(ptr, SPIRVCrossDecorationInterfaceMemberIndex); auto *var = maybe_get_backing_variable(ptr); - bool ptr_is_io_variable = ir.ids[ptr].get_type() == TypeVariable; auto &expr_type = get_pointee_type(ptr_type.self); const auto &iface_type = expression_type(stage_in_ptr_var_id); - if (result_type.array.size() > 2) + if (!flattened_io) + { + // Simplest case for multi-patch workgroups, just unroll array as-is. + if (interface_index == uint32_t(-1)) + return false; + + expr += type_to_glsl(result_type) + "({ "; + uint32_t num_control_points = to_array_size_literal(result_type, uint32_t(result_type.array.size()) - 1); + + for (uint32_t i = 0; i < num_control_points; i++) + { + 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); + if (i + 1 < num_control_points) + expr += ", "; + } + expr += " })"; + } + else if (result_type.array.size() > 2) { SPIRV_CROSS_THROW("Cannot load tessellation IO variables with more than 2 dimensions."); } @@ -6439,7 +6854,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id SPIRV_CROSS_THROW("Loading an array-of-array must be loaded directly from an IO variable."); if (interface_index == uint32_t(-1)) SPIRV_CROSS_THROW("Interface index is unknown. Cannot continue."); - if (result_type.basetype == SPIRType::Struct || flat_matrix) + if (result_type.basetype == SPIRType::Struct || is_matrix(result_type)) SPIRV_CROSS_THROW("Cannot load array-of-array of composite type in tessellation IO."); expr += type_to_glsl(result_type) + "({ "; @@ -6453,44 +6868,19 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id expr += type_to_glsl(sub_type) + "({ "; interface_index = base_interface_index; uint32_t array_size = to_array_size_literal(result_type, 0); - if (multi_patch_tess_ctl) + for (uint32_t j = 0; j < array_size; j++, interface_index++) { - for (uint32_t j = 0; j < array_size; j++) - { - const uint32_t indices[3] = { i, interface_index, j }; + const uint32_t indices[2] = { i, interface_index }; - AccessChainMeta meta; - expr += - access_chain_internal(stage_in_ptr_var_id, indices, 3, - ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); - // If the expression has more vector components than the result type, insert - // a swizzle. This shouldn't happen normally on valid SPIR-V, but it might - // happen if we replace the type of an input variable. - if (!is_matrix(sub_type) && sub_type.basetype != SPIRType::Struct && - expr_type.vecsize > sub_type.vecsize) - expr += vector_swizzle(sub_type.vecsize, 0); + 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); + if (!is_matrix(sub_type) && sub_type.basetype != SPIRType::Struct && + expr_type.vecsize > sub_type.vecsize) + expr += vector_swizzle(sub_type.vecsize, 0); - if (j + 1 < array_size) - expr += ", "; - } - } - else - { - for (uint32_t j = 0; j < array_size; j++, interface_index++) - { - 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); - if (!is_matrix(sub_type) && sub_type.basetype != SPIRType::Struct && - expr_type.vecsize > sub_type.vecsize) - expr += vector_swizzle(sub_type.vecsize, 0); - - if (j + 1 < array_size) - expr += ", "; - } + if (j + 1 < array_size) + expr += ", "; } expr += " })"; if (i + 1 < num_control_points) @@ -6498,7 +6888,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id } expr += " })"; } - else if (flat_struct) + else if (result_type.basetype == SPIRType::Struct) { bool is_array_of_struct = is_array(result_type); if (is_array_of_struct && !ptr_is_io_variable) @@ -6531,7 +6921,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id const auto &mbr_type = get(struct_type.member_types[j]); const auto &expr_mbr_type = get(expr_type.member_types[j]); - if (is_matrix(mbr_type) && ptr_type.storage == StorageClassInput && !multi_patch_tess_ctl) + if (is_matrix(mbr_type) && ptr_type.storage == StorageClassInput) { expr += type_to_glsl(mbr_type) + "("; for (uint32_t k = 0; k < mbr_type.columns; k++, interface_index++) @@ -6541,8 +6931,8 @@ 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); + stage_in_ptr_var_id, indices, 2, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); } else expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); @@ -6558,48 +6948,23 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id { expr += type_to_glsl(mbr_type) + "({ "; uint32_t array_size = to_array_size_literal(mbr_type, 0); - if (multi_patch_tess_ctl) + for (uint32_t k = 0; k < array_size; k++, interface_index++) { - for (uint32_t k = 0; k < array_size; k++) + if (is_array_of_struct) { - if (is_array_of_struct) - { - const uint32_t indices[3] = { i, interface_index, k }; - AccessChainMeta meta; - expr += access_chain_internal( - stage_in_ptr_var_id, indices, 3, - ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); - } - else - expr += join(to_expression(ptr), ".", to_member_name(iface_type, interface_index), "[", - k, "]"); - if (expr_mbr_type.vecsize > mbr_type.vecsize) - expr += vector_swizzle(mbr_type.vecsize, 0); - - if (k + 1 < array_size) - expr += ", "; + 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); } - } - else - { - for (uint32_t k = 0; k < array_size; k++, interface_index++) - { - if (is_array_of_struct) - { - 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); - } - else - expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); - if (expr_mbr_type.vecsize > mbr_type.vecsize) - expr += vector_swizzle(mbr_type.vecsize, 0); + else + expr += to_expression(ptr) + "." + to_member_name(iface_type, interface_index); + if (expr_mbr_type.vecsize > mbr_type.vecsize) + expr += vector_swizzle(mbr_type.vecsize, 0); - if (k + 1 < array_size) - expr += ", "; - } + if (k + 1 < array_size) + expr += ", "; } expr += " })"; } @@ -6629,7 +6994,7 @@ bool CompilerMSL::emit_tessellation_io_load(uint32_t result_type_id, uint32_t id if (is_array_of_struct) expr += " })"; } - else if (flat_matrix) + else if (is_matrix(result_type)) { bool is_array_of_matrix = is_array(result_type); if (is_array_of_matrix && !ptr_is_io_variable) @@ -6655,9 +7020,8 @@ 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); + expr += access_chain_internal(stage_in_ptr_var_id, indices, 2, + ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_PTR_CHAIN_BIT, &meta); if (expr_type.vecsize > result_type.vecsize) expr += vector_swizzle(result_type.vecsize, 0); if (j + 1 < result_type.columns) @@ -6748,48 +7112,112 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l bool patch = false; bool flat_data = false; bool ptr_is_chain = false; - bool multi_patch = get_execution_model() == ExecutionModelTessellationControl && msl_options.multi_patch_workgroup; + bool flatten_composites = false; + + bool is_block = false; + + if (var) + is_block = has_decoration(get_variable_data_type(*var).self, DecorationBlock); if (var) { + flatten_composites = variable_storage_requires_stage_io(var->storage); patch = has_decoration(ops[2], DecorationPatch) || is_patch_block(get_variable_data_type(*var)); // Should match strip_array in add_interface_block. flat_data = var->storage == StorageClassInput || (var->storage == StorageClassOutput && get_execution_model() == ExecutionModelTessellationControl); + // Patch inputs are treated as normal block IO variables, so they don't deal with this path at all. + if (patch && (!is_block || 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]); } - BuiltIn bi_type = BuiltIn(get_decoration(ops[2], DecorationBuiltIn)); - if (var && flat_data && !patch && - (!is_builtin_variable(*var) || bi_type == BuiltInPosition || bi_type == BuiltInPointSize || - bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance || - get_variable_data_type(*var).basetype == SPIRType::Struct)) + bool builtin_variable = false; + bool variable_is_flat = false; + + if (var && flat_data) { + builtin_variable = is_builtin_variable(*var); + + BuiltIn bi_type = BuiltInMax; + if (builtin_variable && !is_block) + bi_type = BuiltIn(get_decoration(var->self, DecorationBuiltIn)); + + variable_is_flat = !builtin_variable || is_block || + bi_type == BuiltInPosition || bi_type == BuiltInPointSize || + bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance; + } + + if (variable_is_flat) + { + // 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) + { + bool masked = false; + if (is_block) + { + uint32_t relevant_member_index = patch ? 3 : 4; + // FIXME: This won't work properly if the application first access chains into gl_out element, + // then access chains into the member. Super weird, but theoretically possible ... + if (length > relevant_member_index) + { + uint32_t mbr_idx = get(ops[relevant_member_index]).scalar(); + masked = is_stage_output_block_member_masked(*var, mbr_idx, true); + } + } + else if (var) + masked = is_stage_output_variable_masked(*var); + + if (masked) + return false; + } + AccessChainMeta meta; SmallVector indices; uint32_t next_id = ir.increase_bound_by(1); indices.reserve(length - 3 + 1); - uint32_t first_non_array_index = ptr_is_chain ? 3 : 4; - VariableID stage_var_id = var->storage == StorageClassInput ? stage_in_ptr_var_id : stage_out_ptr_var_id; + uint32_t first_non_array_index = (ptr_is_chain ? 3 : 4) - (patch ? 1 : 0); + + VariableID stage_var_id; + if (patch) + stage_var_id = var->storage == StorageClassInput ? patch_stage_in_var_id : patch_stage_out_var_id; + 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) + if (!ptr_is_chain && !patch) { // Index into gl_in/gl_out with first array index. - indices.push_back(ops[3]); + indices.push_back(ops[first_non_array_index - 1]); } auto &result_ptr_type = get(ops[0]); uint32_t const_mbr_id = next_id++; - uint32_t index = get_extended_decoration(var->self, SPIRVCrossDecorationInterfaceMemberIndex); - if (var->storage == StorageClassInput || has_decoration(get_variable_element_type(*var).self, DecorationBlock)) + 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)) { uint32_t i = first_non_array_index; auto *type = &get_variable_element_type(*var); @@ -6797,19 +7225,20 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l { // Maybe this is a struct type in the input class, in which case // we put it as a decoration on the corresponding member. - index = get_extended_member_decoration(var->self, get_constant(ops[first_non_array_index]).scalar(), + uint32_t mbr_idx = get_constant(ops[first_non_array_index]).scalar(); + index = get_extended_member_decoration(var->self, mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex); assert(index != uint32_t(-1)); i++; - type = &get(type->member_types[get_constant(ops[first_non_array_index]).scalar()]); + type = &get(type->member_types[mbr_idx]); } // In this case, we're poking into flattened structures and arrays, so now we have to // combine the following indices. If we encounter a non-constant index, // we're hosed. - for (; i < length; ++i) + for (; flatten_composites && i < length; ++i) { - if ((multi_patch || (!is_array(*type) && !is_matrix(*type))) && type->basetype != SPIRType::Struct) + if (!is_array(*type) && !is_matrix(*type) && type->basetype != SPIRType::Struct) break; auto *c = maybe_get(ops[i]); @@ -6820,7 +7249,16 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l // We're in flattened space, so just increment the member index into IO block. // We can only do this once in the current implementation, so either: // Struct, Matrix or 1-dimensional array for a control point. - index += c->scalar(); + if (type->basetype == SPIRType::Struct && var->storage == StorageClassOutput) + { + // Need to consider holes, since individual block members might be masked away. + uint32_t mbr_idx = c->scalar(); + for (uint32_t j = 0; j < mbr_idx; j++) + if (!is_stage_output_block_member_masked(*var, j, true)) + index++; + } + else + index += c->scalar(); if (type->parent_type) type = &get(type->parent_type); @@ -6828,31 +7266,48 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l type = &get(type->member_types[c->scalar()]); } - if ((!multi_patch && (is_matrix(result_ptr_type) || is_array(result_ptr_type))) || - result_ptr_type.basetype == SPIRType::Struct) - { - // We're not going to emit the actual member name, we let any further OpLoad take care of that. - // Tag the access chain with the member index we're referencing. - set_extended_decoration(ops[1], SPIRVCrossDecorationInterfaceMemberIndex, index); - } - else + // We're not going to emit the actual member name, we let any further OpLoad take care of that. + // Tag the access chain with the member index we're referencing. + bool defer_access_chain = flatten_composites && (is_matrix(result_ptr_type) || is_array(result_ptr_type) || + result_ptr_type.basetype == SPIRType::Struct); + + if (!defer_access_chain) { // Access the appropriate member of gl_in/gl_out. set(const_mbr_id, get_uint_type_id(), index, false); indices.push_back(const_mbr_id); + // Member index is now irrelevant. + index = uint32_t(-1); + // Append any straggling access chain indices. if (i < length) indices.insert(indices.end(), ops + i, ops + length); } + else + { + // We must have consumed the entire access chain if we're deferring it. + assert(i == length); + } + + if (index != uint32_t(-1)) + set_extended_decoration(ops[1], SPIRVCrossDecorationInterfaceMemberIndex, index); + else + unset_extended_decoration(ops[1], SPIRVCrossDecorationInterfaceMemberIndex); } else { - assert(index != uint32_t(-1)); - set(const_mbr_id, get_uint_type_id(), index, false); - indices.push_back(const_mbr_id); + if (index != uint32_t(-1)) + { + set(const_mbr_id, get_uint_type_id(), index, false); + indices.push_back(const_mbr_id); + } - indices.insert(indices.end(), ops + 4, ops + length); + // Member index is now irrelevant. + index = uint32_t(-1); + unset_extended_decoration(ops[1], SPIRVCrossDecorationInterfaceMemberIndex); + + indices.insert(indices.end(), ops + first_non_array_index, ops + length); } // We use the pointer to the base of the input/output array here, @@ -6862,7 +7317,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l 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, true); + e = access_chain(ptr, indices.data(), uint32_t(indices.size()), result_ptr_type, &meta, !patch); } else { @@ -6878,7 +7333,11 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l // 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(ptr); - if (ptr_expr && ptr_expr->implied_read_expressions.size() == 2) + 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()), @@ -7011,6 +7470,21 @@ void CompilerMSL::prepare_access_chain_for_scalar_access(std::string &expr, cons } } +bool CompilerMSL::access_chain_needs_stage_io_builtin_translation(uint32_t base) +{ + auto *var = maybe_get_backing_variable(base); + if (!var || !is_tessellation_shader()) + return true; + + // We only need to rewrite builtin access chains when accessing flattened builtins like gl_ClipDistance_N. + // Avoid overriding it back to just gl_ClipDistance. + // This can only happen in scenarios where we cannot flatten/unflatten access chains, so, the only case + // where this triggers is evaluation shader inputs. + bool redirect_builtin = get_execution_model() == ExecutionModelTessellationEvaluation ? + var->storage == StorageClassOutput : false; + return redirect_builtin; +} + // Sets the interface member index for an access chain to a pull-model interpolant. void CompilerMSL::fix_up_interpolant_access_chain(const uint32_t *ops, uint32_t length) { @@ -7038,7 +7512,7 @@ void CompilerMSL::fix_up_interpolant_access_chain(const uint32_t *ops, uint32_t // for that getting the base index. for (uint32_t i = 3; i < length; ++i) { - if (is_vector(*type) && is_scalar(result_type)) + if (is_vector(*type) && !is_array(*type) && is_scalar(result_type)) { // We don't want to combine the next index. Actually, we need to save it // so we know to apply a swizzle to the result of the interpolation. @@ -7261,19 +7735,26 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) break; case OpFMul: - if (msl_options.invariant_float_math) + if (msl_options.invariant_float_math || has_decoration(ops[1], DecorationNoContraction)) MSL_BFOP(spvFMul); else MSL_BOP(*); break; case OpFAdd: - if (msl_options.invariant_float_math) + if (msl_options.invariant_float_math || has_decoration(ops[1], DecorationNoContraction)) MSL_BFOP(spvFAdd); else MSL_BOP(+); break; + case OpFSub: + if (msl_options.invariant_float_math || has_decoration(ops[1], DecorationNoContraction)) + MSL_BFOP(spvFSub); + else + MSL_BOP(-); + break; + // Atomics case OpAtomicExchange: { @@ -7625,28 +8106,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) uint32_t result_type = ops[0]; uint32_t id = ops[1]; uint32_t arg = ops[2]; - - string exp; - auto &type = get(result_type); - - switch (type.vecsize) - { - case 1: - exp = join("float(half(", to_expression(arg), "))"); - break; - case 2: - exp = join("float2(half2(", to_expression(arg), "))"); - break; - case 3: - exp = join("float3(half3(", to_expression(arg), "))"); - break; - case 4: - exp = join("float4(half4(", to_expression(arg), "))"); - break; - default: - SPIRV_CROSS_THROW("Illegal argument to OpQuantizeToF16."); - } - + string exp = join("spvQuantizeToF16(", to_expression(arg), ")"); emit_op(result_type, id, exp, should_forward(arg)); break; } @@ -7699,7 +8159,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) expr += "("; for (uint32_t col = 0; col < type.columns; col++) { - expr += to_enclosed_expression(a); + expr += to_enclosed_unpacked_expression(a); expr += " * "; expr += to_extract_component_expression(b, col); if (col + 1 < type.columns) @@ -7715,7 +8175,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) case OpVectorTimesMatrix: case OpMatrixTimesVector: { - if (!msl_options.invariant_float_math) + if (!msl_options.invariant_float_math && !has_decoration(ops[1], DecorationNoContraction)) { CompilerGLSL::emit_instruction(instruction); break; @@ -7757,7 +8217,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) case OpMatrixTimesMatrix: { - if (!msl_options.invariant_float_math) + if (!msl_options.invariant_float_math && !has_decoration(ops[1], DecorationNoContraction)) { CompilerGLSL::emit_instruction(instruction); break; @@ -7804,19 +8264,19 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) auto &res_type = get(type.member_types[1]); if (opcode == OpIAddCarry) { - statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", to_enclosed_expression(op0), " + ", - to_enclosed_expression(op1), ";"); + statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", + to_enclosed_unpacked_expression(op0), " + ", to_enclosed_unpacked_expression(op1), ";"); statement(to_expression(result_id), ".", to_member_name(type, 1), " = select(", type_to_glsl(res_type), - "(1), ", type_to_glsl(res_type), "(0), ", to_expression(result_id), ".", to_member_name(type, 0), - " >= max(", to_expression(op0), ", ", to_expression(op1), "));"); + "(1), ", type_to_glsl(res_type), "(0), ", to_unpacked_expression(result_id), ".", to_member_name(type, 0), + " >= max(", to_unpacked_expression(op0), ", ", to_unpacked_expression(op1), "));"); } else { - statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", to_enclosed_expression(op0), " - ", - to_enclosed_expression(op1), ";"); + statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", to_enclosed_unpacked_expression(op0), " - ", + to_enclosed_unpacked_expression(op1), ";"); statement(to_expression(result_id), ".", to_member_name(type, 1), " = select(", type_to_glsl(res_type), - "(1), ", type_to_glsl(res_type), "(0), ", to_enclosed_expression(op0), - " >= ", to_enclosed_expression(op1), ");"); + "(1), ", type_to_glsl(res_type), "(0), ", to_enclosed_unpacked_expression(op0), + " >= ", to_enclosed_unpacked_expression(op1), ");"); } break; } @@ -7831,10 +8291,10 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) auto &type = get(result_type); emit_uninitialized_temporary_expression(result_type, result_id); - statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", to_enclosed_expression(op0), " * ", - to_enclosed_expression(op1), ";"); - statement(to_expression(result_id), ".", to_member_name(type, 1), " = mulhi(", to_expression(op0), ", ", - to_expression(op1), ");"); + statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", + to_enclosed_unpacked_expression(op0), " * ", to_enclosed_unpacked_expression(op1), ";"); + statement(to_expression(result_id), ".", to_member_name(type, 1), " = mulhi(", + to_unpacked_expression(op0), ", ", to_unpacked_expression(op1), ");"); break; } @@ -7889,8 +8349,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) uint32_t id = ops[1]; uint32_t a = ops[2], b = ops[3]; bool forward = should_forward(a) && should_forward(b); - emit_op(result_type, id, join("int(short(", to_expression(a), ")) * int(short(", to_expression(b), "))"), - forward); + emit_op(result_type, id, join("int(short(", to_unpacked_expression(a), ")) * int(short(", to_unpacked_expression(b), "))"), forward); inherit_expression_dependencies(id, a); inherit_expression_dependencies(id, b); break; @@ -7902,8 +8361,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) uint32_t id = ops[1]; uint32_t a = ops[2], b = ops[3]; bool forward = should_forward(a) && should_forward(b); - emit_op(result_type, id, join("uint(ushort(", to_expression(a), ")) * uint(ushort(", to_expression(b), "))"), - forward); + emit_op(result_type, id, join("uint(ushort(", to_unpacked_expression(a), ")) * uint(ushort(", to_unpacked_expression(b), "))"), forward); inherit_expression_dependencies(id, a); inherit_expression_dependencies(id, b); break; @@ -7930,6 +8388,98 @@ void CompilerMSL::emit_instruction(const Instruction &instruction) SPIRV_CROSS_THROW("Raster order groups require MSL 2.0."); break; // Nothing to do in the body + case OpConvertUToAccelerationStructureKHR: + SPIRV_CROSS_THROW("ConvertUToAccelerationStructure is not supported in MSL."); + case OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR: + SPIRV_CROSS_THROW("BindingTableRecordOffset is not supported in MSL."); + + case OpRayQueryInitializeKHR: + { + flush_variable_declaration(ops[0]); + + statement(to_expression(ops[0]), ".reset(", "ray(", to_expression(ops[4]), ", ", to_expression(ops[6]), ", ", + to_expression(ops[5]), ", ", to_expression(ops[7]), "), ", to_expression(ops[1]), + ", intersection_params());"); + break; + } + case OpRayQueryProceedKHR: + { + flush_variable_declaration(ops[0]); + emit_op(ops[0], ops[1], join(to_expression(ops[2]), ".next()"), false); + break; + } +#define MSL_RAY_QUERY_IS_CANDIDATE get(ops[3]).scalar_i32() == 0 + +#define MSL_RAY_QUERY_GET_OP(op, msl_op) \ + case OpRayQueryGet##op##KHR: \ + flush_variable_declaration(ops[2]); \ + emit_op(ops[0], ops[1], join(to_expression(ops[2]), ".get_" #msl_op "()"), false); \ + break + +#define MSL_RAY_QUERY_OP_INNER2(op, msl_prefix, msl_op) \ + case OpRayQueryGet##op##KHR: \ + flush_variable_declaration(ops[2]); \ + if (MSL_RAY_QUERY_IS_CANDIDATE) \ + emit_op(ops[0], ops[1], join(to_expression(ops[2]), #msl_prefix "_candidate_" #msl_op "()"), false); \ + else \ + emit_op(ops[0], ops[1], join(to_expression(ops[2]), #msl_prefix "_committed_" #msl_op "()"), false); \ + break + +#define MSL_RAY_QUERY_GET_OP2(op, msl_op) MSL_RAY_QUERY_OP_INNER2(op, .get, msl_op) +#define MSL_RAY_QUERY_IS_OP2(op, msl_op) MSL_RAY_QUERY_OP_INNER2(op, .is, msl_op) + + MSL_RAY_QUERY_GET_OP(RayTMin, ray_min_distance); + MSL_RAY_QUERY_GET_OP(WorldRayOrigin, world_space_ray_direction); + MSL_RAY_QUERY_GET_OP(WorldRayDirection, world_space_ray_origin); + MSL_RAY_QUERY_GET_OP2(IntersectionInstanceId, instance_id); + MSL_RAY_QUERY_GET_OP2(IntersectionInstanceCustomIndex, user_instance_id); + MSL_RAY_QUERY_GET_OP2(IntersectionBarycentrics, triangle_barycentric_coord); + MSL_RAY_QUERY_GET_OP2(IntersectionPrimitiveIndex, primitive_id); + MSL_RAY_QUERY_GET_OP2(IntersectionGeometryIndex, geometry_id); + MSL_RAY_QUERY_GET_OP2(IntersectionObjectRayOrigin, ray_origin); + MSL_RAY_QUERY_GET_OP2(IntersectionObjectRayDirection, ray_direction); + MSL_RAY_QUERY_GET_OP2(IntersectionObjectToWorld, object_to_world_transform); + MSL_RAY_QUERY_GET_OP2(IntersectionWorldToObject, world_to_object_transform); + MSL_RAY_QUERY_IS_OP2(IntersectionFrontFace, triangle_front_facing); + + case OpRayQueryGetIntersectionTypeKHR: + flush_variable_declaration(ops[2]); + if (MSL_RAY_QUERY_IS_CANDIDATE) + emit_op(ops[0], ops[1], join("uint(", to_expression(ops[2]), ".get_candidate_intersection_type()) - 1"), + false); + else + emit_op(ops[0], ops[1], join("uint(", to_expression(ops[2]), ".get_committed_intersection_type())"), false); + break; + case OpRayQueryGetIntersectionTKHR: + flush_variable_declaration(ops[2]); + if (MSL_RAY_QUERY_IS_CANDIDATE) + emit_op(ops[0], ops[1], join(to_expression(ops[2]), ".get_candidate_triangle_distance()"), false); + else + emit_op(ops[0], ops[1], join(to_expression(ops[2]), ".get_committed_distance()"), false); + break; + case OpRayQueryGetIntersectionCandidateAABBOpaqueKHR: + { + flush_variable_declaration(ops[0]); + emit_op(ops[0], ops[1], join(to_expression(ops[2]), ".is_candidate_non_opaque_bounding_box()"), false); + break; + } + case OpRayQueryConfirmIntersectionKHR: + flush_variable_declaration(ops[0]); + statement(to_expression(ops[0]), ".commit_triangle_intersection();"); + break; + case OpRayQueryGenerateIntersectionKHR: + flush_variable_declaration(ops[0]); + statement(to_expression(ops[0]), ".commit_bounding_box_intersection(", to_expression(ops[1]), ");"); + break; + case OpRayQueryTerminateKHR: + flush_variable_declaration(ops[0]); + statement(to_expression(ops[0]), ".abort();"); + break; +#undef MSL_RAY_QUERY_GET_OP +#undef MSL_RAY_QUERY_IS_CANDIDATE +#undef MSL_RAY_QUERY_IS_OP2 +#undef MSL_RAY_QUERY_GET_OP2 +#undef MSL_RAY_QUERY_OP_INNER2 default: CompilerGLSL::emit_instruction(instruction); break; @@ -8046,20 +8596,60 @@ void CompilerMSL::emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uin flush_all_active_variables(); } -void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageClass lhs_storage, - StorageClass rhs_storage) +static bool storage_class_array_is_thread(StorageClass storage) +{ + switch (storage) + { + case StorageClassInput: + case StorageClassOutput: + case StorageClassGeneric: + case StorageClassFunction: + case StorageClassPrivate: + return true; + + default: + return false; + } +} + +void CompilerMSL::emit_array_copy(const string &lhs, uint32_t lhs_id, uint32_t rhs_id, + StorageClass lhs_storage, StorageClass rhs_storage) { // Allow Metal to use the array template to make arrays a value type. // This, however, cannot be used for threadgroup address specifiers, so consider the custom array copy as fallback. - bool lhs_thread = (lhs_storage == StorageClassOutput || lhs_storage == StorageClassFunction || - lhs_storage == StorageClassGeneric || lhs_storage == StorageClassPrivate); - bool rhs_thread = (rhs_storage == StorageClassInput || rhs_storage == StorageClassFunction || - rhs_storage == StorageClassOutput || - rhs_storage == StorageClassGeneric || rhs_storage == StorageClassPrivate); + bool lhs_is_thread_storage = storage_class_array_is_thread(lhs_storage); + bool rhs_is_thread_storage = storage_class_array_is_thread(rhs_storage); + + bool lhs_is_array_template = lhs_is_thread_storage; + bool rhs_is_array_template = rhs_is_thread_storage; + + // Special considerations for stage IO variables. + // If the variable is actually backed by non-user visible device storage, we use array templates for those. + // + // Another special consideration is given to thread local variables which happen to have Offset decorations + // applied to them. Block-like types do not use array templates, so we need to force POD path if we detect + // these scenarios. This check isn't perfect since it would be technically possible to mix and match these things, + // and for a fully correct solution we might have to track array template state through access chains as well, + // but for all reasonable use cases, this should suffice. + // This special case should also only apply to Function/Private storage classes. + // We should not check backing variable for temporaries. + auto *lhs_var = maybe_get_backing_variable(lhs_id); + if (lhs_var && lhs_storage == StorageClassStorageBuffer && storage_class_array_is_thread(lhs_var->storage)) + lhs_is_array_template = true; + else if (lhs_var && (lhs_storage == StorageClassFunction || lhs_storage == StorageClassPrivate) && + type_is_block_like(get(lhs_var->basetype))) + lhs_is_array_template = false; + + auto *rhs_var = maybe_get_backing_variable(rhs_id); + if (rhs_var && rhs_storage == StorageClassStorageBuffer && storage_class_array_is_thread(rhs_var->storage)) + rhs_is_array_template = true; + else if (rhs_var && (rhs_storage == StorageClassFunction || rhs_storage == StorageClassPrivate) && + type_is_block_like(get(rhs_var->basetype))) + 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_thread && rhs_thread && !using_builtin_array()) + if (lhs_is_array_template && rhs_is_array_template && !using_builtin_array()) { statement(lhs, " = ", to_expression(rhs_id), ";"); } @@ -8101,15 +8691,15 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla add_spv_func_and_recompile(SPVFuncImplArrayCopy); const char *tag = nullptr; - if (lhs_thread && is_constant) + if (lhs_is_thread_storage && is_constant) tag = "FromConstantToStack"; else if (lhs_storage == StorageClassWorkgroup && is_constant) tag = "FromConstantToThreadGroup"; - else if (lhs_thread && rhs_thread) + else if (lhs_is_thread_storage && rhs_is_thread_storage) tag = "FromStackToStack"; - else if (lhs_storage == StorageClassWorkgroup && rhs_thread) + else if (lhs_storage == StorageClassWorkgroup && rhs_is_thread_storage) tag = "FromStackToThreadGroup"; - else if (lhs_thread && rhs_storage == StorageClassWorkgroup) + else if (lhs_is_thread_storage && rhs_storage == StorageClassWorkgroup) tag = "FromThreadGroupToStack"; else if (lhs_storage == StorageClassWorkgroup && rhs_storage == StorageClassWorkgroup) tag = "FromThreadGroupToThreadGroup"; @@ -8119,19 +8709,21 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla tag = "FromConstantToDevice"; else if (lhs_storage == StorageClassStorageBuffer && rhs_storage == StorageClassWorkgroup) tag = "FromThreadGroupToDevice"; - else if (lhs_storage == StorageClassStorageBuffer && rhs_thread) + else if (lhs_storage == StorageClassStorageBuffer && rhs_is_thread_storage) tag = "FromStackToDevice"; else if (lhs_storage == StorageClassWorkgroup && rhs_storage == StorageClassStorageBuffer) tag = "FromDeviceToThreadGroup"; - else if (lhs_thread && rhs_storage == StorageClassStorageBuffer) + else if (lhs_is_thread_storage && rhs_storage == StorageClassStorageBuffer) tag = "FromDeviceToStack"; else SPIRV_CROSS_THROW("Unknown storage class used for copying arrays."); // Pass internal array of spvUnsafeArray<> into wrapper functions - if (lhs_thread && !msl_options.force_native_arrays) + if (lhs_is_array_template && rhs_is_array_template && !msl_options.force_native_arrays) + statement("spvArrayCopy", tag, type.array.size(), "(", lhs, ".elements, ", to_expression(rhs_id), ".elements);"); + if (lhs_is_array_template && !msl_options.force_native_arrays) statement("spvArrayCopy", tag, type.array.size(), "(", lhs, ".elements, ", to_expression(rhs_id), ");"); - else if (rhs_thread && !msl_options.force_native_arrays) + else if (rhs_is_array_template && !msl_options.force_native_arrays) statement("spvArrayCopy", tag, type.array.size(), "(", lhs, ", ", to_expression(rhs_id), ".elements);"); else statement("spvArrayCopy", tag, type.array.size(), "(", lhs, ", ", to_expression(rhs_id), ");"); @@ -8198,8 +8790,9 @@ bool CompilerMSL::maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs) if (p_v_lhs) flush_variable_declaration(p_v_lhs->self); - emit_array_copy(to_expression(id_lhs), id_rhs, get_expression_effective_storage_class(id_lhs), - get_expression_effective_storage_class(id_rhs)); + auto lhs_storage = get_expression_effective_storage_class(id_lhs); + auto rhs_storage = get_expression_effective_storage_class(id_rhs); + emit_array_copy(to_expression(id_lhs), id_lhs, id_rhs, lhs_storage, rhs_storage); register_write(id_lhs); return true; @@ -8299,7 +8892,8 @@ const char *CompilerMSL::get_memory_order(uint32_t) return "memory_order_relaxed"; } -// Override for MSL-specific extension syntax instructions +// Override for MSL-specific extension syntax instructions. +// In some cases, deliberately select either the fast or precise versions of the MSL functions to match Vulkan math precision results. void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, const uint32_t *args, uint32_t count) { auto op = static_cast(eop); @@ -8311,8 +8905,17 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, switch (op) { + case GLSLstd450Sinh: + emit_unary_func_op(result_type, id, args[0], "fast::sinh"); + break; + case GLSLstd450Cosh: + emit_unary_func_op(result_type, id, args[0], "fast::cosh"); + break; + case GLSLstd450Tanh: + emit_unary_func_op(result_type, id, args[0], "precise::tanh"); + break; case GLSLstd450Atan2: - emit_binary_func_op(result_type, id, args[0], args[1], "atan2"); + emit_binary_func_op(result_type, id, args[0], args[1], "precise::atan2"); break; case GLSLstd450InverseSqrt: emit_unary_func_op(result_type, id, args[0], "rsqrt"); @@ -8536,25 +9139,20 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, break; case GLSLstd450Length: - // MSL does not support scalar versions here. + // MSL does not support scalar versions, so use abs(). if (expression_type(args[0]).vecsize == 1) - { - // Equivalent to abs(). emit_unary_func_op(result_type, id, args[0], "abs"); - } else CompilerGLSL::emit_glsl_op(result_type, id, eop, args, count); break; case GLSLstd450Normalize: // MSL does not support scalar versions here. + // Returns -1 or 1 for valid input, sign() does the job. if (expression_type(args[0]).vecsize == 1) - { - // Returns -1 or 1 for valid input, sign() does the job. emit_unary_func_op(result_type, id, args[0], "sign"); - } else - CompilerGLSL::emit_glsl_op(result_type, id, eop, args, count); + emit_unary_func_op(result_type, id, args[0], "fast::normalize"); break; case GLSLstd450Reflect: @@ -8582,8 +9180,16 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, case GLSLstd450Frexp: { // Special case. If the variable is a scalar access chain, we cannot use it directly. We have to emit a temporary. + // Another special case is if the variable is in a storage class which is not thread. auto *ptr = maybe_get(args[1]); - if (ptr && ptr->access_chain && is_scalar(expression_type(args[1]))) + auto &type = expression_type(args[1]); + + bool is_thread_storage = storage_class_array_is_thread(type.storage); + if (type.storage == StorageClassOutput && capture_output_to_buffer) + is_thread_storage = false; + + if (!is_thread_storage || + (ptr && ptr->access_chain && is_scalar(expression_type(args[1])))) { register_call_out_argument(args[1]); forced_temporaries.insert(id); @@ -8594,7 +9200,7 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, if (!tmp_id) tmp_id = ir.increase_bound_by(1); - uint32_t tmp_type_id = get_pointee_type_id(ptr->expression_type); + uint32_t tmp_type_id = get_pointee_type_id(expression_type_id(args[1])); emit_uninitialized_temporary_expression(tmp_type_id, tmp_id); emit_binary_func_op(result_type, id, args[0], tmp_id, eop == GLSLstd450Modf ? "modf" : "frexp"); statement(to_expression(args[1]), " = ", to_expression(tmp_id), ";"); @@ -8651,7 +9257,8 @@ void CompilerMSL::emit_interface_block(uint32_t ib_var_id) { auto &ib_var = get(ib_var_id); auto &ib_type = get_variable_data_type(ib_var); - assert(ib_type.basetype == SPIRType::Struct && !ib_type.member_types.empty()); + //assert(ib_type.basetype == SPIRType::Struct && !ib_type.member_types.empty()); + assert(ib_type.basetype == SPIRType::Struct); emit_struct(ib_type); } } @@ -8793,8 +9400,6 @@ static bool needs_chroma_reconstruction(const MSLConstexprSampler *constexpr_sam string CompilerMSL::to_function_name(const TextureFunctionNameArguments &args) { VariableID img = args.base.img; - auto &imgtype = *args.base.imgtype; - const MSLConstexprSampler *constexpr_sampler = nullptr; bool is_dynamic_img_sampler = false; if (auto *var = maybe_get_backing_variable(img)) @@ -8808,8 +9413,9 @@ string CompilerMSL::to_function_name(const TextureFunctionNameArguments &args) if (msl_options.swizzle_texture_samples && args.base.is_gather && !is_dynamic_img_sampler && (!constexpr_sampler || !constexpr_sampler->ycbcr_conversion_enable)) { - add_spv_func_and_recompile(imgtype.image.depth ? SPVFuncImplGatherCompareSwizzle : SPVFuncImplGatherSwizzle); - return imgtype.image.depth ? "spvGatherCompareSwizzle" : "spvGatherSwizzle"; + bool is_compare = comparison_ids.count(img); + add_spv_func_and_recompile(is_compare ? SPVFuncImplGatherCompareSwizzle : SPVFuncImplGatherSwizzle); + return is_compare ? "spvGatherCompareSwizzle" : "spvGatherSwizzle"; } auto *combined = maybe_get(img); @@ -9421,7 +10027,7 @@ string CompilerMSL::to_function_args(const TextureFunctionArguments &args, bool image_var = var->self; } - if (image_var == 0 || !image_is_comparison(expression_type(image_var), image_var)) + if (image_var == 0 || !is_depth_image(expression_type(image_var), image_var)) farg_str += ", " + to_component_argument(args.component); } } @@ -9463,7 +10069,6 @@ string CompilerMSL::to_component_argument(uint32_t id) default: SPIRV_CROSS_THROW("The value (" + to_string(component_index) + ") of OpConstant ID " + to_string(id) + " is not a valid Component index, which must be one of 0, 1, 2, or 3."); - return "component::x"; } } @@ -9671,7 +10276,6 @@ static string create_swizzle(MSLComponentSwizzle swizzle) return "spvSwizzle::alpha"; default: SPIRV_CROSS_THROW("Invalid component swizzle."); - return ""; } } @@ -10045,8 +10649,10 @@ string CompilerMSL::to_struct_member(const SPIRType &type, uint32_t member_type_ // we need flat arrays, but if we're somehow declaring gl_PerVertex for constant array reasons, we want // template array types to be declared. bool is_ib_in_out = - ((stage_out_var_id && get_stage_out_struct_type().self == type.self) || - (stage_in_var_id && get_stage_in_struct_type().self == type.self)); + ((stage_out_var_id && get_stage_out_struct_type().self == type.self && + variable_storage_requires_stage_io(StorageClassOutput)) || + (stage_in_var_id && get_stage_in_struct_type().self == type.self && + variable_storage_requires_stage_io(StorageClassInput))); if (is_ib_in_out && is_member_builtin(type, index, &builtin)) is_using_builtin_array = true; array_type = type_to_array_glsl(physical_type); @@ -10168,8 +10774,14 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return string(" [[") + builtin_qualifier(builtin) + "]]" + (mbr_type.array.empty() ? "" : " "); case BuiltInClipDistance: - if (has_member_decoration(type.self, index, DecorationLocation)) - return join(" [[user(clip", get_member_decoration(type.self, index, DecorationLocation), ")]]"); + if (has_member_decoration(type.self, index, DecorationIndex)) + return join(" [[user(clip", get_member_decoration(type.self, index, DecorationIndex), ")]]"); + else + return string(" [[") + builtin_qualifier(builtin) + "]]" + (mbr_type.array.empty() ? "" : " "); + + case BuiltInCullDistance: + if (has_member_decoration(type.self, index, DecorationIndex)) + return join(" [[user(cull", get_member_decoration(type.self, index, DecorationIndex), ")]]"); else return string(" [[") + builtin_qualifier(builtin) + "]]" + (mbr_type.array.empty() ? "" : " "); @@ -10177,15 +10789,9 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return ""; } } - uint32_t comp; - uint32_t locn = get_member_location(type.self, index, &comp); - if (locn != k_unknown_location) - { - if (comp != k_unknown_component) - return string(" [[user(locn") + convert_to_string(locn) + "_" + convert_to_string(comp) + ")]]"; - else - return string(" [[user(locn") + convert_to_string(locn) + ")]]"; - } + string loc_qual = member_location_attribute_qualifier(type, index); + if (!loc_qual.empty()) + return join(" [[", loc_qual, "]]"); } // Tessellation control function inputs @@ -10290,31 +10896,16 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in break; case BuiltInClipDistance: - return join(" [[user(clip", get_member_decoration(type.self, index, DecorationLocation), ")]]"); + return join(" [[user(clip", get_member_decoration(type.self, index, DecorationIndex), ")]]"); + case BuiltInCullDistance: + return join(" [[user(cull", get_member_decoration(type.self, index, DecorationIndex), ")]]"); default: break; } } else - { - uint32_t comp; - uint32_t locn = get_member_location(type.self, index, &comp); - if (locn != k_unknown_location) - { - // For user-defined attributes, this is fine. From Vulkan spec: - // A user-defined output variable is considered to match an input variable in the subsequent stage if - // the two variables are declared with the same Location and Component decoration and match in type - // and decoration, except that interpolation decorations are not required to match. For the purposes - // of interface matching, variables declared without a Component decoration are considered to have a - // Component decoration of zero. - - if (comp != k_unknown_component && comp != 0) - quals = string("user(locn") + convert_to_string(locn) + "_" + convert_to_string(comp) + ")"; - else - quals = string("user(locn") + convert_to_string(locn) + ")"; - } - } + quals = member_location_attribute_qualifier(type, index); if (builtin == BuiltInBaryCoordNV || builtin == BuiltInBaryCoordNoPerspNV) { @@ -10446,6 +11037,30 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return ""; } +// A user-defined output variable is considered to match an input variable in the subsequent +// stage if the two variables are declared with the same Location and Component decoration and +// match in type and decoration, except that interpolation decorations are not required to match. +// For the purposes of interface matching, variables declared without a Component decoration are +// considered to have a Component decoration of zero. +string CompilerMSL::member_location_attribute_qualifier(const SPIRType &type, uint32_t index) +{ + string quals; + uint32_t comp; + uint32_t locn = get_member_location(type.self, index, &comp); + if (locn != k_unknown_location) + { + quals += "user(locn"; + quals += convert_to_string(locn); + if (comp != k_unknown_component && comp != 0) + { + quals += "_"; + quals += convert_to_string(comp); + } + quals += ")"; + } + return quals; +} + // Returns the location decoration of the member with the specified index in the specified type. // If the location of the member has been explicitly set, that location is used. If not, this // function assumes the members are ordered in their location order, and simply returns the @@ -10482,12 +11097,16 @@ uint32_t CompilerMSL::get_or_allocate_builtin_input_member_location(spv::BuiltIn auto &mbr_type = get(get(type_id).member_types[index]); uint32_t count = type_to_location_count(mbr_type); - // This should always be 1. - if (count != 1) - return k_unknown_location; - loc = 0; - while (location_inputs_in_use.count(loc) != 0) + + const auto location_range_in_use = [this](uint32_t location, uint32_t location_count) -> bool { + for (uint32_t i = 0; i < location_count; i++) + if (location_inputs_in_use.count(location + i) != 0) + return true; + return false; + }; + + while (location_range_in_use(loc, count)) loc++; set_member_decoration(type_id, index, DecorationLocation, loc); @@ -10503,7 +11122,7 @@ uint32_t CompilerMSL::get_or_allocate_builtin_input_member_location(spv::BuiltIn else builtin_to_automatic_input_location[builtin] = loc; - mark_location_as_used_by_shader(loc, mbr_type, StorageClassInput); + mark_location_as_used_by_shader(loc, mbr_type, StorageClassInput, true); return loc; } @@ -10544,10 +11163,7 @@ string CompilerMSL::func_type_decl(SPIRType &type) execution.output_vertices, ") ]] vertex"); break; case ExecutionModelFragment: - entry_type = execution.flags.get(ExecutionModeEarlyFragmentTests) || - execution.flags.get(ExecutionModePostDepthCoverage) ? - "[[ early_fragment_tests ]] fragment" : - "fragment"; + entry_type = uses_explicit_early_fragment_test() ? "[[ early_fragment_tests ]] fragment" : "fragment"; break; case ExecutionModelTessellationControl: if (!msl_options.supports_msl_version(1, 2)) @@ -10567,6 +11183,12 @@ string CompilerMSL::func_type_decl(SPIRType &type) return entry_type + " " + return_type; } +bool CompilerMSL::uses_explicit_early_fragment_test() +{ + auto &ep_flags = get_entry_point().flags; + return ep_flags.get(ExecutionModeEarlyFragmentTests) || ep_flags.get(ExecutionModePostDepthCoverage); +} + // In MSL, address space qualifiers are required for all pointer or reference variables string CompilerMSL::get_argument_address_space(const SPIRVariable &argument) { @@ -10640,7 +11262,25 @@ string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id, bo case StorageClassOutput: if (capture_output_to_buffer) - addr_space = "device"; + { + if (var && type.storage == StorageClassOutput) + { + bool is_masked = is_stage_output_variable_masked(*var); + + if (is_masked) + { + if (is_tessellation_shader()) + addr_space = "threadgroup"; + else + addr_space = "thread"; + } + else if (variable_decl_is_remapped_storage(*var, StorageClassWorkgroup)) + addr_space = "threadgroup"; + } + + if (!addr_space) + addr_space = "device"; + } break; default: @@ -10772,6 +11412,12 @@ bool CompilerMSL::is_sample_rate() const (msl_options.use_framebuffer_fetch_subpasses && need_subpass_input)); } +bool CompilerMSL::is_intersection_query() const +{ + auto &caps = get_declared_capabilities(); + return std::find(caps.begin(), caps.end(), CapabilityRayQueryKHR) != caps.end(); +} + void CompilerMSL::entry_point_args_builtin(string &ep_args) { // Builtin variables @@ -11250,6 +11896,10 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args) } break; } + case SPIRType::AccelerationStructure: + ep_args += ", " + type_to_glsl(type, var_id) + " " + r.name; + ep_args += " [[buffer(" + convert_to_string(r.index) + ")]]"; + break; default: if (!ep_args.empty()) ep_args += ", "; @@ -11416,7 +12066,7 @@ void CompilerMSL::fix_up_shader_inputs_outputs() entry_func.fixup_hooks_in.push_back([=]() { statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = min(", to_expression(builtin_invocation_id_id), ".x / ", this->get_entry_point().output_vertices, - ", spvIndirectParams[1]);"); + ", spvIndirectParams[1] - 1);"); }); break; case BuiltInPatchVertices: @@ -11833,29 +12483,17 @@ void CompilerMSL::fix_up_shader_inputs_outputs() break; } } - else if (var.storage == StorageClassOutput && is_builtin_variable(var) && active_output_builtins.get(bi_type)) + else if (var.storage == StorageClassOutput && get_execution_model() == ExecutionModelFragment && + is_builtin_variable(var) && active_output_builtins.get(bi_type) && + bi_type == BuiltInSampleMask && has_additional_fixed_sample_mask()) { - if (bi_type == BuiltInSampleMask && get_execution_model() == ExecutionModelFragment && - msl_options.additional_fixed_sample_mask != 0xffffffff) - { - // If the additional fixed sample mask was set, we need to adjust the sample_mask - // output to reflect that. If the shader outputs the sample_mask itself too, we need - // to AND the two masks to get the final one. - if (does_shader_write_sample_mask) - { - entry_func.fixup_hooks_out.push_back([=]() { - statement(to_expression(builtin_sample_mask_id), - " &= ", msl_options.additional_fixed_sample_mask, ";"); - }); - } - else - { - entry_func.fixup_hooks_out.push_back([=]() { - statement(to_expression(builtin_sample_mask_id), " = ", - msl_options.additional_fixed_sample_mask, ";"); - }); - } - } + // If the additional fixed sample mask was set, we need to adjust the sample_mask + // output to reflect that. If the shader outputs the sample_mask itself too, we need + // to AND the two masks to get the final one. + string op_str = does_shader_write_sample_mask ? " &= " : " = "; + entry_func.fixup_hooks_out.push_back([=]() { + statement(to_expression(builtin_sample_mask_id), op_str, additional_fixed_sample_mask_str(), ";"); + }); } }); } @@ -12003,7 +12641,7 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) auto &var = get(arg.id); auto &type = get_variable_data_type(var); auto &var_type = get(arg.type); - StorageClass storage = var_type.storage; + StorageClass type_storage = var_type.storage; bool is_pointer = var_type.pointer; // If we need to modify the name of the variable, make sure we use the original variable. @@ -12038,17 +12676,41 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) // Allow Metal to use the array template to make arrays a value type string address_space = get_argument_address_space(var); - bool builtin = is_builtin_variable(var); + bool builtin = has_decoration(var.self, DecorationBuiltIn); auto builtin_type = BuiltIn(get_decoration(arg.id, DecorationBuiltIn)); - is_using_builtin_array = builtin; + if (address_space == "threadgroup") is_using_builtin_array = true; if (var.basevariable && (var.basevariable == stage_in_ptr_var_id || var.basevariable == stage_out_ptr_var_id)) decl = join(cv_qualifier, type_to_glsl(type, arg.id)); else if (builtin) - decl = join(cv_qualifier, builtin_type_decl(builtin_type, arg.id)); - else if ((storage == StorageClassUniform || storage == StorageClassStorageBuffer) && is_array(type)) + { + // Only use templated array for Clip/Cull distance when feasible. + // In other scenarios, we need need to override array length for tess levels (if used as outputs), + // or we need to emit the expected type for builtins (uint vs int). + auto storage = get(var.basetype).storage; + + if (storage == StorageClassInput && + (builtin_type == BuiltInTessLevelInner || builtin_type == BuiltInTessLevelOuter)) + { + is_using_builtin_array = false; + } + else if (builtin_type != BuiltInClipDistance && builtin_type != BuiltInCullDistance) + { + is_using_builtin_array = true; + } + + if (storage == StorageClassOutput && variable_storage_requires_stage_io(storage) && + !is_stage_output_builtin_masked(builtin_type)) + is_using_builtin_array = true; + + if (is_using_builtin_array) + decl = join(cv_qualifier, builtin_type_decl(builtin_type, arg.id)); + else + decl = join(cv_qualifier, type_to_glsl(type, arg.id)); + } + else if ((type_storage == StorageClassUniform || type_storage == StorageClassStorageBuffer) && is_array(type)) { is_using_builtin_array = true; decl += join(cv_qualifier, type_to_glsl(type, arg.id), "*"); @@ -12072,10 +12734,10 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) decl = join(cv_qualifier, type_to_glsl(type, arg.id)); } - bool opaque_handle = storage == StorageClassUniformConstant; + bool opaque_handle = type_storage == StorageClassUniformConstant; if (!builtin && !opaque_handle && !is_pointer && - (storage == StorageClassFunction || storage == StorageClassGeneric)) + (type_storage == StorageClassFunction || type_storage == StorageClassGeneric)) { // If the argument is a pure value and not an opaque type, we will pass by value. if (msl_options.force_native_arrays && is_array(type)) @@ -12116,7 +12778,7 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) if (msl_options.argument_buffers) { uint32_t desc_set = get_decoration(name_id, DecorationDescriptorSet); - if ((storage == StorageClassUniform || storage == StorageClassStorageBuffer) && + if ((type_storage == StorageClassUniform || type_storage == StorageClassStorageBuffer) && descriptor_set_is_argument_buffer(desc_set)) { // An awkward case where we need to emit *more* address space declarations (yay!). @@ -12158,7 +12820,12 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) } else { - decl += " (&"; + auto array_size_decl = type_to_array_glsl(type); + if (array_size_decl.empty()) + decl += "& "; + else + decl += " (&"; + const char *restrict_kw = to_restrict(name_id); if (*restrict_kw) { @@ -12166,8 +12833,12 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) decl += restrict_kw; } decl += to_expression(name_id); - decl += ")"; - decl += type_to_array_glsl(type); + + if (!array_size_decl.empty()) + { + decl += ")"; + decl += array_size_decl; + } } } else if (!opaque_handle && (!pull_model_inputs.count(var.basevariable) || type.basetype == SPIRType::Struct)) @@ -12582,9 +13253,6 @@ void CompilerMSL::sync_entry_point_aliases_and_names() string CompilerMSL::to_member_reference(uint32_t base, const SPIRType &type, uint32_t index, bool ptr_chain) { - if (index < uint32_t(type.member_type_index_redirection.size())) - index = type.member_type_index_redirection[index]; - auto *var = maybe_get(base); // If this is a buffer array, we have to dereference the buffer pointers. // Otherwise, if this is a pointer expression, dereference it. @@ -12612,8 +13280,10 @@ string CompilerMSL::to_qualifiers_glsl(uint32_t id) { string quals; + auto *var = maybe_get(id); auto &type = expression_type(id); - if (type.storage == StorageClassWorkgroup) + + if (type.storage == StorageClassWorkgroup || (var && variable_decl_is_remapped_storage(*var, StorageClassWorkgroup))) quals += "threadgroup "; return quals; @@ -12697,8 +13367,23 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id) // Scalars case SPIRType::Boolean: - type_name = "bool"; + { + auto *var = maybe_get_backing_variable(id); + if (var && var->basevariable) + var = &get(var->basevariable); + + // Need to special-case threadgroup booleans. They are supposed to be logical + // storage, but MSL compilers will sometimes crash if you use threadgroup bool. + // Workaround this by using 16-bit types instead and fixup on load-store to this data. + // FIXME: We have no sane way of working around this problem if a struct member is boolean + // and that struct is used as a threadgroup variable, but ... sigh. + if ((var && var->storage == StorageClassWorkgroup) || type.storage == StorageClassWorkgroup) + type_name = "short"; + else + type_name = "bool"; break; + } + case SPIRType::Char: case SPIRType::SByte: type_name = "char"; @@ -12737,6 +13422,16 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id) case SPIRType::Double: type_name = "double"; // Currently unsupported break; + case SPIRType::AccelerationStructure: + if (msl_options.supports_msl_version(2, 4)) + type_name = "acceleration_structure"; + else if (msl_options.supports_msl_version(2, 3)) + type_name = "instance_acceleration_structure"; + else + SPIRV_CROSS_THROW("Acceleration Structure Type is supported in MSL 2.3 and above."); + break; + case SPIRType::RayQuery: + return "intersection_query"; default: return "unknown_type"; @@ -12781,6 +13476,7 @@ string CompilerMSL::type_to_array_glsl(const SPIRType &type) { case SPIRType::AtomicCounter: case SPIRType::ControlPointArray: + case SPIRType::RayQuery: { return CompilerGLSL::type_to_array_glsl(type); } @@ -12794,18 +13490,67 @@ string CompilerMSL::type_to_array_glsl(const SPIRType &type) } } -// Threadgroup arrays can't have a wrapper type +string CompilerMSL::constant_op_expression(const SPIRConstantOp &cop) +{ + switch (cop.opcode) + { + case OpQuantizeToF16: + add_spv_func_and_recompile(SPVFuncImplQuantizeToF16); + return join("spvQuantizeToF16(", to_expression(cop.arguments[0]), ")"); + default: + return CompilerGLSL::constant_op_expression(cop); + } +} + +bool CompilerMSL::variable_decl_is_remapped_storage(const SPIRVariable &variable, spv::StorageClass storage) const +{ + if (variable.storage == storage) + return true; + + if (storage == StorageClassWorkgroup) + { + auto model = get_execution_model(); + + // Specially masked IO block variable. + // Normally, we will never access IO blocks directly here. + // The only scenario which that should occur is with a masked IO block. + if (model == ExecutionModelTessellationControl && variable.storage == StorageClassOutput && + has_decoration(get(variable.basetype).self, DecorationBlock)) + { + return true; + } + + return variable.storage == StorageClassOutput && + model == ExecutionModelTessellationControl && + is_stage_output_variable_masked(variable); + } + else if (storage == StorageClassStorageBuffer) + { + // We won't be able to catch writes to control point outputs here since variable + // refers to a function local pointer. + // This is fine, as there cannot be concurrent writers to that memory anyways, + // so we just ignore that case. + + return (variable.storage == StorageClassOutput || variable.storage == StorageClassInput) && + !variable_storage_requires_stage_io(variable.storage) && + (variable.storage != StorageClassOutput || !is_stage_output_variable_masked(variable)); + } + else + { + return false; + } +} + std::string CompilerMSL::variable_decl(const SPIRVariable &variable) { - if (variable.storage == StorageClassWorkgroup) - { + bool old_is_using_builtin_array = is_using_builtin_array; + + // Threadgroup arrays can't have a wrapper type. + if (variable_decl_is_remapped_storage(variable, StorageClassWorkgroup)) is_using_builtin_array = true; - } + std::string expr = CompilerGLSL::variable_decl(variable); - if (variable.storage == StorageClassWorkgroup) - { - is_using_builtin_array = false; - } + is_using_builtin_array = old_is_using_builtin_array; return expr; } @@ -12895,7 +13640,7 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id) // Bypass pointers because we need the real image struct auto &img_type = get(type.self).image; - if (image_is_comparison(type, id)) + if (is_depth_image(type, id)) { switch (img_type.dim) { @@ -13192,7 +13937,6 @@ void CompilerMSL::emit_subgroup_op(const Instruction &i) break; default: SPIRV_CROSS_THROW("Invalid BitCount operation."); - break; } break; } @@ -13346,18 +14090,21 @@ string CompilerMSL::bitcast_glsl_op(const SPIRType &out_type, const SPIRType &in assert(out_type.basetype != SPIRType::Boolean); assert(in_type.basetype != SPIRType::Boolean); - bool integral_cast = type_is_integral(out_type) && type_is_integral(in_type); - bool same_size_cast = out_type.width == in_type.width; + bool integral_cast = type_is_integral(out_type) && type_is_integral(in_type) && (out_type.vecsize == in_type.vecsize); + bool same_size_cast = (out_type.width * out_type.vecsize) == (in_type.width * in_type.vecsize); - if (integral_cast && same_size_cast) + // Bitcasting can only be used between types of the same overall size. + // And always formally cast between integers, because it's trivial, and also + // because Metal can internally cast the results of some integer ops to a larger + // size (eg. short shift right becomes int), which means chaining integer ops + // together may introduce size variations that SPIR-V doesn't know about. + if (same_size_cast && !integral_cast) { - // Trivial bitcast case, casts between integers. - return type_to_glsl(out_type); + return "as_type<" + type_to_glsl(out_type) + ">"; } else { - // Fall back to the catch-all bitcast in MSL. - return "as_type<" + type_to_glsl(out_type) + ">"; + return type_to_glsl(out_type); } } @@ -13372,7 +14119,6 @@ string CompilerMSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) { switch (builtin) { - // Handle HLSL-style 0-based vertex/instance index. // Override GLSL compiler strictness case BuiltInVertexId: @@ -13502,12 +14248,29 @@ string CompilerMSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) case BuiltInClipDistance: case BuiltInCullDistance: case BuiltInLayer: - case BuiltInSampleMask: if (get_execution_model() == ExecutionModelTessellationControl) break; - if (storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point)) + if (storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point) && + !is_stage_output_builtin_masked(builtin)) return stage_out_var_name + "." + CompilerGLSL::builtin_to_glsl(builtin, storage); + break; + case BuiltInSampleMask: + if (storage == StorageClassInput && current_function && (current_function->self == ir.default_entry_point) && + (has_additional_fixed_sample_mask() || needs_sample_id)) + { + string samp_mask_in; + samp_mask_in += "(" + CompilerGLSL::builtin_to_glsl(builtin, storage); + if (has_additional_fixed_sample_mask()) + samp_mask_in += " & " + additional_fixed_sample_mask_str(); + if (needs_sample_id) + samp_mask_in += " & (1 << gl_SampleID)"; + samp_mask_in += ")"; + return samp_mask_in; + } + if (storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point) && + !is_stage_output_builtin_masked(builtin)) + return stage_out_var_name + "." + CompilerGLSL::builtin_to_glsl(builtin, storage); break; case BuiltInBaryCoordNV: @@ -13517,31 +14280,21 @@ string CompilerMSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage) break; case BuiltInTessLevelOuter: - if (get_execution_model() == ExecutionModelTessellationEvaluation) + if (get_execution_model() == ExecutionModelTessellationControl && + storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point)) { - if (storage != StorageClassOutput && !get_entry_point().flags.get(ExecutionModeTriangles) && - current_function && (current_function->self == ir.default_entry_point)) - return join(patch_stage_in_var_name, ".", CompilerGLSL::builtin_to_glsl(builtin, storage)); - else - break; - } - if (storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point)) return join(tess_factor_buffer_var_name, "[", to_expression(builtin_primitive_id_id), "].edgeTessellationFactor"); + } break; case BuiltInTessLevelInner: - if (get_execution_model() == ExecutionModelTessellationEvaluation) + if (get_execution_model() == ExecutionModelTessellationControl && + storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point)) { - if (storage != StorageClassOutput && !get_entry_point().flags.get(ExecutionModeTriangles) && - current_function && (current_function->self == ir.default_entry_point)) - return join(patch_stage_in_var_name, ".", CompilerGLSL::builtin_to_glsl(builtin, storage)); - else - break; - } - if (storage != StorageClassInput && current_function && (current_function->self == ir.default_entry_point)) return join(tess_factor_buffer_var_name, "[", to_expression(builtin_primitive_id_id), "].insideTessellationFactor"); + } break; default: @@ -13795,6 +14548,7 @@ string CompilerMSL::builtin_type_decl(BuiltIn builtin, uint32_t id) // Vertex function out case BuiltInClipDistance: + case BuiltInCullDistance: return "float"; case BuiltInPointSize: return "float"; @@ -13916,11 +14670,11 @@ SPIRType CompilerMSL::get_presumed_input_type(const SPIRType &ib_type, uint32_t { SPIRType type = get_physical_member_type(ib_type, index); uint32_t loc = get_member_decoration(ib_type.self, index, DecorationLocation); - if (inputs_by_location.count(loc)) - { - if (inputs_by_location.at(loc).vecsize > type.vecsize) - type.vecsize = inputs_by_location.at(loc).vecsize; - } + uint32_t cmp = get_member_decoration(ib_type.self, index, DecorationComponent); + auto p_va = inputs_by_location.find({loc, cmp}); + if (p_va != end(inputs_by_location) && p_va->second.vecsize > type.vecsize) + type.vecsize = p_va->second.vecsize; + return type; } @@ -14092,10 +14846,6 @@ uint32_t CompilerMSL::get_declared_type_alignment_msl(const SPIRType &type, bool case SPIRType::Sampler: SPIRV_CROSS_THROW("Querying alignment of opaque object."); - case SPIRType::Int64: - SPIRV_CROSS_THROW("long types are not supported in buffers in MSL."); - case SPIRType::UInt64: - SPIRV_CROSS_THROW("ulong types are not supported in buffers in MSL."); case SPIRType::Double: SPIRV_CROSS_THROW("double types are not supported in buffers in MSL."); @@ -14110,6 +14860,10 @@ uint32_t CompilerMSL::get_declared_type_alignment_msl(const SPIRType &type, bool default: { + if (type.basetype == SPIRType::Int64 && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("long types in buffers are only supported in MSL 2.3 and above."); + if (type.basetype == SPIRType::UInt64 && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("ulong types in buffers are only supported in MSL 2.3 and above."); // Alignment of packed type is the same as the underlying component or column size. // Alignment of unpacked type is the same as the vector size. // Alignment of 3-elements vector is the same as 4-elements (including packed using column). @@ -14414,9 +15168,11 @@ CompilerMSL::SPVFuncImpl CompilerMSL::OpCodePreprocessor::get_spv_func_impl(Op o return SPVFuncImplMod; case OpFAdd: - if (compiler.msl_options.invariant_float_math) + case OpFSub: + if (compiler.msl_options.invariant_float_math || + compiler.has_decoration(args[1], DecorationNoContraction)) { - return SPVFuncImplFAdd; + return opcode == OpFAdd ? SPVFuncImplFAdd : SPVFuncImplFSub; } break; @@ -14425,12 +15181,16 @@ CompilerMSL::SPVFuncImpl CompilerMSL::OpCodePreprocessor::get_spv_func_impl(Op o case OpMatrixTimesVector: case OpVectorTimesMatrix: case OpMatrixTimesMatrix: - if (compiler.msl_options.invariant_float_math) + if (compiler.msl_options.invariant_float_math || + compiler.has_decoration(args[1], DecorationNoContraction)) { return SPVFuncImplFMul; } break; + case OpQuantizeToF16: + return SPVFuncImplQuantizeToF16; + case OpTypeArray: { // Allow Metal to use the array template to make arrays a value type @@ -14630,38 +15390,35 @@ void CompilerMSL::MemberSorter::sort() meta.members[mbr_idx] = mbr_meta_cpy[mbr_idxs[mbr_idx]]; } + // If we're sorting by Offset, this might affect user code which accesses a buffer block. + // We will need to redirect member indices from defined index to sorted index using reverse lookup. if (sort_aspect == SortAspect::Offset) { - // If we're sorting by Offset, this might affect user code which accesses a buffer block. - // We will need to redirect member indices from one index to sorted index. - type.member_type_index_redirection = std::move(mbr_idxs); + type.member_type_index_redirection.resize(mbr_cnt); + for (uint32_t map_idx = 0; map_idx < mbr_cnt; map_idx++) + type.member_type_index_redirection[mbr_idxs[map_idx]] = map_idx; } } -// Sort first by builtin status (put builtins at end), then by the sorting aspect. bool CompilerMSL::MemberSorter::operator()(uint32_t mbr_idx1, uint32_t mbr_idx2) { auto &mbr_meta1 = meta.members[mbr_idx1]; auto &mbr_meta2 = meta.members[mbr_idx2]; - if (mbr_meta1.builtin != mbr_meta2.builtin) - return mbr_meta2.builtin; - else - switch (sort_aspect) - { - case Location: + + if (sort_aspect == LocationThenBuiltInType) + { + // Sort first by builtin status (put builtins at end), then by the sorting aspect. + if (mbr_meta1.builtin != mbr_meta2.builtin) + return mbr_meta2.builtin; + else if (mbr_meta1.builtin) + return mbr_meta1.builtin_type < mbr_meta2.builtin_type; + else if (mbr_meta1.location == mbr_meta2.location) + return mbr_meta1.component < mbr_meta2.component; + else return mbr_meta1.location < mbr_meta2.location; - case LocationReverse: - return mbr_meta1.location > mbr_meta2.location; - case Offset: - return mbr_meta1.offset < mbr_meta2.offset; - case OffsetThenLocationReverse: - return (mbr_meta1.offset < mbr_meta2.offset) || - ((mbr_meta1.offset == mbr_meta2.offset) && (mbr_meta1.location > mbr_meta2.location)); - case Alphabetical: - return mbr_meta1.alias < mbr_meta2.alias; - default: - return false; - } + } + else + return mbr_meta1.offset < mbr_meta2.offset; } CompilerMSL::MemberSorter::MemberSorter(SPIRType &t, Meta &m, SortAspect sa) @@ -14689,12 +15446,16 @@ void CompilerMSL::remap_constexpr_sampler_by_binding(uint32_t desc_set, uint32_t constexpr_samplers_by_binding[{ desc_set, binding }] = sampler; } -void CompilerMSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) +void CompilerMSL::cast_from_variable_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) { auto *var = maybe_get_backing_variable(source_id); if (var) source_id = var->self; + // Type fixups for workgroup variables if they are booleans. + if (var && var->storage == StorageClassWorkgroup && expr_type.basetype == SPIRType::Boolean) + expr = join(type_to_glsl(expr_type), "(", expr, ")"); + // Only interested in standalone builtin variables. if (!has_decoration(source_id, DecorationBuiltIn)) return; @@ -14781,12 +15542,20 @@ void CompilerMSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, } } -void CompilerMSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) +void CompilerMSL::cast_to_variable_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) { auto *var = maybe_get_backing_variable(target_id); if (var) target_id = var->self; + // Type fixups for workgroup variables if they are booleans. + if (var && var->storage == StorageClassWorkgroup && expr_type.basetype == SPIRType::Boolean) + { + auto short_type = expr_type; + short_type.basetype = SPIRType::Short; + expr = join(type_to_glsl(short_type), "(", expr, ")"); + } + // Only interested in standalone builtin variables. if (!has_decoration(target_id, DecorationBuiltIn)) return; @@ -15094,10 +15863,63 @@ void CompilerMSL::analyze_argument_buffers() }); uint32_t member_index = 0; + uint32_t next_arg_buff_index = 0; for (auto &resource : resources) { auto &var = *resource.var; auto &type = get_variable_data_type(var); + + // If needed, synthesize and add padding members. + // member_index and next_arg_buff_index are incremented when padding members are added. + if (msl_options.pad_argument_buffer_resources) + { + while (resource.index > next_arg_buff_index) + { + auto &rez_bind = get_argument_buffer_resource(desc_set, next_arg_buff_index); + switch (rez_bind.basetype) + { + case SPIRType::Void: + case SPIRType::Boolean: + case SPIRType::SByte: + case SPIRType::UByte: + case SPIRType::Short: + case SPIRType::UShort: + case SPIRType::Int: + case SPIRType::UInt: + case SPIRType::Int64: + case SPIRType::UInt64: + case SPIRType::AtomicCounter: + case SPIRType::Half: + case SPIRType::Float: + case SPIRType::Double: + add_argument_buffer_padding_buffer_type(buffer_type, member_index, next_arg_buff_index, rez_bind); + break; + case SPIRType::Image: + add_argument_buffer_padding_image_type(buffer_type, member_index, next_arg_buff_index, rez_bind); + break; + case SPIRType::Sampler: + add_argument_buffer_padding_sampler_type(buffer_type, member_index, next_arg_buff_index, rez_bind); + break; + case SPIRType::SampledImage: + if (next_arg_buff_index == rez_bind.msl_sampler) + add_argument_buffer_padding_sampler_type(buffer_type, member_index, next_arg_buff_index, rez_bind); + else + add_argument_buffer_padding_image_type(buffer_type, member_index, next_arg_buff_index, rez_bind); + break; + default: + break; + } + } + + // Adjust the number of slots consumed by current member itself. + // If actual member is an array, allow runtime array resolution as well. + uint32_t elem_cnt = type.array.empty() ? 1 : to_array_size_literal(type); + if (elem_cnt == 0) + elem_cnt = get_resource_array_size(var.self); + + next_arg_buff_index += elem_cnt; + } + string mbr_name = ensure_valid_name(resource.name, "m"); if (resource.plane > 0) mbr_name += join(plane_name_suffix, resource.plane); @@ -15196,6 +16018,125 @@ void CompilerMSL::analyze_argument_buffers() } } +// Return the resource type of the app-provided resources for the descriptor set, +// that matches the resource index of the argument buffer index. +// This is a two-step lookup, first lookup the resource binding number from the argument buffer index, +// then lookup the resource binding using the binding number. +MSLResourceBinding &CompilerMSL::get_argument_buffer_resource(uint32_t desc_set, uint32_t arg_idx) +{ + auto stage = get_entry_point().model; + StageSetBinding arg_idx_tuple = { stage, desc_set, arg_idx }; + auto arg_itr = resource_arg_buff_idx_to_binding_number.find(arg_idx_tuple); + if (arg_itr != end(resource_arg_buff_idx_to_binding_number)) + { + StageSetBinding bind_tuple = { stage, desc_set, arg_itr->second }; + auto bind_itr = resource_bindings.find(bind_tuple); + if (bind_itr != end(resource_bindings)) + return bind_itr->second.first; + } + SPIRV_CROSS_THROW("Argument buffer resource base type could not be determined. When padding argument buffer " + "elements, all descriptor set resources must be supplied with a base type by the app."); +} + +// Adds an argument buffer padding argument buffer type as one or more members of the struct type at the member index. +// Metal does not support arrays of buffers, so these are emitted as multiple struct members. +void CompilerMSL::add_argument_buffer_padding_buffer_type(SPIRType &struct_type, uint32_t &mbr_idx, + uint32_t &arg_buff_index, MSLResourceBinding &rez_bind) +{ + if (!argument_buffer_padding_buffer_type_id) + { + uint32_t buff_type_id = ir.increase_bound_by(2); + auto &buff_type = set(buff_type_id); + buff_type.basetype = rez_bind.basetype; + buff_type.storage = StorageClassUniformConstant; + + uint32_t ptr_type_id = buff_type_id + 1; + auto &ptr_type = set(ptr_type_id); + ptr_type = buff_type; + ptr_type.pointer = true; + ptr_type.pointer_depth++; + ptr_type.parent_type = buff_type_id; + + argument_buffer_padding_buffer_type_id = ptr_type_id; + } + + for (uint32_t rez_idx = 0; rez_idx < rez_bind.count; rez_idx++) + add_argument_buffer_padding_type(argument_buffer_padding_buffer_type_id, struct_type, mbr_idx, arg_buff_index, 1); +} + +// Adds an argument buffer padding argument image type as a member of the struct type at the member index. +void CompilerMSL::add_argument_buffer_padding_image_type(SPIRType &struct_type, uint32_t &mbr_idx, + uint32_t &arg_buff_index, MSLResourceBinding &rez_bind) +{ + if (!argument_buffer_padding_image_type_id) + { + uint32_t base_type_id = ir.increase_bound_by(2); + auto &base_type = set(base_type_id); + base_type.basetype = SPIRType::Float; + base_type.width = 32; + + uint32_t img_type_id = base_type_id + 1; + auto &img_type = set(img_type_id); + img_type.basetype = SPIRType::Image; + img_type.storage = StorageClassUniformConstant; + + img_type.image.type = base_type_id; + img_type.image.dim = Dim2D; + img_type.image.depth = false; + img_type.image.arrayed = false; + img_type.image.ms = false; + img_type.image.sampled = 1; + img_type.image.format = ImageFormatUnknown; + img_type.image.access = AccessQualifierMax; + + argument_buffer_padding_image_type_id = img_type_id; + } + + add_argument_buffer_padding_type(argument_buffer_padding_image_type_id, struct_type, mbr_idx, arg_buff_index, rez_bind.count); +} + +// Adds an argument buffer padding argument sampler type as a member of the struct type at the member index. +void CompilerMSL::add_argument_buffer_padding_sampler_type(SPIRType &struct_type, uint32_t &mbr_idx, + uint32_t &arg_buff_index, MSLResourceBinding &rez_bind) +{ + if (!argument_buffer_padding_sampler_type_id) + { + uint32_t samp_type_id = ir.increase_bound_by(1); + auto &samp_type = set(samp_type_id); + samp_type.basetype = SPIRType::Sampler; + samp_type.storage = StorageClassUniformConstant; + + argument_buffer_padding_sampler_type_id = samp_type_id; + } + + add_argument_buffer_padding_type(argument_buffer_padding_sampler_type_id, struct_type, mbr_idx, arg_buff_index, rez_bind.count); +} + +// Adds the argument buffer padding argument type as a member of the struct type at the member index. +// Advances both arg_buff_index and mbr_idx to next argument slots. +void CompilerMSL::add_argument_buffer_padding_type(uint32_t mbr_type_id, SPIRType &struct_type, uint32_t &mbr_idx, + uint32_t &arg_buff_index, uint32_t count) +{ + uint32_t type_id = mbr_type_id; + if (count > 1) + { + uint32_t ary_type_id = ir.increase_bound_by(1); + auto &ary_type = set(ary_type_id); + ary_type = get(type_id); + ary_type.array.push_back(count); + ary_type.array_size_literal.push_back(true); + ary_type.parent_type = type_id; + type_id = ary_type_id; + } + + set_member_name(struct_type.self, mbr_idx, join("_m", arg_buff_index, "_pad")); + set_extended_member_decoration(struct_type.self, mbr_idx, SPIRVCrossDecorationResourceIndexPrimary, arg_buff_index); + struct_type.member_types.push_back(type_id); + + arg_buff_index += count; + mbr_idx++; +} + void CompilerMSL::activate_argument_buffer_resources() { // For ABI compatibility, force-enable all resources which are part of argument buffers. @@ -15223,3 +16164,14 @@ const char *CompilerMSL::get_combined_sampler_suffix() const { return sampler_name_suffix.c_str(); } + +void CompilerMSL::emit_block_hints(const SPIRBlock &) +{ +} + +string CompilerMSL::additional_fixed_sample_mask_str() const +{ + char print_buffer[32]; + sprintf(print_buffer, "0x%x", msl_options.additional_fixed_sample_mask); + return print_buffer; +} diff --git a/src/libraries/spirv_cross/spirv_msl.hpp b/src/libraries/spirv_cross/spirv_msl.hpp index 52e96761e..f01cceaf7 100644 --- a/src/libraries/spirv_cross/spirv_msl.hpp +++ b/src/libraries/spirv_cross/spirv_msl.hpp @@ -1,5 +1,6 @@ /* * Copyright 2016-2021 The Brenwill Workshop Ltd. + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_MSL_HPP @@ -60,6 +60,7 @@ enum MSLShaderInputFormat struct MSLShaderInput { uint32_t location = 0; + uint32_t component = 0; MSLShaderInputFormat format = MSL_SHADER_INPUT_FORMAT_OTHER; spv::BuiltIn builtin = spv::BuiltInMax; uint32_t vecsize = 0; @@ -71,15 +72,23 @@ struct MSLShaderInput // resources consumed by this binding, if the binding represents an array of resources. // If the resource array is a run-time-sized array, which are legal in GLSL or SPIR-V, this value // will be used to declare the array size in MSL, which does not support run-time-sized arrays. -// For resources that are not held in a run-time-sized array, the count field does not need to be populated. +// If pad_argument_buffer_resources is enabled, the base_type and count values are used to +// specify the base type and array size of the resource in the argument buffer, if that resource +// is not defined and used by the shader. With pad_argument_buffer_resources enabled, this +// information will be used to pad the argument buffer structure, in order to align that +// structure consistently for all uses, across all shaders, of the descriptor set represented +// by the arugment buffer. If pad_argument_buffer_resources is disabled, base_type does not +// need to be populated, and if the resource is also not a run-time sized array, the count +// field does not need to be populated. // If using MSL 2.0 argument buffers, the descriptor set is not marked as a discrete descriptor set, // and (for iOS only) the resource is not a storage image (sampled != 2), the binding reference we // remap to will become an [[id(N)]] attribute within the "descriptor set" argument buffer structure. -// For resources which are bound in the "classic" MSL 1.0 way or discrete descriptors, the remap will become a -// [[buffer(N)]], [[texture(N)]] or [[sampler(N)]] depending on the resource types used. +// For resources which are bound in the "classic" MSL 1.0 way or discrete descriptors, the remap will +// become a [[buffer(N)]], [[texture(N)]] or [[sampler(N)]] depending on the resource types used. struct MSLResourceBinding { spv::ExecutionModel stage = spv::ExecutionModelMax; + SPIRType::BaseType basetype = SPIRType::Unknown; uint32_t desc_set = 0; uint32_t binding = 0; uint32_t count = 0; @@ -346,6 +355,19 @@ public: // and would otherwise declare a different IAB. bool force_active_argument_buffer_resources = false; + // Aligns each resource in an argument buffer to its assigned index value, id(N), + // by adding synthetic padding members in the argument buffer struct for any resources + // in the argument buffer that are not defined and used by the shader. This allows + // the shader to index into the correct argument in a descriptor set argument buffer + // that is shared across shaders, where not all resources in the argument buffer are + // defined in each shader. For this to work, an MSLResourceBinding must be provided for + // all descriptors in any descriptor set held in an argument buffer in the shader, and + // that MSLResourceBinding must have the basetype and count members populated correctly. + // The implementation here assumes any inline blocks in the argument buffer is provided + // in a Metal buffer, and doesn't take into consideration inline blocks that are + // optionally embedded directly into the argument buffer via add_inline_uniform_block(). + bool pad_argument_buffer_resources = false; + // Forces the use of plain arrays, which works around certain driver bugs on certain versions // of Intel Macbooks. See https://github.com/KhronosGroup/SPIRV-Cross/issues/1210. // May reduce performance in scenarios where arrays are copied around as value-types. @@ -634,6 +656,8 @@ protected: SPVFuncImplImage2DAtomicCoords, // Emulate texture2D atomic operations SPVFuncImplFMul, SPVFuncImplFAdd, + SPVFuncImplFSub, + SPVFuncImplQuantizeToF16, SPVFuncImplCubemapTo2DArrayFace, SPVFuncImplUnsafeArray, // Allow Metal to use the array template to make arrays a value type SPVFuncImplInverse4x4, @@ -708,13 +732,17 @@ protected: const std::string &qualifier = "", uint32_t base_offset = 0) override; void emit_struct_padding_target(const SPIRType &type) override; std::string type_to_glsl(const SPIRType &type, uint32_t id = 0) override; + void emit_block_hints(const SPIRBlock &block) override; // Allow Metal to use the array template to make arrays a value type std::string type_to_array_glsl(const SPIRType &type) override; + std::string constant_op_expression(const SPIRConstantOp &cop) override; // Threadgroup arrays can't have a wrapper type std::string variable_decl(const SPIRVariable &variable) override; + bool variable_decl_is_remapped_storage(const SPIRVariable &variable, spv::StorageClass storage) const override; + // GCC workaround of lambdas calling protected functions (for older GCC versions) std::string variable_decl(const SPIRType &type, const std::string &name, uint32_t id = 0) override; @@ -775,33 +803,44 @@ protected: { struct LocationMeta { + uint32_t base_type_id = 0; uint32_t num_components = 0; - uint32_t ib_index = ~0u; + bool flat = false; + bool noperspective = false; + bool centroid = false; + bool sample = false; }; std::unordered_map location_meta; bool strip_array = false; + bool allow_local_declaration = false; }; + std::string to_tesc_invocation_id(); + void emit_local_masked_variable(const SPIRVariable &masked_var, bool strip_array); void add_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, InterfaceBlockMeta &meta); void add_composite_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, InterfaceBlockMeta &meta); void add_plain_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, InterfaceBlockMeta &meta); + bool add_component_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, + SPIRVariable &var, const SPIRType &type, + InterfaceBlockMeta &meta); void add_plain_member_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, uint32_t index, InterfaceBlockMeta &meta); void add_composite_member_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var, uint32_t index, InterfaceBlockMeta &meta); - uint32_t get_accumulated_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array); void add_tess_level_input_to_interface_block(const std::string &ib_var_ref, SPIRType &ib_type, SPIRVariable &var); void fix_up_interface_member_indices(spv::StorageClass storage, uint32_t ib_type_id); - void mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, spv::StorageClass storage); + void mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, + spv::StorageClass storage, bool fallback = false); uint32_t ensure_correct_builtin_type(uint32_t type_id, spv::BuiltIn builtin); - uint32_t ensure_correct_input_type(uint32_t type_id, uint32_t location, uint32_t num_components = 0); + uint32_t ensure_correct_input_type(uint32_t type_id, uint32_t location, uint32_t component, + uint32_t num_components, bool strip_array); void emit_custom_templates(); void emit_custom_functions(); @@ -825,11 +864,13 @@ protected: std::string to_swizzle_expression(uint32_t id); std::string to_buffer_size_expression(uint32_t id); bool is_sample_rate() const; + bool is_intersection_query() const; bool is_direct_input_builtin(spv::BuiltIn builtin); std::string builtin_qualifier(spv::BuiltIn builtin); std::string builtin_type_decl(spv::BuiltIn builtin, uint32_t id = 0); std::string built_in_func_arg(spv::BuiltIn builtin, bool prefix_comma); std::string member_attribute_qualifier(const SPIRType &type, uint32_t index); + std::string member_location_attribute_qualifier(const SPIRType &type, uint32_t index); std::string argument_decl(const SPIRFunction::Parameter &arg); std::string round_fp_tex_coords(std::string tex_coords, bool coord_is_fp); uint32_t get_metal_resource_index(SPIRVariable &var, SPIRType::BaseType basetype, uint32_t plane = 0); @@ -886,11 +927,13 @@ protected: void add_pragma_line(const std::string &line); void add_typedef_line(const std::string &line); void emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uint32_t id_mem_sem); - void emit_array_copy(const std::string &lhs, uint32_t rhs_id, spv::StorageClass lhs_storage, - spv::StorageClass rhs_storage) override; + void emit_array_copy(const std::string &lhs, uint32_t lhs_id, uint32_t rhs_id, + spv::StorageClass lhs_storage, spv::StorageClass rhs_storage) override; void build_implicit_builtins(); uint32_t build_constant_uint_array_pointer(); void emit_entry_point_declarations() override; + bool uses_explicit_early_fragment_test(); + uint32_t builtin_frag_coord_id = 0; uint32_t builtin_sample_id_id = 0; uint32_t builtin_sample_mask_id = 0; @@ -913,15 +956,19 @@ protected: uint32_t view_mask_buffer_id = 0; uint32_t dynamic_offsets_buffer_id = 0; uint32_t uint_type_id = 0; + uint32_t argument_buffer_padding_buffer_type_id = 0; + uint32_t argument_buffer_padding_image_type_id = 0; + uint32_t argument_buffer_padding_sampler_type_id = 0; bool does_shader_write_sample_mask = false; - void cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) override; - void cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) override; + void cast_to_variable_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) override; + void cast_from_variable_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) override; void emit_store_statement(uint32_t lhs_expression, uint32_t rhs_expression) override; void analyze_sampled_image_usage(); + bool access_chain_needs_stage_io_builtin_translation(uint32_t base) override; void prepare_access_chain_for_scalar_access(std::string &expr, const SPIRType &type, spv::StorageClass storage, bool &is_packed) override; void fix_up_interpolant_access_chain(const uint32_t *ops, uint32_t length); @@ -938,9 +985,10 @@ protected: Options msl_options; std::set spv_function_implementations; // Must be ordered to ensure declarations are in a specific order. - std::map inputs_by_location; + std::map inputs_by_location; std::unordered_map inputs_by_builtin; std::unordered_set location_inputs_in_use; + std::unordered_set location_inputs_in_use_fallback; std::unordered_map fragment_output_components; std::unordered_map builtin_to_automatic_input_location; std::set pragma_lines; @@ -948,7 +996,7 @@ protected: SmallVector vars_needing_early_declaration; std::unordered_map, InternalHasher> resource_bindings; - uint32_t type_to_location_count(const SPIRType &type) const; + std::unordered_map resource_arg_buff_idx_to_binding_number; uint32_t next_metal_resource_index_buffer = 0; uint32_t next_metal_resource_index_texture = 0; @@ -962,6 +1010,7 @@ protected: VariableID patch_stage_out_var_id = 0; VariableID stage_in_ptr_var_id = 0; VariableID stage_out_ptr_var_id = 0; + VariableID stage_out_masked_builtin_type_id = 0; // Handle HLSL-style 0-based vertex/instance index. enum class TriState @@ -1027,6 +1076,11 @@ protected: void analyze_argument_buffers(); bool descriptor_set_is_argument_buffer(uint32_t desc_set) const; + MSLResourceBinding &get_argument_buffer_resource(uint32_t desc_set, uint32_t arg_idx); + void add_argument_buffer_padding_buffer_type(SPIRType &struct_type, uint32_t &mbr_idx, uint32_t &arg_buff_index, MSLResourceBinding &rez_bind); + void add_argument_buffer_padding_image_type(SPIRType &struct_type, uint32_t &mbr_idx, uint32_t &arg_buff_index, MSLResourceBinding &rez_bind); + void add_argument_buffer_padding_sampler_type(SPIRType &struct_type, uint32_t &mbr_idx, uint32_t &arg_buff_index, MSLResourceBinding &rez_bind); + void add_argument_buffer_padding_type(uint32_t mbr_type_id, SPIRType &struct_type, uint32_t &mbr_idx, uint32_t &arg_buff_index, uint32_t count); uint32_t get_target_components_for_fragment_location(uint32_t location) const; uint32_t build_extended_vector_type(uint32_t type_id, uint32_t components, @@ -1044,6 +1098,11 @@ protected: bool type_is_pointer_to_pointer(const SPIRType &type) const; bool is_supported_argument_buffer_type(const SPIRType &type) const; + bool variable_storage_requires_stage_io(spv::StorageClass storage) const; + + bool has_additional_fixed_sample_mask() const { return msl_options.additional_fixed_sample_mask != 0xffffffff; } + std::string additional_fixed_sample_mask_str() const; + // OpcodeHandler that handles several MSL preprocessing operations. struct OpCodePreprocessor : OpcodeHandler { @@ -1087,11 +1146,8 @@ protected: { enum SortAspect { - Location, - LocationReverse, - Offset, - OffsetThenLocationReverse, - Alphabetical + LocationThenBuiltInType, + Offset }; void sort(); diff --git a/src/libraries/spirv_cross/spirv_parser.cpp b/src/libraries/spirv_cross/spirv_parser.cpp index 86f7fd350..4faf3ca08 100644 --- a/src/libraries/spirv_cross/spirv_parser.cpp +++ b/src/libraries/spirv_cross/spirv_parser.cpp @@ -1,5 +1,6 @@ /* * Copyright 2018-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_parser.hpp" @@ -961,6 +961,49 @@ void Parser::parse(const Instruction &instruction) current_block->false_block = ops[2]; current_block->terminator = SPIRBlock::Select; + + if (current_block->true_block == current_block->false_block) + { + // Bogus conditional, translate to a direct branch. + // Avoids some ugly edge cases later when analyzing CFGs. + + // There are some super jank cases where the merge block is different from the true/false, + // and later branches can "break" out of the selection construct this way. + // This is complete nonsense, but CTS hits this case. + // In this scenario, we should see the selection construct as more of a Switch with one default case. + // The problem here is that this breaks any attempt to break out of outer switch statements, + // but it's theoretically solvable if this ever comes up using the ladder breaking system ... + + if (current_block->true_block != current_block->next_block && + current_block->merge == SPIRBlock::MergeSelection) + { + uint32_t ids = ir.increase_bound_by(2); + + SPIRType type; + type.basetype = SPIRType::Int; + type.width = 32; + set(ids, type); + auto &c = set(ids + 1, ids); + + current_block->condition = c.self; + current_block->default_block = current_block->true_block; + current_block->terminator = SPIRBlock::MultiSelect; + ir.block_meta[current_block->next_block] &= ~ParsedIR::BLOCK_META_SELECTION_MERGE_BIT; + ir.block_meta[current_block->next_block] |= ParsedIR::BLOCK_META_MULTISELECT_MERGE_BIT; + } + else + { + ir.block_meta[current_block->next_block] &= ~ParsedIR::BLOCK_META_SELECTION_MERGE_BIT; + current_block->next_block = current_block->true_block; + current_block->condition = 0; + current_block->true_block = 0; + current_block->false_block = 0; + current_block->merge_block = 0; + current_block->merge = SPIRBlock::MergeNone; + current_block->terminator = SPIRBlock::Direct; + } + } + current_block = nullptr; break; } @@ -975,8 +1018,21 @@ void Parser::parse(const Instruction &instruction) current_block->condition = ops[0]; current_block->default_block = ops[1]; - for (uint32_t i = 2; i + 2 <= length; i += 2) - current_block->cases.push_back({ ops[i], ops[i + 1] }); + uint32_t remaining_ops = length - 2; + if ((remaining_ops % 2) == 0) + { + for (uint32_t i = 2; i + 2 <= length; i += 2) + current_block->cases_32bit.push_back({ ops[i], ops[i + 1] }); + } + + if ((remaining_ops % 3) == 0) + { + for (uint32_t i = 2; i + 3 <= length; i += 3) + { + uint64_t value = (static_cast(ops[i + 1]) << 32) | ops[i]; + current_block->cases_64bit.push_back({ value, ops[i + 2] }); + } + } // If we jump to next block, make it break instead since we're inside a switch case block at that point. ir.block_meta[current_block->next_block] |= ParsedIR::BLOCK_META_MULTISELECT_MERGE_BIT; @@ -1134,6 +1190,14 @@ void Parser::parse(const Instruction &instruction) // Actual opcodes. default: { + if (length >= 2) + { + const auto *type = maybe_get(ops[0]); + if (type) + { + ir.load_type_width.insert({ ops[1], type->width }); + } + } if (!current_block) SPIRV_CROSS_THROW("Currently no block to insert opcode."); diff --git a/src/libraries/spirv_cross/spirv_parser.hpp b/src/libraries/spirv_cross/spirv_parser.hpp index 7f4a7d856..d72fc71d8 100644 --- a/src/libraries/spirv_cross/spirv_parser.hpp +++ b/src/libraries/spirv_cross/spirv_parser.hpp @@ -1,5 +1,6 @@ /* * Copyright 2018-2021 Arm Limited + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_PARSER_HPP diff --git a/src/libraries/spirv_cross/spirv_reflect.cpp b/src/libraries/spirv_cross/spirv_reflect.cpp index 447bff1c9..ee2fe930d 100644 --- a/src/libraries/spirv_cross/spirv_reflect.cpp +++ b/src/libraries/spirv_cross/spirv_reflect.cpp @@ -1,5 +1,6 @@ /* * Copyright 2018-2021 Bradley Austin Davis + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #include "spirv_reflect.hpp" diff --git a/src/libraries/spirv_cross/spirv_reflect.hpp b/src/libraries/spirv_cross/spirv_reflect.hpp index 8acbd2804..a129ba54d 100644 --- a/src/libraries/spirv_cross/spirv_reflect.hpp +++ b/src/libraries/spirv_cross/spirv_reflect.hpp @@ -1,5 +1,6 @@ /* * Copyright 2018-2021 Bradley Austin Davis + * SPDX-License-Identifier: Apache-2.0 OR MIT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . - * SPDX-License-Identifier: Apache-2.0 OR MIT. */ #ifndef SPIRV_CROSS_REFLECT_HPP