Path: blob/master/dep/reshadefx/src/effect_codegen_spirv.cpp
4246 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) :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{147_glsl_ext = make_id();148}149150private:151struct type_lookup152{153reshadefx::type type;154bool is_ptr;155uint32_t array_stride;156std::pair<spv::StorageClass, spv::ImageFormat> storage;157158friend bool operator==(const type_lookup &lhs, const type_lookup &rhs)159{160return lhs.type == rhs.type && lhs.is_ptr == rhs.is_ptr && lhs.array_stride == rhs.array_stride && lhs.storage == rhs.storage;161}162};163struct function_blocks164{165spirv_basic_block declaration;166spirv_basic_block variables;167spirv_basic_block definition;168reshadefx::type return_type;169std::vector<reshadefx::type> param_types;170171friend bool operator==(const function_blocks &lhs, const function_blocks &rhs)172{173if (lhs.param_types.size() != rhs.param_types.size())174return false;175for (size_t i = 0; i < lhs.param_types.size(); ++i)176if (!(lhs.param_types[i] == rhs.param_types[i]))177return false;178return lhs.return_type == rhs.return_type;179}180};181182bool _debug_info = false;183bool _vulkan_semantics = false;184bool _uniforms_to_spec_constants = false;185bool _enable_16bit_types = false;186bool _flip_vert_y = false;187188spirv_basic_block _entries;189spirv_basic_block _execution_modes;190spirv_basic_block _debug_a;191spirv_basic_block _debug_b;192spirv_basic_block _annotations;193spirv_basic_block _types_and_constants;194spirv_basic_block _variables;195196std::vector<function_blocks> _functions_blocks;197std::unordered_map<id, spirv_basic_block> _block_data;198spirv_basic_block *_current_block_data = nullptr;199200spv::Id _glsl_ext = 0;201spv::Id _global_ubo_type = 0;202spv::Id _global_ubo_variable = 0;203std::vector<spv::Id> _global_ubo_types;204function_blocks *_current_function_blocks = nullptr;205206std::vector<std::pair<type_lookup, spv::Id>> _type_lookup;207std::vector<std::tuple<type, constant, spv::Id>> _constant_lookup;208std::vector<std::pair<function_blocks, spv::Id>> _function_type_lookup;209std::unordered_map<std::string, spv::Id> _string_lookup;210std::unordered_map<spv::Id, std::pair<spv::StorageClass, spv::ImageFormat>> _storage_lookup;211std::unordered_map<std::string, uint32_t> _semantic_to_location;212213std::unordered_set<spv::Id> _spec_constants;214std::unordered_set<spv::Capability> _capabilities;215216void add_location(const location &loc, spirv_basic_block &block)217{218if (loc.source.empty() || !_debug_info)219return;220221spv::Id file;222223if (const auto it = _string_lookup.find(loc.source);224it != _string_lookup.end())225{226file = it->second;227}228else229{230file =231add_instruction(spv::OpString, 0, _debug_a)232.add_string(loc.source.c_str());233_string_lookup.emplace(loc.source, file);234}235236// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpLine237add_instruction_without_result(spv::OpLine, block)238.add(file)239.add(loc.line)240.add(loc.column);241}242spirv_instruction &add_instruction(spv::Op op, spv::Id type = 0)243{244assert(is_in_function() && is_in_block());245246return add_instruction(op, type, *_current_block_data);247}248spirv_instruction &add_instruction(spv::Op op, spv::Id type, spirv_basic_block &block)249{250spirv_instruction &instruction = add_instruction_without_result(op, block);251instruction.type = type;252instruction.result = make_id();253return instruction;254}255spirv_instruction &add_instruction_without_result(spv::Op op)256{257assert(is_in_function() && is_in_block());258259return add_instruction_without_result(op, *_current_block_data);260}261spirv_instruction &add_instruction_without_result(spv::Op op, spirv_basic_block &block)262{263return block.instructions.emplace_back(op);264}265266void finalize_header_section(std::basic_string<char> &spirv) const267{268// Write SPIRV header info269spirv_instruction::write_word(spirv, spv::MagicNumber);270spirv_instruction::write_word(spirv, 0x10300); // Force SPIR-V 1.3271spirv_instruction::write_word(spirv, 0u); // Generator magic number, see https://www.khronos.org/registry/spir-v/api/spir-v.xml272spirv_instruction::write_word(spirv, _next_id); // Maximum ID273spirv_instruction::write_word(spirv, 0u); // Reserved for instruction schema274275// All capabilities276spirv_instruction(spv::OpCapability)277.add(spv::CapabilityShader) // Implicitly declares the Matrix capability too278.write(spirv);279280for (const spv::Capability capability : _capabilities)281spirv_instruction(spv::OpCapability)282.add(capability)283.write(spirv);284285// Optional extension instructions286spirv_instruction(spv::OpExtInstImport, _glsl_ext)287.add_string("GLSL.std.450") // Import GLSL extension288.write(spirv);289290// Single required memory model instruction291spirv_instruction(spv::OpMemoryModel)292.add(spv::AddressingModelLogical)293.add(spv::MemoryModelGLSL450)294.write(spirv);295}296void finalize_debug_info_section(std::basic_string<char> &spirv) const297{298spirv_instruction(spv::OpSource)299.add(spv::SourceLanguageUnknown) // ReShade FX is not a reserved token at the moment300.add(0) // Language version, TODO: Maybe fill in ReShade version here?301.write(spirv);302303if (_debug_info)304{305// All debug instructions306for (const spirv_instruction &inst : _debug_a.instructions)307inst.write(spirv);308for (const spirv_instruction &inst : _debug_b.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);351352// All annotation instructions353for (const spirv_instruction &inst : _annotations.instructions)354inst.write(spirv);355356finalize_type_and_constants_section(spirv);357358for (const spirv_instruction &inst : _variables.instructions)359inst.write(spirv);360361// All function definitions362for (const function_blocks &func : _functions_blocks)363{364if (func.definition.instructions.empty())365continue;366367for (const spirv_instruction &inst : func.declaration.instructions)368inst.write(spirv);369370// Grab first label and move it in front of variable declarations371func.definition.instructions.front().write(spirv);372assert(func.definition.instructions.front().op == spv::OpLabel);373374for (const spirv_instruction &inst : func.variables.instructions)375inst.write(spirv);376for (auto inst_it = func.definition.instructions.begin() + 1; inst_it != func.definition.instructions.end(); ++inst_it)377inst_it->write(spirv);378}379380return spirv;381}382std::basic_string<char> finalize_code_for_entry_point(const std::string &entry_point_name) const override383{384const auto entry_point_it = std::find_if(_functions.begin(), _functions.end(),385[&entry_point_name](const std::unique_ptr<function> &func) {386return func->unique_name == entry_point_name;387});388if (entry_point_it == _functions.end())389return {};390const function &entry_point = *entry_point_it->get();391392const auto write_entry_point = [this](const spirv_instruction& oins, std::basic_string<char>& spirv) {393assert(oins.operands.size() > 2);394spirv_instruction nins(oins.op, oins.type, oins.result);395nins.add(oins.operands[0]);396nins.add(oins.operands[1]);397nins.add_string("main");398399size_t param_start_index = 2;400while (param_start_index < oins.operands.size() && (oins.operands[param_start_index] & 0xFF000000) != 0)401param_start_index++;402403// skip zero404param_start_index++;405406for (size_t i = param_start_index; i < oins.operands.size(); i++)407nins.add(oins.operands[i]);408nins.write(spirv);409};410411// Build list of IDs to remove412std::vector<spv::Id> variables_to_remove;413#if 1414std::vector<spv::Id> functions_to_remove;415#else416for (const sampler &info : _module.samplers)417if (std::find(entry_point.referenced_samplers.begin(), entry_point.referenced_samplers.end(), info.id) == entry_point.referenced_samplers.end())418variables_to_remove.push_back(info.id);419for (const storage &info : _module.storages)420if (std::find(entry_point.referenced_storages.begin(), entry_point.referenced_storages.end(), info.id) == entry_point.referenced_storages.end())421variables_to_remove.push_back(info.id);422#endif423424std::basic_string<char> spirv;425finalize_header_section(spirv);426427// The entry point and execution mode declaration428for (const spirv_instruction &inst : _entries.instructions)429{430assert(inst.op == spv::OpEntryPoint);431432// Only add the matching entry point433if (inst.operands[1] == entry_point.id)434{435write_entry_point(inst, spirv);436}437else438{439#if 1440functions_to_remove.push_back(inst.operands[1]);441#endif442// Add interface variables to list of variables to remove443for (uint32_t k = 2 + static_cast<uint32_t>((std::strlen(reinterpret_cast<const char *>(&inst.operands[2])) + 4) / 4); k < inst.operands.size(); ++k)444variables_to_remove.push_back(inst.operands[k]);445}446}447448for (const spirv_instruction &inst : _execution_modes.instructions)449{450assert(inst.op == spv::OpExecutionMode);451452// Only add execution mode for the matching entry point453if (inst.operands[0] == entry_point.id)454{455inst.write(spirv);456}457}458459finalize_debug_info_section(spirv);460461// All annotation instructions462for (spirv_instruction inst : _annotations.instructions)463{464if (inst.op == spv::OpDecorate)465{466// Remove all decorations targeting any of the interface variables for non-matching entry points467if (std::find(variables_to_remove.begin(), variables_to_remove.end(), inst.operands[0]) != variables_to_remove.end())468continue;469470// Replace bindings471if (inst.operands[1] == spv::DecorationBinding)472{473if (const auto referenced_sampler_it = std::find(entry_point.referenced_samplers.begin(), entry_point.referenced_samplers.end(), inst.operands[0]);474referenced_sampler_it != entry_point.referenced_samplers.end())475inst.operands[2] = static_cast<uint32_t>(std::distance(entry_point.referenced_samplers.begin(), referenced_sampler_it));476else477if (const auto referenced_storage_it = std::find(entry_point.referenced_storages.begin(), entry_point.referenced_storages.end(), inst.operands[0]);478referenced_storage_it != entry_point.referenced_storages.end())479inst.operands[2] = static_cast<uint32_t>(std::distance(entry_point.referenced_storages.begin(), referenced_storage_it));480}481}482483inst.write(spirv);484}485486finalize_type_and_constants_section(spirv);487488for (const spirv_instruction &inst : _variables.instructions)489{490// Remove all declarations of the interface variables for non-matching entry points491if (inst.op == spv::OpVariable && std::find(variables_to_remove.begin(), variables_to_remove.end(), inst.result) != variables_to_remove.end())492continue;493494inst.write(spirv);495}496497// All referenced function definitions498for (const function_blocks &func : _functions_blocks)499{500if (func.definition.instructions.empty())501continue;502503const bool has_line = (_debug_info && func.declaration.instructions[0].op == spv::OpLine);504assert(func.declaration.instructions[has_line ? 1 : 0].op == spv::OpFunction);505const spv::Id definition = func.declaration.instructions[has_line ? 1 : 0].result;506507#if 1508if (std::find(functions_to_remove.begin(), functions_to_remove.end(), definition) != functions_to_remove.end())509#else510if (struct_definition != entry_point.struct_definition &&511entry_point.referenced_functions.find(struct_definition) == entry_point.referenced_functions.end())512#endif513continue;514515for (const spirv_instruction &inst : func.declaration.instructions)516inst.write(spirv);517518// Grab first label and move it in front of variable declarations519func.definition.instructions.front().write(spirv);520assert(func.definition.instructions.front().op == spv::OpLabel);521522for (const spirv_instruction &inst : func.variables.instructions)523inst.write(spirv);524for (auto inst_it = func.definition.instructions.begin() + 1; inst_it != func.definition.instructions.end(); ++inst_it)525inst_it->write(spirv);526}527528return spirv;529}530531spv::Id convert_type(type info, bool is_ptr = false, spv::StorageClass storage = spv::StorageClassFunction, spv::ImageFormat format = spv::ImageFormatUnknown, uint32_t array_stride = 0)532{533assert(array_stride == 0 || info.is_array());534535// The storage class is only relevant for pointers, so ignore it for other types during lookup536if (is_ptr == false)537storage = spv::StorageClassFunction;538// There cannot be sampler variables that are local to a function, so always assume uniform storage for them539if (info.is_object())540storage = spv::StorageClassUniformConstant;541else542assert(format == spv::ImageFormatUnknown);543544if (info.is_sampler() || info.is_storage())545info.rows = info.cols = 1;546547// Fall back to 32-bit types and use relaxed precision decoration instead if 16-bit types are not enabled548if (!_enable_16bit_types && info.is_numeric() && info.precision() < 32)549info.base = static_cast<type::datatype>(info.base + 1); // min16int -> int, min16uint -> uint, min16float -> float550551const type_lookup lookup { info, is_ptr, array_stride, { storage, format } };552553if (const auto lookup_it = std::find_if(_type_lookup.begin(), _type_lookup.end(),554[&lookup](const std::pair<type_lookup, spv::Id> &lookup_entry) { return lookup_entry.first == lookup; });555lookup_it != _type_lookup.end())556return lookup_it->second;557558spv::Id type_id, elem_type_id;559if (is_ptr)560{561elem_type_id = convert_type(info, false, storage, format, array_stride);562type_id =563add_instruction(spv::OpTypePointer, 0, _types_and_constants)564.add(storage)565.add(elem_type_id);566}567else if (info.is_array())568{569type elem_info = info;570elem_info.array_length = 0;571572elem_type_id = convert_type(elem_info, false, storage, format);573574// Make sure we don't get any dynamic arrays here575assert(info.is_bounded_array());576577const spv::Id array_length_id = emit_constant(info.array_length);578579type_id =580add_instruction(spv::OpTypeArray, 0, _types_and_constants)581.add(elem_type_id)582.add(array_length_id);583584if (array_stride != 0)585add_decoration(type_id, spv::DecorationArrayStride, { array_stride });586}587else if (info.is_matrix())588{589// Convert MxN matrix to a SPIR-V matrix with M vectors with N elements590type elem_info = info;591elem_info.rows = info.cols;592elem_info.cols = 1;593594elem_type_id = convert_type(elem_info, false, storage, format);595596// Matrix types with just one row are interpreted as if they were a vector type597if (info.rows == 1)598return elem_type_id;599600type_id =601add_instruction(spv::OpTypeMatrix, 0, _types_and_constants)602.add(elem_type_id)603.add(info.rows);604}605else if (info.is_vector())606{607type elem_info = info;608elem_info.rows = 1;609elem_info.cols = 1;610611elem_type_id = convert_type(elem_info, false, storage, format);612type_id =613add_instruction(spv::OpTypeVector, 0, _types_and_constants)614.add(elem_type_id)615.add(info.rows);616}617else618{619switch (info.base)620{621case type::t_void:622assert(info.rows == 0 && info.cols == 0);623type_id = add_instruction(spv::OpTypeVoid, 0, _types_and_constants);624break;625case type::t_bool:626assert(info.rows == 1 && info.cols == 1);627type_id = add_instruction(spv::OpTypeBool, 0, _types_and_constants);628break;629case type::t_min16int:630assert(_enable_16bit_types && info.rows == 1 && info.cols == 1);631add_capability(spv::CapabilityInt16);632if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput)633add_capability(spv::CapabilityStorageInputOutput16);634type_id =635add_instruction(spv::OpTypeInt, 0, _types_and_constants)636.add(16) // Width637.add(1); // Signedness638break;639case type::t_int:640assert(info.rows == 1 && info.cols == 1);641type_id =642add_instruction(spv::OpTypeInt, 0, _types_and_constants)643.add(32) // Width644.add(1); // Signedness645break;646case type::t_min16uint:647assert(_enable_16bit_types && info.rows == 1 && info.cols == 1);648add_capability(spv::CapabilityInt16);649if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput)650add_capability(spv::CapabilityStorageInputOutput16);651type_id =652add_instruction(spv::OpTypeInt, 0, _types_and_constants)653.add(16) // Width654.add(0); // Signedness655break;656case type::t_uint:657assert(info.rows == 1 && info.cols == 1);658type_id =659add_instruction(spv::OpTypeInt, 0, _types_and_constants)660.add(32) // Width661.add(0); // Signedness662break;663case type::t_min16float:664assert(_enable_16bit_types && info.rows == 1 && info.cols == 1);665add_capability(spv::CapabilityFloat16);666if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput)667add_capability(spv::CapabilityStorageInputOutput16);668type_id =669add_instruction(spv::OpTypeFloat, 0, _types_and_constants)670.add(16); // Width671break;672case type::t_float:673assert(info.rows == 1 && info.cols == 1);674type_id =675add_instruction(spv::OpTypeFloat, 0, _types_and_constants)676.add(32); // Width677break;678case type::t_struct:679assert(info.rows == 0 && info.cols == 0 && info.struct_definition != 0);680type_id = info.struct_definition;681break;682case type::t_sampler1d_int:683case type::t_sampler1d_uint:684case type::t_sampler1d_float:685add_capability(spv::CapabilitySampled1D);686[[fallthrough]];687case type::t_sampler2d_int:688case type::t_sampler2d_uint:689case type::t_sampler2d_float:690case type::t_sampler3d_int:691case type::t_sampler3d_uint:692case type::t_sampler3d_float:693elem_type_id = convert_image_type(info, format);694type_id =695add_instruction(spv::OpTypeSampledImage, 0, _types_and_constants)696.add(elem_type_id);697break;698case type::t_storage1d_int:699case type::t_storage1d_uint:700case type::t_storage1d_float:701add_capability(spv::CapabilityImage1D);702[[fallthrough]];703case type::t_storage2d_int:704case type::t_storage2d_uint:705case type::t_storage2d_float:706case type::t_storage3d_int:707case type::t_storage3d_uint:708case type::t_storage3d_float:709// No format specified for the storage image710if (format == spv::ImageFormatUnknown)711add_capability(spv::CapabilityStorageImageWriteWithoutFormat);712return convert_image_type(info, format);713default:714assert(false);715return 0;716}717}718719_type_lookup.push_back({ lookup, type_id });720721return type_id;722}723spv::Id convert_type(const function_blocks &info)724{725if (const auto lookup_it = std::find_if(_function_type_lookup.begin(), _function_type_lookup.end(),726[&lookup = info](const std::pair<function_blocks, spv::Id> &lookup_entry) { return lookup_entry.first == lookup; });727lookup_it != _function_type_lookup.end())728return lookup_it->second;729730const spv::Id return_type_id = convert_type(info.return_type);731assert(return_type_id != 0);732733std::vector<spv::Id> param_type_ids;734param_type_ids.reserve(info.param_types.size());735for (const type ¶m_type : info.param_types)736param_type_ids.push_back(convert_type(param_type, true));737738spirv_instruction &inst = add_instruction(spv::OpTypeFunction, 0, _types_and_constants)739.add(return_type_id)740.add(param_type_ids.begin(), param_type_ids.end());741742_function_type_lookup.push_back({ info, inst });743744return inst;745}746spv::Id convert_image_type(type info, spv::ImageFormat format = spv::ImageFormatUnknown)747{748type elem_info = info;749elem_info.rows = 1;750elem_info.cols = 1;751752if (!info.is_numeric())753{754if ((info.is_integral() && info.is_signed()) || (format >= spv::ImageFormatRgba32i && format <= spv::ImageFormatR8i))755elem_info.base = type::t_int;756else if ((info.is_integral() && info.is_unsigned()) || (format >= spv::ImageFormatRgba32ui && format <= spv::ImageFormatR8ui))757elem_info.base = type::t_uint;758else759elem_info.base = type::t_float;760}761762type_lookup lookup { info, false, 0u, { spv::StorageClassUniformConstant, format } };763if (!info.is_storage())764{765lookup.type = elem_info;766lookup.type.base = static_cast<type::datatype>(type::t_texture1d + info.texture_dimension() - 1);767lookup.type.struct_definition = static_cast<uint32_t>(elem_info.base);768}769770if (const auto lookup_it = std::find_if(_type_lookup.begin(), _type_lookup.end(),771[&lookup](const std::pair<type_lookup, spv::Id> &lookup_entry) { return lookup_entry.first == lookup; });772lookup_it != _type_lookup.end())773return lookup_it->second;774775spv::Id type_id, elem_type_id = convert_type(elem_info, false, spv::StorageClassUniformConstant);776type_id =777add_instruction(spv::OpTypeImage, 0, _types_and_constants)778.add(elem_type_id) // Sampled Type (always a scalar type)779.add(spv::Dim1D + info.texture_dimension() - 1)780.add(0) // Not a depth image781.add(0) // Not an array782.add(0) // Not multi-sampled783.add(info.is_storage() ? 2 : 1) // Used with a sampler or as storage784.add(format);785786_type_lookup.push_back({ lookup, type_id });787788return type_id;789}790791uint32_t semantic_to_location(const std::string &semantic, uint32_t max_attributes = 1)792{793if (const auto it = _semantic_to_location.find(semantic);794it != _semantic_to_location.end())795return it->second;796797// Extract the semantic index from the semantic name (e.g. 2 for "TEXCOORD2")798size_t digit_index = semantic.size() - 1;799while (digit_index != 0 && semantic[digit_index] >= '0' && semantic[digit_index] <= '9')800digit_index--;801digit_index++;802803const std::string semantic_base = semantic.substr(0, digit_index);804805uint32_t semantic_digit = 0;806std::from_chars(semantic.c_str() + digit_index, semantic.c_str() + semantic.size(), semantic_digit);807808if (semantic_base == "COLOR" || semantic_base == "SV_TARGET")809return semantic_digit;810811uint32_t location = static_cast<uint32_t>(_semantic_to_location.size());812813// Now create adjoining location indices for all possible semantic indices belonging to this semantic name814for (uint32_t a = 0; a < semantic_digit + max_attributes; ++a)815{816const auto insert = _semantic_to_location.emplace(semantic_base + std::to_string(a), location + a);817if (!insert.second)818{819assert(a == 0 || (insert.first->second - a) == location);820821// Semantic was already created with a different location index, so need to remap to that822location = insert.first->second - a;823}824}825826return location + semantic_digit;827}828829spv::BuiltIn semantic_to_builtin(const std::string &semantic, shader_type stype) const830{831if (semantic == "SV_POSITION")832return stype == shader_type::pixel ? spv::BuiltInFragCoord : spv::BuiltInPosition;833if (semantic == "SV_POINTSIZE")834return spv::BuiltInPointSize;835if (semantic == "SV_DEPTH")836return spv::BuiltInFragDepth;837if (semantic == "SV_VERTEXID")838return _vulkan_semantics ? spv::BuiltInVertexIndex : spv::BuiltInVertexId;839if (semantic == "SV_ISFRONTFACE")840return spv::BuiltInFrontFacing;841if (semantic == "SV_GROUPID")842return spv::BuiltInWorkgroupId;843if (semantic == "SV_GROUPINDEX")844return spv::BuiltInLocalInvocationIndex;845if (semantic == "SV_GROUPTHREADID")846return spv::BuiltInLocalInvocationId;847if (semantic == "SV_DISPATCHTHREADID")848return spv::BuiltInGlobalInvocationId;849return spv::BuiltInMax;850}851spv::ImageFormat format_to_image_format(texture_format format)852{853switch (format)854{855default:856assert(false);857[[fallthrough]];858case texture_format::unknown:859return spv::ImageFormatUnknown;860case texture_format::r8:861add_capability(spv::CapabilityStorageImageExtendedFormats);862return spv::ImageFormatR8;863case texture_format::r16:864add_capability(spv::CapabilityStorageImageExtendedFormats);865return spv::ImageFormatR16;866case texture_format::r16f:867add_capability(spv::CapabilityStorageImageExtendedFormats);868return spv::ImageFormatR16f;869case texture_format::r32i:870return spv::ImageFormatR32i;871case texture_format::r32u:872return spv::ImageFormatR32ui;873case texture_format::r32f:874return spv::ImageFormatR32f;875case texture_format::rg8:876add_capability(spv::CapabilityStorageImageExtendedFormats);877return spv::ImageFormatRg8;878case texture_format::rg16:879add_capability(spv::CapabilityStorageImageExtendedFormats);880return spv::ImageFormatRg16;881case texture_format::rg16f:882add_capability(spv::CapabilityStorageImageExtendedFormats);883return spv::ImageFormatRg16f;884case texture_format::rg32f:885add_capability(spv::CapabilityStorageImageExtendedFormats);886return spv::ImageFormatRg32f;887case texture_format::rgba8:888return spv::ImageFormatRgba8;889case texture_format::rgba16:890add_capability(spv::CapabilityStorageImageExtendedFormats);891return spv::ImageFormatRgba16;892case texture_format::rgba16f:893return spv::ImageFormatRgba16f;894case texture_format::rgba32f:895return spv::ImageFormatRgba32f;896case texture_format::rgb10a2:897add_capability(spv::CapabilityStorageImageExtendedFormats);898return spv::ImageFormatRgb10A2;899}900}901902void add_name(id id, const char *name)903{904if (!_debug_info)905return;906907assert(name != nullptr);908// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpName909add_instruction_without_result(spv::OpName, _debug_b)910.add(id)911.add_string(name);912}913void add_builtin(id id, spv::BuiltIn builtin)914{915add_instruction_without_result(spv::OpDecorate, _annotations)916.add(id)917.add(spv::DecorationBuiltIn)918.add(builtin);919}920void add_decoration(id id, spv::Decoration decoration, std::initializer_list<uint32_t> values = {})921{922// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpDecorate923add_instruction_without_result(spv::OpDecorate, _annotations)924.add(id)925.add(decoration)926.add(values.begin(), values.end());927}928void add_member_name(id id, uint32_t member_index, const char *name)929{930if (!_debug_info)931return;932933assert(name != nullptr);934// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpMemberName935add_instruction_without_result(spv::OpMemberName, _debug_b)936.add(id)937.add(member_index)938.add_string(name);939}940void add_member_builtin(id id, uint32_t member_index, spv::BuiltIn builtin)941{942add_instruction_without_result(spv::OpMemberDecorate, _annotations)943.add(id)944.add(member_index)945.add(spv::DecorationBuiltIn)946.add(builtin);947}948void add_member_decoration(id id, uint32_t member_index, spv::Decoration decoration, std::initializer_list<uint32_t> values = {})949{950// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpMemberDecorate951add_instruction_without_result(spv::OpMemberDecorate, _annotations)952.add(id)953.add(member_index)954.add(decoration)955.add(values.begin(), values.end());956}957void add_capability(spv::Capability capability)958{959_capabilities.insert(capability);960}961962id define_struct(const location &loc, struct_type &info) override963{964// First define all member types to make sure they are declared before the struct type references them965std::vector<spv::Id> member_types;966member_types.reserve(info.member_list.size());967for (const member_type &member : info.member_list)968member_types.push_back(convert_type(member.type));969970// Afterwards define the actual struct type971add_location(loc, _types_and_constants);972973const id res = info.id =974add_instruction(spv::OpTypeStruct, 0, _types_and_constants)975.add(member_types.begin(), member_types.end());976977if (!info.unique_name.empty())978add_name(res, info.unique_name.c_str());979980for (uint32_t index = 0; index < info.member_list.size(); ++index)981{982const member_type &member = info.member_list[index];983984add_member_name(res, index, member.name.c_str());985986if (!_enable_16bit_types && member.type.is_numeric() && member.type.precision() < 32)987add_member_decoration(res, index, spv::DecorationRelaxedPrecision);988}989990_structs.push_back(info);991992return res;993}994id define_texture(const location &, texture &info) override995{996const id res = info.id = make_id(); // Need to create an unique ID here too, so that the symbol lookup for textures works997998_module.textures.push_back(info);9991000return res;1001}1002id define_sampler(const location &loc, const texture &, sampler &info) override1003{1004const id res = info.id = define_variable(loc, info.type, info.unique_name.c_str(), spv::StorageClassUniformConstant);10051006// 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)1007const uint32_t default_binding = static_cast<uint32_t>(_module.samplers.size());1008add_decoration(res, spv::DecorationBinding, { default_binding });1009add_decoration(res, spv::DecorationDescriptorSet, { 1 });10101011_module.samplers.push_back(info);10121013return res;1014}1015id define_storage(const location &loc, const texture &tex_info, storage &info) override1016{1017const id res = info.id = define_variable(loc, info.type, info.unique_name.c_str(), spv::StorageClassUniformConstant, format_to_image_format(tex_info.format));10181019// 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)1020const uint32_t default_binding = static_cast<uint32_t>(_module.storages.size());1021add_decoration(res, spv::DecorationBinding, { default_binding });1022add_decoration(res, spv::DecorationDescriptorSet, { 2 });10231024_module.storages.push_back(info);10251026return res;1027}1028id define_uniform(const location &, uniform &info) override1029{1030if (_uniforms_to_spec_constants && info.has_initializer_value)1031{1032const id res = emit_constant(info.type, info.initializer_value, true);10331034add_name(res, info.name.c_str());10351036const auto add_spec_constant = [this](const spirv_instruction &inst, const uniform &info, const constant &initializer_value, size_t initializer_offset) {1037assert(inst.op == spv::OpSpecConstant || inst.op == spv::OpSpecConstantTrue || inst.op == spv::OpSpecConstantFalse);10381039const uint32_t spec_id = static_cast<uint32_t>(_module.spec_constants.size());1040add_decoration(inst, spv::DecorationSpecId, { spec_id });10411042uniform scalar_info = info;1043scalar_info.type.rows = 1;1044scalar_info.type.cols = 1;1045scalar_info.size = 4;1046scalar_info.offset = static_cast<uint32_t>(initializer_offset);1047scalar_info.initializer_value = {};1048scalar_info.initializer_value.as_uint[0] = initializer_value.as_uint[initializer_offset];10491050_module.spec_constants.push_back(std::move(scalar_info));1051};10521053const spirv_instruction &base_inst = _types_and_constants.instructions.back();1054assert(base_inst == res);10551056// External specialization constants need to be scalars1057if (info.type.is_scalar())1058{1059add_spec_constant(base_inst, info, info.initializer_value, 0);1060}1061else1062{1063assert(base_inst.op == spv::OpSpecConstantComposite);10641065// Add each individual scalar component of the constant as a separate external specialization constant1066for (size_t i = 0; i < (info.type.is_array() ? base_inst.operands.size() : 1); ++i)1067{1068constant initializer_value = info.initializer_value;1069spirv_instruction elem_inst = base_inst;10701071if (info.type.is_array())1072{1073elem_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(),1074[operand_id = base_inst.operands[i]](const spirv_instruction &inst) { return inst == operand_id; });10751076assert(initializer_value.array_data.size() == base_inst.operands.size());1077initializer_value = initializer_value.array_data[i];1078}10791080for (size_t row = 0; row < elem_inst.operands.size(); ++row)1081{1082const spirv_instruction &row_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(),1083[operand_id = elem_inst.operands[row]](const spirv_instruction &inst) { return inst == operand_id; });10841085if (row_inst.op != spv::OpSpecConstantComposite)1086{1087add_spec_constant(row_inst, info, initializer_value, row);1088continue;1089}10901091for (size_t col = 0; col < row_inst.operands.size(); ++col)1092{1093const spirv_instruction &col_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(),1094[operand_id = row_inst.operands[col]](const spirv_instruction &inst) { return inst == operand_id; });10951096add_spec_constant(col_inst, info, initializer_value, row * info.type.cols + col);1097}1098}1099}1100}11011102return res;1103}1104else1105{1106// Create global uniform buffer variable on demand1107if (_global_ubo_type == 0)1108{1109_global_ubo_type = make_id();1110make_id(); // Pointer type for '_global_ubo_type'11111112add_decoration(_global_ubo_type, spv::DecorationBlock);1113}1114if (_global_ubo_variable == 0)1115{1116_global_ubo_variable = make_id();11171118add_decoration(_global_ubo_variable, spv::DecorationDescriptorSet, { 0 });1119add_decoration(_global_ubo_variable, spv::DecorationBinding, { 0 });1120}11211122uint32_t alignment = (info.type.rows == 3 ? 4 : info.type.rows) * 4;1123info.size = info.type.rows * 4;11241125uint32_t array_stride = 16;1126const uint32_t matrix_stride = 16;11271128if (info.type.is_matrix())1129{1130alignment = matrix_stride;1131info.size = info.type.rows * matrix_stride;1132}1133if (info.type.is_array())1134{1135alignment = array_stride;1136array_stride = align_up(info.size, array_stride);1137// Uniform block rules do not permit anything in the padding of an array1138info.size = array_stride * info.type.array_length;1139}11401141info.offset = _module.total_uniform_size;1142info.offset = align_up(info.offset, alignment);1143_module.total_uniform_size = info.offset + info.size;11441145type ubo_type = info.type;1146// Convert boolean uniform variables to integer type so that they have a defined size1147if (info.type.is_boolean())1148ubo_type.base = type::t_uint;11491150const uint32_t member_index = static_cast<uint32_t>(_global_ubo_types.size());11511152// Composite objects in the uniform storage class must be explicitly laid out, which includes array types requiring a stride decoration1153_global_ubo_types.push_back(1154convert_type(ubo_type, false, spv::StorageClassUniform, spv::ImageFormatUnknown, info.type.is_array() ? array_stride : 0u));11551156add_member_name(_global_ubo_type, member_index, info.name.c_str());11571158add_member_decoration(_global_ubo_type, member_index, spv::DecorationOffset, { info.offset });11591160if (info.type.is_matrix())1161{1162// 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)1163// TODO: This technically only works with square matrices1164add_member_decoration(_global_ubo_type, member_index, spv::DecorationColMajor);1165add_member_decoration(_global_ubo_type, member_index, spv::DecorationMatrixStride, { matrix_stride });1166}11671168_module.uniforms.push_back(info);11691170return 0xF0000000 | member_index;1171}1172}1173id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override1174{1175spv::StorageClass storage = spv::StorageClassFunction;1176if (type.has(type::q_groupshared))1177storage = spv::StorageClassWorkgroup;1178else if (global)1179storage = spv::StorageClassPrivate;11801181return define_variable(loc, type, name.c_str(), storage, spv::ImageFormatUnknown, initializer_value);1182}1183id define_variable(const location &loc, const type &type, const char *name, spv::StorageClass storage, spv::ImageFormat format = spv::ImageFormatUnknown, id initializer_value = 0)1184{1185assert(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')));11861187spirv_basic_block &block = (storage != spv::StorageClassFunction) ?1188_variables : _current_function_blocks->variables;11891190add_location(loc, block);11911192// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpVariable1193spirv_instruction &inst = add_instruction(spv::OpVariable, convert_type(type, true, storage, format), block);1194inst.add(storage);11951196const id res = inst.result;11971198if (initializer_value != 0)1199{1200if (storage != spv::StorageClassFunction || /* is_entry_point = */ _current_function->unique_name[0] == 'E')1201{1202// The initializer for variables must be a constant1203inst.add(initializer_value);1204}1205else1206{1207// 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 declaration1208expression variable;1209variable.reset_to_lvalue(loc, res, type);1210emit_store(variable, initializer_value);1211}1212}12131214if (name != nullptr && *name != '\0')1215add_name(res, name);12161217if (!_enable_16bit_types && type.is_numeric() && type.precision() < 32)1218add_decoration(res, spv::DecorationRelaxedPrecision);12191220_storage_lookup[res] = { storage, format };12211222return res;1223}1224id define_function(const location &loc, function &info) override1225{1226assert(!is_in_function());12271228function_blocks &func = _functions_blocks.emplace_back();1229func.return_type = info.return_type;12301231for (const member_type ¶m : info.parameter_list)1232func.param_types.push_back(param.type);12331234add_location(loc, func.declaration);12351236// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpFunction1237const id res = info.id =1238add_instruction(spv::OpFunction, convert_type(info.return_type), func.declaration)1239.add(spv::FunctionControlMaskNone)1240.add(convert_type(func));12411242if (!info.name.empty())1243add_name(res, info.name.c_str());12441245for (member_type ¶m : info.parameter_list)1246{1247add_location(param.location, func.declaration);12481249param.id = add_instruction(spv::OpFunctionParameter, convert_type(param.type, true), func.declaration);12501251add_name(param.id, param.name.c_str());1252}12531254_functions.push_back(std::make_unique<function>(info));1255_current_function = _functions.back().get();1256_current_function_blocks = &func;12571258return res;1259}12601261void define_entry_point(function &func) override1262{1263assert(!func.unique_name.empty() && func.unique_name[0] == 'F');1264func.unique_name[0] = 'E';12651266// Modify entry point name so each thread configuration is made separate1267if (func.type == shader_type::compute)1268func.unique_name +=1269'_' + std::to_string(func.num_threads[0]) +1270'_' + std::to_string(func.num_threads[1]) +1271'_' + std::to_string(func.num_threads[2]);12721273if (std::find_if(_module.entry_points.begin(), _module.entry_points.end(),1274[&func](const std::pair<std::string, shader_type> &entry_point) {1275return entry_point.first == func.unique_name;1276}) != _module.entry_points.end())1277return;12781279_module.entry_points.emplace_back(func.unique_name, func.type);12801281spv::Id position_variable = 0;1282spv::Id point_size_variable = 0;1283std::vector<spv::Id> inputs_and_outputs;1284std::vector<expression> call_params;12851286// Generate the glue entry point function1287function entry_point = func;1288entry_point.referenced_functions.push_back(func.id);12891290// Change function signature to 'void main()'1291entry_point.return_type = { type::t_void };1292entry_point.return_semantic.clear();1293entry_point.parameter_list.clear();12941295const id entry_point_definition = define_function({}, entry_point);1296enter_block(create_block());12971298const auto create_varying_param = [this, &call_params](const member_type ¶m) {1299// Initialize all output variables with zero1300const spv::Id variable = define_variable({}, param.type, nullptr, spv::StorageClassFunction, spv::ImageFormatUnknown, emit_constant(param.type, 0u));13011302expression &call_param = call_params.emplace_back();1303call_param.reset_to_lvalue({}, variable, param.type);13041305return variable;1306};13071308const 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) {1309const spv::Id variable = define_variable({}, param_type, nullptr, storage);13101311if (const spv::BuiltIn builtin = semantic_to_builtin(semantic, stype);1312builtin != spv::BuiltInMax)1313{1314assert(a == 0); // Built-in variables cannot be arrays13151316add_builtin(variable, builtin);13171318if (builtin == spv::BuiltInPosition && storage == spv::StorageClassOutput)1319position_variable = variable;1320if (builtin == spv::BuiltInPointSize && storage == spv::StorageClassOutput)1321point_size_variable = variable;1322}1323else1324{1325assert(stype != shader_type::compute); // Compute shaders cannot have custom inputs or outputs13261327const uint32_t location = semantic_to_location(semantic, std::max(1u, param_type.array_length));1328add_decoration(variable, spv::DecorationLocation, { location + a });1329}13301331if (param_type.has(type::q_noperspective))1332add_decoration(variable, spv::DecorationNoPerspective);1333if (param_type.has(type::q_centroid))1334add_decoration(variable, spv::DecorationCentroid);1335if (param_type.has(type::q_nointerpolation))1336add_decoration(variable, spv::DecorationFlat);13371338inputs_and_outputs.push_back(variable);1339return variable;1340};13411342// Translate function parameters to input/output variables1343for (const member_type ¶m : func.parameter_list)1344{1345spv::Id param_var = create_varying_param(param);13461347// Create separate input/output variables for "inout" parameters1348if (param.type.has(type::q_in))1349{1350spv::Id param_value = 0;13511352// Flatten structure parameters1353if (param.type.is_struct())1354{1355const struct_type &struct_definition = get_struct(param.type.struct_definition);13561357type struct_type = param.type;1358const auto array_length = std::max(1u, param.type.array_length);1359struct_type.array_length = 0;13601361// Struct arrays need to be flattened into individual elements as well1362std::vector<spv::Id> array_element_ids;1363array_element_ids.reserve(array_length);1364for (unsigned int a = 0; a < array_length; a++)1365{1366std::vector<spv::Id> struct_element_ids;1367struct_element_ids.reserve(struct_definition.member_list.size());1368for (const member_type &member : struct_definition.member_list)1369{1370const spv::Id input_var = create_varying_variable(member.type, member.semantic, spv::StorageClassInput, a);13711372param_value =1373add_instruction(spv::OpLoad, convert_type(member.type))1374.add(input_var);1375struct_element_ids.push_back(param_value);1376}13771378param_value =1379add_instruction(spv::OpCompositeConstruct, convert_type(struct_type))1380.add(struct_element_ids.begin(), struct_element_ids.end());1381array_element_ids.push_back(param_value);1382}13831384if (param.type.is_array())1385{1386// Build the array from all constructed struct elements1387param_value =1388add_instruction(spv::OpCompositeConstruct, convert_type(param.type))1389.add(array_element_ids.begin(), array_element_ids.end());1390}1391}1392else1393{1394const spv::Id input_var = create_varying_variable(param.type, param.semantic, spv::StorageClassInput);13951396param_value =1397add_instruction(spv::OpLoad, convert_type(param.type))1398.add(input_var);1399}14001401add_instruction_without_result(spv::OpStore)1402.add(param_var)1403.add(param_value);1404}14051406if (param.type.has(type::q_out))1407{1408if (param.type.is_struct())1409{1410const struct_type &struct_definition = get_struct(param.type.struct_definition);14111412for (unsigned int a = 0, array_length = std::max(1u, param.type.array_length); a < array_length; a++)1413{1414for (const member_type &member : struct_definition.member_list)1415{1416create_varying_variable(member.type, member.semantic, spv::StorageClassOutput, a);1417}1418}1419}1420else1421{1422create_varying_variable(param.type, param.semantic, spv::StorageClassOutput);1423}1424}1425}14261427const id call_result = emit_call({}, func.id, func.return_type, call_params);14281429for (size_t i = 0, inputs_and_outputs_index = 0; i < func.parameter_list.size(); ++i)1430{1431const member_type ¶m = func.parameter_list[i];14321433if (param.type.has(type::q_out))1434{1435const spv::Id value =1436add_instruction(spv::OpLoad, convert_type(param.type))1437.add(call_params[i].base);14381439if (param.type.is_struct())1440{1441const struct_type &struct_definition = get_struct(param.type.struct_definition);14421443type struct_type = param.type;1444const auto array_length = std::max(1u, param.type.array_length);1445struct_type.array_length = 0;14461447// Skip input variables if this is an "inout" parameter1448if (param.type.has(type::q_in))1449inputs_and_outputs_index += struct_definition.member_list.size() * array_length;14501451// Split up struct array into individual struct elements again1452for (unsigned int a = 0; a < array_length; a++)1453{1454spv::Id element_value = value;1455if (param.type.is_array())1456{1457element_value =1458add_instruction(spv::OpCompositeExtract, convert_type(struct_type))1459.add(value)1460.add(a);1461}14621463// Split out struct fields into separate output variables again1464for (uint32_t member_index = 0; member_index < struct_definition.member_list.size(); ++member_index)1465{1466const spv::Id member_value =1467add_instruction(spv::OpCompositeExtract, convert_type(struct_definition.member_list[member_index].type))1468.add(element_value)1469.add(member_index);14701471add_instruction_without_result(spv::OpStore)1472.add(inputs_and_outputs[inputs_and_outputs_index++])1473.add(member_value);1474}1475}1476}1477else1478{1479// Skip input variable if this is an "inout" parameter (see loop above)1480if (param.type.has(type::q_in))1481inputs_and_outputs_index += 1;14821483add_instruction_without_result(spv::OpStore)1484.add(inputs_and_outputs[inputs_and_outputs_index++])1485.add(value);1486}1487}1488else1489{1490// Input parameters do not need to store anything, but increase the input/output variable index1491if (param.type.is_struct())1492{1493const struct_type &struct_definition = get_struct(param.type.struct_definition);1494inputs_and_outputs_index += struct_definition.member_list.size() * std::max(1u, param.type.array_length);1495}1496else1497{1498inputs_and_outputs_index += 1;1499}1500}1501}15021503if (func.return_type.is_struct())1504{1505const struct_type &struct_definition = get_struct(func.return_type.struct_definition);15061507for (uint32_t member_index = 0; member_index < struct_definition.member_list.size(); ++member_index)1508{1509const member_type &member = struct_definition.member_list[member_index];15101511const spv::Id result_var = create_varying_variable(member.type, member.semantic, spv::StorageClassOutput);15121513const spv::Id member_result =1514add_instruction(spv::OpCompositeExtract, convert_type(member.type))1515.add(call_result)1516.add(member_index);15171518add_instruction_without_result(spv::OpStore)1519.add(result_var)1520.add(member_result);1521}1522}1523else if (!func.return_type.is_void())1524{1525const spv::Id result_var = create_varying_variable(func.return_type, func.return_semantic, spv::StorageClassOutput);15261527add_instruction_without_result(spv::OpStore)1528.add(result_var)1529.add(call_result);1530}15311532// Add code to flip the output vertically1533if (_flip_vert_y && position_variable != 0 && func.type == shader_type::vertex)1534{1535expression position;1536position.reset_to_lvalue({}, position_variable, { type::t_float, 4, 1 });1537position.add_constant_index_access(1); // Y component15381539// gl_Position.y = -gl_Position.y1540emit_store(position,1541emit_unary_op({}, tokenid::minus, { type::t_float, 1, 1 },1542emit_load(position, false)));1543}15441545#if 01546// Disabled because it breaks on MacOS/Metal - point size should not be defined for a non-point primitive.1547// Add code that sets the point size to a default value (in case this vertex shader is used with point primitives)1548if (point_size_variable == 0 && func.type == shader_type::vertex)1549{1550create_varying_variable({ type::t_float, 1, 1 }, "SV_POINTSIZE", spv::StorageClassOutput);15511552expression point_size;1553point_size.reset_to_lvalue({}, point_size_variable, { type::t_float, 1, 1 });15541555// gl_PointSize = 1.01556emit_store(point_size, emit_constant({ type::t_float, 1, 1 }, 1));1557}1558#endif15591560leave_block_and_return(0);1561leave_function();15621563spv::ExecutionModel model;1564switch (func.type)1565{1566case shader_type::vertex:1567model = spv::ExecutionModelVertex;1568break;1569case shader_type::pixel:1570model = spv::ExecutionModelFragment;1571add_instruction_without_result(spv::OpExecutionMode, _execution_modes)1572.add(entry_point_definition)1573.add(_vulkan_semantics ? spv::ExecutionModeOriginUpperLeft : spv::ExecutionModeOriginLowerLeft);1574break;1575case shader_type::compute:1576model = spv::ExecutionModelGLCompute;1577add_instruction_without_result(spv::OpExecutionMode, _execution_modes)1578.add(entry_point_definition)1579.add(spv::ExecutionModeLocalSize)1580.add(func.num_threads[0])1581.add(func.num_threads[1])1582.add(func.num_threads[2]);1583break;1584default:1585assert(false);1586return;1587}15881589add_instruction_without_result(spv::OpEntryPoint, _entries)1590.add(model)1591.add(entry_point_definition)1592.add_string(func.unique_name.c_str())1593.add(inputs_and_outputs.begin(), inputs_and_outputs.end());1594}15951596id emit_load(const expression &exp, bool) override1597{1598if (exp.is_constant) // Constant expressions do not have a complex access chain1599return emit_constant(exp.type, exp.constant);16001601size_t i = 0;1602spv::Id result = exp.base;1603type base_type = exp.type;1604bool is_uniform_bool = false;16051606if (exp.is_lvalue || !exp.chain.empty())1607add_location(exp.location, *_current_block_data);16081609// If a variable is referenced, load the value first1610if (exp.is_lvalue && _spec_constants.find(exp.base) == _spec_constants.end())1611{1612if (!exp.chain.empty())1613base_type = exp.chain[0].from;16141615std::pair<spv::StorageClass, spv::ImageFormat> storage = { spv::StorageClassFunction, spv::ImageFormatUnknown };1616if (const auto it = _storage_lookup.find(exp.base);1617it != _storage_lookup.end())1618storage = it->second;16191620spirv_instruction *access_chain = nullptr;16211622// Check if this is a uniform variable (see 'define_uniform' function above) and dereference it1623if (result & 0xF0000000)1624{1625const uint32_t member_index = result ^ 0xF0000000;16261627storage.first = spv::StorageClassUniform;1628is_uniform_bool = base_type.is_boolean();16291630if (is_uniform_bool)1631base_type.base = type::t_uint;16321633access_chain = &add_instruction(spv::OpAccessChain)1634.add(_global_ubo_variable)1635.add(emit_constant(member_index));1636}16371638// Any indexing expressions can be resolved during load with an 'OpAccessChain' already1639if (!exp.chain.empty() && (1640exp.chain[0].op == expression::operation::op_member ||1641exp.chain[0].op == expression::operation::op_dynamic_index ||1642exp.chain[0].op == expression::operation::op_constant_index))1643{1644// Ensure that 'access_chain' cannot get invalidated by calls to 'emit_constant' or 'convert_type'1645assert(_current_block_data != &_types_and_constants);16461647// Use access chain from uniform if possible, otherwise create new one1648if (access_chain == nullptr) access_chain =1649&add_instruction(spv::OpAccessChain).add(result); // Base16501651// Ignore first index into 1xN matrices, since they were translated to a vector type in SPIR-V1652if (exp.chain[0].from.rows == 1 && exp.chain[0].from.cols > 1)1653i = 1;16541655for (; i < exp.chain.size() && (1656exp.chain[i].op == expression::operation::op_member ||1657exp.chain[i].op == expression::operation::op_dynamic_index ||1658exp.chain[i].op == expression::operation::op_constant_index); ++i)1659access_chain->add(exp.chain[i].op == expression::operation::op_dynamic_index ?1660exp.chain[i].index :1661emit_constant(exp.chain[i].index)); // Indexes16621663base_type = exp.chain[i - 1].to;1664access_chain->type = convert_type(base_type, true, storage.first, storage.second); // Last type is the result1665result = access_chain->result;1666}1667else if (access_chain != nullptr)1668{1669access_chain->type = convert_type(base_type, true, storage.first, storage.second, base_type.is_array() ? 16u : 0u);1670result = access_chain->result;1671}16721673result =1674add_instruction(spv::OpLoad, convert_type(base_type, false, spv::StorageClassFunction, storage.second))1675.add(result); // Pointer1676}16771678// Need to convert boolean uniforms which are actually integers in SPIR-V1679if (is_uniform_bool)1680{1681base_type.base = type::t_bool;16821683result =1684add_instruction(spv::OpINotEqual, convert_type(base_type))1685.add(result)1686.add(emit_constant(0));1687}16881689// Work through all remaining operations in the access chain and apply them to the value1690for (; i < exp.chain.size(); ++i)1691{1692assert(result != 0);1693const expression::operation &op = exp.chain[i];16941695switch (op.op)1696{1697case expression::operation::op_cast:1698if (op.from.is_scalar() && !op.to.is_scalar())1699{1700type cast_type = op.to;1701cast_type.base = op.from.base;17021703std::vector<expression> args;1704args.reserve(op.to.components());1705for (unsigned int c = 0; c < op.to.components(); ++c)1706args.emplace_back().reset_to_rvalue(exp.location, result, op.from);17071708result = emit_construct(exp.location, cast_type, args);1709}17101711if (op.from.is_boolean())1712{1713const spv::Id true_constant = emit_constant(op.to, 1);1714const spv::Id false_constant = emit_constant(op.to, 0);17151716result =1717add_instruction(spv::OpSelect, convert_type(op.to))1718.add(result) // Condition1719.add(true_constant)1720.add(false_constant);1721}1722else1723{1724spv::Op spv_op = spv::OpNop;1725switch (op.to.base)1726{1727case type::t_bool:1728if (op.from.is_floating_point())1729spv_op = spv::OpFOrdNotEqual;1730else1731spv_op = spv::OpINotEqual;1732// Add instruction to compare value against zero instead of casting1733result =1734add_instruction(spv_op, convert_type(op.to))1735.add(result)1736.add(emit_constant(op.from, 0));1737continue;1738case type::t_min16int:1739case type::t_int:1740if (op.from.is_floating_point())1741spv_op = spv::OpConvertFToS;1742else if (op.from.precision() == op.to.precision())1743spv_op = spv::OpBitcast;1744else if (_enable_16bit_types)1745spv_op = spv::OpSConvert;1746else1747continue; // Do not have to add conversion instruction between min16int/int if 16-bit types are not enabled1748break;1749case type::t_min16uint:1750case type::t_uint:1751if (op.from.is_floating_point())1752spv_op = spv::OpConvertFToU;1753else if (op.from.precision() == op.to.precision())1754spv_op = spv::OpBitcast;1755else if (_enable_16bit_types)1756spv_op = spv::OpUConvert;1757else1758continue;1759break;1760case type::t_min16float:1761case type::t_float:1762if (op.from.is_floating_point() && !_enable_16bit_types)1763continue; // Do not have to add conversion instruction between min16float/float if 16-bit types are not enabled1764else if (op.from.is_floating_point())1765spv_op = spv::OpFConvert;1766else if (op.from.is_signed())1767spv_op = spv::OpConvertSToF;1768else1769spv_op = spv::OpConvertUToF;1770break;1771default:1772assert(false);1773}17741775result =1776add_instruction(spv_op, convert_type(op.to))1777.add(result);1778}1779break;1780case expression::operation::op_dynamic_index:1781assert(op.from.is_vector() && op.to.is_scalar());1782result =1783add_instruction(spv::OpVectorExtractDynamic, convert_type(op.to))1784.add(result) // Vector1785.add(op.index); // Index1786break;1787case expression::operation::op_member: // In case of struct return values, which are r-values1788case expression::operation::op_constant_index:1789assert(op.from.is_vector() || op.from.is_matrix() || op.from.is_struct());1790result =1791add_instruction(spv::OpCompositeExtract, convert_type(op.to))1792.add(result)1793.add(op.index); // Literal Index1794break;1795case expression::operation::op_swizzle:1796if (op.to.is_vector())1797{1798if (op.from.is_matrix())1799{1800spv::Id components[4];1801for (int c = 0; c < 4 && op.swizzle[c] >= 0; ++c)1802{1803const unsigned int row = op.swizzle[c] / 4;1804const unsigned int column = op.swizzle[c] - row * 4;18051806type scalar_type = op.to;1807scalar_type.rows = 1;1808scalar_type.cols = 1;18091810spirv_instruction &inst = add_instruction(spv::OpCompositeExtract, convert_type(scalar_type));1811inst.add(result);1812if (op.from.rows > 1) // Matrix types with a single row are actually vectors, so they don't need the extra index1813inst.add(row);1814inst.add(column);18151816components[c] = inst;1817}18181819spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(op.to));1820for (int c = 0; c < 4 && op.swizzle[c] >= 0; ++c)1821inst.add(components[c]);1822result = inst;1823}1824else if (op.from.is_vector())1825{1826spirv_instruction &inst = add_instruction(spv::OpVectorShuffle, convert_type(op.to));1827inst.add(result); // Vector 11828inst.add(result); // Vector 21829for (int c = 0; c < 4 && op.swizzle[c] >= 0; ++c)1830inst.add(op.swizzle[c]);1831result = inst;1832}1833else1834{1835spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(op.to));1836for (unsigned int c = 0; c < op.to.rows; ++c)1837inst.add(result);1838result = inst;1839}1840break;1841}1842else if (op.from.is_matrix() && op.to.is_scalar())1843{1844assert(op.swizzle[1] < 0);18451846spirv_instruction &inst = add_instruction(spv::OpCompositeExtract, convert_type(op.to));1847inst.add(result); // Composite1848if (op.from.rows > 1)1849{1850const unsigned int row = op.swizzle[0] / 4;1851const unsigned int column = op.swizzle[0] - row * 4;1852inst.add(row);1853inst.add(column);1854}1855else1856{1857inst.add(op.swizzle[0]);1858}1859result = inst;1860break;1861}1862else1863{1864assert(false);1865break;1866}1867}1868}18691870return result;1871}1872void emit_store(const expression &exp, id value) override1873{1874assert(value != 0 && exp.is_lvalue && !exp.is_constant && !exp.type.is_sampler());18751876add_location(exp.location, *_current_block_data);18771878size_t i = 0;1879// Any indexing expressions can be resolved with an 'OpAccessChain' already1880spv::Id target = emit_access_chain(exp, i);1881type base_type = exp.chain.empty() ? exp.type : i == 0 ? exp.chain[0].from : exp.chain[i - 1].to;18821883// TODO: Complex access chains like float4x4[0].m00m10[0] = 0;1884// Work through all remaining operations in the access chain and apply them to the value1885for (; i < exp.chain.size(); ++i)1886{1887const expression::operation &op = exp.chain[i];1888switch (op.op)1889{1890case expression::operation::op_cast:1891case expression::operation::op_member:1892// These should have been handled above already (and casting does not make sense for a store operation)1893break;1894case expression::operation::op_dynamic_index:1895case expression::operation::op_constant_index:1896assert(false);1897break;1898case expression::operation::op_swizzle:1899{1900spv::Id result =1901add_instruction(spv::OpLoad, convert_type(base_type))1902.add(target); // Pointer19031904if (base_type.is_vector())1905{1906spirv_instruction &inst = add_instruction(spv::OpVectorShuffle, convert_type(base_type));1907inst.add(result); // Vector 11908inst.add(value); // Vector 219091910unsigned int shuffle[4] = { 0, 1, 2, 3 };1911for (unsigned int c = 0; c < base_type.rows; ++c)1912if (op.swizzle[c] >= 0)1913shuffle[op.swizzle[c]] = base_type.rows + c;1914for (unsigned int c = 0; c < base_type.rows; ++c)1915inst.add(shuffle[c]);19161917value = inst;1918}1919else if (op.to.is_scalar())1920{1921assert(op.swizzle[1] < 0);19221923spirv_instruction &inst = add_instruction(spv::OpCompositeInsert, convert_type(base_type));1924inst.add(value); // Object1925inst.add(result); // Composite19261927if (op.from.is_matrix() && op.from.rows > 1)1928{1929const unsigned int row = op.swizzle[0] / 4;1930const unsigned int column = op.swizzle[0] - row * 4;1931inst.add(row);1932inst.add(column);1933}1934else1935{1936inst.add(op.swizzle[0]);1937}19381939value = inst;1940}1941else1942{1943// TODO: Implement matrix to vector swizzles1944assert(false);1945}1946break;1947}1948}1949}19501951add_instruction_without_result(spv::OpStore)1952.add(target)1953.add(value);1954}1955id emit_access_chain(const expression &exp, size_t &i) override1956{1957// This function cannot create access chains for uniform variables1958assert((exp.base & 0xF0000000) == 0);19591960i = 0;1961if (exp.chain.empty() || (1962exp.chain[0].op != expression::operation::op_member &&1963exp.chain[0].op != expression::operation::op_dynamic_index &&1964exp.chain[0].op != expression::operation::op_constant_index))1965return exp.base;19661967std::pair<spv::StorageClass, spv::ImageFormat> storage = { spv::StorageClassFunction, spv::ImageFormatUnknown };1968if (const auto it = _storage_lookup.find(exp.base);1969it != _storage_lookup.end())1970storage = it->second;19711972// Ensure that 'access_chain' cannot get invalidated by calls to 'emit_constant' or 'convert_type'1973assert(_current_block_data != &_types_and_constants);19741975spirv_instruction *access_chain =1976&add_instruction(spv::OpAccessChain).add(exp.base); // Base19771978// Ignore first index into 1xN matrices, since they were translated to a vector type in SPIR-V1979if (exp.chain[0].from.rows == 1 && exp.chain[0].from.cols > 1)1980i = 1;19811982for (; i < exp.chain.size() && (1983exp.chain[i].op == expression::operation::op_member ||1984exp.chain[i].op == expression::operation::op_dynamic_index ||1985exp.chain[i].op == expression::operation::op_constant_index); ++i)1986access_chain->add(exp.chain[i].op == expression::operation::op_dynamic_index ?1987exp.chain[i].index :1988emit_constant(exp.chain[i].index)); // Indexes19891990access_chain->type = convert_type(exp.chain[i - 1].to, true, storage.first, storage.second); // Last type is the result1991return access_chain->result;1992}19931994using codegen::emit_constant;1995id emit_constant(uint32_t value)1996{1997return emit_constant({ type::t_uint, 1, 1 }, value);1998}1999id emit_constant(const type &data_type, const constant &data) override2000{2001return emit_constant(data_type, data, false);2002}2003id emit_constant(const type &data_type, const constant &data, bool spec_constant)2004{2005if (!spec_constant) // Specialization constants cannot reuse other constants2006{2007if (const auto it = std::find_if(_constant_lookup.begin(), _constant_lookup.end(),2008[&data_type, &data](std::tuple<type, constant, spv::Id> &x) {2009if (!(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()))2010return false;2011for (size_t i = 0; i < data.array_data.size(); ++i)2012if (std::memcmp(&std::get<1>(x).array_data[i].as_uint[0], &data.array_data[i].as_uint[0], sizeof(uint32_t) * 16) != 0)2013return false;2014return true;2015});2016it != _constant_lookup.end())2017return std::get<2>(*it); // Reuse existing constant instead of duplicating the definition2018}20192020spv::Id result;2021if (data_type.is_array())2022{2023assert(data_type.is_bounded_array()); // Unbounded arrays cannot be constants20242025type elem_type = data_type;2026elem_type.array_length = 0;20272028std::vector<spv::Id> elements;2029elements.reserve(data_type.array_length);20302031// Fill up elements with constant array data2032for (const constant &elem : data.array_data)2033elements.push_back(emit_constant(elem_type, elem, spec_constant));2034// Fill up any remaining elements with a default value (when the array data did not specify them)2035for (size_t i = elements.size(); i < static_cast<size_t>(data_type.array_length); ++i)2036elements.push_back(emit_constant(elem_type, {}, spec_constant));20372038result =2039add_instruction(spec_constant ? spv::OpSpecConstantComposite : spv::OpConstantComposite, convert_type(data_type), _types_and_constants)2040.add(elements.begin(), elements.end());2041}2042else if (data_type.is_struct())2043{2044assert(!spec_constant); // Structures cannot be specialization constants20452046result = add_instruction(spv::OpConstantNull, convert_type(data_type), _types_and_constants);2047}2048else if (data_type.is_vector() || data_type.is_matrix())2049{2050type elem_type = data_type;2051elem_type.rows = data_type.cols;2052elem_type.cols = 1;20532054spv::Id rows[4] = {};20552056// Construct matrix constant out of row vector constants2057// Construct vector constant out of scalar constants for each element2058for (unsigned int i = 0; i < data_type.rows; ++i)2059{2060constant row_data = {};2061for (unsigned int k = 0; k < data_type.cols; ++k)2062row_data.as_uint[k] = data.as_uint[i * data_type.cols + k];20632064rows[i] = emit_constant(elem_type, row_data, spec_constant);2065}20662067if (data_type.rows == 1)2068{2069result = rows[0];2070}2071else2072{2073spirv_instruction &inst = add_instruction(spec_constant ? spv::OpSpecConstantComposite : spv::OpConstantComposite, convert_type(data_type), _types_and_constants);2074for (unsigned int i = 0; i < data_type.rows; ++i)2075inst.add(rows[i]);2076result = inst;2077}2078}2079else if (data_type.is_boolean())2080{2081result = add_instruction(data.as_uint[0] ?2082(spec_constant ? spv::OpSpecConstantTrue : spv::OpConstantTrue) :2083(spec_constant ? spv::OpSpecConstantFalse : spv::OpConstantFalse), convert_type(data_type), _types_and_constants);2084}2085else2086{2087assert(data_type.is_scalar());20882089result =2090add_instruction(spec_constant ? spv::OpSpecConstant : spv::OpConstant, convert_type(data_type), _types_and_constants)2091.add(data.as_uint[0]);2092}20932094if (spec_constant) // Keep track of all specialization constants2095_spec_constants.insert(result);2096else2097_constant_lookup.push_back({ data_type, data, result });20982099return result;2100}21012102id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override2103{2104spv::Op spv_op = spv::OpNop;21052106switch (op)2107{2108case tokenid::minus:2109spv_op = res_type.is_floating_point() ? spv::OpFNegate : spv::OpSNegate;2110break;2111case tokenid::tilde:2112spv_op = spv::OpNot;2113break;2114case tokenid::exclaim:2115spv_op = spv::OpLogicalNot;2116break;2117default:2118return assert(false), 0;2119}21202121add_location(loc, *_current_block_data);21222123spirv_instruction &inst = add_instruction(spv_op, convert_type(res_type));2124inst.add(val); // Operand21252126return inst;2127}2128id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &exp_type, id lhs, id rhs) override2129{2130spv::Op spv_op = spv::OpNop;21312132switch (op)2133{2134case tokenid::plus:2135case tokenid::plus_plus:2136case tokenid::plus_equal:2137spv_op = exp_type.is_floating_point() ? spv::OpFAdd : spv::OpIAdd;2138break;2139case tokenid::minus:2140case tokenid::minus_minus:2141case tokenid::minus_equal:2142spv_op = exp_type.is_floating_point() ? spv::OpFSub : spv::OpISub;2143break;2144case tokenid::star:2145case tokenid::star_equal:2146spv_op = exp_type.is_floating_point() ? spv::OpFMul : spv::OpIMul;2147break;2148case tokenid::slash:2149case tokenid::slash_equal:2150spv_op = exp_type.is_floating_point() ? spv::OpFDiv : exp_type.is_signed() ? spv::OpSDiv : spv::OpUDiv;2151break;2152case tokenid::percent:2153case tokenid::percent_equal:2154spv_op = exp_type.is_floating_point() ? spv::OpFRem : exp_type.is_signed() ? spv::OpSRem : spv::OpUMod;2155break;2156case tokenid::caret:2157case tokenid::caret_equal:2158spv_op = spv::OpBitwiseXor;2159break;2160case tokenid::pipe:2161case tokenid::pipe_equal:2162spv_op = spv::OpBitwiseOr;2163break;2164case tokenid::ampersand:2165case tokenid::ampersand_equal:2166spv_op = spv::OpBitwiseAnd;2167break;2168case tokenid::less_less:2169case tokenid::less_less_equal:2170spv_op = spv::OpShiftLeftLogical;2171break;2172case tokenid::greater_greater:2173case tokenid::greater_greater_equal:2174spv_op = exp_type.is_signed() ? spv::OpShiftRightArithmetic : spv::OpShiftRightLogical;2175break;2176case tokenid::pipe_pipe:2177spv_op = spv::OpLogicalOr;2178break;2179case tokenid::ampersand_ampersand:2180spv_op = spv::OpLogicalAnd;2181break;2182case tokenid::less:2183spv_op = exp_type.is_floating_point() ? spv::OpFOrdLessThan :2184exp_type.is_signed() ? spv::OpSLessThan : spv::OpULessThan;2185break;2186case tokenid::less_equal:2187spv_op = exp_type.is_floating_point() ? spv::OpFOrdLessThanEqual :2188exp_type.is_signed() ? spv::OpSLessThanEqual : spv::OpULessThanEqual;2189break;2190case tokenid::greater:2191spv_op = exp_type.is_floating_point() ? spv::OpFOrdGreaterThan :2192exp_type.is_signed() ? spv::OpSGreaterThan : spv::OpUGreaterThan;2193break;2194case tokenid::greater_equal:2195spv_op = exp_type.is_floating_point() ? spv::OpFOrdGreaterThanEqual :2196exp_type.is_signed() ? spv::OpSGreaterThanEqual : spv::OpUGreaterThanEqual;2197break;2198case tokenid::equal_equal:2199spv_op = exp_type.is_floating_point() ? spv::OpFOrdEqual :2200exp_type.is_boolean() ? spv::OpLogicalEqual : spv::OpIEqual;2201break;2202case tokenid::exclaim_equal:2203spv_op = exp_type.is_floating_point() ? spv::OpFOrdNotEqual :2204exp_type.is_boolean() ? spv::OpLogicalNotEqual : spv::OpINotEqual;2205break;2206default:2207return assert(false), 0;2208}22092210add_location(loc, *_current_block_data);22112212// Binary operators generally only work on scalars and vectors in SPIR-V, so need to apply them to matrices component-wise2213if (exp_type.is_matrix() && exp_type.rows != 1)2214{2215std::vector<spv::Id> ids;2216ids.reserve(exp_type.cols);22172218type vector_type = exp_type;2219vector_type.rows = exp_type.cols;2220vector_type.cols = 1;22212222for (unsigned int row = 0; row < exp_type.rows; ++row)2223{2224const spv::Id lhs_elem = add_instruction(spv::OpCompositeExtract, convert_type(vector_type))2225.add(lhs)2226.add(row);2227const spv::Id rhs_elem = add_instruction(spv::OpCompositeExtract, convert_type(vector_type))2228.add(rhs)2229.add(row);22302231spirv_instruction &inst = add_instruction(spv_op, convert_type(vector_type));2232inst.add(lhs_elem); // Operand 12233inst.add(rhs_elem); // Operand 222342235if (res_type.has(type::q_precise))2236add_decoration(inst, spv::DecorationNoContraction);2237if (!_enable_16bit_types && res_type.precision() < 32)2238add_decoration(inst, spv::DecorationRelaxedPrecision);22392240ids.push_back(inst);2241}22422243spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(res_type));2244inst.add(ids.begin(), ids.end());22452246return inst;2247}2248else2249{2250spirv_instruction &inst = add_instruction(spv_op, convert_type(res_type));2251inst.add(lhs); // Operand 12252inst.add(rhs); // Operand 222532254if (res_type.has(type::q_precise))2255add_decoration(inst, spv::DecorationNoContraction);2256if (!_enable_16bit_types && res_type.precision() < 32)2257add_decoration(inst, spv::DecorationRelaxedPrecision);22582259return inst;2260}2261}2262id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override2263{2264if (op != tokenid::question)2265return assert(false), 0;22662267add_location(loc, *_current_block_data);22682269spirv_instruction &inst = add_instruction(spv::OpSelect, convert_type(res_type));2270inst.add(condition); // Condition2271inst.add(true_value); // Object 12272inst.add(false_value); // Object 222732274return inst;2275}2276id emit_call(const location &loc, id function, const type &res_type, const std::vector<expression> &args) override2277{2278#ifndef NDEBUG2279for (const expression &arg : args)2280assert(arg.chain.empty() && arg.base != 0);2281#endif2282add_location(loc, *_current_block_data);22832284// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpFunctionCall2285spirv_instruction &inst = add_instruction(spv::OpFunctionCall, convert_type(res_type));2286inst.add(function); // Function2287for (const expression &arg : args)2288inst.add(arg.base); // Arguments22892290return inst;2291}2292id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector<expression> &args) override2293{2294#ifndef NDEBUG2295for (const expression &arg : args)2296assert(arg.chain.empty() && arg.base != 0);2297#endif2298add_location(loc, *_current_block_data);22992300enum2301{2302#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) name##i,2303#include "effect_symbol_table_intrinsics.inl"2304};23052306switch (intrinsic)2307{2308#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) case name##i: code2309#include "effect_symbol_table_intrinsics.inl"2310default:2311return assert(false), 0;2312}2313}2314id emit_construct(const location &loc, const type &res_type, const std::vector<expression> &args) override2315{2316#ifndef NDEBUG2317for (const expression &arg : args)2318assert((arg.type.is_scalar() || res_type.is_array()) && arg.chain.empty() && arg.base != 0);2319#endif2320add_location(loc, *_current_block_data);23212322std::vector<spv::Id> ids;2323ids.reserve(args.size());23242325// There must be exactly one constituent for each top-level component of the result2326if (res_type.is_matrix())2327{2328type vector_type = res_type;2329vector_type.rows = res_type.cols;2330vector_type.cols = 1;23312332// Turn the list of scalar arguments into a list of column vectors2333for (size_t arg = 0; arg < args.size(); arg += vector_type.rows)2334{2335spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(vector_type));2336for (unsigned row = 0; row < vector_type.rows; ++row)2337inst.add(args[arg + row].base);23382339ids.push_back(inst);2340}2341}2342else2343{2344assert(res_type.is_vector() || res_type.is_array());23452346// The exception is that for constructing a vector, a contiguous subset of the scalars consumed can be represented by a vector operand instead2347for (const expression &arg : args)2348ids.push_back(arg.base);2349}23502351spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(res_type));2352inst.add(ids.begin(), ids.end());23532354return inst;2355}23562357void emit_if(const location &loc, id, id condition_block, id true_statement_block, id false_statement_block, unsigned int selection_control) override2358{2359spirv_instruction merge_label = _current_block_data->instructions.back();2360assert(merge_label.op == spv::OpLabel);2361_current_block_data->instructions.pop_back();23622363// Add previous block containing the condition value first2364_current_block_data->append(_block_data[condition_block]);23652366spirv_instruction branch_inst = _current_block_data->instructions.back();2367assert(branch_inst.op == spv::OpBranchConditional);2368_current_block_data->instructions.pop_back();23692370// Add structured control flow instruction2371add_location(loc, *_current_block_data);2372add_instruction_without_result(spv::OpSelectionMerge)2373.add(merge_label)2374.add(selection_control & 0x3); // 'SelectionControl' happens to match the flags produced by the parser23752376// Append all blocks belonging to the branch2377_current_block_data->instructions.push_back(branch_inst);2378_current_block_data->append(_block_data[true_statement_block]);2379_current_block_data->append(_block_data[false_statement_block]);23802381_current_block_data->instructions.push_back(merge_label);2382}2383id 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) override2384{2385spirv_instruction merge_label = _current_block_data->instructions.back();2386assert(merge_label.op == spv::OpLabel);2387_current_block_data->instructions.pop_back();23882389// Add previous block containing the condition value first2390_current_block_data->append(_block_data[condition_block]);23912392if (true_statement_block != condition_block)2393_current_block_data->append(_block_data[true_statement_block]);2394if (false_statement_block != condition_block)2395_current_block_data->append(_block_data[false_statement_block]);23962397_current_block_data->instructions.push_back(merge_label);23982399add_location(loc, *_current_block_data);24002401// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpPhi2402spirv_instruction &inst = add_instruction(spv::OpPhi, convert_type(res_type))2403.add(true_value) // Variable 02404.add(true_statement_block) // Parent 02405.add(false_value) // Variable 12406.add(false_statement_block); // Parent 124072408return inst;2409}2410void 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) override2411{2412spirv_instruction merge_label = _current_block_data->instructions.back();2413assert(merge_label.op == spv::OpLabel);2414_current_block_data->instructions.pop_back();24152416// Add previous block first2417_current_block_data->append(_block_data[prev_block]);24182419// Fill header block2420assert(_block_data[header_block].instructions.size() == 2);2421_current_block_data->instructions.push_back(_block_data[header_block].instructions[0]);2422assert(_current_block_data->instructions.back().op == spv::OpLabel);24232424// Add structured control flow instruction2425add_location(loc, *_current_block_data);2426add_instruction_without_result(spv::OpLoopMerge)2427.add(merge_label)2428.add(continue_block)2429.add(loop_control & 0x3); // 'LoopControl' happens to match the flags produced by the parser24302431_current_block_data->instructions.push_back(_block_data[header_block].instructions[1]);2432assert(_current_block_data->instructions.back().op == spv::OpBranch);24332434// Add condition block if it exists2435if (condition_block != 0)2436_current_block_data->append(_block_data[condition_block]);24372438// Append loop body block before continue block2439_current_block_data->append(_block_data[loop_block]);2440_current_block_data->append(_block_data[continue_block]);24412442_current_block_data->instructions.push_back(merge_label);2443}2444void 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) override2445{2446assert(case_blocks.size() == case_literal_and_labels.size() / 2);24472448spirv_instruction merge_label = _current_block_data->instructions.back();2449assert(merge_label.op == spv::OpLabel);2450_current_block_data->instructions.pop_back();24512452// Add previous block containing the selector value first2453_current_block_data->append(_block_data[selector_block]);24542455spirv_instruction switch_inst = _current_block_data->instructions.back();2456assert(switch_inst.op == spv::OpSwitch);2457_current_block_data->instructions.pop_back();24582459// Add structured control flow instruction2460add_location(loc, *_current_block_data);2461add_instruction_without_result(spv::OpSelectionMerge)2462.add(merge_label)2463.add(selection_control & 0x3); // 'SelectionControl' happens to match the flags produced by the parser24642465// Update switch instruction to contain all case labels2466switch_inst.operands[1] = default_label;2467switch_inst.add(case_literal_and_labels.begin(), case_literal_and_labels.end());24682469// Append all blocks belonging to the switch2470_current_block_data->instructions.push_back(switch_inst);24712472std::vector<id> blocks = case_blocks;2473if (default_label != merge_label)2474blocks.push_back(default_block);2475// Eliminate duplicates (because of multiple case labels pointing to the same block)2476std::sort(blocks.begin(), blocks.end());2477blocks.erase(std::unique(blocks.begin(), blocks.end()), blocks.end());2478for (const id case_block : blocks)2479_current_block_data->append(_block_data[case_block]);24802481_current_block_data->instructions.push_back(merge_label);2482}24832484bool is_in_function() const { return _current_function_blocks != nullptr; }24852486id set_block(id id) override2487{2488_last_block = _current_block;2489_current_block = id;2490_current_block_data = &_block_data[id];24912492return _last_block;2493}2494void enter_block(id id) override2495{2496assert(id != 0);2497// Can only use labels inside functions and should never be in another basic block if creating a new one2498assert(is_in_function() && !is_in_block());24992500set_block(id);25012502add_instruction_without_result(spv::OpLabel).result = id;2503}2504id leave_block_and_kill() override2505{2506assert(is_in_function()); // Can only discard inside functions25072508if (!is_in_block())2509return 0;25102511add_instruction_without_result(spv::OpKill);25122513return set_block(0);2514}2515id leave_block_and_return(id value) override2516{2517assert(is_in_function()); // Can only return from inside functions25182519if (!is_in_block()) // Might already have left the last block in which case this has to be ignored2520return 0;25212522if (_current_function_blocks->return_type.is_void())2523{2524add_instruction_without_result(spv::OpReturn);2525}2526else2527{2528if (0 == value) // The implicit return statement needs this2529value = add_instruction(spv::OpUndef, convert_type(_current_function_blocks->return_type), _types_and_constants);25302531add_instruction_without_result(spv::OpReturnValue)2532.add(value);2533}25342535return set_block(0);2536}2537id leave_block_and_switch(id value, id default_target) override2538{2539assert(value != 0 && default_target != 0);2540assert(is_in_function()); // Can only switch inside functions25412542if (!is_in_block())2543return _last_block;25442545add_instruction_without_result(spv::OpSwitch)2546.add(value)2547.add(default_target);25482549return set_block(0);2550}2551id leave_block_and_branch(id target, unsigned int) override2552{2553assert(target != 0);2554assert(is_in_function()); // Can only branch inside functions25552556if (!is_in_block())2557return _last_block;25582559add_instruction_without_result(spv::OpBranch)2560.add(target);25612562return set_block(0);2563}2564id leave_block_and_branch_conditional(id condition, id true_target, id false_target) override2565{2566assert(condition != 0 && true_target != 0 && false_target != 0);2567assert(is_in_function()); // Can only branch inside functions25682569if (!is_in_block())2570return _last_block;25712572add_instruction_without_result(spv::OpBranchConditional)2573.add(condition)2574.add(true_target)2575.add(false_target);25762577return set_block(0);2578}2579void leave_function() override2580{2581assert(is_in_function()); // Can only leave if there was a function to begin with25822583_current_function_blocks->definition = _block_data[_last_block];25842585// Append function end instruction2586add_instruction_without_result(spv::OpFunctionEnd, _current_function_blocks->definition);25872588_current_function = nullptr;2589_current_function_blocks = nullptr;2590}2591};25922593codegen *reshadefx::create_codegen_spirv(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y)2594{2595return new codegen_spirv(vulkan_semantics, debug_info, uniforms_to_spec_constants, enable_16bit_types, flip_vert_y);2596}259725982599