Path: blob/master/dep/reshadefx/src/effect_codegen_spirv.cpp
7328 views
/*1* Copyright (C) 2014 Patrick Mours2* SPDX-License-Identifier: BSD-3-Clause3*/45#include "effect_parser.hpp"6#include "effect_codegen.hpp"7#include <cassert>8#include <cstring> // std::memcmp9#include <charconv> // std::from_chars10#include <algorithm> // std::find_if, std::max, std::sort11#include <unordered_set>1213// Use the C++ variant of the SPIR-V headers14#include <spirv.hpp>15namespace spv {16#include <GLSL.std.450.h>17}1819using namespace reshadefx;2021inline uint32_t align_up(uint32_t size, uint32_t alignment)22{23alignment -= 1;24return ((size + alignment) & ~alignment);25}2627/// <summary>28/// A single instruction in a SPIR-V module29/// </summary>30struct spirv_instruction31{32spv::Op op;33spv::Id type;34spv::Id result;35std::vector<spv::Id> operands;3637explicit spirv_instruction(spv::Op op = spv::OpNop) : op(op), type(0), result(0) {}38spirv_instruction(spv::Op op, spv::Id result) : op(op), type(result), result(0) {}39spirv_instruction(spv::Op op, spv::Id type, spv::Id result) : op(op), type(type), result(result) {}4041/// <summary>42/// Add a single operand to the instruction.43/// </summary>44spirv_instruction &add(spv::Id operand)45{46operands.push_back(operand);47return *this;48}4950/// <summary>51/// Add a range of operands to the instruction.52/// </summary>53template <typename It>54spirv_instruction &add(It begin, It end)55{56operands.insert(operands.end(), begin, end);57return *this;58}5960/// <summary>61/// Add a null-terminated literal UTF-8 string to the instruction.62/// </summary>63spirv_instruction &add_string(const char *string)64{65uint32_t word;66do {67word = 0;68for (uint32_t i = 0; i < 4 && *string; ++i)69reinterpret_cast<uint8_t *>(&word)[i] = *string++;70add(word);71} while (*string || (word & 0xFF000000));72return *this;73}7475/// <summary>76/// Write this instruction to a SPIR-V module.77/// </summary>78/// <param name="output">The output stream to append this instruction to.</param>79void write(std::basic_string<char> &output) const80{81// See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html82// 0 | Opcode: The 16 high-order bits are the WordCount of the instruction. The 16 low-order bits are the opcode enumerant.83// 1 | Optional instruction type <id>84// . | Optional instruction Result <id>85// . | Operand 1 (if needed)86// . | Operand 2 (if needed)87// ... | ...88// WordCount - 1 | Operand N (N is determined by WordCount minus the 1 to 3 words used for the opcode, instruction type <id>, and instruction Result <id>).8990const uint32_t word_count = 1 + (type != 0) + (result != 0) + static_cast<uint32_t>(operands.size());91write_word(output, (word_count << spv::WordCountShift) | op);9293// Optional instruction type ID94if (type != 0)95write_word(output, type);9697// Optional instruction result ID98if (result != 0)99write_word(output, result);100101// Write out the operands102for (const uint32_t operand : operands)103write_word(output, operand);104}105106static void write_word(std::basic_string<char> &output, uint32_t word)107{108output.insert(output.end(), reinterpret_cast<const char *>(&word), reinterpret_cast<const char *>(&word + 1));109}110111operator uint32_t() const112{113assert(result != 0);114115return result;116}117};118119/// <summary>120/// A list of instructions forming a basic block in the SPIR-V module121/// </summary>122struct spirv_basic_block123{124std::vector<spirv_instruction> instructions;125126/// <summary>127/// Append another basic block the end of this one.128/// </summary>129void append(const spirv_basic_block &block)130{131instructions.insert(instructions.end(), block.instructions.begin(), block.instructions.end());132}133};134135class codegen_spirv final : public codegen136{137static_assert(sizeof(id) == sizeof(spv::Id), "unexpected SPIR-V id type size");138139public:140codegen_spirv(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y, bool discard_is_demote) :141_debug_info(debug_info),142_vulkan_semantics(vulkan_semantics),143_uniforms_to_spec_constants(uniforms_to_spec_constants),144_enable_16bit_types(enable_16bit_types),145_flip_vert_y(flip_vert_y),146_discard_is_demote(discard_is_demote)147{148_glsl_ext = make_id();149}150151private:152struct type_lookup153{154reshadefx::type type;155bool is_ptr;156uint32_t array_stride;157std::pair<spv::StorageClass, spv::ImageFormat> storage;158159friend bool operator==(const type_lookup &lhs, const type_lookup &rhs)160{161return lhs.type == rhs.type && lhs.is_ptr == rhs.is_ptr && lhs.array_stride == rhs.array_stride && lhs.storage == rhs.storage;162}163};164struct function_blocks165{166spirv_basic_block declaration;167spirv_basic_block variables;168spirv_basic_block definition;169reshadefx::type return_type;170std::vector<reshadefx::type> param_types;171172friend bool operator==(const function_blocks &lhs, const function_blocks &rhs)173{174if (lhs.param_types.size() != rhs.param_types.size())175return false;176for (size_t i = 0; i < lhs.param_types.size(); ++i)177if (!(lhs.param_types[i] == rhs.param_types[i]))178return false;179return lhs.return_type == rhs.return_type;180}181};182183bool _debug_info = false;184bool _vulkan_semantics = false;185bool _uniforms_to_spec_constants = false;186bool _enable_16bit_types = false;187bool _flip_vert_y = false;188bool _discard_is_demote = false;189190spirv_basic_block _entries;191spirv_basic_block _execution_modes;192spirv_basic_block _debug_a;193spirv_basic_block _debug_b;194spirv_basic_block _annotations;195spirv_basic_block _types_and_constants;196spirv_basic_block _variables;197198std::vector<function_blocks> _functions_blocks;199std::unordered_map<id, spirv_basic_block> _block_data;200spirv_basic_block *_current_block_data = nullptr;201202spv::Id _glsl_ext = 0;203spv::Id _global_ubo_type = 0;204spv::Id _global_ubo_variable = 0;205std::vector<spv::Id> _global_ubo_types;206function_blocks *_current_function_blocks = nullptr;207208std::vector<std::pair<type_lookup, spv::Id>> _type_lookup;209std::vector<std::tuple<type, constant, spv::Id>> _constant_lookup;210std::vector<std::pair<function_blocks, spv::Id>> _function_type_lookup;211std::unordered_map<std::string, spv::Id> _string_lookup;212std::unordered_map<spv::Id, std::pair<spv::StorageClass, spv::ImageFormat>> _storage_lookup;213std::unordered_map<std::string, uint32_t> _semantic_to_location;214215std::unordered_set<spv::Id> _spec_constants;216std::unordered_set<spv::Capability> _capabilities;217218void add_location(const location &loc, spirv_basic_block &block)219{220if (loc.source.empty() || !_debug_info)221return;222223spv::Id file;224225if (const auto it = _string_lookup.find(loc.source);226it != _string_lookup.end())227{228file = it->second;229}230else231{232file =233add_instruction(spv::OpString, 0, _debug_a)234.add_string(loc.source.c_str());235_string_lookup.emplace(loc.source, file);236}237238// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpLine239add_instruction_without_result(spv::OpLine, block)240.add(file)241.add(loc.line)242.add(loc.column);243}244spirv_instruction &add_instruction(spv::Op op, spv::Id type = 0)245{246assert(is_in_function() && is_in_block());247248return add_instruction(op, type, *_current_block_data);249}250spirv_instruction &add_instruction(spv::Op op, spv::Id type, spirv_basic_block &block)251{252spirv_instruction &instruction = add_instruction_without_result(op, block);253instruction.type = type;254instruction.result = make_id();255return instruction;256}257spirv_instruction &add_instruction_without_result(spv::Op op)258{259assert(is_in_function() && is_in_block());260261return add_instruction_without_result(op, *_current_block_data);262}263spirv_instruction &add_instruction_without_result(spv::Op op, spirv_basic_block &block)264{265return block.instructions.emplace_back(op);266}267268void finalize_header_section(std::basic_string<char> &spirv) const269{270// Write SPIRV header info271spirv_instruction::write_word(spirv, spv::MagicNumber);272spirv_instruction::write_word(spirv, 0x10300); // Force SPIR-V 1.3273spirv_instruction::write_word(spirv, 0u); // Generator magic number, see https://www.khronos.org/registry/spir-v/api/spir-v.xml274spirv_instruction::write_word(spirv, _next_id); // Maximum ID275spirv_instruction::write_word(spirv, 0u); // Reserved for instruction schema276277// All capabilities278spirv_instruction(spv::OpCapability)279.add(spv::CapabilityShader) // Implicitly declares the Matrix capability too280.write(spirv);281282for (const spv::Capability capability : _capabilities)283spirv_instruction(spv::OpCapability)284.add(capability)285.write(spirv);286287// Optional extension instructions288spirv_instruction(spv::OpExtInstImport, _glsl_ext)289.add_string("GLSL.std.450") // Import GLSL extension290.write(spirv);291292// Single required memory model instruction293spirv_instruction(spv::OpMemoryModel)294.add(spv::AddressingModelLogical)295.add(spv::MemoryModelGLSL450)296.write(spirv);297}298void finalize_debug_info_section(std::basic_string<char> &spirv) const299{300spirv_instruction(spv::OpSource)301.add(spv::SourceLanguageUnknown) // ReShade FX is not a reserved token at the moment302.add(0) // Language version, TODO: Maybe fill in ReShade version here?303.write(spirv);304305if (_debug_info)306{307// All debug instructions308for (const spirv_instruction &inst : _debug_a.instructions)309inst.write(spirv);310}311}312void finalize_type_and_constants_section(std::basic_string<char> &spirv) const313{314// All type declarations315for (const spirv_instruction &inst : _types_and_constants.instructions)316inst.write(spirv);317318// Initialize the UBO type now that all member types are known319if (_global_ubo_type == 0 || _global_ubo_variable == 0)320return;321322const id global_ubo_type_ptr = _global_ubo_type + 1;323324spirv_instruction(spv::OpTypeStruct, _global_ubo_type)325.add(_global_ubo_types.begin(), _global_ubo_types.end())326.write(spirv);327spirv_instruction(spv::OpTypePointer, global_ubo_type_ptr)328.add(spv::StorageClassUniform)329.add(_global_ubo_type)330.write(spirv);331332spirv_instruction(spv::OpVariable, global_ubo_type_ptr, _global_ubo_variable)333.add(spv::StorageClassUniform)334.write(spirv);335}336337std::basic_string<char> finalize_code() const override338{339std::basic_string<char> spirv;340finalize_header_section(spirv);341342// All entry point declarations343for (const spirv_instruction &inst : _entries.instructions)344inst.write(spirv);345346// All execution mode declarations347for (const spirv_instruction &inst : _execution_modes.instructions)348inst.write(spirv);349350finalize_debug_info_section(spirv);351352for (const spirv_instruction& inst : _debug_b.instructions)353inst.write(spirv);354355// All annotation instructions356for (const spirv_instruction &inst : _annotations.instructions)357inst.write(spirv);358359finalize_type_and_constants_section(spirv);360361for (const spirv_instruction &inst : _variables.instructions)362inst.write(spirv);363364// All function definitions365for (const function_blocks &func : _functions_blocks)366{367if (func.definition.instructions.empty())368continue;369370for (const spirv_instruction &inst : func.declaration.instructions)371inst.write(spirv);372373// Grab first label and move it in front of variable declarations374func.definition.instructions.front().write(spirv);375assert(func.definition.instructions.front().op == spv::OpLabel);376377for (const spirv_instruction &inst : func.variables.instructions)378inst.write(spirv);379for (auto inst_it = func.definition.instructions.begin() + 1; inst_it != func.definition.instructions.end(); ++inst_it)380inst_it->write(spirv);381}382383return spirv;384}385std::basic_string<char> finalize_code_for_entry_point(const std::string &entry_point_name) const override386{387const auto entry_point_it = std::find_if(_functions.begin(), _functions.end(),388[&entry_point_name](const std::unique_ptr<function> &func) {389return func->unique_name == entry_point_name;390});391if (entry_point_it == _functions.end())392return {};393const function &entry_point = *entry_point_it->get();394395const auto write_entry_point = [this](const spirv_instruction& oins, std::basic_string<char>& spirv) {396assert(oins.operands.size() > 2);397spirv_instruction nins(oins.op, oins.type, oins.result);398nins.add(oins.operands[0]);399nins.add(oins.operands[1]);400nins.add_string("main");401402size_t param_start_index = 2;403while (param_start_index < oins.operands.size() && (oins.operands[param_start_index] & 0xFF000000) != 0)404param_start_index++;405406// skip zero407param_start_index++;408409for (size_t i = param_start_index; i < oins.operands.size(); i++)410nins.add(oins.operands[i]);411nins.write(spirv);412};413414// Build list of IDs to remove415std::vector<spv::Id> variables_to_remove;416#if 1417std::vector<spv::Id> functions_to_remove;418#else419for (const sampler &info : _module.samplers)420if (std::find(entry_point.referenced_samplers.begin(), entry_point.referenced_samplers.end(), info.id) == entry_point.referenced_samplers.end())421variables_to_remove.push_back(info.id);422for (const storage &info : _module.storages)423if (std::find(entry_point.referenced_storages.begin(), entry_point.referenced_storages.end(), info.id) == entry_point.referenced_storages.end())424variables_to_remove.push_back(info.id);425#endif426427std::basic_string<char> spirv;428finalize_header_section(spirv);429430// The entry point and execution mode declaration431for (const spirv_instruction &inst : _entries.instructions)432{433assert(inst.op == spv::OpEntryPoint);434435// Only add the matching entry point436if (inst.operands[1] == entry_point.id)437{438write_entry_point(inst, spirv);439}440else441{442#if 1443functions_to_remove.push_back(inst.operands[1]);444#endif445// Add interface variables to list of variables to remove446for (uint32_t k = 2 + static_cast<uint32_t>((std::strlen(reinterpret_cast<const char *>(&inst.operands[2])) + 4) / 4); k < inst.operands.size(); ++k)447variables_to_remove.push_back(inst.operands[k]);448}449}450451for (const spirv_instruction &inst : _execution_modes.instructions)452{453assert(inst.op == spv::OpExecutionMode);454455// Only add execution mode for the matching entry point456if (inst.operands[0] == entry_point.id)457{458inst.write(spirv);459}460}461462finalize_debug_info_section(spirv);463464for (const spirv_instruction &inst : _debug_b.instructions)465{466// Remove all names of interface variables and functions for non-matching entry points467if (std::find(variables_to_remove.begin(), variables_to_remove.end(), inst.operands[0]) != variables_to_remove.end() ||468std::find(functions_to_remove.begin(), functions_to_remove.end(), inst.operands[0]) != functions_to_remove.end())469continue;470471inst.write(spirv);472}473474// All annotation instructions475for (spirv_instruction inst : _annotations.instructions)476{477if (inst.op == spv::OpDecorate)478{479// Remove all decorations targeting any of the interface variables for non-matching entry points480if (std::find(variables_to_remove.begin(), variables_to_remove.end(), inst.operands[0]) != variables_to_remove.end())481continue;482483// Replace bindings484if (inst.operands[1] == spv::DecorationBinding)485{486if (const auto referenced_sampler_it = std::find(entry_point.referenced_samplers.begin(), entry_point.referenced_samplers.end(), inst.operands[0]);487referenced_sampler_it != entry_point.referenced_samplers.end())488inst.operands[2] = static_cast<uint32_t>(std::distance(entry_point.referenced_samplers.begin(), referenced_sampler_it));489else490if (const auto referenced_storage_it = std::find(entry_point.referenced_storages.begin(), entry_point.referenced_storages.end(), inst.operands[0]);491referenced_storage_it != entry_point.referenced_storages.end())492inst.operands[2] = static_cast<uint32_t>(std::distance(entry_point.referenced_storages.begin(), referenced_storage_it));493}494}495496inst.write(spirv);497}498499finalize_type_and_constants_section(spirv);500501for (const spirv_instruction &inst : _variables.instructions)502{503// Remove all declarations of the interface variables for non-matching entry points504if (inst.op == spv::OpVariable && std::find(variables_to_remove.begin(), variables_to_remove.end(), inst.result) != variables_to_remove.end())505continue;506507inst.write(spirv);508}509510// All referenced function definitions511for (const function_blocks &func : _functions_blocks)512{513if (func.definition.instructions.empty())514continue;515516assert(func.declaration.instructions[func.declaration.instructions[0].op != spv::OpFunction ? 1 : 0].op == spv::OpFunction);517const spv::Id definition = func.declaration.instructions[func.declaration.instructions[0].op != spv::OpFunction ? 1 : 0].result;518519#if 1520if (std::find(functions_to_remove.begin(), functions_to_remove.end(), definition) != functions_to_remove.end())521#else522if (struct_definition != entry_point.struct_definition &&523entry_point.referenced_functions.find(struct_definition) == entry_point.referenced_functions.end())524#endif525continue;526527for (const spirv_instruction &inst : func.declaration.instructions)528inst.write(spirv);529530// Grab first label and move it in front of variable declarations531func.definition.instructions.front().write(spirv);532assert(func.definition.instructions.front().op == spv::OpLabel);533534for (const spirv_instruction &inst : func.variables.instructions)535inst.write(spirv);536for (auto inst_it = func.definition.instructions.begin() + 1; inst_it != func.definition.instructions.end(); ++inst_it)537inst_it->write(spirv);538}539540return spirv;541}542543spv::Id convert_type(type info, bool is_ptr = false, spv::StorageClass storage = spv::StorageClassFunction, spv::ImageFormat format = spv::ImageFormatUnknown, uint32_t array_stride = 0)544{545assert(array_stride == 0 || info.is_array());546547// The storage class is only relevant for pointers, so ignore it for other types during lookup548if (is_ptr == false)549storage = spv::StorageClassFunction;550// There cannot be sampler variables that are local to a function, so always assume uniform storage for them551if (info.is_object())552storage = spv::StorageClassUniformConstant;553else554assert(format == spv::ImageFormatUnknown);555556if (info.is_sampler() || info.is_storage())557info.rows = info.cols = 1;558559// Fall back to 32-bit types and use relaxed precision decoration instead if 16-bit types are not enabled560if (!_enable_16bit_types && info.is_numeric() && info.precision() < 32)561info.base = static_cast<type::datatype>(info.base + 1); // min16int -> int, min16uint -> uint, min16float -> float562563const type_lookup lookup { info, is_ptr, array_stride, { storage, format } };564565if (const auto lookup_it = std::find_if(_type_lookup.begin(), _type_lookup.end(),566[&lookup](const std::pair<type_lookup, spv::Id> &lookup_entry) { return lookup_entry.first == lookup; });567lookup_it != _type_lookup.end())568return lookup_it->second;569570spv::Id type_id, elem_type_id;571if (is_ptr)572{573elem_type_id = convert_type(info, false, storage, format, array_stride);574type_id =575add_instruction(spv::OpTypePointer, 0, _types_and_constants)576.add(storage)577.add(elem_type_id);578}579else if (info.is_array())580{581type elem_info = info;582elem_info.array_length = 0;583584elem_type_id = convert_type(elem_info, false, storage, format);585586// Make sure we don't get any dynamic arrays here587assert(info.is_bounded_array());588589const spv::Id array_length_id = emit_constant(info.array_length);590591type_id =592add_instruction(spv::OpTypeArray, 0, _types_and_constants)593.add(elem_type_id)594.add(array_length_id);595596if (array_stride != 0)597add_decoration(type_id, spv::DecorationArrayStride, { array_stride });598}599else if (info.is_matrix())600{601// Convert MxN matrix to a SPIR-V matrix with M vectors with N elements602type elem_info = info;603elem_info.rows = info.cols;604elem_info.cols = 1;605606elem_type_id = convert_type(elem_info, false, storage, format);607608// Matrix types with just one row are interpreted as if they were a vector type609if (info.rows == 1)610return elem_type_id;611612type_id =613add_instruction(spv::OpTypeMatrix, 0, _types_and_constants)614.add(elem_type_id)615.add(info.rows);616}617else if (info.is_vector())618{619type elem_info = info;620elem_info.rows = 1;621elem_info.cols = 1;622623elem_type_id = convert_type(elem_info, false, storage, format);624type_id =625add_instruction(spv::OpTypeVector, 0, _types_and_constants)626.add(elem_type_id)627.add(info.rows);628}629else630{631switch (info.base)632{633case type::t_void:634assert(info.rows == 0 && info.cols == 0);635type_id = add_instruction(spv::OpTypeVoid, 0, _types_and_constants);636break;637case type::t_bool:638assert(info.rows == 1 && info.cols == 1);639type_id = add_instruction(spv::OpTypeBool, 0, _types_and_constants);640break;641case type::t_min16int:642assert(_enable_16bit_types && info.rows == 1 && info.cols == 1);643add_capability(spv::CapabilityInt16);644if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput)645add_capability(spv::CapabilityStorageInputOutput16);646type_id =647add_instruction(spv::OpTypeInt, 0, _types_and_constants)648.add(16) // Width649.add(1); // Signedness650break;651case type::t_int:652assert(info.rows == 1 && info.cols == 1);653type_id =654add_instruction(spv::OpTypeInt, 0, _types_and_constants)655.add(32) // Width656.add(1); // Signedness657break;658case type::t_min16uint:659assert(_enable_16bit_types && info.rows == 1 && info.cols == 1);660add_capability(spv::CapabilityInt16);661if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput)662add_capability(spv::CapabilityStorageInputOutput16);663type_id =664add_instruction(spv::OpTypeInt, 0, _types_and_constants)665.add(16) // Width666.add(0); // Signedness667break;668case type::t_uint:669assert(info.rows == 1 && info.cols == 1);670type_id =671add_instruction(spv::OpTypeInt, 0, _types_and_constants)672.add(32) // Width673.add(0); // Signedness674break;675case type::t_min16float:676assert(_enable_16bit_types && info.rows == 1 && info.cols == 1);677add_capability(spv::CapabilityFloat16);678if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput)679add_capability(spv::CapabilityStorageInputOutput16);680type_id =681add_instruction(spv::OpTypeFloat, 0, _types_and_constants)682.add(16); // Width683break;684case type::t_float:685assert(info.rows == 1 && info.cols == 1);686type_id =687add_instruction(spv::OpTypeFloat, 0, _types_and_constants)688.add(32); // Width689break;690case type::t_struct:691assert(info.rows == 0 && info.cols == 0 && info.struct_definition != 0);692type_id = info.struct_definition;693break;694case type::t_sampler1d_int:695case type::t_sampler1d_uint:696case type::t_sampler1d_float:697add_capability(spv::CapabilitySampled1D);698[[fallthrough]];699case type::t_sampler2d_int:700case type::t_sampler2d_uint:701case type::t_sampler2d_float:702case type::t_sampler3d_int:703case type::t_sampler3d_uint:704case type::t_sampler3d_float:705elem_type_id = convert_image_type(info, format);706type_id =707add_instruction(spv::OpTypeSampledImage, 0, _types_and_constants)708.add(elem_type_id);709break;710case type::t_storage1d_int:711case type::t_storage1d_uint:712case type::t_storage1d_float:713add_capability(spv::CapabilityImage1D);714[[fallthrough]];715case type::t_storage2d_int:716case type::t_storage2d_uint:717case type::t_storage2d_float:718case type::t_storage3d_int:719case type::t_storage3d_uint:720case type::t_storage3d_float:721// No format specified for the storage image722if (format == spv::ImageFormatUnknown)723add_capability(spv::CapabilityStorageImageWriteWithoutFormat);724return convert_image_type(info, format);725default:726assert(false);727return 0;728}729}730731_type_lookup.push_back({ lookup, type_id });732733return type_id;734}735spv::Id convert_type(const function_blocks &info)736{737if (const auto lookup_it = std::find_if(_function_type_lookup.begin(), _function_type_lookup.end(),738[&lookup = info](const std::pair<function_blocks, spv::Id> &lookup_entry) { return lookup_entry.first == lookup; });739lookup_it != _function_type_lookup.end())740return lookup_it->second;741742const spv::Id return_type_id = convert_type(info.return_type);743assert(return_type_id != 0);744745std::vector<spv::Id> param_type_ids;746param_type_ids.reserve(info.param_types.size());747for (const type ¶m_type : info.param_types)748param_type_ids.push_back(convert_type(param_type, true));749750spirv_instruction &inst = add_instruction(spv::OpTypeFunction, 0, _types_and_constants)751.add(return_type_id)752.add(param_type_ids.begin(), param_type_ids.end());753754_function_type_lookup.push_back({ info, inst });755756return inst;757}758spv::Id convert_image_type(type info, spv::ImageFormat format = spv::ImageFormatUnknown)759{760type elem_info = info;761elem_info.rows = 1;762elem_info.cols = 1;763764if (!info.is_numeric())765{766if ((info.is_integral() && info.is_signed()) || (format >= spv::ImageFormatRgba32i && format <= spv::ImageFormatR8i))767elem_info.base = type::t_int;768else if ((info.is_integral() && info.is_unsigned()) || (format >= spv::ImageFormatRgba32ui && format <= spv::ImageFormatR8ui))769elem_info.base = type::t_uint;770else771elem_info.base = type::t_float;772}773774type_lookup lookup { info, false, 0u, { spv::StorageClassUniformConstant, format } };775if (!info.is_storage())776{777lookup.type = elem_info;778lookup.type.base = static_cast<type::datatype>(type::t_texture1d + info.texture_dimension() - 1);779lookup.type.struct_definition = static_cast<uint32_t>(elem_info.base);780}781782if (const auto lookup_it = std::find_if(_type_lookup.begin(), _type_lookup.end(),783[&lookup](const std::pair<type_lookup, spv::Id> &lookup_entry) { return lookup_entry.first == lookup; });784lookup_it != _type_lookup.end())785return lookup_it->second;786787spv::Id type_id, elem_type_id = convert_type(elem_info, false, spv::StorageClassUniformConstant);788type_id =789add_instruction(spv::OpTypeImage, 0, _types_and_constants)790.add(elem_type_id) // Sampled Type (always a scalar type)791.add(spv::Dim1D + info.texture_dimension() - 1)792.add(0) // Not a depth image793.add(0) // Not an array794.add(0) // Not multi-sampled795.add(info.is_storage() ? 2 : 1) // Used with a sampler or as storage796.add(format);797798_type_lookup.push_back({ lookup, type_id });799800return type_id;801}802803uint32_t semantic_to_location(const std::string &semantic, uint32_t max_attributes = 1)804{805if (const auto it = _semantic_to_location.find(semantic);806it != _semantic_to_location.end())807return it->second;808809// Extract the semantic index from the semantic name (e.g. 2 for "TEXCOORD2")810size_t digit_index = semantic.size() - 1;811while (digit_index != 0 && semantic[digit_index] >= '0' && semantic[digit_index] <= '9')812digit_index--;813digit_index++;814815const std::string semantic_base = semantic.substr(0, digit_index);816817uint32_t semantic_digit = 0;818std::from_chars(semantic.c_str() + digit_index, semantic.c_str() + semantic.size(), semantic_digit);819820if (semantic_base == "COLOR" || semantic_base == "SV_TARGET")821return semantic_digit;822823uint32_t location = static_cast<uint32_t>(_semantic_to_location.size());824825// Now create adjoining location indices for all possible semantic indices belonging to this semantic name826for (uint32_t a = 0; a < semantic_digit + max_attributes; ++a)827{828const auto insert = _semantic_to_location.emplace(semantic_base + std::to_string(a), location + a);829if (!insert.second)830{831assert(a == 0 || (insert.first->second - a) == location);832833// Semantic was already created with a different location index, so need to remap to that834location = insert.first->second - a;835}836}837838return location + semantic_digit;839}840841spv::BuiltIn semantic_to_builtin(const std::string &semantic, shader_type stype) const842{843if (semantic == "SV_POSITION")844return stype == shader_type::pixel ? spv::BuiltInFragCoord : spv::BuiltInPosition;845if (semantic == "SV_POINTSIZE")846return spv::BuiltInPointSize;847if (semantic == "SV_DEPTH")848return spv::BuiltInFragDepth;849if (semantic == "SV_VERTEXID")850return _vulkan_semantics ? spv::BuiltInVertexIndex : spv::BuiltInVertexId;851if (semantic == "SV_ISFRONTFACE")852return spv::BuiltInFrontFacing;853if (semantic == "SV_GROUPID")854return spv::BuiltInWorkgroupId;855if (semantic == "SV_GROUPINDEX")856return spv::BuiltInLocalInvocationIndex;857if (semantic == "SV_GROUPTHREADID")858return spv::BuiltInLocalInvocationId;859if (semantic == "SV_DISPATCHTHREADID")860return spv::BuiltInGlobalInvocationId;861return spv::BuiltInMax;862}863spv::ImageFormat format_to_image_format(texture_format format)864{865switch (format)866{867default:868assert(false);869[[fallthrough]];870case texture_format::unknown:871return spv::ImageFormatUnknown;872case texture_format::r8:873add_capability(spv::CapabilityStorageImageExtendedFormats);874return spv::ImageFormatR8;875case texture_format::r16:876add_capability(spv::CapabilityStorageImageExtendedFormats);877return spv::ImageFormatR16;878case texture_format::r16f:879add_capability(spv::CapabilityStorageImageExtendedFormats);880return spv::ImageFormatR16f;881case texture_format::r32i:882return spv::ImageFormatR32i;883case texture_format::r32u:884return spv::ImageFormatR32ui;885case texture_format::r32f:886return spv::ImageFormatR32f;887case texture_format::rg8:888add_capability(spv::CapabilityStorageImageExtendedFormats);889return spv::ImageFormatRg8;890case texture_format::rg16:891add_capability(spv::CapabilityStorageImageExtendedFormats);892return spv::ImageFormatRg16;893case texture_format::rg16f:894add_capability(spv::CapabilityStorageImageExtendedFormats);895return spv::ImageFormatRg16f;896case texture_format::rg32f:897add_capability(spv::CapabilityStorageImageExtendedFormats);898return spv::ImageFormatRg32f;899case texture_format::rgba8:900return spv::ImageFormatRgba8;901case texture_format::rgba16:902add_capability(spv::CapabilityStorageImageExtendedFormats);903return spv::ImageFormatRgba16;904case texture_format::rgba16f:905return spv::ImageFormatRgba16f;906case texture_format::rgba32f:907return spv::ImageFormatRgba32f;908case texture_format::rgb10a2:909add_capability(spv::CapabilityStorageImageExtendedFormats);910return spv::ImageFormatRgb10A2;911}912}913914void add_name(id id, const char *name)915{916if (!_debug_info)917return;918919assert(name != nullptr);920// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpName921add_instruction_without_result(spv::OpName, _debug_b)922.add(id)923.add_string(name);924}925void add_builtin(id id, spv::BuiltIn builtin)926{927add_instruction_without_result(spv::OpDecorate, _annotations)928.add(id)929.add(spv::DecorationBuiltIn)930.add(builtin);931}932void add_decoration(id id, spv::Decoration decoration, std::initializer_list<uint32_t> values = {})933{934// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpDecorate935add_instruction_without_result(spv::OpDecorate, _annotations)936.add(id)937.add(decoration)938.add(values.begin(), values.end());939}940void add_member_name(id id, uint32_t member_index, const char *name)941{942if (!_debug_info)943return;944945assert(name != nullptr);946// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpMemberName947add_instruction_without_result(spv::OpMemberName, _debug_b)948.add(id)949.add(member_index)950.add_string(name);951}952void add_member_builtin(id id, uint32_t member_index, spv::BuiltIn builtin)953{954add_instruction_without_result(spv::OpMemberDecorate, _annotations)955.add(id)956.add(member_index)957.add(spv::DecorationBuiltIn)958.add(builtin);959}960void add_member_decoration(id id, uint32_t member_index, spv::Decoration decoration, std::initializer_list<uint32_t> values = {})961{962// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpMemberDecorate963add_instruction_without_result(spv::OpMemberDecorate, _annotations)964.add(id)965.add(member_index)966.add(decoration)967.add(values.begin(), values.end());968}969void add_capability(spv::Capability capability)970{971_capabilities.insert(capability);972}973974id define_struct(const location &loc, struct_type &info) override975{976// First define all member types to make sure they are declared before the struct type references them977std::vector<spv::Id> member_types;978member_types.reserve(info.member_list.size());979for (const member_type &member : info.member_list)980member_types.push_back(convert_type(member.type));981982// Afterwards define the actual struct type983add_location(loc, _types_and_constants);984985const id res = info.id =986add_instruction(spv::OpTypeStruct, 0, _types_and_constants)987.add(member_types.begin(), member_types.end());988989if (!info.unique_name.empty())990add_name(res, info.unique_name.c_str());991992for (uint32_t index = 0; index < info.member_list.size(); ++index)993{994const member_type &member = info.member_list[index];995996add_member_name(res, index, member.name.c_str());997998if (!_enable_16bit_types && member.type.is_numeric() && member.type.precision() < 32)999add_member_decoration(res, index, spv::DecorationRelaxedPrecision);1000}10011002_structs.push_back(info);10031004return res;1005}1006id define_texture(const location &, texture &info) override1007{1008const id res = info.id = make_id(); // Need to create an unique ID here too, so that the symbol lookup for textures works10091010_module.textures.push_back(info);10111012return res;1013}1014id define_sampler(const location &loc, const texture &, sampler &info) override1015{1016const id res = info.id = define_variable(loc, info.type, info.unique_name.c_str(), spv::StorageClassUniformConstant);10171018// Default to a binding index equivalent to the entry in the sampler list (this is later overwritten in 'finalize_code_for_entry_point' to a more optimal placement)1019const uint32_t default_binding = static_cast<uint32_t>(_module.samplers.size());1020add_decoration(res, spv::DecorationBinding, { default_binding });1021add_decoration(res, spv::DecorationDescriptorSet, { 1 });10221023_module.samplers.push_back(info);10241025return res;1026}1027id define_storage(const location &loc, const texture &tex_info, storage &info) override1028{1029const id res = info.id = define_variable(loc, info.type, info.unique_name.c_str(), spv::StorageClassUniformConstant, format_to_image_format(tex_info.format));10301031// Default to a binding index equivalent to the entry in the storage list (this is later overwritten in 'finalize_code_for_entry_point' to a more optimal placement)1032const uint32_t default_binding = static_cast<uint32_t>(_module.storages.size());1033add_decoration(res, spv::DecorationBinding, { default_binding });1034add_decoration(res, spv::DecorationDescriptorSet, { 2 });10351036_module.storages.push_back(info);10371038return res;1039}1040id define_uniform(const location &, uniform &info) override1041{1042if (_uniforms_to_spec_constants && info.has_initializer_value)1043{1044const id res = emit_constant(info.type, info.initializer_value, true);10451046add_name(res, info.name.c_str());10471048const auto add_spec_constant = [this](const spirv_instruction &inst, const uniform &info, const constant &initializer_value, size_t initializer_offset) {1049assert(inst.op == spv::OpSpecConstant || inst.op == spv::OpSpecConstantTrue || inst.op == spv::OpSpecConstantFalse);10501051const uint32_t spec_id = static_cast<uint32_t>(_module.spec_constants.size());1052add_decoration(inst, spv::DecorationSpecId, { spec_id });10531054uniform scalar_info = info;1055scalar_info.type.rows = 1;1056scalar_info.type.cols = 1;1057scalar_info.size = 4;1058scalar_info.offset = static_cast<uint32_t>(initializer_offset);1059scalar_info.initializer_value = {};1060scalar_info.initializer_value.as_uint[0] = initializer_value.as_uint[initializer_offset];10611062_module.spec_constants.push_back(std::move(scalar_info));1063};10641065const spirv_instruction &base_inst = _types_and_constants.instructions.back();1066assert(base_inst == res);10671068// External specialization constants need to be scalars1069if (info.type.is_scalar())1070{1071add_spec_constant(base_inst, info, info.initializer_value, 0);1072}1073else1074{1075assert(base_inst.op == spv::OpSpecConstantComposite);10761077// Add each individual scalar component of the constant as a separate external specialization constant1078for (size_t i = 0; i < (info.type.is_array() ? base_inst.operands.size() : 1); ++i)1079{1080constant initializer_value = info.initializer_value;1081spirv_instruction elem_inst = base_inst;10821083if (info.type.is_array())1084{1085elem_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(),1086[operand_id = base_inst.operands[i]](const spirv_instruction &inst) { return inst == operand_id; });10871088assert(initializer_value.array_data.size() == base_inst.operands.size());1089initializer_value = initializer_value.array_data[i];1090}10911092for (size_t row = 0; row < elem_inst.operands.size(); ++row)1093{1094const spirv_instruction &row_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(),1095[operand_id = elem_inst.operands[row]](const spirv_instruction &inst) { return inst == operand_id; });10961097if (row_inst.op != spv::OpSpecConstantComposite)1098{1099add_spec_constant(row_inst, info, initializer_value, row);1100continue;1101}11021103for (size_t col = 0; col < row_inst.operands.size(); ++col)1104{1105const spirv_instruction &col_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(),1106[operand_id = row_inst.operands[col]](const spirv_instruction &inst) { return inst == operand_id; });11071108add_spec_constant(col_inst, info, initializer_value, row * info.type.cols + col);1109}1110}1111}1112}11131114return res;1115}1116else1117{1118// Create global uniform buffer variable on demand1119if (_global_ubo_type == 0)1120{1121_global_ubo_type = make_id();1122make_id(); // Pointer type for '_global_ubo_type'11231124add_decoration(_global_ubo_type, spv::DecorationBlock);1125}1126if (_global_ubo_variable == 0)1127{1128_global_ubo_variable = make_id();11291130add_decoration(_global_ubo_variable, spv::DecorationDescriptorSet, { 0 });1131add_decoration(_global_ubo_variable, spv::DecorationBinding, { 0 });1132}11331134uint32_t alignment = (info.type.rows == 3 ? 4 : info.type.rows) * 4;1135info.size = info.type.rows * 4;11361137uint32_t array_stride = 16;1138const uint32_t matrix_stride = 16;11391140if (info.type.is_matrix())1141{1142alignment = matrix_stride;1143info.size = info.type.rows * matrix_stride;1144}1145if (info.type.is_array())1146{1147alignment = array_stride;1148array_stride = align_up(info.size, array_stride);1149// Uniform block rules do not permit anything in the padding of an array1150info.size = array_stride * info.type.array_length;1151}11521153info.offset = _module.total_uniform_size;1154info.offset = align_up(info.offset, alignment);1155_module.total_uniform_size = info.offset + info.size;11561157type ubo_type = info.type;1158// Convert boolean uniform variables to integer type so that they have a defined size1159if (info.type.is_boolean())1160ubo_type.base = type::t_uint;11611162const uint32_t member_index = static_cast<uint32_t>(_global_ubo_types.size());11631164// Composite objects in the uniform storage class must be explicitly laid out, which includes array types requiring a stride decoration1165_global_ubo_types.push_back(1166convert_type(ubo_type, false, spv::StorageClassUniform, spv::ImageFormatUnknown, info.type.is_array() ? array_stride : 0u));11671168add_member_name(_global_ubo_type, member_index, info.name.c_str());11691170add_member_decoration(_global_ubo_type, member_index, spv::DecorationOffset, { info.offset });11711172if (info.type.is_matrix())1173{1174// Read matrices in column major layout, even though they are actually row major, to avoid transposing them on every access (since SPIR-V uses column matrices)1175// TODO: This technically only works with square matrices1176add_member_decoration(_global_ubo_type, member_index, spv::DecorationColMajor);1177add_member_decoration(_global_ubo_type, member_index, spv::DecorationMatrixStride, { matrix_stride });1178}11791180_module.uniforms.push_back(info);11811182return 0xF0000000 | member_index;1183}1184}1185id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override1186{1187spv::StorageClass storage = spv::StorageClassFunction;1188if (type.has(type::q_groupshared))1189storage = spv::StorageClassWorkgroup;1190else if (global)1191storage = spv::StorageClassPrivate;11921193return define_variable(loc, type, name.c_str(), storage, spv::ImageFormatUnknown, initializer_value);1194}1195id define_variable(const location &loc, const type &type, const char *name, spv::StorageClass storage, spv::ImageFormat format = spv::ImageFormatUnknown, id initializer_value = 0)1196{1197assert(storage != spv::StorageClassFunction || (_current_function_blocks != nullptr && _current_function != nullptr && !_current_function->unique_name.empty() && (_current_function->unique_name[0] == 'F' || _current_function->unique_name[0] == 'E')));11981199spirv_basic_block &block = (storage != spv::StorageClassFunction) ?1200_variables : _current_function_blocks->variables;12011202add_location(loc, block);12031204// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpVariable1205spirv_instruction &inst = add_instruction(spv::OpVariable, convert_type(type, true, storage, format), block);1206inst.add(storage);12071208const id res = inst.result;12091210if (initializer_value != 0)1211{1212if (storage != spv::StorageClassFunction || /* is_entry_point = */ _current_function->unique_name[0] == 'E')1213{1214// The initializer for variables must be a constant1215inst.add(initializer_value);1216}1217else1218{1219// Only use the variable initializer on global variables, since local variables for e.g. "for" statements need to be assigned in their respective scope and not their declaration1220expression variable;1221variable.reset_to_lvalue(loc, res, type);1222emit_store(variable, initializer_value);1223}1224}12251226if (name != nullptr && *name != '\0')1227add_name(res, name);12281229if (!_enable_16bit_types && type.is_numeric() && type.precision() < 32)1230add_decoration(res, spv::DecorationRelaxedPrecision);12311232_storage_lookup[res] = { storage, format };12331234return res;1235}1236id define_function(const location &loc, function &info) override1237{1238assert(!is_in_function());12391240function_blocks &func = _functions_blocks.emplace_back();1241func.return_type = info.return_type;12421243for (const member_type ¶m : info.parameter_list)1244func.param_types.push_back(param.type);12451246add_location(loc, func.declaration);12471248// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpFunction1249const id res = info.id =1250add_instruction(spv::OpFunction, convert_type(info.return_type), func.declaration)1251.add(spv::FunctionControlMaskNone)1252.add(convert_type(func));12531254if (!info.name.empty())1255add_name(res, info.name.c_str());12561257for (member_type ¶m : info.parameter_list)1258{1259add_location(param.location, func.declaration);12601261param.id = add_instruction(spv::OpFunctionParameter, convert_type(param.type, true), func.declaration);12621263add_name(param.id, param.name.c_str());1264}12651266_functions.push_back(std::make_unique<function>(info));1267_current_function = _functions.back().get();1268_current_function_blocks = &func;12691270return res;1271}12721273void define_entry_point(function &func) override1274{1275assert(!func.unique_name.empty() && func.unique_name[0] == 'F');1276func.unique_name[0] = 'E';12771278// Modify entry point name so each thread configuration is made separate1279if (func.type == shader_type::compute)1280func.unique_name +=1281'_' + std::to_string(func.num_threads[0]) +1282'_' + std::to_string(func.num_threads[1]) +1283'_' + std::to_string(func.num_threads[2]);12841285if (std::find_if(_module.entry_points.begin(), _module.entry_points.end(),1286[&func](const std::pair<std::string, shader_type> &entry_point) {1287return entry_point.first == func.unique_name;1288}) != _module.entry_points.end())1289return;12901291_module.entry_points.emplace_back(func.unique_name, func.type);12921293spv::Id position_variable = 0;1294spv::Id point_size_variable = 0;1295std::vector<spv::Id> inputs_and_outputs;1296std::vector<expression> call_params;12971298// Generate the glue entry point function1299function entry_point = func;1300entry_point.referenced_functions.push_back(func.id);13011302// Change function signature to 'void main()'1303entry_point.return_type = { type::t_void };1304entry_point.return_semantic.clear();1305entry_point.parameter_list.clear();13061307const id entry_point_definition = define_function({}, entry_point);1308enter_block(create_block());13091310const auto create_varying_param = [this, &call_params](const member_type ¶m) {1311// Initialize all output variables with zero1312const spv::Id variable = define_variable({}, param.type, nullptr, spv::StorageClassFunction, spv::ImageFormatUnknown, emit_constant(param.type, 0u));13131314expression &call_param = call_params.emplace_back();1315call_param.reset_to_lvalue({}, variable, param.type);13161317return variable;1318};13191320const auto create_varying_variable = [this, &inputs_and_outputs, &position_variable, &point_size_variable, stype = func.type](const type ¶m_type, const std::string &semantic, spv::StorageClass storage, int a = 0) {1321const spv::Id variable = define_variable({}, param_type, nullptr, storage);13221323if (const spv::BuiltIn builtin = semantic_to_builtin(semantic, stype);1324builtin != spv::BuiltInMax)1325{1326assert(a == 0); // Built-in variables cannot be arrays13271328add_builtin(variable, builtin);13291330if (builtin == spv::BuiltInPosition && storage == spv::StorageClassOutput)1331position_variable = variable;1332if (builtin == spv::BuiltInPointSize && storage == spv::StorageClassOutput)1333point_size_variable = variable;1334}1335else1336{1337assert(stype != shader_type::compute); // Compute shaders cannot have custom inputs or outputs13381339const uint32_t location = semantic_to_location(semantic, std::max(1u, param_type.array_length));1340add_decoration(variable, spv::DecorationLocation, { location + a });1341}13421343if (param_type.has(type::q_noperspective))1344add_decoration(variable, spv::DecorationNoPerspective);1345if (param_type.has(type::q_centroid))1346add_decoration(variable, spv::DecorationCentroid);1347if (param_type.has(type::q_nointerpolation))1348add_decoration(variable, spv::DecorationFlat);13491350inputs_and_outputs.push_back(variable);1351return variable;1352};13531354// Translate function parameters to input/output variables1355for (const member_type ¶m : func.parameter_list)1356{1357spv::Id param_var = create_varying_param(param);13581359// Create separate input/output variables for "inout" parameters1360if (param.type.has(type::q_in))1361{1362spv::Id param_value = 0;13631364// Flatten structure parameters1365if (param.type.is_struct())1366{1367const struct_type &struct_definition = get_struct(param.type.struct_definition);13681369type struct_type = param.type;1370const auto array_length = std::max(1u, param.type.array_length);1371struct_type.array_length = 0;13721373// Struct arrays need to be flattened into individual elements as well1374std::vector<spv::Id> array_element_ids;1375array_element_ids.reserve(array_length);1376for (unsigned int a = 0; a < array_length; a++)1377{1378std::vector<spv::Id> struct_element_ids;1379struct_element_ids.reserve(struct_definition.member_list.size());1380for (const member_type &member : struct_definition.member_list)1381{1382const spv::Id input_var = create_varying_variable(member.type, member.semantic, spv::StorageClassInput, a);13831384param_value =1385add_instruction(spv::OpLoad, convert_type(member.type))1386.add(input_var);1387struct_element_ids.push_back(param_value);1388}13891390param_value =1391add_instruction(spv::OpCompositeConstruct, convert_type(struct_type))1392.add(struct_element_ids.begin(), struct_element_ids.end());1393array_element_ids.push_back(param_value);1394}13951396if (param.type.is_array())1397{1398// Build the array from all constructed struct elements1399param_value =1400add_instruction(spv::OpCompositeConstruct, convert_type(param.type))1401.add(array_element_ids.begin(), array_element_ids.end());1402}1403}1404else1405{1406const spv::Id input_var = create_varying_variable(param.type, param.semantic, spv::StorageClassInput);14071408param_value =1409add_instruction(spv::OpLoad, convert_type(param.type))1410.add(input_var);1411}14121413add_instruction_without_result(spv::OpStore)1414.add(param_var)1415.add(param_value);1416}14171418if (param.type.has(type::q_out))1419{1420if (param.type.is_struct())1421{1422const struct_type &struct_definition = get_struct(param.type.struct_definition);14231424for (unsigned int a = 0, array_length = std::max(1u, param.type.array_length); a < array_length; a++)1425{1426for (const member_type &member : struct_definition.member_list)1427{1428create_varying_variable(member.type, member.semantic, spv::StorageClassOutput, a);1429}1430}1431}1432else1433{1434create_varying_variable(param.type, param.semantic, spv::StorageClassOutput);1435}1436}1437}14381439const id call_result = emit_call({}, func.id, func.return_type, call_params);14401441for (size_t i = 0, inputs_and_outputs_index = 0; i < func.parameter_list.size(); ++i)1442{1443const member_type ¶m = func.parameter_list[i];14441445if (param.type.has(type::q_out))1446{1447const spv::Id value =1448add_instruction(spv::OpLoad, convert_type(param.type))1449.add(call_params[i].base);14501451if (param.type.is_struct())1452{1453const struct_type &struct_definition = get_struct(param.type.struct_definition);14541455type struct_type = param.type;1456const auto array_length = std::max(1u, param.type.array_length);1457struct_type.array_length = 0;14581459// Skip input variables if this is an "inout" parameter1460if (param.type.has(type::q_in))1461inputs_and_outputs_index += struct_definition.member_list.size() * array_length;14621463// Split up struct array into individual struct elements again1464for (unsigned int a = 0; a < array_length; a++)1465{1466spv::Id element_value = value;1467if (param.type.is_array())1468{1469element_value =1470add_instruction(spv::OpCompositeExtract, convert_type(struct_type))1471.add(value)1472.add(a);1473}14741475// Split out struct fields into separate output variables again1476for (uint32_t member_index = 0; member_index < struct_definition.member_list.size(); ++member_index)1477{1478const spv::Id member_value =1479add_instruction(spv::OpCompositeExtract, convert_type(struct_definition.member_list[member_index].type))1480.add(element_value)1481.add(member_index);14821483add_instruction_without_result(spv::OpStore)1484.add(inputs_and_outputs[inputs_and_outputs_index++])1485.add(member_value);1486}1487}1488}1489else1490{1491// Skip input variable if this is an "inout" parameter (see loop above)1492if (param.type.has(type::q_in))1493inputs_and_outputs_index += 1;14941495add_instruction_without_result(spv::OpStore)1496.add(inputs_and_outputs[inputs_and_outputs_index++])1497.add(value);1498}1499}1500else1501{1502// Input parameters do not need to store anything, but increase the input/output variable index1503if (param.type.is_struct())1504{1505const struct_type &struct_definition = get_struct(param.type.struct_definition);1506inputs_and_outputs_index += struct_definition.member_list.size() * std::max(1u, param.type.array_length);1507}1508else1509{1510inputs_and_outputs_index += 1;1511}1512}1513}15141515if (func.return_type.is_struct())1516{1517const struct_type &struct_definition = get_struct(func.return_type.struct_definition);15181519for (uint32_t member_index = 0; member_index < struct_definition.member_list.size(); ++member_index)1520{1521const member_type &member = struct_definition.member_list[member_index];15221523const spv::Id result_var = create_varying_variable(member.type, member.semantic, spv::StorageClassOutput);15241525const spv::Id member_result =1526add_instruction(spv::OpCompositeExtract, convert_type(member.type))1527.add(call_result)1528.add(member_index);15291530add_instruction_without_result(spv::OpStore)1531.add(result_var)1532.add(member_result);1533}1534}1535else if (!func.return_type.is_void())1536{1537const spv::Id result_var = create_varying_variable(func.return_type, func.return_semantic, spv::StorageClassOutput);15381539add_instruction_without_result(spv::OpStore)1540.add(result_var)1541.add(call_result);1542}15431544// Add code to flip the output vertically1545if (_flip_vert_y && position_variable != 0 && func.type == shader_type::vertex)1546{1547expression position;1548position.reset_to_lvalue({}, position_variable, { type::t_float, 4, 1 });1549position.add_constant_index_access(1); // Y component15501551// gl_Position.y = -gl_Position.y1552emit_store(position,1553emit_unary_op({}, tokenid::minus, { type::t_float, 1, 1 },1554emit_load(position, false)));1555}15561557#if 01558// Disabled because it breaks on MacOS/Metal - point size should not be defined for a non-point primitive.1559// Add code that sets the point size to a default value (in case this vertex shader is used with point primitives)1560if (point_size_variable == 0 && func.type == shader_type::vertex)1561{1562create_varying_variable({ type::t_float, 1, 1 }, "SV_POINTSIZE", spv::StorageClassOutput);15631564expression point_size;1565point_size.reset_to_lvalue({}, point_size_variable, { type::t_float, 1, 1 });15661567// gl_PointSize = 1.01568emit_store(point_size, emit_constant({ type::t_float, 1, 1 }, 1));1569}1570#endif15711572leave_block_and_return(0);1573leave_function();15741575spv::ExecutionModel model;1576switch (func.type)1577{1578case shader_type::vertex:1579model = spv::ExecutionModelVertex;1580break;1581case shader_type::pixel:1582model = spv::ExecutionModelFragment;1583add_instruction_without_result(spv::OpExecutionMode, _execution_modes)1584.add(entry_point_definition)1585.add(_vulkan_semantics ? spv::ExecutionModeOriginUpperLeft : spv::ExecutionModeOriginLowerLeft);1586break;1587case shader_type::compute:1588model = spv::ExecutionModelGLCompute;1589add_instruction_without_result(spv::OpExecutionMode, _execution_modes)1590.add(entry_point_definition)1591.add(spv::ExecutionModeLocalSize)1592.add(func.num_threads[0])1593.add(func.num_threads[1])1594.add(func.num_threads[2]);1595break;1596default:1597assert(false);1598return;1599}16001601add_instruction_without_result(spv::OpEntryPoint, _entries)1602.add(model)1603.add(entry_point_definition)1604.add_string(func.unique_name.c_str())1605.add(inputs_and_outputs.begin(), inputs_and_outputs.end());1606}16071608id emit_load(const expression &exp, bool) override1609{1610if (exp.is_constant) // Constant expressions do not have a complex access chain1611return emit_constant(exp.type, exp.constant);16121613size_t i = 0;1614spv::Id result = exp.base;1615type base_type = exp.type;1616bool is_uniform_bool = false;16171618if (exp.is_lvalue || !exp.chain.empty())1619add_location(exp.location, *_current_block_data);16201621// If a variable is referenced, load the value first1622if (exp.is_lvalue && _spec_constants.find(exp.base) == _spec_constants.end())1623{1624if (!exp.chain.empty())1625base_type = exp.chain[0].from;16261627std::pair<spv::StorageClass, spv::ImageFormat> storage = { spv::StorageClassFunction, spv::ImageFormatUnknown };1628if (const auto it = _storage_lookup.find(exp.base);1629it != _storage_lookup.end())1630storage = it->second;16311632spirv_instruction *access_chain = nullptr;16331634// Check if this is a uniform variable (see 'define_uniform' function above) and dereference it1635if (result & 0xF0000000)1636{1637const uint32_t member_index = result ^ 0xF0000000;16381639storage.first = spv::StorageClassUniform;1640is_uniform_bool = base_type.is_boolean();16411642if (is_uniform_bool)1643base_type.base = type::t_uint;16441645access_chain = &add_instruction(spv::OpAccessChain)1646.add(_global_ubo_variable)1647.add(emit_constant(member_index));1648}16491650// Any indexing expressions can be resolved during load with an 'OpAccessChain' already1651if (!exp.chain.empty() && (1652exp.chain[0].op == expression::operation::op_member ||1653exp.chain[0].op == expression::operation::op_dynamic_index ||1654exp.chain[0].op == expression::operation::op_constant_index))1655{1656// Ensure that 'access_chain' cannot get invalidated by calls to 'emit_constant' or 'convert_type'1657assert(_current_block_data != &_types_and_constants);16581659// Use access chain from uniform if possible, otherwise create new one1660if (access_chain == nullptr) access_chain =1661&add_instruction(spv::OpAccessChain).add(result); // Base16621663// Ignore first index into 1xN matrices, since they were translated to a vector type in SPIR-V1664if (exp.chain[0].from.rows == 1 && exp.chain[0].from.cols > 1)1665i = 1;16661667for (; i < exp.chain.size() && (1668exp.chain[i].op == expression::operation::op_member ||1669exp.chain[i].op == expression::operation::op_dynamic_index ||1670exp.chain[i].op == expression::operation::op_constant_index); ++i)1671access_chain->add(exp.chain[i].op == expression::operation::op_dynamic_index ?1672exp.chain[i].index :1673emit_constant(exp.chain[i].index)); // Indexes16741675base_type = exp.chain[i - 1].to;1676access_chain->type = convert_type(base_type, true, storage.first, storage.second); // Last type is the result1677result = access_chain->result;1678}1679else if (access_chain != nullptr)1680{1681access_chain->type = convert_type(base_type, true, storage.first, storage.second, base_type.is_array() ? 16u : 0u);1682result = access_chain->result;1683}16841685result =1686add_instruction(spv::OpLoad, convert_type(base_type, false, spv::StorageClassFunction, storage.second))1687.add(result); // Pointer1688}16891690// Need to convert boolean uniforms which are actually integers in SPIR-V1691if (is_uniform_bool)1692{1693base_type.base = type::t_bool;16941695result =1696add_instruction(spv::OpINotEqual, convert_type(base_type))1697.add(result)1698.add(emit_constant(0));1699}17001701// Work through all remaining operations in the access chain and apply them to the value1702for (; i < exp.chain.size(); ++i)1703{1704assert(result != 0);1705const expression::operation &op = exp.chain[i];17061707switch (op.op)1708{1709case expression::operation::op_cast:1710if (op.from.is_scalar() && !op.to.is_scalar())1711{1712type cast_type = op.to;1713cast_type.base = op.from.base;17141715std::vector<expression> args;1716args.reserve(op.to.components());1717for (unsigned int c = 0; c < op.to.components(); ++c)1718args.emplace_back().reset_to_rvalue(exp.location, result, op.from);17191720result = emit_construct(exp.location, cast_type, args);1721}17221723if (op.from.is_boolean())1724{1725const spv::Id true_constant = emit_constant(op.to, 1);1726const spv::Id false_constant = emit_constant(op.to, 0);17271728result =1729add_instruction(spv::OpSelect, convert_type(op.to))1730.add(result) // Condition1731.add(true_constant)1732.add(false_constant);1733}1734else1735{1736spv::Op spv_op = spv::OpNop;1737switch (op.to.base)1738{1739case type::t_bool:1740if (op.from.is_floating_point())1741spv_op = spv::OpFOrdNotEqual;1742else1743spv_op = spv::OpINotEqual;1744// Add instruction to compare value against zero instead of casting1745result =1746add_instruction(spv_op, convert_type(op.to))1747.add(result)1748.add(emit_constant(op.from, 0));1749continue;1750case type::t_min16int:1751case type::t_int:1752if (op.from.is_floating_point())1753spv_op = spv::OpConvertFToS;1754else if (op.from.precision() == op.to.precision())1755spv_op = spv::OpBitcast;1756else if (_enable_16bit_types)1757spv_op = spv::OpSConvert;1758else1759continue; // Do not have to add conversion instruction between min16int/int if 16-bit types are not enabled1760break;1761case type::t_min16uint:1762case type::t_uint:1763if (op.from.is_floating_point())1764spv_op = spv::OpConvertFToU;1765else if (op.from.precision() == op.to.precision())1766spv_op = spv::OpBitcast;1767else if (_enable_16bit_types)1768spv_op = spv::OpUConvert;1769else1770continue;1771break;1772case type::t_min16float:1773case type::t_float:1774if (op.from.is_floating_point() && !_enable_16bit_types)1775continue; // Do not have to add conversion instruction between min16float/float if 16-bit types are not enabled1776else if (op.from.is_floating_point())1777spv_op = spv::OpFConvert;1778else if (op.from.is_signed())1779spv_op = spv::OpConvertSToF;1780else1781spv_op = spv::OpConvertUToF;1782break;1783default:1784assert(false);1785}17861787result =1788add_instruction(spv_op, convert_type(op.to))1789.add(result);1790}1791break;1792case expression::operation::op_dynamic_index:1793assert(op.from.is_vector() && op.to.is_scalar());1794result =1795add_instruction(spv::OpVectorExtractDynamic, convert_type(op.to))1796.add(result) // Vector1797.add(op.index); // Index1798break;1799case expression::operation::op_member: // In case of struct return values, which are r-values1800case expression::operation::op_constant_index:1801assert(op.from.is_vector() || op.from.is_matrix() || op.from.is_struct());1802result =1803add_instruction(spv::OpCompositeExtract, convert_type(op.to))1804.add(result)1805.add(op.index); // Literal Index1806break;1807case expression::operation::op_swizzle:1808if (op.to.is_vector())1809{1810if (op.from.is_matrix())1811{1812spv::Id components[4];1813for (int c = 0; c < 4 && op.swizzle[c] >= 0; ++c)1814{1815const unsigned int row = op.swizzle[c] / 4;1816const unsigned int column = op.swizzle[c] - row * 4;18171818type scalar_type = op.to;1819scalar_type.rows = 1;1820scalar_type.cols = 1;18211822spirv_instruction &inst = add_instruction(spv::OpCompositeExtract, convert_type(scalar_type));1823inst.add(result);1824if (op.from.rows > 1) // Matrix types with a single row are actually vectors, so they don't need the extra index1825inst.add(row);1826inst.add(column);18271828components[c] = inst;1829}18301831spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(op.to));1832for (int c = 0; c < 4 && op.swizzle[c] >= 0; ++c)1833inst.add(components[c]);1834result = inst;1835}1836else if (op.from.is_vector())1837{1838spirv_instruction &inst = add_instruction(spv::OpVectorShuffle, convert_type(op.to));1839inst.add(result); // Vector 11840inst.add(result); // Vector 21841for (int c = 0; c < 4 && op.swizzle[c] >= 0; ++c)1842inst.add(op.swizzle[c]);1843result = inst;1844}1845else1846{1847spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(op.to));1848for (unsigned int c = 0; c < op.to.rows; ++c)1849inst.add(result);1850result = inst;1851}1852break;1853}1854else if (op.from.is_matrix() && op.to.is_scalar())1855{1856assert(op.swizzle[1] < 0);18571858spirv_instruction &inst = add_instruction(spv::OpCompositeExtract, convert_type(op.to));1859inst.add(result); // Composite1860if (op.from.rows > 1)1861{1862const unsigned int row = op.swizzle[0] / 4;1863const unsigned int column = op.swizzle[0] - row * 4;1864inst.add(row);1865inst.add(column);1866}1867else1868{1869inst.add(op.swizzle[0]);1870}1871result = inst;1872break;1873}1874else1875{1876assert(false);1877break;1878}1879}1880}18811882return result;1883}1884void emit_store(const expression &exp, id value) override1885{1886assert(value != 0 && exp.is_lvalue && !exp.is_constant && !exp.type.is_sampler());18871888add_location(exp.location, *_current_block_data);18891890size_t i = 0;1891// Any indexing expressions can be resolved with an 'OpAccessChain' already1892spv::Id target = emit_access_chain(exp, i);1893type base_type = exp.chain.empty() ? exp.type : i == 0 ? exp.chain[0].from : exp.chain[i - 1].to;18941895// TODO: Complex access chains like float4x4[0].m00m10[0] = 0;1896// Work through all remaining operations in the access chain and apply them to the value1897for (; i < exp.chain.size(); ++i)1898{1899const expression::operation &op = exp.chain[i];1900switch (op.op)1901{1902case expression::operation::op_cast:1903case expression::operation::op_member:1904// These should have been handled above already (and casting does not make sense for a store operation)1905break;1906case expression::operation::op_dynamic_index:1907case expression::operation::op_constant_index:1908assert(false);1909break;1910case expression::operation::op_swizzle:1911{1912spv::Id result =1913add_instruction(spv::OpLoad, convert_type(base_type))1914.add(target); // Pointer19151916if (base_type.is_vector())1917{1918spirv_instruction &inst = add_instruction(spv::OpVectorShuffle, convert_type(base_type));1919inst.add(result); // Vector 11920inst.add(value); // Vector 219211922unsigned int shuffle[4] = { 0, 1, 2, 3 };1923for (unsigned int c = 0; c < base_type.rows; ++c)1924if (op.swizzle[c] >= 0)1925shuffle[op.swizzle[c]] = base_type.rows + c;1926for (unsigned int c = 0; c < base_type.rows; ++c)1927inst.add(shuffle[c]);19281929value = inst;1930}1931else if (op.to.is_scalar())1932{1933assert(op.swizzle[1] < 0);19341935spirv_instruction &inst = add_instruction(spv::OpCompositeInsert, convert_type(base_type));1936inst.add(value); // Object1937inst.add(result); // Composite19381939if (op.from.is_matrix() && op.from.rows > 1)1940{1941const unsigned int row = op.swizzle[0] / 4;1942const unsigned int column = op.swizzle[0] - row * 4;1943inst.add(row);1944inst.add(column);1945}1946else1947{1948inst.add(op.swizzle[0]);1949}19501951value = inst;1952}1953else1954{1955// TODO: Implement matrix to vector swizzles1956assert(false);1957}1958break;1959}1960}1961}19621963add_instruction_without_result(spv::OpStore)1964.add(target)1965.add(value);1966}1967id emit_access_chain(const expression &exp, size_t &i) override1968{1969// This function cannot create access chains for uniform variables1970assert((exp.base & 0xF0000000) == 0);19711972i = 0;1973if (exp.chain.empty() || (1974exp.chain[0].op != expression::operation::op_member &&1975exp.chain[0].op != expression::operation::op_dynamic_index &&1976exp.chain[0].op != expression::operation::op_constant_index))1977return exp.base;19781979std::pair<spv::StorageClass, spv::ImageFormat> storage = { spv::StorageClassFunction, spv::ImageFormatUnknown };1980if (const auto it = _storage_lookup.find(exp.base);1981it != _storage_lookup.end())1982storage = it->second;19831984// Ensure that 'access_chain' cannot get invalidated by calls to 'emit_constant' or 'convert_type'1985assert(_current_block_data != &_types_and_constants);19861987spirv_instruction *access_chain =1988&add_instruction(spv::OpAccessChain).add(exp.base); // Base19891990// Ignore first index into 1xN matrices, since they were translated to a vector type in SPIR-V1991if (exp.chain[0].from.rows == 1 && exp.chain[0].from.cols > 1)1992i = 1;19931994for (; i < exp.chain.size() && (1995exp.chain[i].op == expression::operation::op_member ||1996exp.chain[i].op == expression::operation::op_dynamic_index ||1997exp.chain[i].op == expression::operation::op_constant_index); ++i)1998access_chain->add(exp.chain[i].op == expression::operation::op_dynamic_index ?1999exp.chain[i].index :2000emit_constant(exp.chain[i].index)); // Indexes20012002access_chain->type = convert_type(exp.chain[i - 1].to, true, storage.first, storage.second); // Last type is the result2003return access_chain->result;2004}20052006using codegen::emit_constant;2007id emit_constant(uint32_t value)2008{2009return emit_constant({ type::t_uint, 1, 1 }, value);2010}2011id emit_constant(const type &data_type, const constant &data) override2012{2013return emit_constant(data_type, data, false);2014}2015id emit_constant(const type &data_type, const constant &data, bool spec_constant)2016{2017if (!spec_constant) // Specialization constants cannot reuse other constants2018{2019if (const auto it = std::find_if(_constant_lookup.begin(), _constant_lookup.end(),2020[&data_type, &data](std::tuple<type, constant, spv::Id> &x) {2021if (!(std::get<0>(x) == data_type && std::memcmp(&std::get<1>(x).as_uint[0], &data.as_uint[0], sizeof(uint32_t) * 16) == 0 && std::get<1>(x).array_data.size() == data.array_data.size()))2022return false;2023for (size_t i = 0; i < data.array_data.size(); ++i)2024if (std::memcmp(&std::get<1>(x).array_data[i].as_uint[0], &data.array_data[i].as_uint[0], sizeof(uint32_t) * 16) != 0)2025return false;2026return true;2027});2028it != _constant_lookup.end())2029return std::get<2>(*it); // Reuse existing constant instead of duplicating the definition2030}20312032spv::Id result;2033if (data_type.is_array())2034{2035assert(data_type.is_bounded_array()); // Unbounded arrays cannot be constants20362037type elem_type = data_type;2038elem_type.array_length = 0;20392040std::vector<spv::Id> elements;2041elements.reserve(data_type.array_length);20422043// Fill up elements with constant array data2044for (const constant &elem : data.array_data)2045elements.push_back(emit_constant(elem_type, elem, spec_constant));2046// Fill up any remaining elements with a default value (when the array data did not specify them)2047for (size_t i = elements.size(); i < static_cast<size_t>(data_type.array_length); ++i)2048elements.push_back(emit_constant(elem_type, {}, spec_constant));20492050result =2051add_instruction(spec_constant ? spv::OpSpecConstantComposite : spv::OpConstantComposite, convert_type(data_type), _types_and_constants)2052.add(elements.begin(), elements.end());2053}2054else if (data_type.is_struct())2055{2056assert(!spec_constant); // Structures cannot be specialization constants20572058result = add_instruction(spv::OpConstantNull, convert_type(data_type), _types_and_constants);2059}2060else if (data_type.is_vector() || data_type.is_matrix())2061{2062type elem_type = data_type;2063elem_type.rows = data_type.cols;2064elem_type.cols = 1;20652066spv::Id rows[4] = {};20672068// Construct matrix constant out of row vector constants2069// Construct vector constant out of scalar constants for each element2070for (unsigned int i = 0; i < data_type.rows; ++i)2071{2072constant row_data = {};2073for (unsigned int k = 0; k < data_type.cols; ++k)2074row_data.as_uint[k] = data.as_uint[i * data_type.cols + k];20752076rows[i] = emit_constant(elem_type, row_data, spec_constant);2077}20782079if (data_type.rows == 1)2080{2081result = rows[0];2082}2083else2084{2085spirv_instruction &inst = add_instruction(spec_constant ? spv::OpSpecConstantComposite : spv::OpConstantComposite, convert_type(data_type), _types_and_constants);2086for (unsigned int i = 0; i < data_type.rows; ++i)2087inst.add(rows[i]);2088result = inst;2089}2090}2091else if (data_type.is_boolean())2092{2093result = add_instruction(data.as_uint[0] ?2094(spec_constant ? spv::OpSpecConstantTrue : spv::OpConstantTrue) :2095(spec_constant ? spv::OpSpecConstantFalse : spv::OpConstantFalse), convert_type(data_type), _types_and_constants);2096}2097else2098{2099assert(data_type.is_scalar());21002101result =2102add_instruction(spec_constant ? spv::OpSpecConstant : spv::OpConstant, convert_type(data_type), _types_and_constants)2103.add(data.as_uint[0]);2104}21052106if (spec_constant) // Keep track of all specialization constants2107_spec_constants.insert(result);2108else2109_constant_lookup.push_back({ data_type, data, result });21102111return result;2112}21132114id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override2115{2116spv::Op spv_op = spv::OpNop;21172118switch (op)2119{2120case tokenid::minus:2121spv_op = res_type.is_floating_point() ? spv::OpFNegate : spv::OpSNegate;2122break;2123case tokenid::tilde:2124spv_op = spv::OpNot;2125break;2126case tokenid::exclaim:2127spv_op = spv::OpLogicalNot;2128break;2129default:2130return assert(false), 0;2131}21322133add_location(loc, *_current_block_data);21342135spirv_instruction &inst = add_instruction(spv_op, convert_type(res_type));2136inst.add(val); // Operand21372138return inst;2139}2140id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &exp_type, id lhs, id rhs) override2141{2142spv::Op spv_op = spv::OpNop;21432144switch (op)2145{2146case tokenid::plus:2147case tokenid::plus_plus:2148case tokenid::plus_equal:2149spv_op = exp_type.is_floating_point() ? spv::OpFAdd : spv::OpIAdd;2150break;2151case tokenid::minus:2152case tokenid::minus_minus:2153case tokenid::minus_equal:2154spv_op = exp_type.is_floating_point() ? spv::OpFSub : spv::OpISub;2155break;2156case tokenid::star:2157case tokenid::star_equal:2158spv_op = exp_type.is_floating_point() ? spv::OpFMul : spv::OpIMul;2159break;2160case tokenid::slash:2161case tokenid::slash_equal:2162spv_op = exp_type.is_floating_point() ? spv::OpFDiv : exp_type.is_signed() ? spv::OpSDiv : spv::OpUDiv;2163break;2164case tokenid::percent:2165case tokenid::percent_equal:2166spv_op = exp_type.is_floating_point() ? spv::OpFRem : exp_type.is_signed() ? spv::OpSRem : spv::OpUMod;2167break;2168case tokenid::caret:2169case tokenid::caret_equal:2170spv_op = spv::OpBitwiseXor;2171break;2172case tokenid::pipe:2173case tokenid::pipe_equal:2174spv_op = spv::OpBitwiseOr;2175break;2176case tokenid::ampersand:2177case tokenid::ampersand_equal:2178spv_op = spv::OpBitwiseAnd;2179break;2180case tokenid::less_less:2181case tokenid::less_less_equal:2182spv_op = spv::OpShiftLeftLogical;2183break;2184case tokenid::greater_greater:2185case tokenid::greater_greater_equal:2186spv_op = exp_type.is_signed() ? spv::OpShiftRightArithmetic : spv::OpShiftRightLogical;2187break;2188case tokenid::pipe_pipe:2189spv_op = spv::OpLogicalOr;2190break;2191case tokenid::ampersand_ampersand:2192spv_op = spv::OpLogicalAnd;2193break;2194case tokenid::less:2195spv_op = exp_type.is_floating_point() ? spv::OpFOrdLessThan :2196exp_type.is_signed() ? spv::OpSLessThan : spv::OpULessThan;2197break;2198case tokenid::less_equal:2199spv_op = exp_type.is_floating_point() ? spv::OpFOrdLessThanEqual :2200exp_type.is_signed() ? spv::OpSLessThanEqual : spv::OpULessThanEqual;2201break;2202case tokenid::greater:2203spv_op = exp_type.is_floating_point() ? spv::OpFOrdGreaterThan :2204exp_type.is_signed() ? spv::OpSGreaterThan : spv::OpUGreaterThan;2205break;2206case tokenid::greater_equal:2207spv_op = exp_type.is_floating_point() ? spv::OpFOrdGreaterThanEqual :2208exp_type.is_signed() ? spv::OpSGreaterThanEqual : spv::OpUGreaterThanEqual;2209break;2210case tokenid::equal_equal:2211spv_op = exp_type.is_floating_point() ? spv::OpFOrdEqual :2212exp_type.is_boolean() ? spv::OpLogicalEqual : spv::OpIEqual;2213break;2214case tokenid::exclaim_equal:2215spv_op = exp_type.is_floating_point() ? spv::OpFOrdNotEqual :2216exp_type.is_boolean() ? spv::OpLogicalNotEqual : spv::OpINotEqual;2217break;2218default:2219return assert(false), 0;2220}22212222add_location(loc, *_current_block_data);22232224// Binary operators generally only work on scalars and vectors in SPIR-V, so need to apply them to matrices component-wise2225if (exp_type.is_matrix() && exp_type.rows != 1)2226{2227std::vector<spv::Id> ids;2228ids.reserve(exp_type.cols);22292230type vector_type = exp_type;2231vector_type.rows = exp_type.cols;2232vector_type.cols = 1;22332234for (unsigned int row = 0; row < exp_type.rows; ++row)2235{2236const spv::Id lhs_elem = add_instruction(spv::OpCompositeExtract, convert_type(vector_type))2237.add(lhs)2238.add(row);2239const spv::Id rhs_elem = add_instruction(spv::OpCompositeExtract, convert_type(vector_type))2240.add(rhs)2241.add(row);22422243spirv_instruction &inst = add_instruction(spv_op, convert_type(vector_type));2244inst.add(lhs_elem); // Operand 12245inst.add(rhs_elem); // Operand 222462247if (res_type.has(type::q_precise))2248add_decoration(inst, spv::DecorationNoContraction);2249if (!_enable_16bit_types && res_type.precision() < 32)2250add_decoration(inst, spv::DecorationRelaxedPrecision);22512252ids.push_back(inst);2253}22542255spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(res_type));2256inst.add(ids.begin(), ids.end());22572258return inst;2259}2260else2261{2262spirv_instruction &inst = add_instruction(spv_op, convert_type(res_type));2263inst.add(lhs); // Operand 12264inst.add(rhs); // Operand 222652266if (res_type.has(type::q_precise))2267add_decoration(inst, spv::DecorationNoContraction);2268if (!_enable_16bit_types && res_type.precision() < 32)2269add_decoration(inst, spv::DecorationRelaxedPrecision);22702271return inst;2272}2273}2274id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override2275{2276if (op != tokenid::question)2277return assert(false), 0;22782279add_location(loc, *_current_block_data);22802281spirv_instruction &inst = add_instruction(spv::OpSelect, convert_type(res_type));2282inst.add(condition); // Condition2283inst.add(true_value); // Object 12284inst.add(false_value); // Object 222852286return inst;2287}2288id emit_call(const location &loc, id function, const type &res_type, const std::vector<expression> &args) override2289{2290#ifndef NDEBUG2291for (const expression &arg : args)2292assert(arg.chain.empty() && arg.base != 0);2293#endif2294add_location(loc, *_current_block_data);22952296// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpFunctionCall2297spirv_instruction &inst = add_instruction(spv::OpFunctionCall, convert_type(res_type));2298inst.add(function); // Function2299for (const expression &arg : args)2300inst.add(arg.base); // Arguments23012302return inst;2303}2304id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector<expression> &args) override2305{2306#ifndef NDEBUG2307for (const expression &arg : args)2308assert(arg.chain.empty() && arg.base != 0);2309#endif2310add_location(loc, *_current_block_data);23112312enum2313{2314#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) name##i,2315#include "effect_symbol_table_intrinsics.inl"2316};23172318switch (intrinsic)2319{2320#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) case name##i: code2321#include "effect_symbol_table_intrinsics.inl"2322default:2323return assert(false), 0;2324}2325}2326id emit_construct(const location &loc, const type &res_type, const std::vector<expression> &args) override2327{2328#ifndef NDEBUG2329for (const expression &arg : args)2330assert((arg.type.is_scalar() || res_type.is_array()) && arg.chain.empty() && arg.base != 0);2331#endif2332add_location(loc, *_current_block_data);23332334std::vector<spv::Id> ids;2335ids.reserve(args.size());23362337// There must be exactly one constituent for each top-level component of the result2338if (res_type.is_matrix())2339{2340type vector_type = res_type;2341vector_type.rows = res_type.cols;2342vector_type.cols = 1;23432344// Turn the list of scalar arguments into a list of column vectors2345for (size_t arg = 0; arg < args.size(); arg += vector_type.rows)2346{2347spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(vector_type));2348for (unsigned row = 0; row < vector_type.rows; ++row)2349inst.add(args[arg + row].base);23502351ids.push_back(inst);2352}2353}2354else2355{2356assert(res_type.is_vector() || res_type.is_array());23572358// The exception is that for constructing a vector, a contiguous subset of the scalars consumed can be represented by a vector operand instead2359for (const expression &arg : args)2360ids.push_back(arg.base);2361}23622363spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(res_type));2364inst.add(ids.begin(), ids.end());23652366return inst;2367}23682369void emit_if(const location &loc, id, id condition_block, id true_statement_block, id false_statement_block, unsigned int selection_control) override2370{2371spirv_instruction merge_label = _current_block_data->instructions.back();2372assert(merge_label.op == spv::OpLabel);2373_current_block_data->instructions.pop_back();23742375// Add previous block containing the condition value first2376_current_block_data->append(_block_data[condition_block]);23772378spirv_instruction branch_inst = _current_block_data->instructions.back();2379assert(branch_inst.op == spv::OpBranchConditional);2380_current_block_data->instructions.pop_back();23812382// Add structured control flow instruction2383add_location(loc, *_current_block_data);2384add_instruction_without_result(spv::OpSelectionMerge)2385.add(merge_label)2386.add(selection_control & 0x3); // 'SelectionControl' happens to match the flags produced by the parser23872388// Append all blocks belonging to the branch2389_current_block_data->instructions.push_back(branch_inst);2390_current_block_data->append(_block_data[true_statement_block]);2391_current_block_data->append(_block_data[false_statement_block]);23922393_current_block_data->instructions.push_back(merge_label);2394}2395id emit_phi(const location &loc, id, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &res_type) override2396{2397spirv_instruction merge_label = _current_block_data->instructions.back();2398assert(merge_label.op == spv::OpLabel);2399_current_block_data->instructions.pop_back();24002401// Add previous block containing the condition value first2402_current_block_data->append(_block_data[condition_block]);24032404if (true_statement_block != condition_block)2405_current_block_data->append(_block_data[true_statement_block]);2406if (false_statement_block != condition_block)2407_current_block_data->append(_block_data[false_statement_block]);24082409_current_block_data->instructions.push_back(merge_label);24102411add_location(loc, *_current_block_data);24122413// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpPhi2414spirv_instruction &inst = add_instruction(spv::OpPhi, convert_type(res_type))2415.add(true_value) // Variable 02416.add(true_statement_block) // Parent 02417.add(false_value) // Variable 12418.add(false_statement_block); // Parent 124192420return inst;2421}2422void emit_loop(const location &loc, id, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int loop_control) override2423{2424spirv_instruction merge_label = _current_block_data->instructions.back();2425assert(merge_label.op == spv::OpLabel);2426_current_block_data->instructions.pop_back();24272428// Add previous block first2429_current_block_data->append(_block_data[prev_block]);24302431// Fill header block2432assert(_block_data[header_block].instructions.size() == 2);2433_current_block_data->instructions.push_back(_block_data[header_block].instructions[0]);2434assert(_current_block_data->instructions.back().op == spv::OpLabel);24352436// Add structured control flow instruction2437add_location(loc, *_current_block_data);2438add_instruction_without_result(spv::OpLoopMerge)2439.add(merge_label)2440.add(continue_block)2441.add(loop_control & 0x3); // 'LoopControl' happens to match the flags produced by the parser24422443_current_block_data->instructions.push_back(_block_data[header_block].instructions[1]);2444assert(_current_block_data->instructions.back().op == spv::OpBranch);24452446// Add condition block if it exists2447if (condition_block != 0)2448_current_block_data->append(_block_data[condition_block]);24492450// Append loop body block before continue block2451_current_block_data->append(_block_data[loop_block]);2452_current_block_data->append(_block_data[continue_block]);24532454_current_block_data->instructions.push_back(merge_label);2455}2456void emit_switch(const location &loc, id, id selector_block, id default_label, id default_block, const std::vector<id> &case_literal_and_labels, const std::vector<id> &case_blocks, unsigned int selection_control) override2457{2458assert(case_blocks.size() == case_literal_and_labels.size() / 2);24592460spirv_instruction merge_label = _current_block_data->instructions.back();2461assert(merge_label.op == spv::OpLabel);2462_current_block_data->instructions.pop_back();24632464// Add previous block containing the selector value first2465_current_block_data->append(_block_data[selector_block]);24662467spirv_instruction switch_inst = _current_block_data->instructions.back();2468assert(switch_inst.op == spv::OpSwitch);2469_current_block_data->instructions.pop_back();24702471// Add structured control flow instruction2472add_location(loc, *_current_block_data);2473add_instruction_without_result(spv::OpSelectionMerge)2474.add(merge_label)2475.add(selection_control & 0x3); // 'SelectionControl' happens to match the flags produced by the parser24762477// Update switch instruction to contain all case labels2478switch_inst.operands[1] = default_label;2479switch_inst.add(case_literal_and_labels.begin(), case_literal_and_labels.end());24802481// Append all blocks belonging to the switch2482_current_block_data->instructions.push_back(switch_inst);24832484std::vector<id> blocks = case_blocks;2485if (default_label != merge_label)2486blocks.push_back(default_block);2487// Eliminate duplicates (because of multiple case labels pointing to the same block)2488std::sort(blocks.begin(), blocks.end());2489blocks.erase(std::unique(blocks.begin(), blocks.end()), blocks.end());2490for (const id case_block : blocks)2491_current_block_data->append(_block_data[case_block]);24922493_current_block_data->instructions.push_back(merge_label);2494}24952496bool is_in_function() const { return _current_function_blocks != nullptr; }24972498id set_block(id id) override2499{2500_last_block = _current_block;2501_current_block = id;2502_current_block_data = &_block_data[id];25032504return _last_block;2505}2506void enter_block(id id) override2507{2508assert(id != 0);2509// Can only use labels inside functions and should never be in another basic block if creating a new one2510assert(is_in_function() && !is_in_block());25112512set_block(id);25132514add_instruction_without_result(spv::OpLabel).result = id;2515}2516id leave_block_and_kill() override2517{2518assert(is_in_function()); // Can only discard inside functions25192520if (!is_in_block())2521return 0;25222523// DXC chokes when discarding inside a function. Return a null value and use demote instead, since that's2524// what the HLSL discard instruction compiles to anyway.2525if (!_discard_is_demote || _current_function_blocks->return_type.is_void())2526{2527add_instruction_without_result(spv::OpKill);2528}2529else2530{2531add_instruction_without_result(spv::OpDemoteToHelperInvocation);25322533const id return_id = emit_constant(_current_function_blocks->return_type, constant{}, false);2534add_instruction_without_result(spv::OpReturnValue).add(return_id);2535}25362537return set_block(0);2538}2539id leave_block_and_return(id value) override2540{2541assert(is_in_function()); // Can only return from inside functions25422543if (!is_in_block()) // Might already have left the last block in which case this has to be ignored2544return 0;25452546if (_current_function_blocks->return_type.is_void())2547{2548add_instruction_without_result(spv::OpReturn);2549}2550else2551{2552if (0 == value) // The implicit return statement needs this2553value = add_instruction(spv::OpUndef, convert_type(_current_function_blocks->return_type), _types_and_constants);25542555add_instruction_without_result(spv::OpReturnValue)2556.add(value);2557}25582559return set_block(0);2560}2561id leave_block_and_switch(id value, id default_target) override2562{2563assert(value != 0 && default_target != 0);2564assert(is_in_function()); // Can only switch inside functions25652566if (!is_in_block())2567return _last_block;25682569add_instruction_without_result(spv::OpSwitch)2570.add(value)2571.add(default_target);25722573return set_block(0);2574}2575id leave_block_and_branch(id target, unsigned int) override2576{2577assert(target != 0);2578assert(is_in_function()); // Can only branch inside functions25792580if (!is_in_block())2581return _last_block;25822583add_instruction_without_result(spv::OpBranch)2584.add(target);25852586return set_block(0);2587}2588id leave_block_and_branch_conditional(id condition, id true_target, id false_target) override2589{2590assert(condition != 0 && true_target != 0 && false_target != 0);2591assert(is_in_function()); // Can only branch inside functions25922593if (!is_in_block())2594return _last_block;25952596add_instruction_without_result(spv::OpBranchConditional)2597.add(condition)2598.add(true_target)2599.add(false_target);26002601return set_block(0);2602}2603void leave_function() override2604{2605assert(is_in_function()); // Can only leave if there was a function to begin with26062607_current_function_blocks->definition = _block_data[_last_block];26082609// Append function end instruction2610add_instruction_without_result(spv::OpFunctionEnd, _current_function_blocks->definition);26112612_current_function = nullptr;2613_current_function_blocks = nullptr;2614}2615};26162617codegen *reshadefx::create_codegen_spirv(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y, bool discard_is_demote)2618{2619return new codegen_spirv(vulkan_semantics, debug_info, uniforms_to_spec_constants, enable_16bit_types, flip_vert_y, discard_is_demote);2620}262126222623