Path: blob/master/dep/reshadefx/src/effect_codegen_glsl.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 <cmath> // std::isinf, std::isnan, std::signbit8#include <cassert>9#include <cstring> // std::memcmp10#include <charconv> // std::from_chars, std::to_chars11#include <algorithm> // std::find, std::find_if, std::max12#include <locale>13#include <sstream>14#include <iomanip>15#include <unordered_set>1617using namespace reshadefx;1819namespace {2021inline char to_digit(unsigned int value)22{23assert(value < 10);24return '0' + static_cast<char>(value);25}2627inline uint32_t align_up(uint32_t size, uint32_t alignment)28{29alignment -= 1;30return ((size + alignment) & ~alignment);31}3233class codegen_glsl final : public codegen34{35public:36codegen_glsl(unsigned version, bool gles, bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y) :37_glsl_version(version),38_gles(gles),39_debug_info(debug_info),40_vulkan_semantics(vulkan_semantics),41_uniforms_to_spec_constants(uniforms_to_spec_constants),42_enable_16bit_types(enable_16bit_types),43_flip_vert_y(flip_vert_y)44{45// Create default block and reserve a memory block to avoid frequent reallocations46std::string &block = _blocks.emplace(0, std::string()).first->second;47block.reserve(8192);48}4950private:51enum class naming52{53// After escaping, name should already be unique, so no additional steps are taken54unique,55// After escaping, will be numbered when clashing with another name56general,57// This is a special name that is not modified and should be unique58reserved,59// Replace name with a code snippet60expression,61};6263unsigned _glsl_version = 0;64bool _gles = false;65bool _debug_info = false;66bool _vulkan_semantics = false;67bool _uniforms_to_spec_constants = false;68bool _enable_16bit_types = false;69bool _flip_vert_y = false;7071std::unordered_map<id, std::string> _names;72std::unordered_map<id, std::string> _blocks;73std::string _ubo_block;74std::string _compute_block;75std::string _current_function_declaration;7677std::unordered_map<id, id> _remapped_sampler_variables;78std::unordered_map<std::string, uint32_t> _semantic_to_location;79std::vector<std::tuple<type, constant, id>> _constant_lookup;8081// Only write compatibility intrinsics to result if they are actually in use82bool _uses_fmod = false;83bool _uses_componentwise_or = false;84bool _uses_componentwise_and = false;85bool _uses_componentwise_cond = false;86bool _uses_control_flow_attributes = false;8788std::string finalize_preamble() const89{90std::string preamble = "#version " + std::to_string(_glsl_version) + (_gles ? " es\n" : ((_glsl_version >= 330) ? " core\n" : "\n"));9192if (_gles)93preamble += "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\n";9495if (_enable_16bit_types)96// GL_NV_gpu_shader5, GL_AMD_gpu_shader_half_float or GL_EXT_shader_16bit_storage97preamble += "#extension GL_NV_gpu_shader5 : require\n";98if (_uses_control_flow_attributes)99preamble += "#extension GL_EXT_control_flow_attributes : enable\n";100101if (_uses_fmod)102preamble += "float fmodHLSL(float x, float y) { return x - y * trunc(x / y); }\n"103"vec2 fmodHLSL(vec2 x, vec2 y) { return x - y * trunc(x / y); }\n"104"vec3 fmodHLSL(vec3 x, vec3 y) { return x - y * trunc(x / y); }\n"105"vec4 fmodHLSL(vec4 x, vec4 y) { return x - y * trunc(x / y); }\n"106"mat2 fmodHLSL(mat2 x, mat2 y) { return x - matrixCompMult(y, mat2(trunc(x[0] / y[0]), trunc(x[1] / y[1]))); }\n"107"mat3 fmodHLSL(mat3 x, mat3 y) { return x - matrixCompMult(y, mat3(trunc(x[0] / y[0]), trunc(x[1] / y[1]), trunc(x[2] / y[2]))); }\n"108"mat4 fmodHLSL(mat4 x, mat4 y) { return x - matrixCompMult(y, mat4(trunc(x[0] / y[0]), trunc(x[1] / y[1]), trunc(x[2] / y[2]), trunc(x[3] / y[3]))); }\n";109if (_uses_componentwise_or)110preamble +=111"bvec2 compOr(bvec2 a, bvec2 b) { return bvec2(a.x || b.x, a.y || b.y); }\n"112"bvec3 compOr(bvec3 a, bvec3 b) { return bvec3(a.x || b.x, a.y || b.y, a.z || b.z); }\n"113"bvec4 compOr(bvec4 a, bvec4 b) { return bvec4(a.x || b.x, a.y || b.y, a.z || b.z, a.w || b.w); }\n";114if (_uses_componentwise_and)115preamble +=116"bvec2 compAnd(bvec2 a, bvec2 b) { return bvec2(a.x && b.x, a.y && b.y); }\n"117"bvec3 compAnd(bvec3 a, bvec3 b) { return bvec3(a.x && b.x, a.y && b.y, a.z && b.z); }\n"118"bvec4 compAnd(bvec4 a, bvec4 b) { return bvec4(a.x && b.x, a.y && b.y, a.z && b.z, a.w && b.w); }\n";119if (_uses_componentwise_cond)120preamble +=121"vec2 compCond(bvec2 cond, vec2 a, vec2 b) { return vec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n"122"vec3 compCond(bvec3 cond, vec3 a, vec3 b) { return vec3(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z); }\n"123"vec4 compCond(bvec4 cond, vec4 a, vec4 b) { return vec4(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z, cond.w ? a.w : b.w); }\n"124"bvec2 compCond(bvec2 cond, bvec2 a, bvec2 b) { return bvec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n"125"bvec3 compCond(bvec3 cond, bvec3 a, bvec3 b) { return bvec3(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z); }\n"126"bvec4 compCond(bvec4 cond, bvec4 a, bvec4 b) { return bvec4(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z, cond.w ? a.w : b.w); }\n"127"ivec2 compCond(bvec2 cond, ivec2 a, ivec2 b) { return ivec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n"128"ivec3 compCond(bvec3 cond, ivec3 a, ivec3 b) { return ivec3(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z); }\n"129"ivec4 compCond(bvec4 cond, ivec4 a, ivec4 b) { return ivec4(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z, cond.w ? a.w : b.w); }\n"130"uvec2 compCond(bvec2 cond, uvec2 a, uvec2 b) { return uvec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n"131"uvec3 compCond(bvec3 cond, uvec3 a, uvec3 b) { return uvec3(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z); }\n"132"uvec4 compCond(bvec4 cond, uvec4 a, uvec4 b) { return uvec4(cond.x ? a.x : b.x, cond.y ? a.y : b.y, cond.z ? a.z : b.z, cond.w ? a.w : b.w); }\n";133134if (!_ubo_block.empty())135{136if (_vulkan_semantics)137{138preamble += "layout(std140, set = 0, binding = 0) uniform _Globals {\n" + _ubo_block + "};\n";139}140else141{142preamble += "layout(std140, binding = 0) uniform _Globals {\n" + _ubo_block + "};\n";143}144}145146return preamble;147}148149std::string finalize_code() const override150{151std::string code = finalize_preamble();152153// Add sampler definitions154for (const sampler &info : _module.samplers)155code += _blocks.at(info.id);156157// Add storage definitions158for (const storage &info : _module.storages)159code += _blocks.at(info.id);160161// Add global definitions (struct types, global variables, ...)162code += _blocks.at(0);163164// Add function definitions165for (const std::unique_ptr<function> &func : _functions)166{167const bool is_entry_point = func->unique_name[0] == 'E';168if (is_entry_point)169code += "#ifdef " + func->unique_name + '\n';170171code += _blocks.at(func->id);172173if (is_entry_point)174code += "#endif\n";175}176177return code;178}179std::string finalize_code_for_entry_point(const std::string &entry_point_name) const override180{181const auto entry_point_it = std::find_if(_functions.begin(), _functions.end(),182[&entry_point_name](const std::unique_ptr<function> &func) {183return func->unique_name == entry_point_name;184});185if (entry_point_it == _functions.end())186return {};187const function &entry_point = *entry_point_it->get();188189std::string code = finalize_preamble();190191if (entry_point.type != shader_type::pixel)192code +=193// OpenGL does not allow using 'discard' in the vertex shader profile194"#define discard\n"195// 'dFdx', 'dFdx' and 'fwidth' too are only available in fragment shaders196"#define dFdx(x) x\n"197"#define dFdy(y) y\n"198"#define fwidth(p) p\n";199200if (entry_point.type != shader_type::compute)201code +=202// OpenGL does not allow using 'shared' in vertex/fragment shader profile203"#define shared\n"204"#define atomicAdd(a, b) a\n"205"#define atomicAnd(a, b) a\n"206"#define atomicOr(a, b) a\n"207"#define atomicXor(a, b) a\n"208"#define atomicMin(a, b) a\n"209"#define atomicMax(a, b) a\n"210"#define atomicExchange(a, b) a\n"211"#define atomicCompSwap(a, b, c) a\n"212// Barrier intrinsics are only available in compute shaders213"#define barrier()\n"214"#define memoryBarrier()\n"215"#define groupMemoryBarrier()\n";216217const auto replace_binding =218[](std::string &code, uint32_t binding) {219const size_t beg = code.find("layout(binding = ") + 17;220const size_t end = code.find_first_of("),", beg);221code.replace(beg, end - beg, std::to_string(binding));222};223224// Add referenced sampler definitions225for (uint32_t binding = 0; binding < entry_point.referenced_samplers.size(); ++binding)226{227if (entry_point.referenced_samplers[binding] == 0)228continue;229230std::string block_code = _blocks.at(entry_point.referenced_samplers[binding]);231replace_binding(block_code, binding);232code += block_code;233}234235// Add referenced storage definitions236for (uint32_t binding = 0; binding < entry_point.referenced_storages.size(); ++binding)237{238if (entry_point.referenced_storages[binding] == 0)239continue;240241std::string block_code = _blocks.at(entry_point.referenced_storages[binding]);242replace_binding(block_code, binding);243code += block_code;244}245246// Add global definitions (struct types, global variables, ...)247code += _blocks.at(0);248249// Add referenced function definitions250for (const std::unique_ptr<function> &func : _functions)251{252if (func->id != entry_point.id &&253std::find(entry_point.referenced_functions.begin(), entry_point.referenced_functions.end(), func->id) == entry_point.referenced_functions.end())254continue;255256code += _blocks.at(func->id);257}258259return code;260}261262template <bool is_param = false, bool is_decl = true, bool is_interface = false>263void write_type(std::string &s, const type &type) const264{265if constexpr (is_decl)266{267// Global variables are implicitly 'static' in GLSL, so the keyword does not exist268if (type.has(type::q_precise))269s += "precise ";270if (type.has(type::q_groupshared))271s += "shared ";272}273274if constexpr (is_interface)275{276if (type.has(type::q_linear))277s += "smooth ";278if (type.has(type::q_noperspective))279s += "noperspective ";280if (type.has(type::q_centroid))281s += "centroid ";282if (type.has(type::q_nointerpolation))283s += "flat ";284}285286if constexpr (is_interface || is_param)287{288if (type.has(type::q_inout))289s += "inout ";290else if (type.has(type::q_in))291s += "in ";292else if (type.has(type::q_out))293s += "out ";294}295296switch (type.base)297{298case type::t_void:299s += "void";300break;301case type::t_bool:302if (type.cols > 1)303s += "mat", s += to_digit(type.rows), s += 'x', s += to_digit(type.cols);304else if (type.rows > 1)305s += "bvec", s += to_digit(type.rows);306else307s += "bool";308break;309case type::t_min16int:310if (_enable_16bit_types)311{312assert(type.cols == 1);313if (type.rows > 1)314s += "i16vec", s += to_digit(type.rows);315else316s += "int16_t";317break;318}319else if constexpr (is_decl)320s += "mediump ";321[[fallthrough]];322case type::t_int:323if (type.cols > 1)324s += "mat", s += to_digit(type.rows), s += 'x', s += to_digit(type.cols);325else if (type.rows > 1)326s += "ivec", s += to_digit(type.rows);327else328s += "int";329break;330case type::t_min16uint:331if (_enable_16bit_types)332{333assert(type.cols == 1);334if (type.rows > 1)335s += "u16vec", s += to_digit(type.rows);336else337s += "uint16_t";338break;339}340else if constexpr (is_decl)341s += "mediump ";342[[fallthrough]];343case type::t_uint:344if (type.cols > 1)345s += "mat", s += to_digit(type.rows), s += 'x', s += to_digit(type.cols);346else if (type.rows > 1)347s += "uvec", s += to_digit(type.rows);348else349s += "uint";350break;351case type::t_min16float:352if (_enable_16bit_types)353{354assert(type.cols == 1);355if (type.rows > 1)356s += "f16vec", s += to_digit(type.rows);357else358s += "float16_t";359break;360}361else if constexpr (is_decl)362s += "mediump ";363[[fallthrough]];364case type::t_float:365if (type.cols > 1)366s += "mat", s += to_digit(type.rows), s += 'x', s += to_digit(type.cols);367else if (type.rows > 1)368s += "vec", s += to_digit(type.rows);369else370s += "float";371break;372case type::t_struct:373s += id_to_name(type.struct_definition);374break;375case type::t_sampler1d_int:376s += "isampler1D";377break;378case type::t_sampler2d_int:379s += "isampler2D";380break;381case type::t_sampler3d_int:382s += "isampler3D";383break;384case type::t_sampler1d_uint:385s += "usampler1D";386break;387case type::t_sampler3d_uint:388s += "usampler3D";389break;390case type::t_sampler2d_uint:391s += "usampler2D";392break;393case type::t_sampler1d_float:394s += "sampler1D";395break;396case type::t_sampler2d_float:397s += "sampler2D";398break;399case type::t_sampler3d_float:400s += "sampler3D";401break;402case type::t_storage1d_int:403if constexpr (is_param)404s += "writeonly ";405s += "iimage1D";406break;407case type::t_storage2d_int:408if constexpr (is_param)409s += "writeonly ";410s += "iimage2D";411break;412case type::t_storage3d_int:413if constexpr (is_param)414s += "writeonly ";415s += "iimage3D";416break;417case type::t_storage1d_uint:418if constexpr (is_param)419s += "writeonly ";420s += "uimage1D";421break;422case type::t_storage2d_uint:423if constexpr (is_param)424s += "writeonly ";425s += "uimage2D";426break;427case type::t_storage3d_uint:428if constexpr (is_param)429s += "writeonly ";430s += "uimage3D";431break;432case type::t_storage1d_float:433if constexpr (is_param)434s += "writeonly ";435s += "image1D";436break;437case type::t_storage2d_float:438if constexpr (is_param) // Images need a format to be readable, but declaring that on function parameters is not well supported, so can only support write-only images there439s += "writeonly ";440s += "image2D";441break;442case type::t_storage3d_float:443if constexpr (is_param)444s += "writeonly ";445s += "image3D";446break;447default:448assert(false);449}450}451void write_constant(std::string &s, const type &data_type, const constant &data) const452{453if (data_type.is_array())454{455assert(data_type.is_bounded_array());456457type elem_type = data_type;458elem_type.array_length = 0;459460write_type<false, false>(s, elem_type);461s += '[' + std::to_string(data_type.array_length) + "](";462463for (unsigned int a = 0; a < data_type.array_length; ++a)464{465write_constant(s, elem_type, a < static_cast<unsigned int>(data.array_data.size()) ? data.array_data[a] : constant {});466s += ", ";467}468469// Remove trailing ", "470s.erase(s.size() - 2);471472s += ')';473return;474}475476// There can only be numeric constants477assert(data_type.is_numeric());478479if (!data_type.is_scalar())480write_type<false, false>(s, data_type), s += '(';481482for (unsigned int i = 0; i < data_type.components(); ++i)483{484switch (data_type.base)485{486case type::t_bool:487s += data.as_uint[i] ? "true" : "false";488break;489case type::t_min16int:490case type::t_int:491s += std::to_string(data.as_int[i]);492break;493case type::t_min16uint:494case type::t_uint:495s += std::to_string(data.as_uint[i]) + 'u';496break;497case type::t_min16float:498case type::t_float:499if (std::isnan(data.as_float[i])) {500s += "0.0/0.0/*nan*/";501break;502}503if (std::isinf(data.as_float[i])) {504s += std::signbit(data.as_float[i]) ? "1.0/0.0/*inf*/" : "-1.0/0.0/*-inf*/";505break;506}507508{509#ifdef _MSC_VER510char temp[64];511const std::to_chars_result res = std::to_chars(temp, temp + sizeof(temp), data.as_float[i], std::chars_format::scientific, 8);512if (res.ec == std::errc())513s.append(temp, res.ptr);514else515assert(false);516#else517std::ostringstream ss;518ss.imbue(std::locale::classic());519ss << std::scientific << data.as_float[i];520s += ss.str();521#endif522}523break;524default:525assert(false);526}527528s += ", ";529}530531// Remove trailing ", "532s.erase(s.size() - 2);533534if (!data_type.is_scalar())535s += ')';536}537void write_location(std::string &s, const location &loc) const538{539if (loc.source.empty() || !_debug_info)540return;541542s += "#line " + std::to_string(loc.line) + '\n';543}544void write_texture_format(std::string &s, texture_format format)545{546switch (format)547{548case texture_format::r8:549s += "r8";550break;551case texture_format::r16:552s += "r16";553break;554case texture_format::r16f:555s += "r16f";556break;557case texture_format::r32i:558s += "r32i";559break;560case texture_format::r32u:561s += "r32ui";562break;563case texture_format::r32f:564s += "r32f";565break;566case texture_format::rg8:567s += "rg8";568break;569case texture_format::rg16:570s += "rg16";571break;572case texture_format::rg16f:573s += "rg16f";574break;575case texture_format::rg32f:576s += "rg32f";577break;578case texture_format::rgba8:579s += "rgba8";580break;581case texture_format::rgba16:582s += "rgba16";583break;584case texture_format::rgba16f:585s += "rgba16f";586break;587case texture_format::rgba32f:588s += "rgba32f";589break;590case texture_format::rgb10a2:591s += "rgb10_a2";592break;593default:594assert(false);595}596}597598std::string id_to_name(id id) const599{600if (const auto it = _remapped_sampler_variables.find(id);601it != _remapped_sampler_variables.end())602id = it->second;603604assert(id != 0);605if (const auto names_it = _names.find(id);606names_it != _names.end())607return names_it->second;608return '_' + std::to_string(id);609}610611template <naming naming_type = naming::general>612void define_name(const id id, std::string name)613{614assert(!name.empty());615if constexpr (naming_type != naming::expression)616if (name[0] == '_')617return; // Filter out names that may clash with automatic ones618if constexpr (naming_type != naming::reserved)619name = escape_name(std::move(name));620if constexpr (naming_type == naming::general)621if (std::find_if(_names.begin(), _names.end(),622[&name](const auto &names_it) { return names_it.second == name; }) != _names.end())623name += '_' + std::to_string(id); // Append a numbered suffix if the name already exists624_names[id] = std::move(name);625}626627uint32_t semantic_to_location(const std::string &semantic, uint32_t max_attributes = 1)628{629if (const auto location_it = _semantic_to_location.find(semantic);630location_it != _semantic_to_location.end())631return location_it->second;632633// Extract the semantic index from the semantic name (e.g. 2 for "TEXCOORD2")634size_t digit_index = semantic.size() - 1;635while (digit_index != 0 && semantic[digit_index] >= '0' && semantic[digit_index] <= '9')636digit_index--;637digit_index++;638639const std::string semantic_base = semantic.substr(0, digit_index);640641uint32_t semantic_digit = 0;642std::from_chars(semantic.c_str() + digit_index, semantic.c_str() + semantic.size(), semantic_digit);643644if (semantic_base == "COLOR" || semantic_base == "SV_TARGET")645return semantic_digit;646647uint32_t location = static_cast<uint32_t>(_semantic_to_location.size());648649// Now create adjoining location indices for all possible semantic indices belonging to this semantic name650for (uint32_t a = 0; a < semantic_digit + max_attributes; ++a)651{652const auto insert = _semantic_to_location.emplace(semantic_base + std::to_string(a), location + a);653if (!insert.second)654{655assert(a == 0 || (insert.first->second - a) == location);656657// Semantic was already created with a different location index, so need to remap to that658location = insert.first->second - a;659}660}661662return location + semantic_digit;663}664665std::string escape_name(std::string name) const666{667static const std::unordered_set<std::string> s_reserverd_names = {668"common", "partition", "input", "output", "active", "filter", "superp", "invariant",669"attribute", "varying", "buffer", "resource", "coherent", "readonly", "writeonly",670"layout", "flat", "smooth", "lowp", "mediump", "highp", "precision", "patch", "subroutine",671"atomic_uint", "fixed",672"vec2", "vec3", "vec4", "ivec2", "dvec2", "dvec3", "dvec4", "ivec3", "ivec4", "uvec2", "uvec3", "uvec4", "bvec2", "bvec3", "bvec4", "fvec2", "fvec3", "fvec4", "hvec2", "hvec3", "hvec4",673"mat2", "mat3", "mat4", "dmat2", "dmat3", "dmat4", "mat2x2", "mat2x3", "mat2x4", "dmat2x2", "dmat2x3", "dmat2x4", "mat3x2", "mat3x3", "mat3x4", "dmat3x2", "dmat3x3", "dmat3x4", "mat4x2", "mat4x3", "mat4x4", "dmat4x2", "dmat4x3", "dmat4x4",674"sampler1DShadow", "sampler1DArrayShadow", "isampler1D", "isampler1DArray", "usampler1D", "usampler1DArray",675"sampler2DShadow", "sampler2DArrayShadow", "isampler2D", "isampler2DArray", "usampler2D", "usampler2DArray", "sampler2DRect", "sampler2DRectShadow", "isampler2DRect", "usampler2DRect", "isampler2DMS", "usampler2DMS", "isampler2DMSArray", "usampler2DMSArray",676"isampler3D", "usampler3D", "sampler3DRect",677"samplerCubeShadow", "samplerCubeArrayShadow", "isamplerCube", "isamplerCubeArray", "usamplerCube", "usamplerCubeArray",678"samplerBuffer", "isamplerBuffer", "usamplerBuffer",679"image1D", "iimage1D", "uimage1D", "image1DArray", "iimage1DArray", "uimage1DArray",680"image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", "image2DRect", "iimage2DRect", "uimage2DRect", "image2DMS", "iimage2DMS", "uimage2DMS", "image2DMSArray", "iimage2DMSArray", "uimage2DMSArray",681"image3D", "iimage3D", "uimage3D",682"imageCube", "iimageCube", "uimageCube", "imageCubeArray", "iimageCubeArray", "uimageCubeArray",683"imageBuffer", "iimageBuffer", "uimageBuffer",684"abs", "sign", "all", "any", "sin", "sinh", "cos", "cosh", "tan", "tanh", "asin", "acos", "atan",685"exp", "exp2", "log", "log2", "sqrt", "inversesqrt", "ceil", "floor", "fract", "trunc", "round",686"radians", "degrees", "length", "normalize", "transpose", "determinant", "intBitsToFloat", "uintBitsToFloat",687"floatBitsToInt", "floatBitsToUint", "matrixCompMult", "not", "lessThan", "greaterThan", "lessThanEqual",688"greaterThanEqual", "equal", "notEqual", "dot", "cross", "distance", "pow", "modf", "frexp", "ldexp",689"min", "max", "step", "reflect", "texture", "textureOffset", "fma", "mix", "clamp", "smoothstep", "refract",690"faceforward", "textureLod", "textureLodOffset", "texelFetch", "main"691};692693// Escape reserved names so that they do not fail to compile694if (name.compare(0, 3, "gl_") == 0 || s_reserverd_names.count(name))695// Append an underscore at start instead of the end, since another one may get added in 'define_name' when there is a suffix696// This is guaranteed to not clash with user defined names, since those starting with an underscore are filtered out in 'define_name'697name = '_' + name;698699// Remove duplicated underscore symbols from name which can occur due to namespaces but are not allowed in GLSL700for (size_t pos = 0; (pos = name.find("__", pos)) != std::string::npos;)701name.replace(pos, 2, "_x");702703return name;704}705std::string semantic_to_builtin(std::string name, const std::string &semantic, shader_type stype) const706{707if (semantic == "SV_POSITION")708return stype == shader_type::pixel ? "gl_FragCoord" : "gl_Position";709if (semantic == "SV_POINTSIZE")710return "gl_PointSize";711if (semantic == "SV_DEPTH")712return "gl_FragDepth";713if (semantic == "SV_VERTEXID")714return _vulkan_semantics ? "gl_VertexIndex" : "gl_VertexID";715if (semantic == "SV_ISFRONTFACE")716return "gl_FrontFacing";717if (semantic == "SV_GROUPID")718return "gl_WorkGroupID";719if (semantic == "SV_GROUPINDEX")720return "gl_LocalInvocationIndex";721if (semantic == "SV_GROUPTHREADID")722return "gl_LocalInvocationID";723if (semantic == "SV_DISPATCHTHREADID")724return "gl_GlobalInvocationID";725726return escape_name(std::move(name));727}728729static void increase_indentation_level(std::string &block)730{731if (block.empty())732return;733734for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3)735block.replace(pos, 2, "\n\t\t");736737block.insert(block.begin(), '\t');738}739740id define_struct(const location &loc, struct_type &info) override741{742const id res = info.id = make_id();743define_name<naming::unique>(res, info.unique_name);744745_structs.push_back(info);746747std::string &code = _blocks.at(_current_block);748749write_location(code, loc);750751code += "struct " + id_to_name(res) + "\n{\n";752753for (const member_type &member : info.member_list)754{755code += '\t';756write_type(code, member.type); // GLSL does not allow interpolation attributes on struct members757code += ' ';758code += escape_name(member.name);759if (member.type.is_array())760code += '[' + std::to_string(member.type.array_length) + ']';761code += ";\n";762}763764if (info.member_list.empty())765code += "float _dummy;\n";766767code += "};\n";768769return res;770}771id define_texture(const location &, texture &info) override772{773const id res = info.id = make_id();774775_module.textures.push_back(info);776777return res;778}779id define_sampler(const location &loc, const texture &, sampler &info) override780{781const id res = info.id = create_block();782define_name<naming::unique>(res, info.unique_name);783784std::string &code = _blocks.at(res);785786write_location(code, loc);787788// 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)789const uint32_t default_binding = static_cast<uint32_t>(_module.samplers.size());790791code += "layout(binding = " + std::to_string(default_binding);792if (_vulkan_semantics)793code += ", set = 1";794code += ") uniform ";795write_type(code, info.type);796code += ' ' + id_to_name(res) + ";\n";797798_module.samplers.push_back(info);799800return res;801}802id define_storage(const location &loc, const texture &tex_info, storage &info) override803{804const id res = info.id = create_block();805define_name<naming::unique>(res, info.unique_name);806807std::string &code = _blocks.at(res);808809write_location(code, loc);810811// 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)812const uint32_t default_binding = static_cast<uint32_t>(_module.storages.size());813814code += "layout(binding = " + std::to_string(default_binding) + ", ";815write_texture_format(code, tex_info.format);816code += ") uniform ";817write_type(code, info.type);818code += ' ' + id_to_name(res) + ";\n";819820_module.storages.push_back(info);821822return res;823}824id define_uniform(const location &loc, uniform &info) override825{826const id res = make_id();827define_name<naming::unique>(res, info.name);828829if (_uniforms_to_spec_constants && info.has_initializer_value)830{831info.size = info.type.components() * 4;832if (info.type.is_array())833info.size *= info.type.array_length;834835std::string &code = _blocks.at(_current_block);836837write_location(code, loc);838839assert(!info.type.has(type::q_static) && !info.type.has(type::q_const));840841if (!_gles)842code += "const ";843write_type(code, info.type);844code += ' ' + id_to_name(res) + " = ";845if (!info.type.is_scalar())846write_type<false, false>(code, info.type);847code += "(SPEC_CONSTANT_" + info.name + ");\n";848849_module.spec_constants.push_back(info);850}851else852{853// GLSL specification on std140 layout:854// 1. If the member is a scalar consuming N basic machine units, the base alignment is N.855// 2. If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively.856// 3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.857// 4. If the member is an array of scalars or vectors, the base alignment and array stride are set to match the base alignment of a single array element,858// according to rules (1), (2), and (3), and rounded up to the base alignment of a four-component vector.859// 7. If the member is a row-major matrix with C columns and R rows, the matrix is stored identically to an array of R row vectors with C components each, according to rule (4).860// 8. If the member is an array of S row-major matrices with C columns and R rows, the matrix is stored identically to a row of S*R row vectors with C components each, according to rule (4).861uint32_t alignment = (info.type.rows == 3 ? 4 /* (3) */ : info.type.rows /* (2) */) * 4 /* (1) */;862info.size = info.type.rows * 4;863864if (info.type.is_matrix())865{866alignment = 16 /* (4) */;867info.size = info.type.rows * alignment /* (7), (8) */;868}869if (info.type.is_array())870{871alignment = 16 /* (4) */;872info.size = align_up(info.size, alignment) * info.type.array_length;873}874875// Adjust offset according to alignment rules from above876info.offset = _module.total_uniform_size;877info.offset = align_up(info.offset, alignment);878_module.total_uniform_size = info.offset + info.size;879880write_location(_ubo_block, loc);881882_ubo_block += '\t';883// Note: All matrices are floating-point, even if the uniform type says different!!884write_type(_ubo_block, info.type);885_ubo_block += ' ' + id_to_name(res);886887if (info.type.is_array())888_ubo_block += '[' + std::to_string(info.type.array_length) + ']';889890_ubo_block += ";\n";891892_module.uniforms.push_back(info);893}894895return res;896}897id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override898{899// Constant variables with a constant initializer can just point to the initializer SSA variable, since they cannot be modified anyway, thus saving an unnecessary assignment900if (initializer_value != 0 && type.has(type::q_const) &&901std::find_if(_constant_lookup.begin(), _constant_lookup.end(),902[initializer_value](const auto &x) {903return initializer_value == std::get<2>(x);904}) != _constant_lookup.end())905return initializer_value;906907const id res = make_id();908909// GLSL does not allow local sampler variables, so try to remap those910if (!global && type.is_sampler())911{912_remapped_sampler_variables[res] = 0;913return res;914}915916if (!name.empty())917define_name<naming::general>(res, name);918919std::string &code = _blocks.at(_current_block);920921write_location(code, loc);922923if (!global)924code += '\t';925926write_type(code, type);927code += ' ' + id_to_name(res);928929if (type.is_array())930code += '[' + std::to_string(type.array_length) + ']';931932if (initializer_value != 0)933code += " = " + id_to_name(initializer_value);934935code += ";\n";936937return res;938}939id define_function(const location &loc, function &info) override940{941const id res = info.id = make_id();942943// Name is used in other places like the entry point defines, so escape it here944info.unique_name = escape_name(info.unique_name);945946assert(!info.unique_name.empty() && (info.unique_name[0] == 'F' || info.unique_name[0] == 'E'));947const bool is_entry_point = info.unique_name[0] == 'E';948if (!is_entry_point)949define_name<naming::unique>(res, info.unique_name);950else951define_name<naming::reserved>(res, "main");952953assert(_current_block == 0 && (_current_function_declaration.empty() || is_entry_point));954std::string &code = _current_function_declaration;955956write_location(code, loc);957958write_type(code, info.return_type);959code += ' ' + id_to_name(res) + '(';960961assert(info.parameter_list.empty() || !is_entry_point);962963for (member_type ¶m : info.parameter_list)964{965param.id = make_id();966define_name<naming::unique>(param.id, param.name);967968code += '\n';969write_location(code, param.location);970code += '\t';971write_type<true>(code, param.type); // GLSL does not allow interpolation attributes on function parameters972code += ' ' + id_to_name(param.id);973974if (param.type.is_array())975code += '[' + std::to_string(param.type.array_length) + ']';976977code += ',';978}979980// Remove trailing comma981if (!info.parameter_list.empty())982code.pop_back();983984code += ")\n";985986_functions.push_back(std::make_unique<function>(info));987_current_function = _functions.back().get();988989return res;990}991992void define_entry_point(function &func) override993{994assert(!func.unique_name.empty() && func.unique_name[0] == 'F');995func.unique_name[0] = 'E';996997// Modify entry point name so each thread configuration is made separate998if (func.type == shader_type::compute)999func.unique_name +=1000'_' + std::to_string(func.num_threads[0]) +1001'_' + std::to_string(func.num_threads[1]) +1002'_' + std::to_string(func.num_threads[2]);10031004if (std::find_if(_module.entry_points.begin(), _module.entry_points.end(),1005[&func](const std::pair<std::string, shader_type> &entry_point) {1006return entry_point.first == func.unique_name;1007}) != _module.entry_points.end())1008return;10091010_module.entry_points.emplace_back(func.unique_name, func.type);10111012assert(_current_function_declaration.empty());1013if (func.type == shader_type::compute)1014_current_function_declaration +=1015"layout(local_size_x = " + std::to_string(func.num_threads[0]) +1016", local_size_y = " + std::to_string(func.num_threads[1]) +1017", local_size_z = " + std::to_string(func.num_threads[2]) + ") in;\n";10181019// Generate the glue entry point function1020function entry_point = func;1021entry_point.referenced_functions.push_back(func.id);10221023// Change function signature to 'void main()'1024entry_point.return_type = { type::t_void };1025entry_point.return_semantic.clear();1026entry_point.parameter_list.clear();10271028std::unordered_map<std::string, std::string> semantic_to_varying_variable;10291030const auto create_varying_variable = [this, stype = func.type, &semantic_to_varying_variable](type type, unsigned int extra_qualifiers, const std::string &name, const std::string &semantic) {1031// Skip built in variables1032if (!semantic_to_builtin(std::string(), semantic, stype).empty())1033return;10341035// Do not create multiple input/output variables for duplicate semantic usage (since every input/output location may only be defined once in GLSL)1036if ((extra_qualifiers & type::q_in) != 0 &&1037!semantic_to_varying_variable.emplace(semantic, name).second)1038return;10391040type.qualifiers |= extra_qualifiers;1041assert((type.has(type::q_in) || type.has(type::q_out)) && !type.has(type::q_inout));10421043// OpenGL does not allow varying of type boolean1044if (type.is_boolean())1045type.base = type::t_float;10461047const uint32_t location = semantic_to_location(semantic, std::max(1u, type.array_length));10481049for (unsigned int a = 0; a < std::max(1u, type.array_length); ++a)1050{1051_current_function_declaration += "layout(location = " + std::to_string(location + a) + ") ";1052write_type<false, false, true>(_current_function_declaration, type);1053_current_function_declaration += ' ';1054_current_function_declaration += escape_name(type.is_array() ? name + '_' + std::to_string(a) : name);1055_current_function_declaration += ";\n";1056}1057};10581059// Translate return value to output variable1060if (func.return_type.is_struct())1061{1062const struct_type &definition = get_struct(func.return_type.struct_definition);10631064for (const member_type &member : definition.member_list)1065create_varying_variable(member.type, type::q_out, "_return_" + member.name, member.semantic);1066}1067else if (!func.return_type.is_void())1068{1069create_varying_variable(func.return_type, type::q_out, "_return", func.return_semantic);1070}10711072// Translate function parameters to input/output variables1073for (const member_type ¶m : func.parameter_list)1074{1075type param_type = param.type;1076param_type.qualifiers &= ~type::q_inout;10771078// Create separate input/output variables for "inout" parameters (since "inout" is not valid on those in GLSL)1079if (param.type.has(type::q_in))1080{1081// Flatten structure parameters1082if (param_type.is_struct())1083{1084const struct_type &definition = get_struct(param_type.struct_definition);10851086for (unsigned int a = 0, array_length = std::max(1u, param_type.array_length); a < array_length; a++)1087{1088for (const member_type &member : definition.member_list)1089create_varying_variable(member.type, param_type.qualifiers | type::q_in, "_in_" + id_to_name(param.id) + '_' + std::to_string(a) + '_' + member.name, member.semantic);1090}1091}1092else1093{1094create_varying_variable(param_type, type::q_in, "_in_" + id_to_name(param.id), param.semantic);1095}1096}10971098if (param.type.has(type::q_out))1099{1100if (param_type.is_struct())1101{1102const struct_type &definition = get_struct(param_type.struct_definition);11031104for (unsigned int a = 0, array_length = std::max(1u, param_type.array_length); a < array_length; a++)1105{1106for (const member_type &member : definition.member_list)1107create_varying_variable(member.type, param_type.qualifiers | type::q_out, "_out_" + id_to_name(param.id) + '_' + std::to_string(a) + '_' + member.name, member.semantic);1108}1109}1110else1111{1112create_varying_variable(param_type, type::q_out, "_out_" + id_to_name(param.id), param.semantic);1113}1114}1115}11161117define_function({}, entry_point);1118enter_block(create_block());11191120std::string &code = _blocks.at(_current_block);11211122// Handle input parameters1123for (const member_type ¶m : func.parameter_list)1124{1125if (param.type.has(type::q_in))1126{1127const std::string param_name = id_to_name(param.id);11281129// Create local array element variables1130for (unsigned int a = 0, array_length = std::max(1u, param.type.array_length); a < array_length; a++)1131{1132if (param.type.is_struct())1133{1134// Build struct from separate member input variables1135code += '\t';1136write_type<false, true>(code, param.type);1137code += ' ';1138code += escape_name(param.type.is_array() ? "_in_" + param_name + '_' + std::to_string(a) : "_in_" + param_name);1139code += " = ";1140write_type<false, false>(code, param.type);1141code += '(';11421143const struct_type &struct_definition = get_struct(param.type.struct_definition);11441145for (const member_type &member : struct_definition.member_list)1146{1147std::string in_param_name;1148{1149if (const auto it = semantic_to_varying_variable.find(member.semantic);1150it != semantic_to_varying_variable.end())1151in_param_name = it->second;1152else1153in_param_name = "_in_" + param_name + '_' + std::to_string(a) + '_' + member.name;1154}11551156if (member.type.is_array())1157{1158write_type<false, false>(code, member.type);1159code += "[](";11601161for (unsigned int b = 0; b < member.type.array_length; b++)1162{1163// OpenGL does not allow varying of type boolean, so need to cast here1164if (member.type.is_boolean())1165{1166write_type<false, false>(code, member.type);1167code += '(';1168}11691170code += escape_name(in_param_name + '_' + std::to_string(b));11711172if (member.type.is_boolean())1173code += ')';11741175code += ", ";1176}11771178// Remove trailing ", "1179code.erase(code.size() - 2);11801181code += ')';1182}1183else1184{1185if (member.type.is_boolean() || (_gles && member.type.is_integral()))1186{1187write_type<false, false>(code, member.type);1188code += '(';1189}11901191code += semantic_to_builtin(std::move(in_param_name), member.semantic, func.type);11921193if (member.type.is_boolean() || (_gles && member.type.is_integral()))1194code += ')';1195}11961197code += ", ";1198}11991200// There can be no empty structs, so can assume that the last two characters are always ", "1201code.erase(code.size() - 2);12021203code += ");\n";1204}1205else1206if (const auto it = semantic_to_varying_variable.find(param.semantic);1207it != semantic_to_varying_variable.end() &&1208it->second != "_in_" + id_to_name(param.id))1209{1210// Create local variables for duplicated semantics (since no input/output variable is created for those, see 'create_varying_variable')1211code += '\t';1212write_type<false, true>(code, param.type);1213code += ' ';1214code += escape_name(param.type.is_array() ? "_in_" + id_to_name(param.id) + '_' + std::to_string(a) : "_in_" + id_to_name(param.id));1215code += " = ";12161217if (param.type.is_boolean())1218{1219write_type<false, false>(code, param.type);1220code += '(';1221}12221223code += escape_name(param.type.is_array() ? it->second + '_' + std::to_string(a) : it->second);12241225if (param.type.is_boolean())1226code += ')';12271228code += ";\n";1229}1230}1231}12321233// Create local parameter variables which are used as arguments in the entry point function call below1234code += '\t';1235write_type<false, true>(code, param.type);1236code += ' ';1237code += id_to_name(param.id);1238if (param.type.is_array())1239code += '[' + std::to_string(param.type.array_length) + ']';12401241// Initialize those local variables with the input value if existing1242// Parameters with only an "out" qualifier are written to by the entry point function, so do not need to be initialized1243if (param.type.has(type::q_in))1244{1245code += " = ";12461247// Build array from separate array element variables1248if (param.type.is_array())1249{1250write_type<false, false>(code, param.type);1251code += "[](";12521253for (unsigned int a = 0; a < param.type.array_length; ++a)1254{1255// OpenGL does not allow varying of type boolean, so need to cast here1256if (param.type.is_boolean())1257{1258write_type<false, false>(code, param.type);1259code += '(';1260}12611262code += escape_name("_in_" + id_to_name(param.id) + '_' + std::to_string(a));12631264if (param.type.is_boolean())1265code += ')';12661267code += ", ";1268}12691270// Remove trailing ", "1271code.erase(code.size() - 2);12721273code += ')';1274}1275else1276{1277if (param.type.is_boolean() || (_gles && param.type.is_integral()))1278{1279write_type<false, false>(code, param.type);1280code += '(';1281}12821283code += semantic_to_builtin("_in_" + id_to_name(param.id), param.semantic, func.type);12841285if (param.type.is_boolean() || (_gles && param.type.is_integral()))1286code += ')';1287}1288}12891290code += ";\n";1291}12921293code += '\t';1294// Structs cannot be output variables, so have to write to a temporary first and then output each member separately1295if (func.return_type.is_struct())1296{1297write_type(code, func.return_type);1298code += " _return = ";1299}1300// All other output types can write to the output variable directly1301else if (!func.return_type.is_void())1302{1303code += semantic_to_builtin("_return", func.return_semantic, func.type);1304code += " = ";1305}13061307// Call the function this entry point refers to1308code += id_to_name(func.id) + '(';13091310for (const member_type ¶m : func.parameter_list)1311{1312code += id_to_name(param.id);1313code += ", ";1314}13151316// Remove trailing ", "1317if (!func.parameter_list.empty())1318code.erase(code.size() - 2);13191320code += ");\n";13211322// Handle output parameters1323for (const member_type ¶m : func.parameter_list)1324{1325if (param.type.has(type::q_out))1326{1327const std::string param_name = id_to_name(param.id);13281329if (param.type.is_struct())1330{1331const struct_type &definition = get_struct(param.type.struct_definition);13321333// Split out struct fields into separate output variables again1334for (unsigned int a = 0, array_length = std::max(1u, param.type.array_length); a < array_length; a++)1335{1336for (const member_type &member : definition.member_list)1337{1338if (member.type.is_array())1339{1340for (unsigned int b = 0; b < member.type.array_length; b++)1341{1342code += '\t';1343code += escape_name("_out_" + param_name + '_' + std::to_string(a) + '_' + member.name + '_' + std::to_string(b));1344code += " = ";13451346// OpenGL does not allow varying of type boolean, so need to cast here1347if (member.type.is_boolean())1348{1349type varying_type = member.type;1350varying_type.base = type::t_float;1351write_type<false, false>(code, varying_type);1352code += '(';1353}13541355code += param_name;1356if (param.type.is_array())1357code += '[' + std::to_string(a) + ']';1358code += '.';1359code += member.name;1360code += '[' + std::to_string(b) + ']';13611362if (member.type.is_boolean())1363code += ')';13641365code += ";\n";1366}1367}1368else1369{1370code += '\t';1371code += semantic_to_builtin("_out_" + param_name + '_' + std::to_string(a) + '_' + member.name, member.semantic, func.type);1372code += " = ";13731374if (member.type.is_boolean())1375{1376type varying_type = member.type;1377varying_type.base = type::t_float;1378write_type<false, false>(code, varying_type);1379code += '(';1380}13811382code += param_name;1383if (param.type.is_array())1384code += '[' + std::to_string(a) + ']';1385code += '.';1386code += member.name;13871388if (member.type.is_boolean())1389code += ')';13901391code += ";\n";1392}1393}1394}1395}1396else if (param.type.is_array())1397{1398// Split up array output into individual array elements again1399for (unsigned int a = 0; a < param.type.array_length; a++)1400{1401code += '\t';1402code += escape_name("_out_" + param_name + '_' + std::to_string(a));1403code += " = ";14041405// OpenGL does not allow varying of type boolean, so need to cast here1406if (param.type.is_boolean())1407{1408type varying_type = param.type;1409varying_type.base = type::t_float;1410write_type<false, false>(code, varying_type);1411code += '(';1412}14131414code += param_name;1415code += '[' + std::to_string(a) + ']';14161417if (param.type.is_boolean())1418code += ')';14191420code += ";\n";1421}1422}1423else1424{1425code += '\t';1426code += semantic_to_builtin("_out_" + param_name, param.semantic, func.type);1427code += " = ";14281429if (param.type.is_boolean())1430{1431type varying_type = param.type;1432varying_type.base = type::t_float;1433write_type<false, false>(code, varying_type);1434code += '(';1435}14361437code += param_name;14381439if (param.type.is_boolean())1440code += ')';14411442code += ";\n";1443}1444}1445}14461447// Handle return struct output variables1448if (func.return_type.is_struct())1449{1450const struct_type &struct_definition = get_struct(func.return_type.struct_definition);14511452for (const member_type &member : struct_definition.member_list)1453{1454code += '\t';1455code += semantic_to_builtin("_return_" + member.name, member.semantic, func.type);1456code += " = _return." + escape_name(member.name) + ";\n";1457}1458}14591460// Add code to flip the output vertically1461if (_flip_vert_y && func.type == shader_type::vertex)1462code += "\tgl_Position.y = -gl_Position.y;\n";14631464leave_block_and_return(0);1465leave_function();1466}14671468id emit_load(const expression &exp, bool force_new_id) override1469{1470if (exp.is_constant)1471return emit_constant(exp.type, exp.constant);1472else if (exp.chain.empty() && !force_new_id) // Can refer to values without access chain directly1473return exp.base;14741475const id res = make_id();14761477std::string type, expr_code = id_to_name(exp.base);14781479for (const expression::operation &op : exp.chain)1480{1481switch (op.op)1482{1483case expression::operation::op_cast:1484type.clear();1485write_type<false, false>(type, op.to);1486expr_code = type + '(' + expr_code + ')';1487break;1488case expression::operation::op_member:1489expr_code += '.';1490expr_code += escape_name(get_struct(op.from.struct_definition).member_list[op.index].name);1491break;1492case expression::operation::op_dynamic_index:1493// For matrices this will extract a column, but that is fine, since they are initialized column-wise too1494// Also cast to an integer, since it could be a boolean too, but GLSL does not allow those in index expressions1495expr_code += "[int(" + id_to_name(op.index) + ")]";1496break;1497case expression::operation::op_constant_index:1498if (op.from.is_vector() && !op.from.is_array())1499expr_code += '.',1500expr_code += "xyzw"[op.index];1501else1502expr_code += '[' + std::to_string(op.index) + ']';1503break;1504case expression::operation::op_swizzle:1505if (op.from.is_matrix())1506{1507if (op.swizzle[1] < 0)1508{1509const char row = (op.swizzle[0] % 4);1510const char col = (op.swizzle[0] - row) / 4;15111512expr_code += '[';1513expr_code += to_digit(row);1514expr_code += "][";1515expr_code += to_digit(col);1516expr_code += ']';1517}1518else1519{1520// TODO: Implement matrix to vector swizzles1521assert(false);1522expr_code += "_NOT_IMPLEMENTED_"; // Make sure compilation fails1523}1524}1525else1526{1527// can't swizzle scalars1528if (_gles && op.from.is_scalar())1529{1530// => e.g. vec3(expr, expr, expr).xyz1531type.clear();1532write_type<false, false>(type, op.to);1533std::string new_code = type;1534new_code += '(';15351536const unsigned int components = op.to.components();1537for (unsigned int i = 0; i < components; ++i)1538{1539if (i > 0)1540new_code += ',';1541new_code += '(' + expr_code + ')';1542}1543new_code += ')';1544expr_code = std::move(new_code);1545}1546else1547{1548expr_code += '.';1549for (int i = 0; i < 4 && op.swizzle[i] >= 0; ++i)1550expr_code += "xyzw"[op.swizzle[i]];1551}1552}1553break;1554}1555}15561557// GLSL matrices are always floating point, so need to cast result to the target type1558if (!exp.chain.empty() && exp.chain[0].from.is_matrix() && !exp.chain[0].from.is_floating_point())1559{1560type.clear();1561write_type<false, false>(type, exp.type);1562expr_code = type + '(' + expr_code + ')';1563}15641565if (force_new_id)1566{1567// Need to store value in a new variable to comply with request for a new ID1568std::string &code = _blocks.at(_current_block);15691570code += '\t';1571write_type(code, exp.type);1572code += ' ' + id_to_name(res) + " = " + expr_code + ";\n";1573}1574else1575{1576// Avoid excessive variable definitions by instancing simple load operations in code every time1577define_name<naming::expression>(res, std::move(expr_code));1578}15791580return res;1581}1582void emit_store(const expression &exp, id value) override1583{1584if (const auto it = _remapped_sampler_variables.find(exp.base);1585it != _remapped_sampler_variables.end())1586{1587assert(it->second == 0);1588it->second = value;1589return;1590}15911592std::string &code = _blocks.at(_current_block);15931594write_location(code, exp.location);15951596code += '\t' + id_to_name(exp.base);15971598for (const expression::operation &op : exp.chain)1599{1600switch (op.op)1601{1602case expression::operation::op_member:1603code += '.';1604code += escape_name(get_struct(op.from.struct_definition).member_list[op.index].name);1605break;1606case expression::operation::op_dynamic_index:1607code += "[int(" + id_to_name(op.index) + ")]";1608break;1609case expression::operation::op_constant_index:1610code += '[' + std::to_string(op.index) + ']';1611break;1612case expression::operation::op_swizzle:1613if (op.from.is_matrix())1614{1615if (op.swizzle[1] < 0)1616{1617const char row = (op.swizzle[0] % 4);1618const char col = (op.swizzle[0] - row) / 4;16191620code += '[';1621code += '1' + row - 1;1622code += "][";1623code += '1' + col - 1;1624code += ']';1625}1626else1627{1628// TODO: Implement matrix to vector swizzles1629assert(false);1630code += "_NOT_IMPLEMENTED_"; // Make sure compilation fails1631}1632}1633else1634{1635code += '.';1636for (int i = 0; i < 4 && op.swizzle[i] >= 0; ++i)1637code += "xyzw"[op.swizzle[i]];1638}1639break;1640}1641}16421643code += " = ";16441645// GLSL matrices are always floating point, so need to cast type1646if (!exp.chain.empty() && exp.chain[0].from.is_matrix() && !exp.chain[0].from.is_floating_point())1647// Only supporting scalar assignments to matrices currently, so can assume to always cast to float1648code += "float(" + id_to_name(value) + ");\n";1649else1650code += id_to_name(value) + ";\n";1651}16521653id emit_constant(const type &data_type, const constant &data) override1654{1655const id res = make_id();16561657if (data_type.is_array() || data_type.is_struct())1658{1659assert(data_type.has(type::q_const));16601661if (const auto it = std::find_if(_constant_lookup.begin(), _constant_lookup.end(),1662[&data_type, &data](const std::tuple<type, constant, id> &x) {1663if (!(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()))1664return false;1665for (size_t i = 0; i < data.array_data.size(); ++i)1666if (std::memcmp(&std::get<1>(x).array_data[i].as_uint[0], &data.array_data[i].as_uint[0], sizeof(uint32_t) * 16) != 0)1667return false;1668return true;1669});1670it != _constant_lookup.end())1671return std::get<2>(*it); // Reuse existing constant instead of duplicating the definition1672else if (data_type.is_array())1673_constant_lookup.push_back({ data_type, data, res });16741675// Put constant variable into global scope, so that it can be reused in different blocks1676std::string &code = _blocks.at(0);16771678// GLSL requires constants to be initialized, but struct initialization is not supported right now1679if (!data_type.is_struct())1680code += "const ";16811682write_type<false, false>(code, data_type);1683code += ' ' + id_to_name(res);16841685// Array constants need to be stored in a constant variable as they cannot be used in-place1686if (data_type.is_array())1687code += '[' + std::to_string(data_type.array_length) + ']';16881689// Struct initialization is not supported right now1690if (!data_type.is_struct()) {1691code += " = ";1692write_constant(code, data_type, data);1693}16941695code += ";\n";1696return res;1697}16981699std::string code;1700write_constant(code, data_type, data);1701define_name<naming::expression>(res, std::move(code));17021703return res;1704}17051706id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override1707{1708const id res = make_id();17091710std::string &code = _blocks.at(_current_block);17111712write_location(code, loc);17131714code += '\t';1715write_type(code, res_type);1716code += ' ' + id_to_name(res) + " = ";17171718switch (op)1719{1720case tokenid::minus:1721code += '-';1722break;1723case tokenid::tilde:1724code += '~';1725break;1726case tokenid::exclaim:1727if (res_type.is_vector())1728code += "not";1729else1730code += "!bool";1731break;1732default:1733assert(false);1734}17351736code += '(' + id_to_name(val) + ");\n";17371738return res;1739}1740id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &exp_type, id lhs, id rhs) override1741{1742const id res = make_id();17431744std::string &code = _blocks.at(_current_block);17451746write_location(code, loc);17471748code += '\t';1749write_type(code, res_type);1750code += ' ' + id_to_name(res) + " = ";17511752std::string intrinsic, operator_code;17531754switch (op)1755{1756case tokenid::plus:1757case tokenid::plus_plus:1758case tokenid::plus_equal:1759operator_code = '+';1760break;1761case tokenid::minus:1762case tokenid::minus_minus:1763case tokenid::minus_equal:1764operator_code = '-';1765break;1766case tokenid::star:1767case tokenid::star_equal:1768if (exp_type.is_matrix())1769intrinsic = "matrixCompMult";1770else1771operator_code = '*';1772break;1773case tokenid::slash:1774case tokenid::slash_equal:1775operator_code = '/';1776break;1777case tokenid::percent:1778case tokenid::percent_equal:1779if (exp_type.is_floating_point())1780intrinsic = "fmodHLSL",1781_uses_fmod = true;1782else1783operator_code = '%';1784break;1785case tokenid::caret:1786case tokenid::caret_equal:1787operator_code = '^';1788break;1789case tokenid::pipe:1790case tokenid::pipe_equal:1791operator_code = '|';1792break;1793case tokenid::ampersand:1794case tokenid::ampersand_equal:1795operator_code = '&';1796break;1797case tokenid::less_less:1798case tokenid::less_less_equal:1799operator_code = "<<";1800break;1801case tokenid::greater_greater:1802case tokenid::greater_greater_equal:1803operator_code = ">>";1804break;1805case tokenid::pipe_pipe:1806if (exp_type.is_vector())1807intrinsic = "compOr",1808_uses_componentwise_or = true;1809else1810operator_code = "||";1811break;1812case tokenid::ampersand_ampersand:1813if (exp_type.is_vector())1814intrinsic = "compAnd",1815_uses_componentwise_and = true;1816else1817operator_code = "&&";1818break;1819case tokenid::less:1820if (exp_type.is_vector())1821intrinsic = "lessThan";1822else1823operator_code = '<';1824break;1825case tokenid::less_equal:1826if (exp_type.is_vector())1827intrinsic = "lessThanEqual";1828else1829operator_code = "<=";1830break;1831case tokenid::greater:1832if (exp_type.is_vector())1833intrinsic = "greaterThan";1834else1835operator_code = '>';1836break;1837case tokenid::greater_equal:1838if (exp_type.is_vector())1839intrinsic = "greaterThanEqual";1840else1841operator_code = ">=";1842break;1843case tokenid::equal_equal:1844if (exp_type.is_vector())1845intrinsic = "equal";1846else1847operator_code = "==";1848break;1849case tokenid::exclaim_equal:1850if (exp_type.is_vector())1851intrinsic = "notEqual";1852else1853operator_code = "!=";1854break;1855default:1856assert(false);1857}18581859if (!intrinsic.empty())1860code += intrinsic + '(' + id_to_name(lhs) + ", " + id_to_name(rhs) + ')';1861else1862code += id_to_name(lhs) + ' ' + operator_code + ' ' + id_to_name(rhs);18631864code += ";\n";18651866return res;1867}1868id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override1869{1870if (op != tokenid::question)1871return assert(false), 0; // Should never happen, since this is the only ternary operator currently supported18721873const id res = make_id();18741875std::string &code = _blocks.at(_current_block);18761877write_location(code, loc);18781879code += '\t';1880write_type(code, res_type);1881code += ' ' + id_to_name(res);18821883if (res_type.is_array())1884code += '[' + std::to_string(res_type.array_length) + ']';18851886code += " = ";18871888if (res_type.is_vector())1889code += "compCond(" + id_to_name(condition) + ", " + id_to_name(true_value) + ", " + id_to_name(false_value) + ");\n",1890_uses_componentwise_cond = true;1891else // GLSL requires the conditional expression to be a scalar boolean1892code += id_to_name(condition) + " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n";18931894return res;1895}1896id emit_call(const location &loc, id function, const type &res_type, const std::vector<expression> &args) override1897{1898#ifndef NDEBUG1899for (const expression &arg : args)1900assert(arg.chain.empty() && arg.base != 0);1901#endif19021903const id res = make_id();19041905std::string &code = _blocks.at(_current_block);19061907write_location(code, loc);19081909code += '\t';19101911if (!res_type.is_void())1912{1913write_type(code, res_type);1914code += ' ' + id_to_name(res);19151916if (res_type.is_array())1917code += '[' + std::to_string(res_type.array_length) + ']';19181919code += " = ";1920}19211922code += id_to_name(function) + '(';19231924for (const expression &arg : args)1925{1926code += id_to_name(arg.base);1927code += ", ";1928}19291930// Remove trailing ", "1931if (!args.empty())1932code.erase(code.size() - 2);19331934code += ");\n";19351936return res;1937}1938id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector<expression> &args) override1939{1940#ifndef NDEBUG1941for (const expression &arg : args)1942assert(arg.chain.empty() && arg.base != 0);1943#endif19441945const id res = make_id();19461947std::string &code = _blocks.at(_current_block);19481949write_location(code, loc);19501951code += '\t';19521953if (!res_type.is_void())1954{1955write_type(code, res_type);1956code += ' ' + id_to_name(res) + " = ";1957}19581959enum1960{1961#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) name##i,1962#include "effect_symbol_table_intrinsics.inl"1963};19641965switch (intrinsic)1966{1967#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) case name##i: code break;1968#include "effect_symbol_table_intrinsics.inl"1969default:1970assert(false);1971}19721973code += ";\n";19741975return res;1976}1977id emit_construct(const location &loc, const type &res_type, const std::vector<expression> &args) override1978{1979#ifndef NDEBUG1980for (const expression &arg : args)1981assert((arg.type.is_scalar() || res_type.is_array()) && arg.chain.empty() && arg.base != 0);1982#endif19831984const id res = make_id();19851986std::string &code = _blocks.at(_current_block);19871988write_location(code, loc);19891990code += '\t';1991write_type(code, res_type);1992code += ' ' + id_to_name(res);19931994if (res_type.is_array())1995code += '[' + std::to_string(res_type.array_length) + ']';19961997code += " = ";19981999write_type<false, false>(code, res_type);20002001if (res_type.is_array())2002code += '[' + std::to_string(res_type.array_length) + ']';20032004code += '(';20052006for (const expression &arg : args)2007{2008code += id_to_name(arg.base);2009code += ", ";2010}20112012// Remove trailing ", "2013if (!args.empty())2014code.erase(code.size() - 2);20152016code += ");\n";20172018return res;2019}20202021void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) override2022{2023assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0);20242025std::string &code = _blocks.at(_current_block);20262027std::string &true_statement_data = _blocks.at(true_statement_block);2028std::string &false_statement_data = _blocks.at(false_statement_block);20292030increase_indentation_level(true_statement_data);2031increase_indentation_level(false_statement_data);20322033code += _blocks.at(condition_block);20342035write_location(code, loc);20362037if (flags != 0 && !_gles)2038{2039_uses_control_flow_attributes = true;20402041code += "#if GL_EXT_control_flow_attributes\n\t[[";2042if ((flags & 0x1) == 0x1)2043code += "flatten";2044if ((flags & 0x3) == 0x3)2045code += ", ";2046if ((flags & 0x2) == 0x2)2047code += "dont_flatten";2048code += "]]\n#endif\n";2049}20502051code += '\t';2052code += "if (" + id_to_name(condition_value) + ")\n\t{\n";2053code += true_statement_data;2054code += "\t}\n";20552056if (!false_statement_data.empty())2057{2058code += "\telse\n\t{\n";2059code += false_statement_data;2060code += "\t}\n";2061}20622063// Remove consumed blocks to save memory2064_blocks.erase(condition_block);2065_blocks.erase(true_statement_block);2066_blocks.erase(false_statement_block);2067}2068id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &res_type) override2069{2070assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0);20712072std::string &code = _blocks.at(_current_block);20732074std::string &true_statement_data = _blocks.at(true_statement_block);2075std::string &false_statement_data = _blocks.at(false_statement_block);20762077increase_indentation_level(true_statement_data);2078increase_indentation_level(false_statement_data);20792080const id res = make_id();20812082code += _blocks.at(condition_block);20832084code += '\t';2085write_type(code, res_type);2086code += ' ' + id_to_name(res) + ";\n";20872088write_location(code, loc);20892090code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n";2091code += (true_statement_block != condition_block ? true_statement_data : std::string());2092code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n";2093code += "\t}\n\telse\n\t{\n";2094code += (false_statement_block != condition_block ? false_statement_data : std::string());2095code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n";2096code += "\t}\n";20972098// Remove consumed blocks to save memory2099_blocks.erase(condition_block);2100_blocks.erase(true_statement_block);2101_blocks.erase(false_statement_block);21022103return res;2104}2105void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int flags) override2106{2107assert(prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0);21082109std::string &code = _blocks.at(_current_block);21102111std::string &loop_data = _blocks.at(loop_block);2112std::string &continue_data = _blocks.at(continue_block);21132114increase_indentation_level(loop_data);2115increase_indentation_level(loop_data);2116increase_indentation_level(continue_data);21172118code += _blocks.at(prev_block);21192120std::string attributes;2121if (flags != 0 && !_gles)2122{2123_uses_control_flow_attributes = true;21242125attributes += "#if GL_EXT_control_flow_attributes\n\t[[";2126if ((flags & 0x1) == 0x1)2127attributes += "unroll";2128if ((flags & 0x3) == 0x3)2129attributes += ", ";2130if ((flags & 0x2) == 0x2)2131attributes += "dont_unroll";2132attributes += "]]\n#endif\n";2133}21342135// Condition value can be missing in infinite loop constructs like "for (;;)"2136std::string condition_name = condition_value != 0 ? id_to_name(condition_value) : "true";21372138if (condition_block == 0)2139{2140// Convert the last SSA variable initializer to an assignment statement2141const size_t pos_assign = continue_data.rfind(condition_name);2142const size_t pos_prev_assign = continue_data.rfind('\t', pos_assign);2143continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1);21442145// We need to add the continue block to all "continue" statements as well2146const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block);2147for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size())2148loop_data.replace(offset, continue_id.size(), continue_data);21492150code += "\tbool " + condition_name + ";\n";21512152write_location(code, loc);21532154code += attributes;2155code += '\t';2156code += "do\n\t{\n\t\t{\n";2157code += loop_data; // Encapsulate loop body into another scope, so not to confuse any local variables with the current iteration variable accessed in the continue block below2158code += "\t\t}\n";2159code += continue_data;2160code += "\t}\n\twhile (" + condition_name + ");\n";2161}2162else2163{2164std::string &condition_data = _blocks.at(condition_block);21652166// If the condition data is just a single line, then it is a simple expression, which we can just put into the loop condition as-is2167if (std::count(condition_data.begin(), condition_data.end(), '\n') == 1)2168{2169// Convert SSA variable initializer back to a condition expression2170const size_t pos_assign = condition_data.find('=');2171condition_data.erase(0, pos_assign + 2);2172const size_t pos_semicolon = condition_data.rfind(';');2173condition_data.erase(pos_semicolon);21742175condition_name = std::move(condition_data);2176assert(condition_data.empty());2177}2178else2179{2180code += condition_data;21812182increase_indentation_level(condition_data);21832184// Convert the last SSA variable initializer to an assignment statement2185const size_t pos_assign = condition_data.rfind(condition_name);2186const size_t pos_prev_assign = condition_data.rfind('\t', pos_assign);2187condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1);2188}21892190const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block);2191for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size())2192loop_data.replace(offset, continue_id.size(), continue_data + condition_data);21932194code += attributes;2195code += '\t';2196code += "while (" + condition_name + ")\n\t{\n\t\t{\n";2197code += loop_data;2198code += "\t\t}\n";2199code += continue_data;2200code += condition_data;2201code += "\t}\n";22022203_blocks.erase(condition_block);2204}22052206// Remove consumed blocks to save memory2207_blocks.erase(prev_block);2208_blocks.erase(header_block);2209_blocks.erase(loop_block);2210_blocks.erase(continue_block);2211}2212void emit_switch(const location &loc, id selector_value, 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) override2213{2214assert(selector_value != 0 && selector_block != 0 && default_label != 0 && default_block != 0);2215assert(case_blocks.size() == case_literal_and_labels.size() / 2);22162217std::string &code = _blocks.at(_current_block);22182219code += _blocks.at(selector_block);22202221write_location(code, loc);22222223code += "\tswitch (" + id_to_name(selector_value) + ")\n\t{\n";22242225std::vector<id> labels = case_literal_and_labels;2226for (size_t i = 0; i < labels.size(); i += 2)2227{2228if (labels[i + 1] == 0)2229continue; // Happens if a case was already handled, see below22302231code += "\tcase " + std::to_string(labels[i]) + ": ";22322233if (labels[i + 1] == default_label)2234{2235code += "default: ";2236default_label = 0;2237}2238else2239{2240for (size_t k = i + 2; k < labels.size(); k += 2)2241{2242if (labels[k + 1] == 0 || labels[k + 1] != labels[i + 1])2243continue;22442245code += "case " + std::to_string(labels[k]) + ": ";2246labels[k + 1] = 0;2247}2248}22492250assert(case_blocks[i / 2] != 0);2251std::string &case_data = _blocks.at(case_blocks[i / 2]);22522253increase_indentation_level(case_data);22542255code += "{\n";2256code += case_data;2257code += "\t}\n";2258}225922602261if (default_label != 0 && default_block != _current_block)2262{2263std::string &default_data = _blocks.at(default_block);22642265increase_indentation_level(default_data);22662267code += "\tdefault: {\n";2268code += default_data;2269code += "\t}\n";22702271_blocks.erase(default_block);2272}22732274code += "\t}\n";22752276// Remove consumed blocks to save memory2277_blocks.erase(selector_block);2278for (const id case_block : case_blocks)2279_blocks.erase(case_block);2280}22812282id create_block() override2283{2284const id res = make_id();22852286std::string &block = _blocks.emplace(res, std::string()).first->second;2287// Reserve a decently big enough memory block to avoid frequent reallocations2288block.reserve(4096);22892290return res;2291}2292id set_block(id id) override2293{2294_last_block = _current_block;2295_current_block = id;22962297return _last_block;2298}2299void enter_block(id id) override2300{2301_current_block = id;2302}2303id leave_block_and_kill() override2304{2305if (!is_in_block())2306return 0;23072308std::string &code = _blocks.at(_current_block);23092310code += "\tdiscard;\n";23112312const type &return_type = _current_function->return_type;2313if (!return_type.is_void())2314{2315// Add a return statement to exit functions in case discard is the last control flow statement2316code += "\treturn ";2317write_constant(code, return_type, constant());2318code += ";\n";2319}23202321return set_block(0);2322}2323id leave_block_and_return(id value) override2324{2325if (!is_in_block())2326return 0;23272328// Skip implicit return statement2329if (!_current_function->return_type.is_void() && value == 0)2330return set_block(0);23312332std::string &code = _blocks.at(_current_block);23332334code += "\treturn";23352336if (value != 0)2337code += ' ' + id_to_name(value);23382339code += ";\n";23402341return set_block(0);2342}2343id leave_block_and_switch(id, id) override2344{2345if (!is_in_block())2346return _last_block;23472348return set_block(0);2349}2350id leave_block_and_branch(id target, unsigned int loop_flow) override2351{2352if (!is_in_block())2353return _last_block;23542355std::string &code = _blocks.at(_current_block);23562357switch (loop_flow)2358{2359case 1:2360code += "\tbreak;\n";2361break;2362case 2: // Keep track of continue target block, so we can insert its code here later2363code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n";2364break;2365}23662367return set_block(0);2368}2369id leave_block_and_branch_conditional(id, id, id) override2370{2371if (!is_in_block())2372return _last_block;23732374return set_block(0);2375}2376void leave_function() override2377{2378assert(_current_function != nullptr && _last_block != 0);23792380_blocks.emplace(_current_function->id, _current_function_declaration + "{\n" + _blocks.at(_last_block) + "}\n");23812382_current_function = nullptr;2383_current_function_declaration.clear();2384}2385};2386} // namespace23872388codegen *reshadefx::create_codegen_glsl(unsigned version, bool gles, bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y)2389{2390return new codegen_glsl(version, gles, vulkan_semantics, debug_info, uniforms_to_spec_constants, enable_16bit_types, flip_vert_y);2391}239223932394