From 8c638b4c78f18e3a57103d3a8a0f2ea1daf0c8c8 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 13 Aug 2023 14:03:17 +1000 Subject: [PATCH] dep: Add reshadefx --- dep/reshadefx/CMakeLists.txt | 29 + dep/reshadefx/LICENSE.md | 9 + dep/reshadefx/README.md | 3 + dep/reshadefx/include/effect_codegen.hpp | 377 ++ dep/reshadefx/include/effect_expression.hpp | 249 + dep/reshadefx/include/effect_lexer.hpp | 112 + dep/reshadefx/include/effect_module.hpp | 350 ++ dep/reshadefx/include/effect_parser.hpp | 88 + dep/reshadefx/include/effect_preprocessor.hpp | 166 + dep/reshadefx/include/effect_symbol_table.hpp | 103 + dep/reshadefx/include/effect_token.hpp | 252 + dep/reshadefx/reshadefx.vcxproj | 39 + dep/reshadefx/reshadefx.vcxproj.filters | 27 + dep/reshadefx/src/effect_codegen_glsl.cpp | 2151 +++++++++ dep/reshadefx/src/effect_codegen_hlsl.cpp | 1845 ++++++++ dep/reshadefx/src/effect_codegen_spirv.cpp | 2394 ++++++++++ dep/reshadefx/src/effect_expression.cpp | 623 +++ dep/reshadefx/src/effect_lexer.cpp | 1166 +++++ dep/reshadefx/src/effect_parser_exp.cpp | 1538 ++++++ dep/reshadefx/src/effect_parser_stmt.cpp | 2003 ++++++++ dep/reshadefx/src/effect_preprocessor.cpp | 1295 +++++ dep/reshadefx/src/effect_symbol_table.cpp | 477 ++ .../src/effect_symbol_table_intrinsics.inl | 4196 +++++++++++++++++ duckstation.sln | 27 + 24 files changed, 19519 insertions(+) create mode 100644 dep/reshadefx/CMakeLists.txt create mode 100644 dep/reshadefx/LICENSE.md create mode 100644 dep/reshadefx/README.md create mode 100644 dep/reshadefx/include/effect_codegen.hpp create mode 100644 dep/reshadefx/include/effect_expression.hpp create mode 100644 dep/reshadefx/include/effect_lexer.hpp create mode 100644 dep/reshadefx/include/effect_module.hpp create mode 100644 dep/reshadefx/include/effect_parser.hpp create mode 100644 dep/reshadefx/include/effect_preprocessor.hpp create mode 100644 dep/reshadefx/include/effect_symbol_table.hpp create mode 100644 dep/reshadefx/include/effect_token.hpp create mode 100644 dep/reshadefx/reshadefx.vcxproj create mode 100644 dep/reshadefx/reshadefx.vcxproj.filters create mode 100644 dep/reshadefx/src/effect_codegen_glsl.cpp create mode 100644 dep/reshadefx/src/effect_codegen_hlsl.cpp create mode 100644 dep/reshadefx/src/effect_codegen_spirv.cpp create mode 100644 dep/reshadefx/src/effect_expression.cpp create mode 100644 dep/reshadefx/src/effect_lexer.cpp create mode 100644 dep/reshadefx/src/effect_parser_exp.cpp create mode 100644 dep/reshadefx/src/effect_parser_stmt.cpp create mode 100644 dep/reshadefx/src/effect_preprocessor.cpp create mode 100644 dep/reshadefx/src/effect_symbol_table.cpp create mode 100644 dep/reshadefx/src/effect_symbol_table_intrinsics.inl diff --git a/dep/reshadefx/CMakeLists.txt b/dep/reshadefx/CMakeLists.txt new file mode 100644 index 000000000..4224d8f21 --- /dev/null +++ b/dep/reshadefx/CMakeLists.txt @@ -0,0 +1,29 @@ +add_library(reshadefx + include/effect_codegen.hpp + include/effect_expression.hpp + include/effect_lexer.hpp + include/effect_module.hpp + include/effect_parser.hpp + include/effect_preprocessor.hpp + include/effect_symbol_table.hpp + include/effect_token.hpp + src/effect_codegen_glsl.cpp + src/effect_codegen_hlsl.cpp + src/effect_codegen_spirv.cpp + src/effect_expression.cpp + src/effect_lexer.cpp + src/effect_parser_exp.cpp + src/effect_parser_stmt.cpp + src/effect_preprocessor.cpp + src/effect_symbol_table.cpp +) + +target_include_directories(reshadefx PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}/../spirv-cross/include/spirv-cross" # SPIR-V +) +target_include_directories(reshadefx INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + diff --git a/dep/reshadefx/LICENSE.md b/dep/reshadefx/LICENSE.md new file mode 100644 index 000000000..52d296580 --- /dev/null +++ b/dep/reshadefx/LICENSE.md @@ -0,0 +1,9 @@ +Copyright 2014 Patrick Mours. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/dep/reshadefx/README.md b/dep/reshadefx/README.md new file mode 100644 index 000000000..96e125cce --- /dev/null +++ b/dep/reshadefx/README.md @@ -0,0 +1,3 @@ +This directory contains a partial copy of the ReShade post-processing injector, from: + +https://github.com/crosire/reshade diff --git a/dep/reshadefx/include/effect_codegen.hpp b/dep/reshadefx/include/effect_codegen.hpp new file mode 100644 index 000000000..87336891c --- /dev/null +++ b/dep/reshadefx/include/effect_codegen.hpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_module.hpp" +#include // std::unique_ptr +#include // std::find_if + +namespace reshadefx +{ + /// + /// A SSA code generation back-end interface for the parser to call into. + /// + class codegen + { + public: + /// + /// Virtual destructor to guarantee that memory of the implementations deriving from this interface is properly destroyed. + /// + virtual ~codegen() {} + + /// + /// Writes result of the code generation to the specified . + /// + /// Target module to fill. + virtual void write_result(module &module) = 0; + + public: + /// + /// An opaque ID referring to a SSA value or basic block. + /// + using id = uint32_t; + + /// + /// Defines a new struct type. + /// + /// Source location matching this definition (for debugging). + /// Description of the type. + /// New SSA ID of the type. + virtual id define_struct(const location &loc, struct_info &info) = 0; + /// + /// Defines a new texture binding. + /// + /// Source location matching this definition (for debugging). + /// Description of the texture object. + /// New SSA ID of the binding. + virtual id define_texture(const location &loc, texture_info &info) = 0; + /// + /// Defines a new sampler binding. + /// + /// Source location matching this definition (for debugging). + /// Description of the texture this sampler object references. + /// Description of the sampler object. + /// New SSA ID of the binding. + virtual id define_sampler(const location &loc, const texture_info &tex_info, sampler_info &info) = 0; + /// + /// Defines a new storage binding. + /// + /// Source location matching this definition (for debugging). + /// Description of the texture this storage object references. + /// Description of the storage object. + /// New SSA ID of the binding. + virtual id define_storage(const location &loc, const texture_info &tex_info, storage_info &info) = 0; + /// + /// Defines a new uniform variable. + /// + /// Source location matching this definition (for debugging). + /// Description of the uniform variable. + /// New SSA ID of the variable. + virtual id define_uniform(const location &loc, uniform_info &info) = 0; + /// + /// Defines a new variable. + /// + /// Source location matching this definition (for debugging). + /// Data type of the variable. + /// Name of the variable. + /// true if this variable is in global scope, false otherwise. + /// SSA ID of an optional initializer value. + /// New SSA ID of the variable. + virtual id define_variable(const location &loc, const type &type, std::string name = std::string(), bool global = false, id initializer_value = 0) = 0; + /// + /// Defines a new function and its function parameters and make it current. Any code added after this call is added to this function. + /// + /// Source location matching this definition (for debugging). + /// Description of the function. + /// New SSA ID of the function. + virtual id define_function(const location &loc, function_info &info) = 0; + + /// + /// Defines a new effect technique. + /// + /// Source location matching this definition (for debugging). + /// Description of the technique. + void define_technique(technique_info &&info) { _module.techniques.push_back(std::move(info)); } + /// + /// Makes a function a shader entry point. + /// + /// Function to use as entry point. May be overwritten to point to a new unique function for this entry point. + /// Shader type (vertex, pixel or compute shader). + /// Number of local threads it this is a compute entry point. + virtual void define_entry_point(function_info &function, shader_type type, int num_threads[3] = nullptr) = 0; + + /// + /// Resolves the access chain and add a load operation to the output. + /// + /// Access chain pointing to the variable to load from. + /// Set to to force this to return a new SSA ID for l-value loads. + /// New SSA ID with the loaded value. + virtual id emit_load(const expression &chain, bool force_new_id = false) = 0; + /// + /// Resolves the access chain and add a store operation to the output. + /// + /// Access chain pointing to the variable to store to. + /// SSA ID of the value to store. + virtual void emit_store(const expression &chain, id value) = 0; + /// + /// Resolves the access chain, but do not add a load operation. This returns a pointer instead. + /// + /// Access chain pointing to the variable to resolve. + /// Output value which is set to the index in the access chain up to which the access chain went. + /// New SSA ID with a pointer to the value. + virtual id emit_access_chain(const expression &chain, size_t &chain_index) { chain_index = chain.chain.size(); return emit_load(chain); } + + /// + /// Creates a SSA constant value. + /// + /// Data type of the constant. + /// Actual constant data to convert into a SSA ID. + /// New SSA ID with the constant value. + virtual id emit_constant(const type &type, const constant &data) = 0; + + /// + /// Adds an unary operation to the output (built-in operation with one argument). + /// + /// Source location matching this operation (for debugging). + /// Unary operator to use. + /// Data type of the input value. + /// SSA ID of value to perform the operation on. + /// New SSA ID with the result of the operation. + virtual id emit_unary_op(const location &loc, tokenid op, const type &type, id val) = 0; + /// + /// Adds a binary operation to the output (built-in operation with two arguments). + /// + /// Source location matching this operation (for debugging). + /// Binary operator to use. + /// Data type of the result. + /// Data type of the input values. + /// SSA ID of the value on the left-hand side of the binary operation. + /// SSA ID of the value on the right-hand side of the binary operation. + /// New SSA ID with the result of the operation. + virtual id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) = 0; + id emit_binary_op(const location &loc, tokenid op, const type &type, id lhs, id rhs) { return emit_binary_op(loc, op, type, type, lhs, rhs); } + /// + /// Adds a ternary operation to the output (built-in operation with three arguments). + /// + /// Source location matching this operation (for debugging). + /// Ternary operator to use. + /// Data type of the input values. + /// SSA ID of the condition value of the ternary operation. + /// SSA ID of the first value of the ternary operation. + /// SSA ID of the second value of the ternary operation. + /// New SSA ID with the result of the operation. + virtual id emit_ternary_op(const location &loc, tokenid op, const type &type, id condition, id true_value, id false_value) = 0; + /// + /// Adds a function call to the output. + /// + /// Source location matching this operation (for debugging). + /// SSA ID of the function to call. + /// Data type of the call result. + /// List of SSA IDs representing the call arguments. + /// New SSA ID with the result of the function call. + virtual id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) = 0; + /// + /// Adds an intrinsic function call to the output. + /// + /// Source location matching this operation (for debugging). + /// Intrinsic to call. + /// Data type of the call result. + /// List of SSA IDs representing the call arguments. + /// New SSA ID with the result of the function call. + virtual id emit_call_intrinsic(const location &loc, id function, const type &res_type, const std::vector &args) = 0; + /// + /// Adds a type constructor call to the output. + /// + /// Data type to construct. + /// List of SSA IDs representing the scalar constructor arguments. + /// New SSA ID with the constructed value. + virtual id emit_construct(const location &loc, const type &type, const std::vector &args) = 0; + + /// + /// Adds a structured branch control flow to the output. + /// + /// Source location matching this branch (for debugging). + /// 0 - default, 1 - flatten, 2 - do not flatten + virtual void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) = 0; + /// + /// Adds a branch control flow with a SSA phi operation to the output. + /// + /// Source location matching this branch (for debugging). + /// New SSA ID with the result of the phi operation. + virtual id 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 &type) = 0; + /// + /// Adds a structured loop control flow to the output. + /// + /// Source location matching this loop (for debugging). + /// 0 - default, 1 - unroll, 2 - do not unroll + virtual void 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) = 0; + /// + /// Adds a structured switch control flow to the output. + /// + /// Source location matching this switch (for debugging). + /// 0 - default, 1 - flatten, 2 - do not flatten + virtual void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, id default_block, const std::vector &case_literal_and_labels, const std::vector &case_blocks, unsigned int flags) = 0; + + /// + /// Returns if code is currently added to a basic block. + /// + bool is_in_block() const { return _current_block != 0; } + /// + /// Returns if code is currently added to a function. + /// + virtual bool is_in_function() const { return is_in_block(); } + + /// + /// Creates a new basic block. + /// + /// New ID of the basic block. + virtual id create_block() { return make_id(); } + /// + /// Overwrites the current block ID. + /// + /// ID of the block to make current. + /// ID of the previous basic block. + virtual id set_block(id id) = 0; + /// + /// Creates a new basic block and make it current. + /// + /// ID of the basic block to create and make current. + virtual void enter_block(id id) = 0; + /// + /// Returns from the current basic block and kill the shader invocation. + /// + /// ID of the current basic block. + virtual id leave_block_and_kill() = 0; + /// + /// Returns from the current basic block and hand control flow over to the function call side. + /// + /// Optional SSA ID of a return value. + /// ID of the current basic block. + virtual id leave_block_and_return(id value = 0) = 0; + /// + /// Diverges the current control flow and enter a switch. + /// + /// SSA ID of the selector value to decide the switch path. + /// ID of the current basic block. + virtual id leave_block_and_switch(id value, id default_target) = 0; + /// + /// Diverges the current control flow and jump to the specified target block. + /// + /// ID of the basic block to jump to. + /// Set to if this corresponds to a loop continue statement. + /// ID of the current basic block. + virtual id leave_block_and_branch(id target, unsigned int loop_flow = 0) = 0; + /// + /// Diverges the current control flow and jump to one of the specified target blocks, depending on the condition. + /// + /// SSA ID of a value used to choose which path to take. + /// ID of the basic block to jump to when the condition is true. + /// ID of the basic block to jump to when the condition is false. + /// ID of the current basic block. + virtual id leave_block_and_branch_conditional(id condition, id true_target, id false_target) = 0; + /// + /// Leaves the current function. Any code added after this call is added in the global scope. + /// + virtual void leave_function() = 0; + + /// + /// Looks up an existing struct type. + /// + /// SSA ID of the type to find. + /// Reference to the struct description. + const struct_info &get_struct(id id) const + { + return *std::find_if(_structs.begin(), _structs.end(), + [id](const auto &it) { return it.definition == id; }); + } + /// + /// Looks up an existing texture binding. + /// + /// SSA ID of the texture binding to find. + /// Reference to the texture description. + texture_info &get_texture(id id) + { + return *std::find_if(_module.textures.begin(), _module.textures.end(), + [id](const auto &it) { return it.id == id; }); + } + /// + /// Looks up an existing sampler binding. + /// + /// SSA ID of the sampler binding to find. + /// Reference to the sampler description. + const sampler_info &get_sampler(id id) const + { + return *std::find_if(_module.samplers.begin(), _module.samplers.end(), + [id](const auto &it) { return it.id == id; }); + } + /// + /// Looks up an existing storage binding. + /// + /// SSA ID of the storage binding to find. + /// Reference to the storage description. + const storage_info &get_storage(id id) const + { + return *std::find_if(_module.storages.begin(), _module.storages.end(), + [id](const auto &it) { return it.id == id; }); + } + /// + /// Looks up an existing function definition. + /// + /// SSA ID of the function variable to find. + /// Reference to the function description. + function_info &get_function(id id) + { + return *std::find_if(_functions.begin(), _functions.end(), + [id](const auto &it) { return it->definition == id; })->get(); + } + + protected: + id make_id() { return _next_id++; } + + static uint32_t align_up(uint32_t size, uint32_t alignment) + { + alignment -= 1; + return ((size + alignment) & ~alignment); + } + static uint32_t align_up(uint32_t size, uint32_t alignment, uint32_t elements) + { + return align_up(size, alignment) * (elements - 1) + size; + } + + reshadefx::module _module; + std::vector _structs; + std::vector> _functions; + id _next_id = 1; + id _last_block = 0; + id _current_block = 0; + }; + + /// + /// Creates a back-end implementation for GLSL code generation. + /// + /// Generate GLSL for OpenGL or for Vulkan. + /// Whether to append debug information like line directives to the generated code. + /// Whether to convert uniform variables to specialization constants. + /// Use real 16-bit types for the minimum precision types "min16int", "min16uint" and "min16float". + /// Insert code to flip the Y component of the output position in vertex shaders. + codegen *create_codegen_glsl(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types = false, bool flip_vert_y = false); + /// + /// Creates a back-end implementation for HLSL code generation. + /// + /// The HLSL shader model version (e.g. 30, 41, 50, 60, ...) + /// Whether to append debug information like line directives to the generated code. + /// Whether to convert uniform variables to specialization constants. + codegen *create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants); + /// + /// Creates a back-end implementation for SPIR-V code generation. + /// + /// Generate SPIR-V for OpenGL or for Vulkan. + /// Whether to append debug information like line directives to the generated code. + /// Whether to convert uniform variables to specialization constants. + /// Use real 16-bit types for the minimum precision types "min16int", "min16uint" and "min16float". + /// Insert code to flip the Y component of the output position in vertex shaders. + codegen *create_codegen_spirv(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types = false, bool flip_vert_y = false); +} diff --git a/dep/reshadefx/include/effect_expression.hpp b/dep/reshadefx/include/effect_expression.hpp new file mode 100644 index 000000000..d149cd265 --- /dev/null +++ b/dep/reshadefx/include/effect_expression.hpp @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_token.hpp" + +namespace reshadefx +{ + /// + /// Structure which encapsulates a parsed value type + /// + struct type + { + enum datatype : uint8_t + { + t_void, + t_bool, + t_min16int, + t_int, + t_min16uint, + t_uint, + t_min16float, + t_float, + t_string, + t_struct, + t_texture1d, + t_texture2d, + t_texture3d, + t_sampler1d_int, + t_sampler2d_int, + t_sampler3d_int, + t_sampler1d_uint, + t_sampler2d_uint, + t_sampler3d_uint, + t_sampler1d_float, + t_sampler2d_float, + t_sampler3d_float, + t_storage1d_int, + t_storage2d_int, + t_storage3d_int, + t_storage1d_uint, + t_storage2d_uint, + t_storage3d_uint, + t_storage1d_float, + t_storage2d_float, + t_storage3d_float, + t_function, + }; + enum qualifier : uint32_t + { + q_extern = 1 << 0, + q_static = 1 << 1, + q_uniform = 1 << 2, + q_volatile = 1 << 3, + q_precise = 1 << 4, + q_groupshared = 1 << 14, + q_in = 1 << 5, + q_out = 1 << 6, + q_inout = q_in | q_out, + q_const = 1 << 8, + q_linear = 1 << 10, + q_noperspective = 1 << 11, + q_centroid = 1 << 12, + q_nointerpolation = 1 << 13, + }; + + /// + /// Gets the result type of an operation involving the two input types. + /// + static type merge(const type &lhs, const type &rhs); + + /// + /// Calculates the ranking between two types which can be used to select the best matching function overload. The higher the rank, the better the match. A value of zero indicates that the types are not compatible. + /// + static unsigned int rank(const type &src, const type &dst); + + /// + /// Returns a human-readable description of this type definition. + /// + std::string description() const; + + bool has(qualifier x) const { return (qualifiers & x) == x; } + + bool is_void() const { return base == t_void; } + bool is_boolean() const { return base == t_bool; } + bool is_numeric() const { return base >= t_bool && base <= t_float; } + bool is_integral() const { return (base >= t_bool && base <= t_uint) || (base >= t_sampler1d_int && base <= t_sampler3d_uint) || (base >= t_storage1d_int && base <= t_storage3d_uint); } + bool is_floating_point() const { return base == t_min16float || base == t_float || (base >= t_sampler1d_float && base <= t_sampler3d_float) || (base >= t_storage1d_float && base <= t_storage3d_float); } + bool is_signed() const { return base == t_min16int || base == t_int || (base >= t_sampler1d_int && base <= t_sampler3d_int) || (base >= t_storage1d_int && base <= t_storage3d_int) || is_floating_point(); } + bool is_unsigned() const { return base == t_min16uint || base == t_uint || (base >= t_sampler1d_uint && base <= t_sampler3d_uint) || (base >= t_storage1d_uint && base <= t_storage3d_uint); } + + bool is_struct() const { return base == t_struct; } + bool is_object() const { return is_texture() || is_sampler() || is_storage(); } + bool is_texture() const { return base >= t_texture1d && base <= t_texture3d; } + bool is_sampler() const { return base >= t_sampler1d_int && base <= t_sampler3d_float; } + bool is_storage() const { return base >= t_storage1d_int && base <= t_storage3d_float; } + bool is_function() const { return base == t_function; } + + bool is_array() const { return array_length != 0; } + bool is_scalar() const { return is_numeric() && !is_matrix() && !is_vector() && !is_array(); } + bool is_vector() const { return is_numeric() && rows > 1 && cols == 1; } + bool is_matrix() const { return is_numeric() && rows >= 1 && cols > 1; } + + unsigned int precision() const { return base == t_min16int || base == t_min16uint || base == t_min16float ? 16 : 32; } + unsigned int components() const { return rows * cols; } + unsigned int texture_dimension() const { return base >= t_texture1d && base <= t_storage3d_float ? ((base - t_texture1d) % 3) + 1 : 0; } + + friend inline bool operator==(const type &lhs, const type &rhs) + { + return lhs.base == rhs.base && lhs.rows == rhs.rows && lhs.cols == rhs.cols && lhs.array_length == rhs.array_length && lhs.definition == rhs.definition; + } + friend inline bool operator!=(const type &lhs, const type &rhs) + { + return !operator==(lhs, rhs); + } + + // Underlying base type ('int', 'float', ...) + datatype base = t_void; + // Number of rows if this is a vector type + unsigned int rows = 0; + // Number of columns if this is a matrix type + unsigned int cols = 0; + // Bit mask of all the qualifiers decorating the type + unsigned int qualifiers = 0; + // Negative if an unsized array, otherwise the number of elements if this is an array type + int array_length = 0; + // ID of the matching struct if this is a struct type + uint32_t definition = 0; + }; + + /// + /// Structure which encapsulates a parsed constant value + /// + struct constant + { + union + { + float as_float[16]; + int32_t as_int[16]; + uint32_t as_uint[16]; + }; + + // Optional string associated with this constant + std::string string_data; + // Optional additional elements if this is an array constant + std::vector array_data; + }; + + /// + /// Structures which keeps track of the access chain of an expression + /// + struct expression + { + struct operation + { + enum op_type + { + op_cast, + op_member, + op_dynamic_index, + op_constant_index, + op_swizzle, + }; + + op_type op; + reshadefx::type from, to; + uint32_t index = 0; + signed char swizzle[4] = {}; + }; + + uint32_t base = 0; + reshadefx::type type = {}; + reshadefx::constant constant = {}; + bool is_lvalue = false; + bool is_constant = false; + reshadefx::location location; + std::vector chain; + + /// + /// Initializes the expression to a l-value. + /// + /// Code location of the expression. + /// SSA ID of the l-value. + /// Value type of the expression result. + void reset_to_lvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type); + /// + /// Initializes the expression to a r-value. + /// + /// Code location of the expression. + /// SSA ID of the r-value. + /// Value type of the expression result. + void reset_to_rvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type); + + /// + /// Initializes the expression to a constant value. + /// + /// Code location of the constant expression. + /// Constant value to initialize to. + void reset_to_rvalue_constant(const reshadefx::location &loc, bool data); + void reset_to_rvalue_constant(const reshadefx::location &loc, float data); + void reset_to_rvalue_constant(const reshadefx::location &loc, int32_t data); + void reset_to_rvalue_constant(const reshadefx::location &loc, uint32_t data); + void reset_to_rvalue_constant(const reshadefx::location &loc, std::string data); + void reset_to_rvalue_constant(const reshadefx::location &loc, reshadefx::constant data, const reshadefx::type &type); + + /// + /// Adds a cast operation to the current access chain. + /// + /// Type to cast the expression to. + void add_cast_operation(const reshadefx::type &type); + /// + /// Adds a structure member lookup to the current access chain. + /// + /// Index of the member to dereference. + /// Value type of the member. + void add_member_access(unsigned int index, const reshadefx::type &type); + /// + /// Adds an index operation to the current access chain. + /// + /// SSA ID of the indexing value. + void add_dynamic_index_access(uint32_t index_expression); + /// + /// Adds an constant index operation to the current access chain. + /// + /// Constant indexing value. + void add_constant_index_access(unsigned int index); + /// + /// Adds a swizzle operation to the current access chain. + /// + /// Swizzle for each component. -1 = unused, 0 = x, 1 = y, 2 = z, 3 = w. + /// Number of components in the swizzle. The maximum is 4. + void add_swizzle_access(const signed char swizzle[4], unsigned int length); + + /// + /// Applies an unary operation to this constant expression. + /// + /// Unary operator to apply. + bool evaluate_constant_expression(reshadefx::tokenid op); + /// + /// Applies a binary operation to this constant expression. + /// + /// Binary operator to apply. + /// Constant value to use as right-hand side of the binary operation. + bool evaluate_constant_expression(reshadefx::tokenid op, const reshadefx::constant &rhs); + }; +} diff --git a/dep/reshadefx/include/effect_lexer.hpp b/dep/reshadefx/include/effect_lexer.hpp new file mode 100644 index 000000000..20686c307 --- /dev/null +++ b/dep/reshadefx/include/effect_lexer.hpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_token.hpp" + +namespace reshadefx +{ + /// + /// A lexical analyzer for C-like languages. + /// + class lexer + { + public: + explicit lexer( + std::string input, + bool ignore_comments = true, + bool ignore_whitespace = true, + bool ignore_pp_directives = true, + bool ignore_line_directives = false, + bool ignore_keywords = false, + bool escape_string_literals = true, + const location &start_location = location()) : + _input(std::move(input)), + _cur_location(start_location), + _ignore_comments(ignore_comments), + _ignore_whitespace(ignore_whitespace), + _ignore_pp_directives(ignore_pp_directives), + _ignore_line_directives(ignore_line_directives), + _ignore_keywords(ignore_keywords), + _escape_string_literals(escape_string_literals) + { + _cur = _input.data(); + _end = _cur + _input.size(); + } + + lexer(const lexer &lexer) { operator=(lexer); } + lexer &operator=(const lexer &lexer) + { + _input = lexer._input; + _cur_location = lexer._cur_location; + reset_to_offset(lexer._cur - lexer._input.data()); + _end = _input.data() + _input.size(); + _ignore_comments = lexer._ignore_comments; + _ignore_whitespace = lexer._ignore_whitespace; + _ignore_pp_directives = lexer._ignore_pp_directives; + _ignore_keywords = lexer._ignore_keywords; + _escape_string_literals = lexer._escape_string_literals; + _ignore_line_directives = lexer._ignore_line_directives; + + return *this; + } + + /// + /// Gets the current position in the input string. + /// + size_t input_offset() const { return _cur - _input.data(); } + + /// + /// Gets the input string this lexical analyzer works on. + /// + /// Constant reference to the input string. + const std::string &input_string() const { return _input; } + + /// + /// Performs lexical analysis on the input string and return the next token in sequence. + /// + /// Next token from the input string. + token lex(); + + /// + /// Advances to the next token that is not whitespace. + /// + void skip_space(); + /// + /// Advances to the next new line, ignoring all tokens. + /// + void skip_to_next_line(); + + /// + /// Resets position to the specified . + /// + /// Offset in characters from the start of the input string. + void reset_to_offset(size_t offset); + + private: + /// + /// Skips an arbitrary amount of characters in the input string. + /// + /// Number of input characters to skip. + void skip(size_t length); + + void parse_identifier(token &tok) const; + bool parse_pp_directive(token &tok); + void parse_string_literal(token &tok, bool escape); + void parse_numeric_literal(token &tok) const; + + std::string _input; + location _cur_location; + const std::string::value_type *_cur, *_end; + + bool _ignore_comments; + bool _ignore_whitespace; + bool _ignore_pp_directives; + bool _ignore_line_directives; + bool _ignore_keywords; + bool _escape_string_literals; + }; +} diff --git a/dep/reshadefx/include/effect_module.hpp b/dep/reshadefx/include/effect_module.hpp new file mode 100644 index 000000000..ccf2cbe2c --- /dev/null +++ b/dep/reshadefx/include/effect_module.hpp @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_expression.hpp" +#include + +namespace reshadefx +{ + /// + /// A list of supported texture types. + /// + enum class texture_type + { + texture_1d = 1, + texture_2d = 2, + texture_3d = 3 + }; + + /// + /// A list of supported texture formats. + /// + enum class texture_format + { + unknown, + + r8, + r16, + r16f, + r32i, + r32u, + r32f, + rg8, + rg16, + rg16f, + rg32f, + rgba8, + rgba16, + rgba16f, + rgba32f, + rgb10a2 + }; + + /// + /// A filtering type used for texture lookups. + /// + enum class filter_mode + { + min_mag_mip_point = 0, + min_mag_point_mip_linear = 0x1, + min_point_mag_linear_mip_point = 0x4, + min_point_mag_mip_linear = 0x5, + min_linear_mag_mip_point = 0x10, + min_linear_mag_point_mip_linear = 0x11, + min_mag_linear_mip_point = 0x14, + min_mag_mip_linear = 0x15 + }; + + /// + /// Specifies behavior of sampling with texture coordinates outside an image. + /// + enum class texture_address_mode + { + wrap = 1, + mirror = 2, + clamp = 3, + border = 4 + }; + + /// + /// Specifies RGB or alpha blending operations. + /// + enum class pass_blend_op : uint8_t + { + add = 1, + subtract, + reverse_subtract, + min, + max + }; + + /// + /// Specifies blend factors, which modulate values between the pixel shader output and render target. + /// + enum class pass_blend_factor : uint8_t + { + zero = 0, + one = 1, + source_color, + one_minus_source_color, + dest_color, + one_minus_dest_color, + source_alpha, + one_minus_source_alpha, + dest_alpha, + one_minus_dest_alpha + }; + + /// + /// Specifies the stencil operations that can be performed during depth-stencil testing. + /// + enum class pass_stencil_op : uint8_t + { + zero = 0, + keep, + replace, + increment_saturate, + decrement_saturate, + invert, + increment, + decrement + }; + + /// + /// Specifies comparison options for depth-stencil testing. + /// + enum class pass_stencil_func : uint8_t + { + never, + less, + equal, + less_equal, + greater, + not_equal, + greater_equal, + always + }; + + /// + /// Specifies the possible primitives. + /// + enum class primitive_topology : uint8_t + { + point_list = 1, + line_list, + line_strip, + triangle_list, + triangle_strip + }; + + /// + /// A struct type defined in the effect code. + /// + struct struct_info + { + std::string name; + std::string unique_name; + std::vector member_list; + uint32_t definition = 0; + }; + + /// + /// A struct field defined in the effect code. + /// + struct struct_member_info + { + reshadefx::type type; + std::string name; + std::string semantic; + reshadefx::location location; + uint32_t definition = 0; + }; + + /// + /// An annotation attached to a variable. + /// + struct annotation + { + reshadefx::type type; + std::string name; + reshadefx::constant value; + }; + + /// + /// A texture defined in the effect code. + /// + struct texture_info + { + uint32_t id = 0; + uint32_t binding = 0; + std::string name; + std::string semantic; + std::string unique_name; + std::vector annotations; + texture_type type = texture_type::texture_2d; + uint32_t width = 1; + uint32_t height = 1; + uint16_t depth = 1; + uint16_t levels = 1; + texture_format format = texture_format::rgba8; + bool render_target = false; + bool storage_access = false; + }; + + /// + /// A texture sampler defined in the effect code. + /// + struct sampler_info + { + uint32_t id = 0; + uint32_t binding = 0; + uint32_t texture_binding = 0; + std::string name; + reshadefx::type type; + std::string unique_name; + std::string texture_name; + std::vector annotations; + filter_mode filter = filter_mode::min_mag_mip_linear; + texture_address_mode address_u = texture_address_mode::clamp; + texture_address_mode address_v = texture_address_mode::clamp; + texture_address_mode address_w = texture_address_mode::clamp; + float min_lod = -3.402823466e+38f; + float max_lod = +3.402823466e+38f; // FLT_MAX + float lod_bias = 0.0f; + uint8_t srgb = false; + }; + + /// + /// A texture storage object defined in the effect code. + /// + struct storage_info + { + uint32_t id = 0; + uint32_t binding = 0; + std::string name; + reshadefx::type type; + std::string unique_name; + std::string texture_name; + uint16_t level = 0; + }; + + /// + /// An uniform variable defined in the effect code. + /// + struct uniform_info + { + std::string name; + reshadefx::type type; + uint32_t size = 0; + uint32_t offset = 0; + std::vector annotations; + bool has_initializer_value = false; + reshadefx::constant initializer_value; + }; + + /// + /// Type of a shader entry point. + /// + enum class shader_type + { + vs, + ps, + cs, + }; + + /// + /// A shader entry point function. + /// + struct entry_point + { + std::string name; + shader_type type; + }; + + /// + /// A function defined in the effect code. + /// + struct function_info + { + uint32_t definition; + std::string name; + std::string unique_name; + reshadefx::type return_type; + std::string return_semantic; + std::vector parameter_list; + std::unordered_set referenced_samplers; + std::unordered_set referenced_storages; + }; + + /// + /// A render pass with all its state info. + /// + struct pass_info + { + std::string name; + std::string render_target_names[8] = {}; + std::string vs_entry_point; + std::string ps_entry_point; + std::string cs_entry_point; + uint8_t generate_mipmaps = true; + uint8_t clear_render_targets = false; + uint8_t srgb_write_enable = false; + uint8_t blend_enable[8] = { false, false, false, false, false, false, false, false }; + uint8_t stencil_enable = false; + uint8_t color_write_mask[8] = { 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF }; + uint8_t stencil_read_mask = 0xFF; + uint8_t stencil_write_mask = 0xFF; + pass_blend_op blend_op[8] = { pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add }; + pass_blend_op blend_op_alpha[8] = { pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add, pass_blend_op::add }; + pass_blend_factor src_blend[8] = { pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one }; + pass_blend_factor dest_blend[8] = { pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero }; + pass_blend_factor src_blend_alpha[8] = { pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one, pass_blend_factor::one }; + pass_blend_factor dest_blend_alpha[8] = { pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero, pass_blend_factor::zero }; + pass_stencil_func stencil_comparison_func = pass_stencil_func::always; + uint32_t stencil_reference_value = 0; + pass_stencil_op stencil_op_pass = pass_stencil_op::keep; + pass_stencil_op stencil_op_fail = pass_stencil_op::keep; + pass_stencil_op stencil_op_depth_fail = pass_stencil_op::keep; + uint32_t num_vertices = 3; + primitive_topology topology = primitive_topology::triangle_list; + uint32_t viewport_width = 0; + uint32_t viewport_height = 0; + uint32_t viewport_dispatch_z = 1; + std::vector samplers; + std::vector storages; + }; + + /// + /// A collection of passes that make up an effect. + /// + struct technique_info + { + std::string name; + std::vector passes; + std::vector annotations; + }; + + /// + /// In-memory representation of an effect file. + /// + struct module + { + std::vector code; + + std::vector entry_points; + std::vector textures; + std::vector samplers; + std::vector storages; + std::vector uniforms, spec_constants; + std::vector techniques; + + uint32_t total_uniform_size = 0; + uint32_t num_texture_bindings = 0; + uint32_t num_sampler_bindings = 0; + uint32_t num_storage_bindings = 0; + }; +} diff --git a/dep/reshadefx/include/effect_parser.hpp b/dep/reshadefx/include/effect_parser.hpp new file mode 100644 index 000000000..f073b4790 --- /dev/null +++ b/dep/reshadefx/include/effect_parser.hpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_symbol_table.hpp" +#include // std::unique_ptr + +namespace reshadefx +{ + /// + /// A parser for the ReShade FX shader language. + /// + class parser : symbol_table + { + public: + // Define constructor explicitly because lexer class is not included here + parser(); + ~parser(); + + /// + /// Parses the provided input string. + /// + /// String to analyze. + /// Code generation implementation to use. + /// if parsing was successfull, otherwise. + bool parse(std::string source, class codegen *backend); + + /// + /// Gets the list of error messages. + /// + const std::string &errors() const { return _errors; } + + private: + void error(const location &location, unsigned int code, const std::string &message); + void warning(const location &location, unsigned int code, const std::string &message); + + void backup(); + void restore(); + + bool peek(char tok) const { return _token_next.id == static_cast(tok); } + bool peek(tokenid tokid) const { return _token_next.id == tokid; } + void consume(); + void consume_until(char tok) { return consume_until(static_cast(tok)); } + void consume_until(tokenid tokid); + bool accept(char tok) { return accept(static_cast(tok)); } + bool accept(tokenid tokid); + bool expect(char tok) { return expect(static_cast(tok)); } + bool expect(tokenid tokid); + + bool accept_symbol(std::string &identifier, scoped_symbol &symbol); + bool accept_type_class(type &type); + bool accept_type_qualifiers(type &type); + bool accept_unary_op(); + bool accept_postfix_op(); + bool peek_multary_op(unsigned int &precedence) const; + bool accept_assignment_op(); + + void parse_top(bool &parse_success); + bool parse_struct(); + bool parse_function(type type, std::string name); + bool parse_variable(type type, std::string name, bool global = false); + bool parse_technique(); + bool parse_technique_pass(pass_info &info); + bool parse_type(type &type); + bool parse_array_size(type &type); + bool parse_expression(expression &expression); + bool parse_expression_unary(expression &expression); + bool parse_expression_multary(expression &expression, unsigned int precedence = 0); + bool parse_expression_assignment(expression &expression); + bool parse_annotations(std::vector &annotations); + bool parse_statement(bool scoped); + bool parse_statement_block(bool scoped); + + codegen *_codegen = nullptr; + std::string _errors; + + token _token, _token_next, _token_backup; + std::unique_ptr _lexer; + size_t _lexer_backup_offset = 0; + + std::vector _loop_break_target_stack; + std::vector _loop_continue_target_stack; + reshadefx::function_info *_current_function = nullptr; + }; +} diff --git a/dep/reshadefx/include/effect_preprocessor.hpp b/dep/reshadefx/include/effect_preprocessor.hpp new file mode 100644 index 000000000..4cc823555 --- /dev/null +++ b/dep/reshadefx/include/effect_preprocessor.hpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_token.hpp" +#include // std::unique_ptr +#include +#include +#include + +namespace reshadefx +{ + /// + /// A C-style preprocessor implementation. + /// + class preprocessor + { + public: + struct macro + { + std::string replacement_list; + std::vector parameters; + bool is_predefined = false; + bool is_variadic = false; + bool is_function_like = false; + }; + + // Define constructor explicitly because lexer class is not included here + preprocessor(); + ~preprocessor(); + + /// + /// Adds an include directory to the list of search paths used when resolving #include directives. + /// + /// Path to the directory to add. + void add_include_path(const std::filesystem::path &path); + + /// + /// Adds a new macro definition. This is equal to appending '#define name macro' to this preprocessor instance. + /// + /// Name of the macro to define. + /// Definition of the macro function or value. + /// + bool add_macro_definition(const std::string &name, const macro ¯o); + /// + /// Adds a new macro value definition. This is equal to appending '#define name macro' to this preprocessor instance. + /// + /// Name of the macro to define. + /// Value to define that macro to. + /// + bool add_macro_definition(const std::string &name, std::string value = "1") + { + return add_macro_definition(name, macro { std::move(value), {}, true }); + } + + /// + /// Opens the specified file, parses its contents and appends them to the output. + /// + /// Path to the file to parse. + /// if parsing was successful, otherwise. + bool append_file(const std::filesystem::path &path); + /// + /// Parses the specified string and appends it to the output. + /// + /// String to parse. + /// Optional file path to identify this string with. + /// if parsing was successful, otherwise. + bool append_string(std::string source_code, const std::filesystem::path &path = std::filesystem::path()); + + /// + /// Gets the list of error messages. + /// + const std::string &errors() const { return _errors; } + /// + /// Gets the current pre-processed output string. + /// + const std::string &output() const { return _output; } + + /// + /// Gets a list of all included files. + /// + std::vector included_files() const; + + /// + /// Gets a list of all defines that were used in #ifdef and #ifndef lines. + /// + std::vector> used_macro_definitions() const; + + /// + /// Gets a list of pragma directives that occured. + /// + std::vector> used_pragma_directives() const { return _used_pragmas; } + + private: + struct if_level + { + bool value; + bool skipping; + token pp_token; + size_t input_index; + }; + struct input_level + { + std::string name; + std::unique_ptr lexer; + token next_token; + std::unordered_set hidden_macros; + }; + + void error(const location &location, const std::string &message); + void warning(const location &location, const std::string &message); + + void push(std::string input, const std::string &name = std::string()); + + bool peek(tokenid tokid) const; + void consume(); + void consume_until(tokenid tokid); + bool accept(tokenid tokid, bool ignore_whitespace = true); + bool expect(tokenid tokid); + + void parse(); + void parse_def(); + void parse_undef(); + void parse_if(); + void parse_ifdef(); + void parse_ifndef(); + void parse_elif(); + void parse_else(); + void parse_endif(); + void parse_error(); + void parse_warning(); + void parse_pragma(); + void parse_include(); + + bool evaluate_expression(); + bool evaluate_identifier_as_macro(); + + bool is_defined(const std::string &name) const; + void expand_macro(const std::string &name, const macro ¯o, const std::vector &arguments); + void create_macro_replacement_list(macro ¯o); + + bool _success = true; + std::string _output, _errors; + + std::string _current_token_raw_data; + reshadefx::token _token; + location _output_location; + std::vector _input_stack; + size_t _next_input_index = 0; + size_t _current_input_index = 0; + + std::vector _if_stack; + + unsigned short _recursion_count = 0; + std::unordered_set _used_macros; + std::unordered_map _macros; + + std::vector _include_paths; + std::unordered_map _file_cache; + + std::vector> _used_pragmas; + }; +} diff --git a/dep/reshadefx/include/effect_symbol_table.hpp b/dep/reshadefx/include/effect_symbol_table.hpp new file mode 100644 index 000000000..37c8d78c6 --- /dev/null +++ b/dep/reshadefx/include/effect_symbol_table.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include "effect_module.hpp" +#include // Used for symbol lookup table + +namespace reshadefx +{ + /// + /// A scope encapsulating symbols. + /// + struct scope + { + std::string name; + uint32_t level, namespace_level; + }; + + /// + /// Enumeration of all possible symbol types. + /// + enum class symbol_type + { + invalid, + variable, + constant, + function, + intrinsic, + structure, + }; + + /// + /// A single symbol in the symbol table. + /// + struct symbol + { + symbol_type op = symbol_type::invalid; + uint32_t id = 0; + reshadefx::type type = {}; + reshadefx::constant constant = {}; + const reshadefx::function_info *function = nullptr; + }; + struct scoped_symbol : symbol + { + struct scope scope; // Store scope together with symbol data + }; + + /// + /// A symbol table managing a list of scopes and symbols. + /// + class symbol_table + { + public: + symbol_table(); + + /// + /// Enters a new scope as child of the current one. + /// + void enter_scope(); + /// + /// Enters a new namespace as child of the current one. + /// + void enter_namespace(const std::string &name); + /// + /// Leaves the current scope and enter the parent one. + /// + void leave_scope(); + /// + /// Leaves the current namespace and enter the parent one. + /// + void leave_namespace(); + + /// + /// Gets the current scope the symbol table operates in. + /// + const scope ¤t_scope() const { return _current_scope; } + + /// + /// Inserts an new symbol in the symbol table. + /// Returns if a symbol by that name and type already exists. + /// + bool insert_symbol(const std::string &name, const symbol &symbol, bool global = false); + + /// + /// Looks for an existing symbol with the specified . + /// + scoped_symbol find_symbol(const std::string &name) const; + scoped_symbol find_symbol(const std::string &name, const scope &scope, bool exclusive) const; + + /// + /// Searches for the best function or intrinsic overload matching the argument list. + /// + bool resolve_function_call(const std::string &name, const std::vector &args, const scope &scope, symbol &data, bool &ambiguous) const; + + private: + scope _current_scope; + // Lookup table from name to matching symbols + std::unordered_map> _symbol_stack; + }; +} diff --git a/dep/reshadefx/include/effect_token.hpp b/dep/reshadefx/include/effect_token.hpp new file mode 100644 index 000000000..072d4398a --- /dev/null +++ b/dep/reshadefx/include/effect_token.hpp @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +namespace reshadefx +{ + /// + /// Structure which keeps track of a code location. + /// + struct location + { + location() : line(1), column(1) {} + explicit location(uint32_t line, uint32_t column = 1) : line(line), column(column) {} + explicit location(std::string source, uint32_t line, uint32_t column = 1) : source(std::move(source)), line(line), column(column) {} + + std::string source; + uint32_t line, column; + }; + + /// + /// A collection of identifiers for various possible tokens. + /// + enum class tokenid + { + unknown = -1, + end_of_file = 0, + end_of_line = '\n', + + // operators + space = ' ', + exclaim = '!', + hash = '#', + dollar = '$', + percent = '%', + ampersand = '&', + parenthesis_open = '(', + parenthesis_close = ')', + star = '*', + plus = '+', + comma = ',', + minus = '-', + dot = '.', + slash = '/', + colon = ':', + semicolon = ';', + less = '<', + equal = '=', + greater = '>', + question = '?', + at = '@', + bracket_open = '[', + backslash = '\\', + bracket_close = ']', + caret = '^', + brace_open = '{', + pipe = '|', + brace_close = '}', + tilde = '~', + exclaim_equal = 256 /* != */, + percent_equal /* %= */, + ampersand_ampersand /* && */, + ampersand_equal /* &= */, + star_equal /* *= */, + plus_plus /* ++*/, + plus_equal /* += */, + minus_minus /* -- */, + minus_equal /* -= */, + arrow /* -> */, + ellipsis /* ... */, + slash_equal /* /= */, + colon_colon /* :: */, + less_less_equal /* <<= */, + less_less /* << */, + less_equal /* <= */, + equal_equal /* == */, + greater_greater_equal /* >>= */, + greater_greater /* >> */, + greater_equal /* >= */, + caret_equal /* ^= */, + pipe_equal /* |= */, + pipe_pipe /* || */, + + // identifiers + reserved, + identifier, + + // literals + true_literal, + false_literal, + int_literal, + uint_literal, + float_literal, + double_literal, + string_literal, + + // keywords + namespace_, + struct_, + technique, + pass, + for_, + while_, + do_, + if_, + else_, + switch_, + case_, + default_, + break_, + continue_, + return_, + discard_, + extern_, + static_, + uniform_, + volatile_, + precise, + groupshared, + in, + out, + inout, + const_, + linear, + noperspective, + centroid, + nointerpolation, + + void_, + bool_, + bool2, + bool3, + bool4, + bool2x2, + bool2x3, + bool2x4, + bool3x2, + bool3x3, + bool3x4, + bool4x2, + bool4x3, + bool4x4, + int_, + int2, + int3, + int4, + int2x2, + int2x3, + int2x4, + int3x2, + int3x3, + int3x4, + int4x2, + int4x3, + int4x4, + min16int, + min16int2, + min16int3, + min16int4, + uint_, + uint2, + uint3, + uint4, + uint2x2, + uint2x3, + uint2x4, + uint3x2, + uint3x3, + uint3x4, + uint4x2, + uint4x3, + uint4x4, + min16uint, + min16uint2, + min16uint3, + min16uint4, + float_, + float2, + float3, + float4, + float2x2, + float2x3, + float2x4, + float3x2, + float3x3, + float3x4, + float4x2, + float4x3, + float4x4, + min16float, + min16float2, + min16float3, + min16float4, + vector, + matrix, + string_, + texture1d, + texture2d, + texture3d, + sampler1d, + sampler2d, + sampler3d, + storage1d, + storage2d, + storage3d, + + // preprocessor directives + hash_def, + hash_undef, + hash_if, + hash_ifdef, + hash_ifndef, + hash_else, + hash_elif, + hash_endif, + hash_error, + hash_warning, + hash_pragma, + hash_include, + hash_unknown, + + single_line_comment, + multi_line_comment, + }; + + /// + /// A structure describing a single token in the input string. + /// + struct token + { + tokenid id; + reshadefx::location location; + size_t offset, length; + union + { + int literal_as_int; + unsigned int literal_as_uint; + float literal_as_float; + double literal_as_double; + }; + std::string literal_as_string; + + inline operator tokenid() const { return id; } + + static std::string id_to_name(tokenid id); + }; +} diff --git a/dep/reshadefx/reshadefx.vcxproj b/dep/reshadefx/reshadefx.vcxproj new file mode 100644 index 000000000..67d8dec14 --- /dev/null +++ b/dep/reshadefx/reshadefx.vcxproj @@ -0,0 +1,39 @@ + + + + + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TurnOffAllWarnings + $(ProjectDir)src;$(ProjectDir)include;$(ProjectDir)..\spirv-cross\include\spirv-cross;%(AdditionalIncludeDirectories) + + + + \ No newline at end of file diff --git a/dep/reshadefx/reshadefx.vcxproj.filters b/dep/reshadefx/reshadefx.vcxproj.filters new file mode 100644 index 000000000..4f714429b --- /dev/null +++ b/dep/reshadefx/reshadefx.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dep/reshadefx/src/effect_codegen_glsl.cpp b/dep/reshadefx/src/effect_codegen_glsl.cpp new file mode 100644 index 000000000..a84f48104 --- /dev/null +++ b/dep/reshadefx/src/effect_codegen_glsl.cpp @@ -0,0 +1,2151 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include // signbit, isinf, isnan +#include // snprintf +#include +#include // std::find_if, std::max +#include + +using namespace reshadefx; + +class codegen_glsl final : public codegen +{ +public: + codegen_glsl(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y) + : _debug_info(debug_info), _vulkan_semantics(vulkan_semantics), _uniforms_to_spec_constants(uniforms_to_spec_constants), _enable_16bit_types(enable_16bit_types), _flip_vert_y(flip_vert_y) + { + // Create default block and reserve a memory block to avoid frequent reallocations + std::string &block = _blocks.emplace(0, std::string()).first->second; + block.reserve(8192); + } + +private: + enum class naming + { + // After escaping, name should already be unique, so no additional steps are taken + unique, + // After escaping, will be numbered when clashing with another name + general, + // This is a special name that is not modified and should be unique + reserved, + // Replace name with a code snippet + expression, + }; + + std::string _ubo_block; + std::string _compute_block; + std::unordered_map _names; + std::unordered_map _blocks; + bool _debug_info = false; + bool _vulkan_semantics = false; + bool _uniforms_to_spec_constants = false; + bool _enable_16bit_types = false; + bool _flip_vert_y = false; + bool _enable_control_flow_attributes = false; + std::unordered_map _remapped_sampler_variables; + std::unordered_map _semantic_to_location; + + // Only write compatibility intrinsics to result if they are actually in use + bool _uses_fmod = false; + bool _uses_componentwise_or = false; + bool _uses_componentwise_and = false; + bool _uses_componentwise_cond = false; + + void write_result(module &module) override + { + module = std::move(_module); + + std::string preamble; + + if (_enable_16bit_types) + // GL_NV_gpu_shader5, GL_AMD_gpu_shader_half_float or GL_EXT_shader_16bit_storage + preamble += "#extension GL_NV_gpu_shader5 : require\n"; + if (_enable_control_flow_attributes) + preamble += "#extension GL_EXT_control_flow_attributes : enable\n"; + + if (_uses_fmod) + preamble += "float fmodHLSL(float x, float y) { return x - y * trunc(x / y); }\n" + "vec2 fmodHLSL(vec2 x, vec2 y) { return x - y * trunc(x / y); }\n" + "vec3 fmodHLSL(vec3 x, vec3 y) { return x - y * trunc(x / y); }\n" + "vec4 fmodHLSL(vec4 x, vec4 y) { return x - y * trunc(x / y); }\n" + "mat2 fmodHLSL(mat2 x, mat2 y) { return x - matrixCompMult(y, mat2(trunc(x[0] / y[0]), trunc(x[1] / y[1]))); }\n" + "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" + "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"; + if (_uses_componentwise_or) + preamble += + "bvec2 compOr(bvec2 a, bvec2 b) { return bvec2(a.x || b.x, a.y || b.y); }\n" + "bvec3 compOr(bvec3 a, bvec3 b) { return bvec3(a.x || b.x, a.y || b.y, a.z || b.z); }\n" + "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"; + if (_uses_componentwise_and) + preamble += + "bvec2 compAnd(bvec2 a, bvec2 b) { return bvec2(a.x && b.x, a.y && b.y); }\n" + "bvec3 compAnd(bvec3 a, bvec3 b) { return bvec3(a.x && b.x, a.y && b.y, a.z && b.z); }\n" + "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"; + if (_uses_componentwise_cond) + preamble += + "vec2 compCond(bvec2 cond, vec2 a, vec2 b) { return vec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n" + "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" + "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" + "ivec2 compCond(bvec2 cond, ivec2 a, ivec2 b) { return ivec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n" + "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" + "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" + "uvec2 compCond(bvec2 cond, uvec2 a, uvec2 b) { return uvec2(cond.x ? a.x : b.x, cond.y ? a.y : b.y); }\n" + "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" + "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"; + + if (!_ubo_block.empty()) + // Read matrices in column major layout, even though they are actually row major, to avoid transposing them on every access (since GLSL uses column matrices) + // TODO: This technically only works with square matrices + preamble += "layout(std140, column_major, binding = 0) uniform _Globals {\n" + _ubo_block + "};\n"; + + module.code.assign(preamble.begin(), preamble.end()); + + const std::string &main_block = _blocks.at(0); + module.code.insert(module.code.end(), main_block.begin(), main_block.end()); + } + + template + void write_type(std::string &s, const type &type) const + { + if constexpr (is_decl) + { + // Global variables are implicitly 'static' in GLSL, so the keyword does not exist + if (type.has(type::q_precise)) + s += "precise "; + if (type.has(type::q_groupshared)) + s += "shared "; + } + + if constexpr (is_interface) + { + if (type.has(type::q_linear)) + s += "smooth "; + if (type.has(type::q_noperspective)) + s += "noperspective "; + if (type.has(type::q_centroid)) + s += "centroid "; + if (type.has(type::q_nointerpolation)) + s += "flat "; + } + + if constexpr (is_interface || is_param) + { + if (type.has(type::q_inout)) + s += "inout "; + else if (type.has(type::q_in)) + s += "in "; + else if (type.has(type::q_out)) + s += "out "; + } + + switch (type.base) + { + case type::t_void: + s += "void"; + break; + case type::t_bool: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "bvec" + std::to_string(type.rows); + else + s += "bool"; + break; + case type::t_min16int: + if (_enable_16bit_types) + { + assert(type.cols == 1); + if (type.rows > 1) + s += "i16vec" + std::to_string(type.rows); + else + s += "int16_t"; + break; + } + else if constexpr (is_decl) + s += "mediump "; + [[fallthrough]]; + case type::t_int: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "ivec" + std::to_string(type.rows); + else + s += "int"; + break; + case type::t_min16uint: + if (_enable_16bit_types) + { + assert(type.cols == 1); + if (type.rows > 1) + s += "u16vec" + std::to_string(type.rows); + else + s += "uint16_t"; + break; + } + else if constexpr (is_decl) + s += "mediump "; + [[fallthrough]]; + case type::t_uint: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "uvec" + std::to_string(type.rows); + else + s += "uint"; + break; + case type::t_min16float: + if (_enable_16bit_types) + { + assert(type.cols == 1); + if (type.rows > 1) + s += "f16vec" + std::to_string(type.rows); + else + s += "float16_t"; + break; + } + else if constexpr (is_decl) + s += "mediump "; + [[fallthrough]]; + case type::t_float: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "vec" + std::to_string(type.rows); + else + s += "float"; + break; + case type::t_struct: + s += id_to_name(type.definition); + break; + case type::t_sampler1d_int: + s += "isampler1D"; + break; + case type::t_sampler2d_int: + s += "isampler2D"; + break; + case type::t_sampler3d_int: + s += "isampler3D"; + break; + case type::t_sampler1d_uint: + s += "usampler1D"; + break; + case type::t_sampler3d_uint: + s += "usampler3D"; + break; + case type::t_sampler2d_uint: + s += "usampler2D"; + break; + case type::t_sampler1d_float: + s += "sampler1D"; + break; + case type::t_sampler2d_float: + s += "sampler2D"; + break; + case type::t_sampler3d_float: + s += "sampler3D"; + break; + case type::t_storage1d_int: + if constexpr (is_param) + s += "writeonly "; + s += "iimage1D"; + break; + case type::t_storage2d_int: + if constexpr (is_param) + s += "writeonly "; + s += "iimage2D"; + break; + case type::t_storage3d_int: + if constexpr (is_param) + s += "writeonly "; + s += "iimage3D"; + break; + case type::t_storage1d_uint: + if constexpr (is_param) + s += "writeonly "; + s += "uimage1D"; + break; + case type::t_storage2d_uint: + if constexpr (is_param) + s += "writeonly "; + s += "uimage2D"; + break; + case type::t_storage3d_uint: + if constexpr (is_param) + s += "writeonly "; + s += "uimage3D"; + break; + case type::t_storage1d_float: + if constexpr (is_param) + s += "writeonly "; + s += "image1D"; + break; + case type::t_storage2d_float: + if 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 there + s += "writeonly "; + s += "image2D"; + break; + case type::t_storage3d_float: + if constexpr (is_param) + s += "writeonly "; + s += "image3D"; + break; + default: + assert(false); + } + } + void write_constant(std::string &s, const type &type, const constant &data) const + { + if (type.is_array()) + { + auto elem_type = type; + elem_type.array_length = 0; + + write_type(s, elem_type); + s += '[' + std::to_string(type.array_length) + "]("; + + for (int i = 0; i < type.array_length; ++i) + { + write_constant(s, elem_type, i < static_cast(data.array_data.size()) ? data.array_data[i] : constant()); + + if (i < type.array_length - 1) + s += ", "; + } + + s += ')'; + return; + } + + // There can only be numeric constants + assert(type.is_numeric()); + + if (!type.is_scalar()) + write_type(s, type), s += '('; + + for (unsigned int i = 0, components = type.components(); i < components; ++i) + { + switch (type.base) + { + case type::t_bool: + s += data.as_uint[i] ? "true" : "false"; + break; + case type::t_min16int: + case type::t_int: + s += std::to_string(data.as_int[i]); + break; + case type::t_min16uint: + case type::t_uint: + s += std::to_string(data.as_uint[i]) + 'u'; + break; + case type::t_min16float: + case type::t_float: + if (std::isnan(data.as_float[i])) { + s += "0.0/0.0/*nan*/"; + break; + } + if (std::isinf(data.as_float[i])) { + s += std::signbit(data.as_float[i]) ? "1.0/0.0/*inf*/" : "-1.0/0.0/*-inf*/"; + break; + } + char temp[64]; // Will be null-terminated by snprintf + std::snprintf(temp, sizeof(temp), "%1.8e", data.as_float[i]); + s += temp; + break; + default: + assert(false); + } + + if (i < components - 1) + s += ", "; + } + + if (!type.is_scalar()) + s += ')'; + } + void write_location(std::string &s, const location &loc) const + { + if (loc.source.empty() || !_debug_info) + return; + + s += "#line " + std::to_string(loc.line) + '\n'; + } + void write_texture_format(std::string &s, texture_format format) + { + switch (format) + { + case texture_format::r8: + s += "r8"; + break; + case texture_format::r16: + s += "r16"; + break; + case texture_format::r16f: + s += "r16f"; + break; + case texture_format::r32i: + s += "r32i"; + break; + case texture_format::r32u: + s += "r32u"; + break; + case texture_format::r32f: + s += "r32f"; + break; + case texture_format::rg8: + s += "rg8"; + break; + case texture_format::rg16: + s += "rg16"; + break; + case texture_format::rg16f: + s += "rg16f"; + break; + case texture_format::rg32f: + s += "rg32f"; + break; + case texture_format::rgba8: + s += "rgba8"; + break; + case texture_format::rgba16: + s += "rgba16"; + break; + case texture_format::rgba16f: + s += "rgba16f"; + break; + case texture_format::rgba32f: + s += "rgba32f"; + break; + case texture_format::rgb10a2: + s += "rgb10_a2"; + break; + default: + assert(false); + } + } + + std::string id_to_name(id id) const + { + if (const auto it = _remapped_sampler_variables.find(id); + it != _remapped_sampler_variables.end()) + id = it->second; + + assert(id != 0); + if (const auto names_it = _names.find(id); + names_it != _names.end()) + return names_it->second; + return '_' + std::to_string(id); + } + + template + void define_name(const id id, std::string name) + { + assert(!name.empty()); + if constexpr (naming_type != naming::expression) + if (name[0] == '_') + return; // Filter out names that may clash with automatic ones + if constexpr (naming_type != naming::reserved) + name = escape_name(std::move(name)); + if constexpr (naming_type == naming::general) + if (std::find_if(_names.begin(), _names.end(), [&name](const auto &it) { return it.second == name; }) != _names.end()) + name += '_' + std::to_string(id); // Append a numbered suffix if the name already exists + _names[id] = std::move(name); + } + + uint32_t semantic_to_location(const std::string &semantic, uint32_t max_array_length = 1) + { + if (semantic.compare(0, 5, "COLOR") == 0) + return std::strtoul(semantic.c_str() + 5, nullptr, 10); + if (semantic.compare(0, 9, "SV_TARGET") == 0) + return std::strtoul(semantic.c_str() + 9, nullptr, 10); + + if (const auto it = _semantic_to_location.find(semantic); + it != _semantic_to_location.end()) + return it->second; + + // Extract the semantic index from the semantic name (e.g. 2 for "TEXCOORD2") + size_t digit_index = semantic.size() - 1; + while (digit_index != 0 && semantic[digit_index] >= '0' && semantic[digit_index] <= '9') + digit_index--; + digit_index++; + + const uint32_t semantic_digit = std::strtoul(semantic.c_str() + digit_index, nullptr, 10); + const std::string semantic_base = semantic.substr(0, digit_index); + + uint32_t location = static_cast(_semantic_to_location.size()); + + // Now create adjoining location indices for all possible semantic indices belonging to this semantic name + for (uint32_t a = 0; a < semantic_digit + max_array_length; ++a) + { + const auto insert = _semantic_to_location.emplace(semantic_base + std::to_string(a), location + a); + if (!insert.second) + { + assert(a == 0 || (insert.first->second - a) == location); + + // Semantic was already created with a different location index, so need to remap to that + location = insert.first->second - a; + } + } + + return location + semantic_digit; + } + + std::string escape_name(std::string name) const + { + static const std::unordered_set s_reserverd_names = { + "common", "partition", "input", "output", "active", "filter", "superp", "invariant", + "attribute", "varying", "buffer", "resource", "coherent", "readonly", "writeonly", + "layout", "flat", "smooth", "lowp", "mediump", "highp", "precision", "patch", "subroutine", + "atomic_uint", "fixed", + "vec2", "vec3", "vec4", "ivec2", "dvec2", "dvec3", "dvec4", "ivec3", "ivec4", "uvec2", "uvec3", "uvec4", "bvec2", "bvec3", "bvec4", "fvec2", "fvec3", "fvec4", "hvec2", "hvec3", "hvec4", + "mat2", "mat3", "mat4", "dmat2", "dmat3", "dmat4", "mat2x2", "mat2x3", "mat2x4", "dmat2x2", "dmat2x3", "dmat2x4", "mat3x2", "mat3x3", "mat3x4", "dmat3x2", "dmat3x3", "dmat3x4", "mat4x2", "mat4x3", "mat4x4", "dmat4x2", "dmat4x3", "dmat4x4", + "sampler1DShadow", "sampler1DArrayShadow", "isampler1D", "isampler1DArray", "usampler1D", "usampler1DArray", + "sampler2DShadow", "sampler2DArrayShadow", "isampler2D", "isampler2DArray", "usampler2D", "usampler2DArray", "sampler2DRect", "sampler2DRectShadow", "isampler2DRect", "usampler2DRect", "isampler2DMS", "usampler2DMS", "isampler2DMSArray", "usampler2DMSArray", + "isampler3D", "usampler3D", "sampler3DRect", + "samplerCubeShadow", "samplerCubeArrayShadow", "isamplerCube", "isamplerCubeArray", "usamplerCube", "usamplerCubeArray", + "samplerBuffer", "isamplerBuffer", "usamplerBuffer", + "image1D", "iimage1D", "uimage1D", "image1DArray", "iimage1DArray", "uimage1DArray", + "image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", "image2DRect", "iimage2DRect", "uimage2DRect", "image2DMS", "iimage2DMS", "uimage2DMS", "image2DMSArray", "iimage2DMSArray", "uimage2DMSArray", + "image3D", "iimage3D", "uimage3D", + "imageCube", "iimageCube", "uimageCube", "imageCubeArray", "iimageCubeArray", "uimageCubeArray", + "imageBuffer", "iimageBuffer", "uimageBuffer", + "abs", "sign", "all", "any", "sin", "sinh", "cos", "cosh", "tan", "tanh", "asin", "acos", "atan", + "exp", "exp2", "log", "log2", "sqrt", "inversesqrt", "ceil", "floor", "fract", "trunc", "round", + "radians", "degrees", "length", "normalize", "transpose", "determinant", "intBitsToFloat", "uintBitsToFloat", + "floatBitsToInt", "floatBitsToUint", "matrixCompMult", "not", "lessThan", "greaterThan", "lessThanEqual", + "greaterThanEqual", "equal", "notEqual", "dot", "cross", "distance", "pow", "modf", "frexp", "ldexp", + "min", "max", "step", "reflect", "texture", "textureOffset", "fma", "mix", "clamp", "smoothstep", "refract", + "faceforward", "textureLod", "textureLodOffset", "texelFetch", "main" + }; + + // Escape reserved names so that they do not fail to compile + if (name.compare(0, 3, "gl_") == 0 || s_reserverd_names.count(name)) + // Append an underscore at start instead of the end, since another one may get added in 'define_name' when there is a suffix + // This is guaranteed to not clash with user defined names, since those starting with an underscore are filtered out in 'define_name' + name = '_' + name; + + // Remove duplicated underscore symbols from name which can occur due to namespaces but are not allowed in GLSL + for (size_t pos = 0; (pos = name.find("__", pos)) != std::string::npos;) + name.replace(pos, 2, "_"); + + return name; + } + std::string semantic_to_builtin(std::string name, const std::string &semantic, shader_type stype) const + { + if (semantic == "SV_POSITION") + return stype == shader_type::ps ? "gl_FragCoord" : "gl_Position"; + if (semantic == "SV_POINTSIZE") + return "gl_PointSize"; + if (semantic == "SV_DEPTH") + return "gl_FragDepth"; + if (semantic == "SV_VERTEXID") + return _vulkan_semantics ? "gl_VertexIndex" : "gl_VertexID"; + if (semantic == "SV_ISFRONTFACE") + return "gl_FrontFacing"; + if (semantic == "SV_GROUPID") + return "gl_WorkGroupID"; + if (semantic == "SV_GROUPINDEX") + return "gl_LocalInvocationIndex"; + if (semantic == "SV_GROUPTHREADID") + return "gl_LocalInvocationID"; + if (semantic == "SV_DISPATCHTHREADID") + return "gl_GlobalInvocationID"; + + return escape_name(std::move(name)); + } + + static void increase_indentation_level(std::string &block) + { + if (block.empty()) + return; + + for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3) + block.replace(pos, 2, "\n\t\t"); + + block.insert(block.begin(), '\t'); + } + + id define_struct(const location &loc, struct_info &info) override + { + info.definition = make_id(); + define_name(info.definition, info.unique_name); + + _structs.push_back(info); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "struct " + id_to_name(info.definition) + "\n{\n"; + + for (const struct_member_info &member : info.member_list) + { + code += '\t'; + write_type(code, member.type); // GLSL does not allow interpolation attributes on struct members + code += ' '; + code += escape_name(member.name); + if (member.type.is_array()) + code += '[' + std::to_string(member.type.array_length) + ']'; + code += ";\n"; + } + + if (info.member_list.empty()) + code += "float _dummy;\n"; + + code += "};\n"; + + return info.definition; + } + id define_texture(const location &, texture_info &info) override + { + info.id = make_id(); + info.binding = ~0u; + + _module.textures.push_back(info); + + return info.id; + } + id define_sampler(const location &loc, const texture_info &, sampler_info &info) override + { + info.id = make_id(); + info.binding = _module.num_sampler_bindings++; + info.texture_binding = ~0u; // Unset texture bindings + + define_name(info.id, info.unique_name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "layout(binding = " + std::to_string(info.binding); + code += ") uniform "; + write_type(code, info.type); + code += ' ' + id_to_name(info.id) + ";\n"; + + _module.samplers.push_back(info); + + return info.id; + } + id define_storage(const location &loc, const texture_info &tex_info, storage_info &info) override + { + info.id = make_id(); + info.binding = _module.num_storage_bindings++; + + define_name(info.id, info.unique_name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "layout(binding = " + std::to_string(info.binding) + ", "; + write_texture_format(code, tex_info.format); + code += ") uniform "; + write_type(code, info.type); + code += ' ' + id_to_name(info.id) + ";\n"; + + _module.storages.push_back(info); + + return info.id; + } + id define_uniform(const location &loc, uniform_info &info) override + { + const id res = make_id(); + + define_name(res, info.name); + + if (_uniforms_to_spec_constants && info.has_initializer_value) + { + info.size = info.type.components() * 4; + if (info.type.is_array()) + info.size *= info.type.array_length; + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + assert(!info.type.has(type::q_static) && !info.type.has(type::q_const)); + + code += "const "; + write_type(code, info.type); + code += ' ' + id_to_name(res) + " = "; + if (!info.type.is_scalar()) + write_type(code, info.type); + code += "(SPEC_CONSTANT_" + info.name + ");\n"; + + _module.spec_constants.push_back(info); + } + else + { + // GLSL specification on std140 layout: + // 1. If the member is a scalar consuming N basic machine units, the base alignment is N. + // 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. + // 3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N. + // 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, + // according to rules (1), (2), and (3), and rounded up to the base alignment of a four-component vector. + // 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). + // 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). + uint32_t alignment = (info.type.rows == 3 ? 4 /* (3) */ : info.type.rows /* (2)*/) * 4 /* (1)*/; + info.size = info.type.rows * 4; + + if (info.type.is_matrix()) + { + alignment = 16 /* (4) */; + info.size = info.type.rows * alignment /* (7), (8) */; + } + if (info.type.is_array()) + { + alignment = 16 /* (4) */; + info.size = align_up(info.size, alignment) * info.type.array_length; + } + + // Adjust offset according to alignment rules from above + info.offset = _module.total_uniform_size; + info.offset = align_up(info.offset, alignment); + _module.total_uniform_size = info.offset + info.size; + + write_location(_ubo_block, loc); + + _ubo_block += '\t'; + // Note: All matrices are floating-point, even if the uniform type says different!! + write_type(_ubo_block, info.type); + _ubo_block += ' ' + id_to_name(res); + + if (info.type.is_array()) + _ubo_block += '[' + std::to_string(info.type.array_length) + ']'; + + _ubo_block += ";\n"; + + _module.uniforms.push_back(info); + } + + return res; + } + id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override + { + const id res = make_id(); + + // GLSL does not allow local sampler variables, so try to remap those + if (!global && type.is_sampler()) + return (_remapped_sampler_variables[res] = 0), res; + + if (!name.empty()) + define_name(res, name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + if (!global) + code += '\t'; + + if (initializer_value != 0 && type.has(type::q_const)) + code += "const "; + + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + if (initializer_value != 0) + code += " = " + id_to_name(initializer_value); + + code += ";\n"; + + return res; + } + id define_function(const location &loc, function_info &info) override + { + return define_function(loc, info, false); + } + + id define_function(const location &loc, function_info &info, bool is_entry_point) + { + info.definition = make_id(); + + // Name is used in other places like the "ENTRY_POINT" defines, so escape it here + info.unique_name = escape_name(info.unique_name); + + if (!is_entry_point) + define_name(info.definition, info.unique_name); + else + define_name(info.definition, "main"); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + write_type(code, info.return_type); + code += ' ' + id_to_name(info.definition) + '('; + + assert(info.parameter_list.empty() || !is_entry_point); + + for (size_t i = 0, num_params = info.parameter_list.size(); i < num_params; ++i) + { + auto ¶m = info.parameter_list[i]; + + param.definition = make_id(); + define_name(param.definition, param.name); + + code += '\n'; + write_location(code, param.location); + code += '\t'; + write_type(code, param.type); // GLSL does not allow interpolation attributes on function parameters + code += ' ' + id_to_name(param.definition); + + if (param.type.is_array()) + code += '[' + std::to_string(param.type.array_length) + ']'; + + if (i < num_params - 1) + code += ','; + } + + code += ")\n"; + + _functions.push_back(std::make_unique(info)); + + return info.definition; + } + + void define_entry_point(function_info &func, shader_type stype, int num_threads[3]) override + { + // Modify entry point name so each thread configuration is made separate + if (stype == shader_type::cs) + func.unique_name = 'E' + func.unique_name + + '_' + std::to_string(num_threads[0]) + + '_' + std::to_string(num_threads[1]) + + '_' + std::to_string(num_threads[2]); + + if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), + [&func](const auto &ep) { return ep.name == func.unique_name; }); + it != _module.entry_points.end()) + return; + + _module.entry_points.push_back({ func.unique_name, stype }); + + _blocks.at(0) += "#ifdef ENTRY_POINT_" + func.unique_name + '\n'; + if (stype == shader_type::cs) + _blocks.at(0) += "layout(local_size_x = " + std::to_string(num_threads[0]) + + ", local_size_y = " + std::to_string(num_threads[1]) + + ", local_size_z = " + std::to_string(num_threads[2]) + ") in;\n"; + + function_info entry_point; + entry_point.return_type = { type::t_void }; + + std::unordered_map semantic_to_varying_variable; + + const auto create_varying_variable = [this, stype, &semantic_to_varying_variable](type type, unsigned int extra_qualifiers, const std::string &name, const std::string &semantic) { + // Skip built in variables + if (!semantic_to_builtin(std::string(), semantic, stype).empty()) + return; + + // Do not create multiple input/output variables for duplicate semantic usage (since every input/output location may only be defined once in GLSL) + if ((extra_qualifiers & type::q_in) != 0 && + !semantic_to_varying_variable.emplace(semantic, name).second) + return; + + type.qualifiers |= extra_qualifiers; + assert((type.has(type::q_in) || type.has(type::q_out)) && !type.has(type::q_inout)); + + // OpenGL does not allow varying of type boolean + if (type.is_boolean()) + type.base = type::t_float; + + std::string &code = _blocks.at(_current_block); + + const int array_length = std::max(1, type.array_length); + const uint32_t location = semantic_to_location(semantic, array_length); + + for (int a = 0; a < array_length; ++a) + { + code += "layout(location = " + std::to_string(location + a) + ") "; + write_type(code, type); + code += ' '; + code += escape_name(type.is_array() ? + name + '_' + std::to_string(a) : + name); + code += ";\n"; + } + }; + + // Translate function parameters to input/output variables + if (func.return_type.is_struct()) + { + const struct_info &definition = get_struct(func.return_type.definition); + + for (const struct_member_info &member : definition.member_list) + create_varying_variable(member.type, type::q_out, "_return_" + member.name, member.semantic); + } + else if (!func.return_type.is_void()) + { + create_varying_variable(func.return_type, type::q_out, "_return", func.return_semantic); + } + + const auto num_params = func.parameter_list.size(); + for (size_t i = 0; i < num_params; ++i) + { + type param_type = func.parameter_list[i].type; + param_type.qualifiers &= ~type::q_inout; + + // Create separate input/output variables for "inout" parameters (since "inout" is not valid on those in GLSL) + if (func.parameter_list[i].type.has(type::q_in)) + { + // Flatten structure parameters + if (param_type.is_struct()) + { + const struct_info &definition = get_struct(param_type.definition); + + for (int a = 0, array_length = std::max(1, param_type.array_length); a < array_length; a++) + for (const struct_member_info &member : definition.member_list) + create_varying_variable(member.type, param_type.qualifiers | type::q_in, "_in_param" + std::to_string(i) + '_' + std::to_string(a) + '_' + member.name, member.semantic); + } + else + { + create_varying_variable(param_type, type::q_in, "_in_param" + std::to_string(i), func.parameter_list[i].semantic); + } + } + + if (func.parameter_list[i].type.has(type::q_out)) + { + if (param_type.is_struct()) + { + const struct_info &definition = get_struct(param_type.definition); + + for (int a = 0, array_length = std::max(1, param_type.array_length); a < array_length; a++) + for (const struct_member_info &member : definition.member_list) + create_varying_variable(member.type, param_type.qualifiers | type::q_out, "_out_param" + std::to_string(i) + '_' + std::to_string(a) + '_' + member.name, member.semantic); + } + else + { + create_varying_variable(param_type, type::q_out, "_out_param" + std::to_string(i), func.parameter_list[i].semantic); + } + } + } + + // Translate return value to output variable + define_function({}, entry_point, true); + enter_block(create_block()); + + std::string &code = _blocks.at(_current_block); + + // Handle input parameters + for (size_t i = 0; i < num_params; ++i) + { + const type ¶m_type = func.parameter_list[i].type; + + if (param_type.has(type::q_in)) + { + // Create local array element variables + for (int a = 0, array_length = std::max(1, param_type.array_length); a < array_length; a++) + { + if (param_type.is_struct()) + { + // Build struct from separate member input variables + code += '\t'; + write_type(code, param_type); + code += ' '; + code += escape_name(param_type.is_array() ? + "_in_param" + std::to_string(i) + '_' + std::to_string(a) : + "_in_param" + std::to_string(i)); + code += " = "; + write_type(code, param_type); + code += '('; + + const struct_info &definition = get_struct(param_type.definition); + + for (const struct_member_info &member : definition.member_list) + { + std::string in_param_name = "_in_param" + std::to_string(i) + '_' + std::to_string(a) + '_' + member.name; + if (const auto it = semantic_to_varying_variable.find(member.semantic); + it != semantic_to_varying_variable.end() && it->second != in_param_name) + in_param_name = it->second; + + if (member.type.is_array()) + { + write_type(code, member.type); + code += "[]("; + + for (int b = 0; b < member.type.array_length; b++) + { + // OpenGL does not allow varying of type boolean, so need to cast here + if (member.type.is_boolean()) + { + write_type(code, member.type); + code += '('; + } + + code += escape_name(in_param_name + '_' + std::to_string(b)); + + if (member.type.is_boolean()) + code += ')'; + + if (b < member.type.array_length - 1) + code += ", "; + } + + code += ')'; + } + else + { + if (member.type.is_boolean()) + { + write_type(code, member.type); + code += '('; + } + + code += semantic_to_builtin(std::move(in_param_name), member.semantic, stype); + + if (member.type.is_boolean()) + code += ')'; + } + + code += ", "; + } + + // There can be no empty structs, so can assume that the last two characters are always ", " + code.pop_back(); + code.pop_back(); + + code += ");\n"; + } + else if (const auto it = semantic_to_varying_variable.find(func.parameter_list[i].semantic); + it != semantic_to_varying_variable.end() && it->second != "_in_param" + std::to_string(i)) + { + // Create local variables for duplicated semantics (since no input/output variable is created for those, see 'create_varying_variable') + code += '\t'; + write_type(code, param_type); + code += ' '; + code += escape_name(param_type.is_array() ? + "_in_param" + std::to_string(i) + '_' + std::to_string(a) : + "_in_param" + std::to_string(i)); + code += " = "; + + if (param_type.is_boolean()) + { + write_type(code, param_type); + code += '('; + } + + code += escape_name(param_type.is_array() ? + it->second + '_' + std::to_string(a) : + it->second); + + if (param_type.is_boolean()) + code += ')'; + + code += ";\n"; + } + } + } + + // Create local parameter variables which are used as arguments in the entry point function call below + code += '\t'; + write_type(code, param_type); + code += ' '; + code += escape_name("_param" + std::to_string(i)); + if (param_type.is_array()) + code += '[' + std::to_string(param_type.array_length) + ']'; + + // Initialize those local variables with the input value if existing + // Parameters with only an "out" qualifier are written to by the entry point function, so do not need to be initialized + if (param_type.has(type::q_in)) + { + code += " = "; + + // Build array from separate array element variables + if (param_type.is_array()) + { + write_type(code, param_type); + code += "[]("; + + for (int a = 0; a < param_type.array_length; ++a) + { + // OpenGL does not allow varying of type boolean, so need to cast here + if (param_type.is_boolean()) + { + write_type(code, param_type); + code += '('; + } + + code += escape_name("_in_param" + std::to_string(i) + '_' + std::to_string(a)); + + if (param_type.is_boolean()) + code += ')'; + + if (a < param_type.array_length - 1) + code += ", "; + } + + code += ')'; + } + else + { + if (param_type.is_boolean()) + { + write_type(code, param_type); + code += '('; + } + + code += semantic_to_builtin("_in_param" + std::to_string(i), func.parameter_list[i].semantic, stype); + + if (param_type.is_boolean()) + code += ')'; + } + } + + code += ";\n"; + } + + code += '\t'; + // Structs cannot be output variables, so have to write to a temporary first and then output each member separately + if (func.return_type.is_struct()) + { + write_type(code, func.return_type); + code += " _return = "; + } + // All other output types can write to the output variable directly + else if (!func.return_type.is_void()) + { + code += semantic_to_builtin("_return", func.return_semantic, stype); + code += " = "; + } + + // Call the function this entry point refers to + code += id_to_name(func.definition) + '('; + + for (size_t i = 0; i < num_params; ++i) + { + code += "_param" + std::to_string(i); + + if (i < num_params - 1) + code += ", "; + } + + code += ");\n"; + + // Handle output parameters + for (size_t i = 0; i < num_params; ++i) + { + const type ¶m_type = func.parameter_list[i].type; + if (!param_type.has(type::q_out)) + continue; + + if (param_type.is_struct()) + { + const struct_info &definition = get_struct(param_type.definition); + + // Split out struct fields into separate output variables again + for (int a = 0, array_length = std::max(1, param_type.array_length); a < array_length; a++) + { + for (const struct_member_info &member : definition.member_list) + { + if (member.type.is_array()) + { + for (int b = 0; b < member.type.array_length; b++) + { + code += '\t'; + code += escape_name("_out_param" + std::to_string(i) + '_' + std::to_string(a) + '_' + member.name + '_' + std::to_string(b)); + code += " = "; + + // OpenGL does not allow varying of type boolean, so need to cast here + if (member.type.is_boolean()) + { + type varying_type = member.type; + varying_type.base = type::t_float; + write_type(code, varying_type); + code += '('; + } + + code += escape_name("_param" + std::to_string(i)); + if (param_type.is_array()) + code += '[' + std::to_string(a) + ']'; + code += '.'; + code += member.name; + code += '[' + std::to_string(b) + ']'; + + if (member.type.is_boolean()) + code += ')'; + + code += ";\n"; + } + } + else + { + code += '\t'; + code += semantic_to_builtin("_out_param" + std::to_string(i) + '_' + std::to_string(a) + '_' + member.name, member.semantic, stype); + code += " = "; + + if (member.type.is_boolean()) + { + type varying_type = member.type; + varying_type.base = type::t_float; + write_type(code, varying_type); + code += '('; + } + + code += escape_name("_param" + std::to_string(i)); + if (param_type.is_array()) + code += '[' + std::to_string(a) + ']'; + code += '.'; + code += member.name; + + if (member.type.is_boolean()) + code += ')'; + + code += ";\n"; + } + } + } + } + else + { + if (param_type.is_array()) + { + // Split up array output into individual array elements again + for (int a = 0; a < param_type.array_length; a++) + { + code += '\t'; + code += escape_name("_out_param" + std::to_string(i) + '_' + std::to_string(a)); + code += " = "; + + // OpenGL does not allow varying of type boolean, so need to cast here + if (param_type.is_boolean()) + { + type varying_type = param_type; + varying_type.base = type::t_float; + write_type(code, varying_type); + code += '('; + } + + code += escape_name("_param" + std::to_string(i)); + code += '[' + std::to_string(a) + ']'; + + if (param_type.is_boolean()) + code += ')'; + + code += ";\n"; + } + } + else + { + code += '\t'; + code += semantic_to_builtin("_out_param" + std::to_string(i), func.parameter_list[i].semantic, stype); + code += " = "; + + if (param_type.is_boolean()) + { + type varying_type = param_type; + varying_type.base = type::t_float; + write_type(code, varying_type); + code += '('; + } + + code += escape_name("_param" + std::to_string(i)); + + if (param_type.is_boolean()) + code += ')'; + + code += ";\n"; + } + } + } + + // Handle return struct output variables + if (func.return_type.is_struct()) + { + const struct_info &definition = get_struct(func.return_type.definition); + + for (const struct_member_info &member : definition.member_list) + { + code += '\t'; + code += semantic_to_builtin("_return_" + member.name, member.semantic, stype); + code += " = _return." + escape_name(member.name) + ";\n"; + } + } + + // Add code to flip the output vertically + if (_flip_vert_y && stype == shader_type::vs) + code += "\tgl_Position.y = -gl_Position.y;\n"; + + leave_block_and_return(0); + leave_function(); + + _blocks.at(0) += "#endif\n"; + } + + id emit_load(const expression &exp, bool force_new_id) override + { + if (exp.is_constant) + return emit_constant(exp.type, exp.constant); + else if (exp.chain.empty() && !force_new_id) // Can refer to values without access chain directly + return exp.base; + + const id res = make_id(); + + std::string type, expr_code = id_to_name(exp.base); + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_cast: + type.clear(); + write_type(type, op.to); + expr_code = type + '(' + expr_code + ')'; + break; + case expression::operation::op_member: + expr_code += '.'; + expr_code += escape_name(get_struct(op.from.definition).member_list[op.index].name); + break; + case expression::operation::op_dynamic_index: + // For matrices this will extract a column, but that is fine, since they are initialized column-wise too + // Also cast to an integer, since it could be a boolean too, but GLSL does not allow those in index expressions + expr_code += "[int(" + id_to_name(op.index) + ")]"; + break; + case expression::operation::op_constant_index: + if (op.from.is_vector() && !op.from.is_array()) + expr_code += '.', + expr_code += "xyzw"[op.index]; + else + expr_code += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + if (op.from.is_matrix()) + { + if (op.swizzle[1] < 0) + { + const int row = (op.swizzle[0] % 4); + const int col = (op.swizzle[0] - row) / 4; + + expr_code += '[' + std::to_string(row) + "][" + std::to_string(col) + ']'; + } + else + { + // TODO: Implement matrix to vector swizzles + assert(false); + expr_code += "_NOT_IMPLEMENTED_"; // Make sure compilation fails + } + } + else + { + expr_code += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + expr_code += "xyzw"[op.swizzle[i]]; + } + break; + } + } + + // GLSL matrices are always floating point, so need to cast result to the target type + if (!exp.chain.empty() && exp.chain[0].from.is_matrix() && !exp.chain[0].from.is_floating_point()) + { + type.clear(); + write_type(type, exp.type); + expr_code = type + '(' + expr_code + ')'; + } + + if (force_new_id) + { + // Need to store value in a new variable to comply with request for a new ID + std::string &code = _blocks.at(_current_block); + + code += '\t'; + write_type(code, exp.type); + code += ' ' + id_to_name(res) + " = " + expr_code + ";\n"; + } + else + { + // Avoid excessive variable definitions by instancing simple load operations in code every time + define_name(res, std::move(expr_code)); + } + + return res; + } + void emit_store(const expression &exp, id value) override + { + if (const auto it = _remapped_sampler_variables.find(exp.base); + it != _remapped_sampler_variables.end()) + { + assert(it->second == 0); + it->second = value; + return; + } + + std::string &code = _blocks.at(_current_block); + + write_location(code, exp.location); + + code += '\t' + id_to_name(exp.base); + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_member: + code += '.'; + code += escape_name(get_struct(op.from.definition).member_list[op.index].name); + break; + case expression::operation::op_dynamic_index: + code += "[int(" + id_to_name(op.index) + ")]"; + break; + case expression::operation::op_constant_index: + code += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + if (op.from.is_matrix()) + { + if (op.swizzle[1] < 0) + { + const int row = (op.swizzle[0] % 4); + const int col = (op.swizzle[0] - row) / 4; + + code += '[' + std::to_string(row) + "][" + std::to_string(col) + ']'; + } + else + { + // TODO: Implement matrix to vector swizzles + assert(false); + code += "_NOT_IMPLEMENTED_"; // Make sure compilation fails + } + } + else + { + code += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + code += "xyzw"[op.swizzle[i]]; + } + break; + } + } + + code += " = "; + + // GLSL matrices are always floating point, so need to cast type + if (!exp.chain.empty() && exp.chain[0].from.is_matrix() && !exp.chain[0].from.is_floating_point()) + // Only supporting scalar assignments to matrices currently, so can assume to always cast to float + code += "float(" + id_to_name(value) + ");\n"; + else + code += id_to_name(value) + ";\n"; + } + + id emit_constant(const type &type, const constant &data) override + { + const id res = make_id(); + + if (type.is_array() || type.is_struct()) + { + assert(type.has(type::q_const)); + + std::string &code = _blocks.at(_current_block); + + code += '\t'; + + // GLSL requires constants to be initialized, but struct initialization is not supported right now + if (!type.is_struct()) + code += "const "; + + write_type(code, type); + code += ' ' + id_to_name(res); + + // Array constants need to be stored in a constant variable as they cannot be used in-place + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + // Struct initialization is not supported right now + if (!type.is_struct()) { + code += " = "; + write_constant(code, type, data); + } + + code += ";\n"; + return res; + } + + std::string code; + write_constant(code, type, data); + define_name(res, std::move(code)); + + return res; + } + + id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + switch (op) + { + case tokenid::minus: + code += '-'; + break; + case tokenid::tilde: + code += '~'; + break; + case tokenid::exclaim: + if (res_type.is_vector()) + code += "not"; + else + code += "!bool"; + break; + default: + assert(false); + } + + code += '(' + id_to_name(val) + ");\n"; + + return res; + } + id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + std::string intrinsic, operator_code; + + switch (op) + { + case tokenid::plus: + case tokenid::plus_plus: + case tokenid::plus_equal: + operator_code = '+'; + break; + case tokenid::minus: + case tokenid::minus_minus: + case tokenid::minus_equal: + operator_code = '-'; + break; + case tokenid::star: + case tokenid::star_equal: + if (type.is_matrix()) + intrinsic = "matrixCompMult"; + else + operator_code = '*'; + break; + case tokenid::slash: + case tokenid::slash_equal: + operator_code = '/'; + break; + case tokenid::percent: + case tokenid::percent_equal: + if (type.is_floating_point()) + intrinsic = "fmodHLSL", + _uses_fmod = true; + else + operator_code = '%'; + break; + case tokenid::caret: + case tokenid::caret_equal: + operator_code = '^'; + break; + case tokenid::pipe: + case tokenid::pipe_equal: + operator_code = '|'; + break; + case tokenid::ampersand: + case tokenid::ampersand_equal: + operator_code = '&'; + break; + case tokenid::less_less: + case tokenid::less_less_equal: + operator_code = "<<"; + break; + case tokenid::greater_greater: + case tokenid::greater_greater_equal: + operator_code = ">>"; + break; + case tokenid::pipe_pipe: + if (type.is_vector()) + intrinsic = "compOr", + _uses_componentwise_or = true; + else + operator_code = "||"; + break; + case tokenid::ampersand_ampersand: + if (type.is_vector()) + intrinsic = "compAnd", + _uses_componentwise_and = true; + else + operator_code = "&&"; + break; + case tokenid::less: + if (type.is_vector()) + intrinsic = "lessThan"; + else + operator_code = '<'; + break; + case tokenid::less_equal: + if (type.is_vector()) + intrinsic = "lessThanEqual"; + else + operator_code = "<="; + break; + case tokenid::greater: + if (type.is_vector()) + intrinsic = "greaterThan"; + else + operator_code = '>'; + break; + case tokenid::greater_equal: + if (type.is_vector()) + intrinsic = "greaterThanEqual"; + else + operator_code = ">="; + break; + case tokenid::equal_equal: + if (type.is_vector()) + intrinsic = "equal"; + else + operator_code = "=="; + break; + case tokenid::exclaim_equal: + if (type.is_vector()) + intrinsic = "notEqual"; + else + operator_code = "!="; + break; + default: + assert(false); + } + + if (!intrinsic.empty()) + code += intrinsic + '(' + id_to_name(lhs) + ", " + id_to_name(rhs) + ')'; + else + code += id_to_name(lhs) + ' ' + operator_code + ' ' + id_to_name(rhs); + + code += ";\n"; + + return res; + } + id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override + { + if (op != tokenid::question) + return assert(false), 0; // Should never happen, since this is the only ternary operator currently supported + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = "; + + if (res_type.is_vector()) + code += "compCond(" + id_to_name(condition) + ", " + id_to_name(true_value) + ", " + id_to_name(false_value) + ");\n", + _uses_componentwise_cond = true; + else // GLSL requires the conditional expression to be a scalar boolean + code += id_to_name(condition) + " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n"; + + return res; + } + id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert(arg.chain.empty() && arg.base != 0); +#endif + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = "; + } + + code += id_to_name(function) + '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + code += ");\n"; + + return res; + } + id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert(arg.chain.empty() && arg.base != 0); +#endif + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + } + + enum + { + #define IMPLEMENT_INTRINSIC_GLSL(name, i, code) name##i, + #include "effect_symbol_table_intrinsics.inl" + }; + + switch (intrinsic) + { + #define IMPLEMENT_INTRINSIC_GLSL(name, i, code) case name##i: code break; + #include "effect_symbol_table_intrinsics.inl" + default: + assert(false); + } + + code += ";\n"; + + return res; + } + id emit_construct(const location &loc, const type &type, const std::vector &args) override + { +#ifndef NDEBUG + for (const auto &arg : args) + assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); +#endif + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += " = "; + + write_type(code, type); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + code += ");\n"; + + return res; + } + + void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) override + { + assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + code += _blocks.at(condition_block); + + write_location(code, loc); + + if (flags != 0) + { + _enable_control_flow_attributes = true; + + code += "#if GL_EXT_control_flow_attributes\n\t[["; + if ((flags & 0x1) == 0x1) + code += "flatten"; + if ((flags & 0x3) == 0x3) + code += ", "; + if ((flags & 0x2) == 0x2) + code += "dont_flatten"; + code += "]]\n#endif\n"; + } + + code += '\t'; + code += "if (" + id_to_name(condition_value) + ")\n\t{\n"; + code += true_statement_data; + code += "\t}\n"; + + if (!false_statement_data.empty()) + { + code += "\telse\n\t{\n"; + code += false_statement_data; + code += "\t}\n"; + } + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + } + id 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 &type) override + { + assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + const id res = make_id(); + + code += _blocks.at(condition_block); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res) + ";\n"; + + write_location(code, loc); + + code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; + code += (true_statement_block != condition_block ? true_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n"; + code += "\t}\n\telse\n\t{\n"; + code += (false_statement_block != condition_block ? false_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n"; + code += "\t}\n"; + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + + return res; + } + void 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) override + { + assert(prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &loop_data = _blocks.at(loop_block); + std::string &continue_data = _blocks.at(continue_block); + + increase_indentation_level(loop_data); + increase_indentation_level(loop_data); + increase_indentation_level(continue_data); + + code += _blocks.at(prev_block); + + std::string attributes; + if (flags != 0) + { + _enable_control_flow_attributes = true; + + attributes += "#if GL_EXT_control_flow_attributes\n\t[["; + if ((flags & 0x1) == 0x1) + attributes += "unroll"; + if ((flags & 0x3) == 0x3) + attributes += ", "; + if ((flags & 0x2) == 0x2) + attributes += "dont_unroll"; + attributes += "]]\n#endif\n"; + } + + // Condition value can be missing in infinite loop constructs like "for (;;)" + std::string condition_name = condition_value != 0 ? id_to_name(condition_value) : "true"; + + if (condition_block == 0) + { + // Convert the last SSA variable initializer to an assignment statement + auto pos_assign = continue_data.rfind(condition_name); + auto pos_prev_assign = continue_data.rfind('\t', pos_assign); + continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + + // We need to add the continue block to all "continue" statements as well + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data); + + code += "\tbool " + condition_name + ";\n"; + + write_location(code, loc); + + code += attributes; + code += '\t'; + code += "do\n\t{\n\t\t{\n"; + code += 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 below + code += "\t\t}\n"; + code += continue_data; + code += "\t}\n\twhile (" + condition_name + ");\n"; + } + else + { + std::string &condition_data = _blocks.at(condition_block); + + // 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-is + if (std::count(condition_data.begin(), condition_data.end(), '\n') == 1) + { + // Convert SSA variable initializer back to a condition expression + auto pos_assign = condition_data.find('='); + condition_data.erase(0, pos_assign + 2); + auto pos_semicolon = condition_data.rfind(';'); + condition_data.erase(pos_semicolon); + + condition_name = std::move(condition_data); + assert(condition_data.empty()); + } + else + { + code += condition_data; + + increase_indentation_level(condition_data); + + // Convert the last SSA variable initializer to an assignment statement + auto pos_assign = condition_data.rfind(condition_name); + auto pos_prev_assign = condition_data.rfind('\t', pos_assign); + condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + } + + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data + condition_data); + + code += attributes; + code += '\t'; + code += "while (" + condition_name + ")\n\t{\n\t\t{\n"; + code += loop_data; + code += "\t\t}\n"; + code += continue_data; + code += condition_data; + code += "\t}\n"; + + _blocks.erase(condition_block); + } + + // Remove consumed blocks to save memory + _blocks.erase(prev_block); + _blocks.erase(header_block); + _blocks.erase(loop_block); + _blocks.erase(continue_block); + } + void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, id default_block, const std::vector &case_literal_and_labels, const std::vector &case_blocks, unsigned int) override + { + assert(selector_value != 0 && selector_block != 0 && default_label != 0 && default_block != 0); + assert(case_blocks.size() == case_literal_and_labels.size() / 2); + + std::string &code = _blocks.at(_current_block); + + code += _blocks.at(selector_block); + + write_location(code, loc); + + code += "\tswitch (" + id_to_name(selector_value) + ")\n\t{\n"; + + std::vector labels = case_literal_and_labels; + for (size_t i = 0; i < labels.size(); i += 2) + { + if (labels[i + 1] == 0) + continue; // Happens if a case was already handled, see below + + code += "\tcase " + std::to_string(labels[i]) + ": "; + + if (labels[i + 1] == default_label) + { + code += "default: "; + default_label = 0; + } + else + { + for (size_t k = i + 2; k < labels.size(); k += 2) + { + if (labels[k + 1] == 0 || labels[k + 1] != labels[i + 1]) + continue; + + code += "case " + std::to_string(labels[k]) + ": "; + labels[k + 1] = 0; + } + } + + assert(case_blocks[i / 2] != 0); + std::string &case_data = _blocks.at(case_blocks[i / 2]); + + increase_indentation_level(case_data); + + code += "{\n"; + code += case_data; + code += "\t}\n"; + } + + + if (default_label != 0 && default_block != _current_block) + { + std::string &default_data = _blocks.at(default_block); + + increase_indentation_level(default_data); + + code += "\tdefault: {\n"; + code += default_data; + code += "\t}\n"; + + _blocks.erase(default_block); + } + + code += "\t}\n"; + + // Remove consumed blocks to save memory + _blocks.erase(selector_block); + for (const id case_block : case_blocks) + _blocks.erase(case_block); + } + + id create_block() override + { + const id res = make_id(); + + std::string &block = _blocks.emplace(res, std::string()).first->second; + // Reserve a decently big enough memory block to avoid frequent reallocations + block.reserve(4096); + + return res; + } + id set_block(id id) override + { + _last_block = _current_block; + _current_block = id; + + return _last_block; + } + void enter_block(id id) override + { + _current_block = id; + } + id leave_block_and_kill() override + { + if (!is_in_block()) + return 0; + + std::string &code = _blocks.at(_current_block); + + code += "\tdiscard;\n"; + + const auto &return_type = _functions.back()->return_type; + if (!return_type.is_void()) + { + // Add a return statement to exit functions in case discard is the last control flow statement + code += "\treturn "; + write_constant(code, return_type, constant()); + code += ";\n"; + } + + return set_block(0); + } + id leave_block_and_return(id value) override + { + if (!is_in_block()) + return 0; + + // Skip implicit return statement + if (!_functions.back()->return_type.is_void() && value == 0) + return set_block(0); + + std::string &code = _blocks.at(_current_block); + + code += "\treturn"; + + if (value != 0) + code += ' ' + id_to_name(value); + + code += ";\n"; + + return set_block(0); + } + id leave_block_and_switch(id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + id leave_block_and_branch(id target, unsigned int loop_flow) override + { + if (!is_in_block()) + return _last_block; + + std::string &code = _blocks.at(_current_block); + + switch (loop_flow) + { + case 1: + code += "\tbreak;\n"; + break; + case 2: // Keep track of continue target block, so we can insert its code here later + code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n"; + break; + } + + return set_block(0); + } + id leave_block_and_branch_conditional(id, id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + void leave_function() override + { + assert(_last_block != 0); + + _blocks.at(0) += "{\n" + _blocks.at(_last_block) + "}\n"; + } +}; + +codegen *reshadefx::create_codegen_glsl(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y) +{ + return new codegen_glsl(vulkan_semantics, debug_info, uniforms_to_spec_constants, enable_16bit_types, flip_vert_y); +} diff --git a/dep/reshadefx/src/effect_codegen_hlsl.cpp b/dep/reshadefx/src/effect_codegen_hlsl.cpp new file mode 100644 index 000000000..4fa6bbf68 --- /dev/null +++ b/dep/reshadefx/src/effect_codegen_hlsl.cpp @@ -0,0 +1,1845 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include // std::signbit, std::isinf, std::isnan +#include // std::tolower +#include // std::snprintf +#include +#include // stricmp +#include // std::find_if, std::max + +using namespace reshadefx; + +class codegen_hlsl final : public codegen +{ +public: + codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants) + : _shader_model(shader_model), _debug_info(debug_info), _uniforms_to_spec_constants(uniforms_to_spec_constants) + { + // Create default block and reserve a memory block to avoid frequent reallocations + std::string &block = _blocks.emplace(0, std::string()).first->second; + block.reserve(8192); + } + +private: + enum class naming + { + // Name should already be unique, so no additional steps are taken + unique, + // Will be numbered when clashing with another name + general, + // Replace name with a code snippet + expression, + }; + + std::string _cbuffer_block; + std::string _current_location; + std::unordered_map _names; + std::unordered_map _blocks; + unsigned int _shader_model = 0; + bool _debug_info = false; + bool _uniforms_to_spec_constants = false; + std::unordered_map _remapped_semantics; + + // Only write compatibility intrinsics to result if they are actually in use + bool _uses_bitwise_cast = false; + + void write_result(module &module) override + { + module = std::move(_module); + + std::string preamble; + + if (_shader_model >= 40) + { + preamble += + "struct __sampler1D_int { Texture1D t; SamplerState s; };\n" + "struct __sampler2D_int { Texture2D t; SamplerState s; };\n" + "struct __sampler3D_int { Texture3D t; SamplerState s; };\n" + "struct __sampler1D_uint { Texture1D t; SamplerState s; };\n" + "struct __sampler2D_uint { Texture2D t; SamplerState s; };\n" + "struct __sampler3D_uint { Texture3D t; SamplerState s; };\n" + "struct __sampler1D_float { Texture1D t; SamplerState s; };\n" + "struct __sampler2D_float { Texture2D t; SamplerState s; };\n" + "struct __sampler3D_float { Texture3D t; SamplerState s; };\n" + "struct __sampler1D_float4 { Texture1D t; SamplerState s; };\n" + "struct __sampler2D_float4 { Texture2D t; SamplerState s; };\n" + "struct __sampler3D_float4 { Texture3D t; SamplerState s; };\n"; + + if (!_cbuffer_block.empty()) + { + if (_shader_model >= 60) + preamble += "[[vk::binding(0, 0)]] "; // Descriptor set 0 + + preamble += "cbuffer _Globals {\n" + _cbuffer_block + "};\n"; + } + } + else + { + preamble += + "struct __sampler1D { sampler1D s; float1 pixelsize; };\n" + "struct __sampler2D { sampler2D s; float2 pixelsize; };\n" + "struct __sampler3D { sampler3D s; float3 pixelsize; };\n" + "uniform float2 __TEXEL_SIZE__ : register(c255);\n"; + + if (_uses_bitwise_cast) + preamble += + "int __asint(float v) {" + " if (v == 0) return 0;" // Zero (does not handle negative zero) + // if (isinf(v)) return v < 0 ? 4286578688 : 2139095040; // Infinity + // if (isnan(v)) return 2147483647; // NaN (does not handle negative NaN) + " float e = 0;" + " float f = frexp(v, e) * 2 - 1;" // frexp does not include sign bit in HLSL, so can use as is + " float m = ldexp(f, 23);" + " return (v < 0 ? 2147483648 : 0) + ldexp(e + 126, 23) + m;" + "}\n" + "int2 __asint(float2 v) { return int2(__asint(v.x), __asint(v.y)); }\n" + "int3 __asint(float3 v) { return int3(__asint(v.x), __asint(v.y), __asint(v.z)); }\n" + "int4 __asint(float4 v) { return int4(__asint(v.x), __asint(v.y), __asint(v.z), __asint(v.w)); }\n" + + "int __asuint(float v) { return __asint(v); }\n" + "int2 __asuint(float2 v) { return int2(__asint(v.x), __asint(v.y)); }\n" + "int3 __asuint(float3 v) { return int3(__asint(v.x), __asint(v.y), __asint(v.z)); }\n" + "int4 __asuint(float4 v) { return int4(__asint(v.x), __asint(v.y), __asint(v.z), __asint(v.w)); }\n" + + "float __asfloat(int v) {" + " float m = v % exp2(23);" + " float f = ldexp(m, -23);" + " float e = floor(ldexp(v, -23) % 256);" + " return (v > 2147483647 ? -1 : 1) * (" + // e == 0 ? ldexp(f, -126) : // Denormalized + // e == 255 ? (m == 0 ? 1.#INF : -1.#IND) : // Infinity and NaN + " ldexp(1 + f, e - 127));" + "}\n" + "float2 __asfloat(int2 v) { return float2(__asfloat(v.x), __asfloat(v.y)); }\n" + "float3 __asfloat(int3 v) { return float3(__asfloat(v.x), __asfloat(v.y), __asfloat(v.z)); }\n" + "float4 __asfloat(int4 v) { return float4(__asfloat(v.x), __asfloat(v.y), __asfloat(v.z), __asfloat(v.w)); }\n"; + + if (!_cbuffer_block.empty()) + preamble += _cbuffer_block; + + // Offsets were multiplied in 'define_uniform', so adjust total size here accordingly + module.total_uniform_size *= 4; + } + + module.code.assign(preamble.begin(), preamble.end()); + + const std::string &main_block = _blocks.at(0); + module.code.insert(module.code.end(), main_block.begin(), main_block.end()); + } + + template + void write_type(std::string &s, const type &type) const + { + if constexpr (is_decl) + { + if (type.has(type::q_static)) + s += "static "; + if (type.has(type::q_precise)) + s += "precise "; + if (type.has(type::q_groupshared)) + s += "groupshared "; + } + + if constexpr (is_param) + { + if (type.has(type::q_linear)) + s += "linear "; + if (type.has(type::q_noperspective)) + s += "noperspective "; + if (type.has(type::q_centroid)) + s += "centroid "; + if (type.has(type::q_nointerpolation)) + s += "nointerpolation "; + + if (type.has(type::q_inout)) + s += "inout "; + else if (type.has(type::q_in)) + s += "in "; + else if (type.has(type::q_out)) + s += "out "; + } + + switch (type.base) + { + case type::t_void: + s += "void"; + return; + case type::t_bool: + s += "bool"; + break; + case type::t_min16int: + // Minimum precision types are only supported in shader model 4 and up + // Real 16-bit types were added in shader model 6.2 + s += _shader_model >= 62 ? "int16_t" : _shader_model >= 40 ? "min16int" : "int"; + break; + case type::t_int: + s += "int"; + break; + case type::t_min16uint: + s += _shader_model >= 62 ? "uint16_t" : _shader_model >= 40 ? "min16uint" : "int"; + break; + case type::t_uint: + // In shader model 3, uints can only be used with known-positive values, so use ints instead + s += _shader_model >= 40 ? "uint" : "int"; + break; + case type::t_min16float: + s += _shader_model >= 62 ? "float16_t" : _shader_model >= 40 ? "min16float" : "float"; + break; + case type::t_float: + s += "float"; + break; + case type::t_struct: + s += id_to_name(type.definition); + return; + case type::t_sampler1d_int: + s += "__sampler1D"; + if (_shader_model >= 40) + s += "_int" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler2d_int: + s += "__sampler2D"; + if (_shader_model >= 40) + s += "_int" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler3d_int: + s += "__sampler3D"; + if (_shader_model >= 40) + s += "_int" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler1d_uint: + s += "__sampler1D"; + if (_shader_model >= 40) + s += "_uint" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler2d_uint: + s += "__sampler2D"; + if (_shader_model >= 40) + s += "_uint" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler3d_uint: + s += "__sampler3D"; + if (_shader_model >= 40) + s += "_uint" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler1d_float: + s += "__sampler1D"; + if (_shader_model >= 40) + s += "_float" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler2d_float: + s += "__sampler2D"; + if (_shader_model >= 40) + s += "_float" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_sampler3d_float: + s += "__sampler3D"; + if (_shader_model >= 40) + s += "_float" + (type.rows > 1 ? std::to_string(type.rows) : std::string()); + return; + case type::t_storage1d_int: + s += "RWTexture1D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage2d_int: + s += "RWTexture2D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage3d_int: + s += "RWTexture3D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage1d_uint: + s += "RWTexture1D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage2d_uint: + s += "RWTexture2D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage3d_uint: + s += "RWTexture3D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage1d_float: + s += "RWTexture1D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage2d_float: + s += "RWTexture2D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + case type::t_storage3d_float: + s += "RWTexture3D 1 ? std::to_string(type.rows) : std::string()) + '>'; + return; + default: + assert(false); + return; + } + + if (type.rows > 1) + s += std::to_string(type.rows); + if (type.cols > 1) + s += 'x' + std::to_string(type.cols); + } + void write_constant(std::string &s, const type &type, const constant &data) const + { + if (type.is_array()) + { + auto elem_type = type; + elem_type.array_length = 0; + + s += "{ "; + + for (int i = 0; i < type.array_length; ++i) + { + write_constant(s, elem_type, i < static_cast(data.array_data.size()) ? data.array_data[i] : constant()); + + if (i < type.array_length - 1) + s += ", "; + } + + s += " }"; + return; + } + + if (type.is_struct()) + { + // The can only be zero initializer struct constants + assert(data.as_uint[0] == 0); + + s += '(' + id_to_name(type.definition) + ")0"; + return; + } + + // There can only be numeric constants + assert(type.is_numeric()); + + if (!type.is_scalar()) + write_type(s, type), s += '('; + + for (unsigned int i = 0, components = type.components(); i < components; ++i) + { + switch (type.base) + { + case type::t_bool: + s += data.as_uint[i] ? "true" : "false"; + break; + case type::t_min16int: + case type::t_int: + s += std::to_string(data.as_int[i]); + break; + case type::t_min16uint: + case type::t_uint: + s += std::to_string(data.as_uint[i]); + break; + case type::t_min16float: + case type::t_float: + if (std::isnan(data.as_float[i])) { + s += "-1.#IND"; + break; + } + if (std::isinf(data.as_float[i])) { + s += std::signbit(data.as_float[i]) ? "1.#INF" : "-1.#INF"; + break; + } + char temp[64]; // Will be null-terminated by snprintf + std::snprintf(temp, sizeof(temp), "%1.8e", data.as_float[i]); + s += temp; + break; + default: + assert(false); + } + + if (i < components - 1) + s += ", "; + } + + if (!type.is_scalar()) + s += ')'; + } + template + void write_location(std::string &s, const location &loc) + { + if (loc.source.empty() || !_debug_info) + return; + + s += "#line " + std::to_string(loc.line); + + size_t offset = s.size(); + + // Avoid writing the file name every time to reduce output text size + if constexpr (force_source) + { + s += " \"" + loc.source + '\"'; + } + else if (loc.source != _current_location) + { + s += " \"" + loc.source + '\"'; + + _current_location = loc.source; + } + + // Need to escape string for new DirectX Shader Compiler (dxc) + if (_shader_model >= 60) + { + for (; (offset = s.find('\\', offset)) != std::string::npos; offset += 2) + s.insert(offset, "\\", 1); + } + + s += '\n'; + } + void write_texture_format(std::string &s, texture_format format) + { + switch (format) + { + case texture_format::r32i: + s += "int"; + break; + case texture_format::r32u: + s += "uint"; + break; + case texture_format::r8: + case texture_format::r16: + case texture_format::r16f: + case texture_format::r32f: + s += "float"; + break; + default: + assert(false); + [[fallthrough]]; + case texture_format::unknown: + case texture_format::rg8: + case texture_format::rg16: + case texture_format::rg16f: + case texture_format::rg32f: + case texture_format::rgba8: + case texture_format::rgba16: + case texture_format::rgba16f: + case texture_format::rgba32f: + case texture_format::rgb10a2: + s += "float4"; + break; + } + } + + std::string id_to_name(id id) const + { + assert(id != 0); + if (const auto names_it = _names.find(id); + names_it != _names.end()) + return names_it->second; + return '_' + std::to_string(id); + } + + template + void define_name(const id id, std::string name) + { + assert(!name.empty()); + if constexpr (naming_type != naming::expression) + if (name[0] == '_') + return; // Filter out names that may clash with automatic ones + name = escape_name(std::move(name)); + if constexpr (naming_type == naming::general) + if (std::find_if(_names.begin(), _names.end(), [&name](const auto &it) { return it.second == name; }) != _names.end()) + name += '_' + std::to_string(id); // Append a numbered suffix if the name already exists + _names[id] = std::move(name); + } + + std::string convert_semantic(const std::string &semantic) + { + if (_shader_model < 40) + { + if (semantic == "SV_POSITION") + return "POSITION"; // For pixel shaders this has to be "VPOS", so need to redefine that in post + if (semantic == "SV_POINTSIZE") + return "PSIZE"; + if (semantic.compare(0, 9, "SV_TARGET") == 0) + return "COLOR" + semantic.substr(9); + if (semantic == "SV_DEPTH") + return "DEPTH"; + if (semantic == "SV_VERTEXID") + return "TEXCOORD0 /* VERTEXID */"; + if (semantic == "SV_ISFRONTFACE") + return "VFACE"; + + if (semantic != "VPOS" && + semantic.compare(0, 5, "COLOR") != 0 && + semantic.compare(0, 6, "NORMAL") != 0 && + semantic.compare(0, 7, "TANGENT") != 0) + { + // Shader model 3 only supports a selected list of semantic names, so need to remap custom ones to that + if (const auto it = _remapped_semantics.find(semantic); + it != _remapped_semantics.end()) + return it->second; + + // Legal semantic indices are between 0 and 15 + if (_remapped_semantics.size() < 15) + { + const std::string remapped_semantic = "TEXCOORD" + std::to_string(_remapped_semantics.size()) + " /* " + semantic + " */"; + _remapped_semantics.emplace(semantic, remapped_semantic); + return remapped_semantic; + } + } + } + else + { + if (semantic.compare(0, 5, "COLOR") == 0) + return "SV_TARGET" + semantic.substr(5); + } + + return semantic; + } + + static std::string escape_name(std::string name) + { + static const auto stringicmp = [](const std::string &a, const std::string &b) { +#ifdef _WIN32 + return _stricmp(a.c_str(), b.c_str()) == 0; +#else + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](std::string::value_type a, std::string::value_type b) { return std::tolower(a) == std::tolower(b); }); +#endif + }; + + // HLSL compiler complains about "technique" and "pass" names in strict mode (no matter the casing) + if (stringicmp(name, "line") || + stringicmp(name, "pass") || + stringicmp(name, "technique")) + // This is guaranteed to not clash with user defined names, since those starting with an underscore are filtered out in 'define_name' + name = '_' + name; + + return name; + } + + static void increase_indentation_level(std::string &block) + { + if (block.empty()) + return; + + for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3) + block.replace(pos, 2, "\n\t\t"); + + block.insert(block.begin(), '\t'); + } + + id define_struct(const location &loc, struct_info &info) override + { + info.definition = make_id(); + define_name(info.definition, info.unique_name); + + _structs.push_back(info); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "struct " + id_to_name(info.definition) + "\n{\n"; + + for (const struct_member_info &member : info.member_list) + { + code += '\t'; + write_type(code, member.type); // HLSL allows interpolation attributes on struct members, so handle this like a parameter + code += ' ' + member.name; + if (member.type.is_array()) + code += '[' + std::to_string(member.type.array_length) + ']'; + if (!member.semantic.empty()) + code += " : " + convert_semantic(member.semantic); + code += ";\n"; + } + + code += "};\n"; + + return info.definition; + } + id define_texture(const location &loc, texture_info &info) override + { + info.id = make_id(); + info.binding = ~0u; + + define_name(info.id, info.unique_name); + + if (_shader_model >= 40) + { + info.binding = _module.num_texture_bindings; + _module.num_texture_bindings += 2; + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + if (_shader_model >= 60) + code += "[[vk::binding(" + std::to_string(info.binding + 0) + ", 2)]] "; // Descriptor set 2 + + code += "Texture" + std::to_string(static_cast(info.type)) + "D<"; + write_texture_format(code, info.format); + code += "> __" + info.unique_name + " : register(t" + std::to_string(info.binding + 0) + "); \n"; + + if (_shader_model >= 60) + code += "[[vk::binding(" + std::to_string(info.binding + 1) + ", 2)]] "; // Descriptor set 2 + + code += "Texture" + std::to_string(static_cast(info.type)) + "D<"; + write_texture_format(code, info.format); + code += "> __srgb" + info.unique_name + " : register(t" + std::to_string(info.binding + 1) + "); \n"; + } + + _module.textures.push_back(info); + + return info.id; + } + id define_sampler(const location &loc, const texture_info &tex_info, sampler_info &info) override + { + info.id = make_id(); + + define_name(info.id, info.unique_name); + + std::string &code = _blocks.at(_current_block); + + if (_shader_model >= 40) + { + // Try and reuse a sampler binding with the same sampler description + const auto existing_sampler = std::find_if(_module.samplers.begin(), _module.samplers.end(), + [&info](const auto &it) { + return it.filter == info.filter && it.address_u == info.address_u && it.address_v == info.address_v && it.address_w == info.address_w && it.min_lod == info.min_lod && it.max_lod == info.max_lod && it.lod_bias == info.lod_bias; + }); + + if (existing_sampler != _module.samplers.end()) + { + info.binding = existing_sampler->binding; + } + else + { + info.binding = _module.num_sampler_bindings++; + + if (_shader_model >= 60) + code += "[[vk::binding(" + std::to_string(info.binding) + ", 1)]] "; // Descriptor set 1 + + code += "SamplerState __s" + std::to_string(info.binding) + " : register(s" + std::to_string(info.binding) + ");\n"; + } + + assert(info.srgb == 0 || info.srgb == 1); + info.texture_binding = tex_info.binding + info.srgb; // Offset binding by one to choose the SRGB variant + + write_location(code, loc); + + code += "static const "; + write_type(code, info.type); + code += ' ' + id_to_name(info.id) + " = { " + (info.srgb ? "__srgb" : "__") + info.texture_name + ", __s" + std::to_string(info.binding) + " };\n"; + } + else + { + info.binding = _module.num_sampler_bindings++; + info.texture_binding = ~0u; // Unset texture binding + + const unsigned int texture_dimension = info.type.texture_dimension(); + + code += "sampler" + std::to_string(texture_dimension) + "D __" + info.unique_name + "_s : register(s" + std::to_string(info.binding) + ");\n"; + + write_location(code, loc); + + code += "static const "; + write_type(code, info.type); + code += ' ' + id_to_name(info.id) + " = { __" + info.unique_name + "_s, float" + std::to_string(texture_dimension) + '('; + + if (tex_info.semantic.empty()) + { + code += "1.0 / " + std::to_string(tex_info.width); + if (texture_dimension >= 2) + code += ", 1.0 / " + std::to_string(tex_info.height); + if (texture_dimension >= 3) + code += ", 1.0 / " + std::to_string(tex_info.depth); + } + else + { + // Expect application to set inverse texture size via a define if it is not known here + code += tex_info.semantic + "_PIXEL_SIZE"; + } + + code += ") }; \n"; + } + + _module.samplers.push_back(info); + + return info.id; + } + id define_storage(const location &loc, const texture_info &, storage_info &info) override + { + info.id = make_id(); + info.binding = ~0u; + + define_name(info.id, info.unique_name); + + if (_shader_model >= 50) + { + info.binding = _module.num_storage_bindings++; + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + if (_shader_model >= 60) + code += "[[vk::binding(" + std::to_string(info.binding) + ", 3)]] "; // Descriptor set 3 + + write_type(code, info.type); + code += ' ' + info.unique_name + " : register(u" + std::to_string(info.binding) + ");\n"; + } + + _module.storages.push_back(info); + + return info.id; + } + id define_uniform(const location &loc, uniform_info &info) override + { + const id res = make_id(); + + define_name(res, info.name); + + if (_uniforms_to_spec_constants && info.has_initializer_value) + { + info.size = info.type.components() * 4; + if (info.type.is_array()) + info.size *= info.type.array_length; + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + assert(!info.type.has(type::q_static) && !info.type.has(type::q_const)); + + code += "static const "; + write_type(code, info.type); + code += ' ' + id_to_name(res) + " = "; + if (!info.type.is_scalar()) + write_type(code, info.type); + code += "(SPEC_CONSTANT_" + info.name + ");\n"; + + _module.spec_constants.push_back(info); + } + else + { + if (info.type.is_matrix()) + info.size = align_up(info.type.cols * 4, 16, info.type.rows); + else // Vectors are column major (1xN), matrices are row major (NxM) + info.size = info.type.rows * 4; + // Arrays are not packed in HLSL by default, each element is stored in a four-component vector (16 bytes) + if (info.type.is_array()) + info.size = align_up(info.size, 16, info.type.array_length); + + // Data is packed into 4-byte boundaries (see https://docs.microsoft.com/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules) + // This is already guaranteed, since all types are at least 4-byte in size + info.offset = _module.total_uniform_size; + // Additionally, HLSL packs data so that it does not cross a 16-byte boundary + const uint32_t remaining = 16 - (info.offset & 15); + if (remaining != 16 && info.size > remaining) + info.offset += remaining; + _module.total_uniform_size = info.offset + info.size; + + write_location(_cbuffer_block, loc); + + if (_shader_model >= 40) + _cbuffer_block += '\t'; + if (info.type.is_matrix()) // Force row major matrices + _cbuffer_block += "row_major "; + + type type = info.type; + if (_shader_model < 40) + { + // The HLSL compiler tries to evaluate boolean values with temporary registers, which breaks branches, so force it to use constant float registers + if (type.is_boolean()) + type.base = type::t_float; + + // Simply put each uniform into a separate constant register in shader model 3 for now + info.offset *= 4; + } + + write_type(_cbuffer_block, type); + _cbuffer_block += ' ' + id_to_name(res); + + if (info.type.is_array()) + _cbuffer_block += '[' + std::to_string(info.type.array_length) + ']'; + + if (_shader_model < 40) + { + // Every constant register is 16 bytes wide, so divide memory offset by 16 to get the constant register index + // Note: All uniforms are floating-point in shader model 3, even if the uniform type says different!! + _cbuffer_block += " : register(c" + std::to_string(info.offset / 16) + ')'; + } + + _cbuffer_block += ";\n"; + + _module.uniforms.push_back(info); + } + + return res; + } + id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override + { + const id res = make_id(); + + if (!name.empty()) + define_name(res, name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + if (!global) + code += '\t'; + + if (initializer_value != 0 && type.has(type::q_const)) + code += "const "; + + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + if (initializer_value != 0) + code += " = " + id_to_name(initializer_value); + + code += ";\n"; + + return res; + } + id define_function(const location &loc, function_info &info) override + { + info.definition = make_id(); + + define_name(info.definition, info.unique_name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + write_type(code, info.return_type); + code += ' ' + id_to_name(info.definition) + '('; + + for (size_t i = 0, num_params = info.parameter_list.size(); i < num_params; ++i) + { + auto ¶m = info.parameter_list[i]; + + param.definition = make_id(); + define_name(param.definition, param.name); + + code += '\n'; + write_location(code, param.location); + code += '\t'; + write_type(code, param.type); + code += ' ' + id_to_name(param.definition); + + if (param.type.is_array()) + code += '[' + std::to_string(param.type.array_length) + ']'; + + if (!param.semantic.empty()) + code += " : " + convert_semantic(param.semantic); + + if (i < num_params - 1) + code += ','; + } + + code += ')'; + + if (!info.return_semantic.empty()) + code += " : " + convert_semantic(info.return_semantic); + + code += '\n'; + + _functions.push_back(std::make_unique(info)); + + return info.definition; + } + + void define_entry_point(function_info &func, shader_type stype, int num_threads[3]) override + { + // Modify entry point name since a new function is created for it below + if (stype == shader_type::cs) + func.unique_name = 'E' + func.unique_name + + '_' + std::to_string(num_threads[0]) + + '_' + std::to_string(num_threads[1]) + + '_' + std::to_string(num_threads[2]); + else if (_shader_model < 40) + func.unique_name = 'E' + func.unique_name; + + if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), + [&func](const auto &ep) { return ep.name == func.unique_name; }); + it != _module.entry_points.end()) + return; + + _module.entry_points.push_back({ func.unique_name, stype }); + + // Only have to rewrite the entry point function signature in shader model 3 and for compute (to write "numthreads" attribute) + if (_shader_model >= 40 && stype != shader_type::cs) + return; + + auto entry_point = func; + + const auto is_color_semantic = [](const std::string &semantic) { + return semantic.compare(0, 9, "SV_TARGET") == 0 || semantic.compare(0, 5, "COLOR") == 0; }; + const auto is_position_semantic = [](const std::string &semantic) { + return semantic == "SV_POSITION" || semantic == "POSITION"; }; + + const auto ret = make_id(); + define_name(ret, "ret"); + + std::string position_variable_name; + { + if (func.return_type.is_struct() && stype == shader_type::vs) + { + // If this function returns a struct which contains a position output, keep track of its member name + for (const struct_member_info &member : get_struct(func.return_type.definition).member_list) + if (is_position_semantic(member.semantic)) + position_variable_name = id_to_name(ret) + '.' + member.name; + } + + if (is_color_semantic(func.return_semantic)) + { + // The COLOR output semantic has to be a four-component vector in shader model 3, so enforce that + entry_point.return_type.rows = 4; + } + if (is_position_semantic(func.return_semantic)) + { + if (stype == shader_type::vs) + // Keep track of the position output variable + position_variable_name = id_to_name(ret); + } + } + for (struct_member_info ¶m : entry_point.parameter_list) + { + if (param.type.is_struct() && stype == shader_type::vs) + { + for (const struct_member_info &member : get_struct(param.type.definition).member_list) + if (is_position_semantic(member.semantic)) + position_variable_name = param.name + '.' + member.name; + } + + if (is_color_semantic(param.semantic)) + { + param.type.rows = 4; + } + if (is_position_semantic(param.semantic)) + { + if (stype == shader_type::vs) + // Keep track of the position output variable + position_variable_name = param.name; + else if (stype == shader_type::ps) + // Change the position input semantic in pixel shaders + param.semantic = "VPOS"; + } + } + + if (stype == shader_type::cs) + _blocks.at(_current_block) += "[numthreads(" + + std::to_string(num_threads[0]) + ", " + + std::to_string(num_threads[1]) + ", " + + std::to_string(num_threads[2]) + ")]\n"; + + define_function({}, entry_point); + enter_block(create_block()); + + std::string &code = _blocks.at(_current_block); + + // Clear all color output parameters so no component is left uninitialized + for (struct_member_info ¶m : entry_point.parameter_list) + { + if (is_color_semantic(param.semantic)) + code += '\t' + param.name + " = float4(0.0, 0.0, 0.0, 0.0);\n"; + } + + code += '\t'; + if (is_color_semantic(func.return_semantic)) + { + code += "const float4 " + id_to_name(ret) + " = float4("; + } + else if (!func.return_type.is_void()) + { + write_type(code, func.return_type); + code += ' ' + id_to_name(ret) + " = "; + } + + // Call the function this entry point refers to + code += id_to_name(func.definition) + '('; + + for (size_t i = 0, num_params = func.parameter_list.size(); i < num_params; ++i) + { + code += func.parameter_list[i].name; + + if (is_color_semantic(func.parameter_list[i].semantic)) + { + code += '.'; + for (unsigned int k = 0; k < func.parameter_list[i].type.rows; k++) + code += "xyzw"[k]; + } + + if (i < num_params - 1) + code += ", "; + } + + code += ')'; + + // Cast the output value to a four-component vector + if (is_color_semantic(func.return_semantic)) + { + for (unsigned int i = 0; i < 4 - func.return_type.rows; i++) + code += ", 0.0"; + code += ')'; + } + + code += ";\n"; + + // Shift everything by half a viewport pixel to workaround the different half-pixel offset in D3D9 (https://aras-p.info/blog/2016/04/08/solving-dx9-half-pixel-offset/) + if (!position_variable_name.empty() && stype == shader_type::vs) // Check if we are in a vertex shader definition + code += '\t' + position_variable_name + ".xy += __TEXEL_SIZE__ * " + position_variable_name + ".ww;\n"; + + leave_block_and_return(func.return_type.is_void() ? 0 : ret); + leave_function(); + } + + id emit_load(const expression &exp, bool force_new_id) override + { + if (exp.is_constant) + return emit_constant(exp.type, exp.constant); + else if (exp.chain.empty() && !force_new_id) // Can refer to values without access chain directly + return exp.base; + + const id res = make_id(); + + static const char s_matrix_swizzles[16][5] = { + "_m00", "_m01", "_m02", "_m03", + "_m10", "_m11", "_m12", "_m13", + "_m20", "_m21", "_m22", "_m23", + "_m30", "_m31", "_m32", "_m33" + }; + + std::string type, expr_code = id_to_name(exp.base); + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_cast: + type.clear(); + write_type(type, op.to); + // Cast is in parentheses so that a subsequent operation operates on the casted value + expr_code = "((" + type + ')' + expr_code + ')'; + break; + case expression::operation::op_member: + expr_code += '.'; + expr_code += get_struct(op.from.definition).member_list[op.index].name; + break; + case expression::operation::op_dynamic_index: + expr_code += '[' + id_to_name(op.index) + ']'; + break; + case expression::operation::op_constant_index: + if (op.from.is_vector() && !op.from.is_array()) + expr_code += '.', + expr_code += "xyzw"[op.index]; + else + expr_code += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + expr_code += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + if (op.from.is_matrix()) + expr_code += s_matrix_swizzles[op.swizzle[i]]; + else + expr_code += "xyzw"[op.swizzle[i]]; + break; + } + } + + if (force_new_id) + { + // Need to store value in a new variable to comply with request for a new ID + std::string &code = _blocks.at(_current_block); + + code += '\t'; + write_type(code, exp.type); + code += ' ' + id_to_name(res) + " = " + expr_code + ";\n"; + } + else + { + // Avoid excessive variable definitions by instancing simple load operations in code every time + define_name(res, std::move(expr_code)); + } + + return res; + } + void emit_store(const expression &exp, id value) override + { + std::string &code = _blocks.at(_current_block); + + write_location(code, exp.location); + + code += '\t' + id_to_name(exp.base); + + static const char s_matrix_swizzles[16][5] = { + "_m00", "_m01", "_m02", "_m03", + "_m10", "_m11", "_m12", "_m13", + "_m20", "_m21", "_m22", "_m23", + "_m30", "_m31", "_m32", "_m33" + }; + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_member: + code += '.'; + code += get_struct(op.from.definition).member_list[op.index].name; + break; + case expression::operation::op_dynamic_index: + code += '[' + id_to_name(op.index) + ']'; + break; + case expression::operation::op_constant_index: + code += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + code += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + if (op.from.is_matrix()) + code += s_matrix_swizzles[op.swizzle[i]]; + else + code += "xyzw"[op.swizzle[i]]; + break; + } + } + + code += " = " + id_to_name(value) + ";\n"; + } + + id emit_constant(const type &type, const constant &data) override + { + const id res = make_id(); + + if (type.is_array()) + { + assert(type.has(type::q_const)); + + std::string &code = _blocks.at(_current_block); + + // Array constants need to be stored in a constant variable as they cannot be used in-place + code += '\t'; + code += "const "; + write_type(code, type); + code += ' ' + id_to_name(res); + code += '[' + std::to_string(type.array_length) + ']'; + code += " = "; + write_constant(code, type, data); + code += ";\n"; + return res; + } + + std::string code; + write_constant(code, type, data); + define_name(res, std::move(code)); + + return res; + } + + id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + if (_shader_model < 40 && op == tokenid::tilde) + code += "0xFFFFFFFF - "; // Emulate bitwise not operator on shader model 3 + else + code += char(op); + + code += id_to_name(val) + ";\n"; + + return res; + } + id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &, id lhs, id rhs) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + if (_shader_model < 40) + { + // See bitwise shift operator emulation below + if (op == tokenid::less_less || op == tokenid::less_less_equal) + code += '('; + else if (op == tokenid::greater_greater || op == tokenid::greater_greater_equal) + code += "floor("; + } + + code += id_to_name(lhs) + ' '; + + switch (op) + { + case tokenid::plus: + case tokenid::plus_plus: + case tokenid::plus_equal: + code += '+'; + break; + case tokenid::minus: + case tokenid::minus_minus: + case tokenid::minus_equal: + code += '-'; + break; + case tokenid::star: + case tokenid::star_equal: + code += '*'; + break; + case tokenid::slash: + case tokenid::slash_equal: + code += '/'; + break; + case tokenid::percent: + case tokenid::percent_equal: + code += '%'; + break; + case tokenid::caret: + case tokenid::caret_equal: + code += '^'; + break; + case tokenid::pipe: + case tokenid::pipe_equal: + code += '|'; + break; + case tokenid::ampersand: + case tokenid::ampersand_equal: + code += '&'; + break; + case tokenid::less_less: + case tokenid::less_less_equal: + code += _shader_model >= 40 ? "<<" : ") * exp2("; // Emulate bitwise shift operators on shader model 3 + break; + case tokenid::greater_greater: + case tokenid::greater_greater_equal: + code += _shader_model >= 40 ? ">>" : ") / exp2("; + break; + case tokenid::pipe_pipe: + code += "||"; + break; + case tokenid::ampersand_ampersand: + code += "&&"; + break; + case tokenid::less: + code += '<'; + break; + case tokenid::less_equal: + code += "<="; + break; + case tokenid::greater: + code += '>'; + break; + case tokenid::greater_equal: + code += ">="; + break; + case tokenid::equal_equal: + code += "=="; + break; + case tokenid::exclaim_equal: + code += "!="; + break; + default: + assert(false); + } + + code += ' ' + id_to_name(rhs); + + if (_shader_model < 40) + { + // See bitwise shift operator emulation above + if (op == tokenid::less_less || op == tokenid::less_less_equal || + op == tokenid::greater_greater || op == tokenid::greater_greater_equal) + code += ')'; + } + + code += ";\n"; + + return res; + } + id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override + { + if (op != tokenid::question) + return assert(false), 0; // Should never happen, since this is the only ternary operator currently supported + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = " + id_to_name(condition) + " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n"; + + return res; + } + id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert(arg.chain.empty() && arg.base != 0); +#endif + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = "; + } + + code += id_to_name(function) + '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + code += ");\n"; + + return res; + } + id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert(arg.chain.empty() && arg.base != 0); +#endif + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + enum + { + #define IMPLEMENT_INTRINSIC_HLSL(name, i, code) name##i, + #include "effect_symbol_table_intrinsics.inl" + }; + + write_location(code, loc); + + code += '\t'; + + if (_shader_model >= 40 && ( + (intrinsic >= tex1Dsize0 && intrinsic <= tex3Dsize2) || + (intrinsic >= atomicAdd0 && intrinsic <= atomicCompareExchange1) || + (!(res_type.is_floating_point() || _shader_model >= 67) && (intrinsic >= tex1D0 && intrinsic <= tex3Dlod1)))) + { + // Implementation of the 'tex2Dsize' intrinsic passes the result variable into 'GetDimensions' as output argument + // Same with the atomic intrinsics, which use the last parameter to return the previous value of the target + write_type(code, res_type); + code += ' ' + id_to_name(res) + "; "; + } + else if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + } + + switch (intrinsic) + { + #define IMPLEMENT_INTRINSIC_HLSL(name, i, code) case name##i: code break; + #include "effect_symbol_table_intrinsics.inl" + default: + assert(false); + } + + code += ";\n"; + + return res; + } + id emit_construct(const location &loc, const type &type, const std::vector &args) override + { +#ifndef NDEBUG + for (const auto &arg : args) + assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); +#endif + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += " = "; + + if (type.is_array()) + code += "{ "; + else + write_type(code, type), code += '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + if (type.is_array()) + code += " }"; + else + code += ')'; + + code += ";\n"; + + return res; + } + + void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) override + { + assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + code += _blocks.at(condition_block); + + write_location(code, loc); + + code += '\t'; + + if (flags & 0x1) code += "[flatten] "; + if (flags & 0x2) code += "[branch] "; + + code += "if (" + id_to_name(condition_value) + ")\n\t{\n"; + code += true_statement_data; + code += "\t}\n"; + + if (!false_statement_data.empty()) + { + code += "\telse\n\t{\n"; + code += false_statement_data; + code += "\t}\n"; + } + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + } + id 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 &type) override + { + assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + const id res = make_id(); + + code += _blocks.at(condition_block); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res) + ";\n"; + + write_location(code, loc); + + code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; + code += (true_statement_block != condition_block ? true_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n"; + code += "\t}\n\telse\n\t{\n"; + code += (false_statement_block != condition_block ? false_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n"; + code += "\t}\n"; + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + + return res; + } + void 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) override + { + assert(prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &loop_data = _blocks.at(loop_block); + std::string &continue_data = _blocks.at(continue_block); + + increase_indentation_level(loop_data); + increase_indentation_level(loop_data); + increase_indentation_level(continue_data); + + code += _blocks.at(prev_block); + + std::string attributes; + if (flags & 0x1) + attributes += "[unroll] "; + if (flags & 0x2) + attributes += _shader_model >= 40 ? "[fastopt] " : "[loop] "; + + // Condition value can be missing in infinite loop constructs like "for (;;)" + std::string condition_name = condition_value != 0 ? id_to_name(condition_value) : "true"; + + if (condition_block == 0) + { + // Convert the last SSA variable initializer to an assignment statement + auto pos_assign = continue_data.rfind(condition_name); + auto pos_prev_assign = continue_data.rfind('\t', pos_assign); + continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + + // We need to add the continue block to all "continue" statements as well + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data); + + code += "\tbool " + condition_name + ";\n"; + + write_location(code, loc); + + code += '\t' + attributes; + code += "do\n\t{\n\t\t{\n"; + code += 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 below + code += "\t\t}\n"; + code += continue_data; + code += "\t}\n\twhile (" + condition_name + ");\n"; + } + else + { + std::string &condition_data = _blocks.at(condition_block); + + // Work around D3DCompiler putting uniform variables that are used as the loop count register into integer registers (only in SM3) + // Only applies to dynamic loops with uniform variables in the condition, where it generates a loop instruction like "rep i0", but then expects the "i0" register to be set externally + // Moving the loop condition into the loop body forces it to move the uniform variable into a constant register instead and geneates a fixed number of loop iterations with "defi i0, 255, ..." + // Check 'condition_name' instead of 'condition_value' here to also catch cases where a constant boolean expression was passed in as loop condition + bool use_break_statement_for_condition = (_shader_model < 40 && condition_name != "true") && + std::find_if(_module.uniforms.begin(), _module.uniforms.end(), + [&](const uniform_info &info) { + return condition_data.find(info.name) != std::string::npos || condition_name.find(info.name) != std::string::npos; + }) != _module.uniforms.end(); + + // 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-is + if (!use_break_statement_for_condition && std::count(condition_data.begin(), condition_data.end(), '\n') == 1) + { + // Convert SSA variable initializer back to a condition expression + auto pos_assign = condition_data.find('='); + condition_data.erase(0, pos_assign + 2); + auto pos_semicolon = condition_data.rfind(';'); + condition_data.erase(pos_semicolon); + + condition_name = std::move(condition_data); + assert(condition_data.empty()); + } + else + { + code += condition_data; + + increase_indentation_level(condition_data); + + // Convert the last SSA variable initializer to an assignment statement + auto pos_assign = condition_data.rfind(condition_name); + auto pos_prev_assign = condition_data.rfind('\t', pos_assign); + condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + } + + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data + condition_data); + + write_location(code, loc); + + code += '\t' + attributes; + if (use_break_statement_for_condition) + code += "while (true)\n\t{\n\t\tif (" + condition_name + ")\n\t\t{\n"; + else + code += "while (" + condition_name + ")\n\t{\n\t\t{\n"; + code += loop_data; + code += "\t\t}\n"; + if (use_break_statement_for_condition) + code += "\t\telse break;\n"; + code += continue_data; + code += condition_data; + code += "\t}\n"; + + _blocks.erase(condition_block); + } + + // Remove consumed blocks to save memory + _blocks.erase(prev_block); + _blocks.erase(header_block); + _blocks.erase(loop_block); + _blocks.erase(continue_block); + } + void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, id default_block, const std::vector &case_literal_and_labels, const std::vector &case_blocks, unsigned int flags) override + { + assert(selector_value != 0 && selector_block != 0 && default_label != 0 && default_block != 0); + assert(case_blocks.size() == case_literal_and_labels.size() / 2); + + std::string &code = _blocks.at(_current_block); + + code += _blocks.at(selector_block); + + if (_shader_model >= 40) + { + write_location(code, loc); + + code += '\t'; + + if (flags & 0x1) code += "[flatten] "; + if (flags & 0x2) code += "[branch] "; + if (flags & 0x4) code += "[forcecase] "; + if (flags & 0x8) code += "[call] "; + + code += "switch (" + id_to_name(selector_value) + ")\n\t{\n"; + + std::vector labels = case_literal_and_labels; + for (size_t i = 0; i < labels.size(); i += 2) + { + if (labels[i + 1] == 0) + continue; // Happens if a case was already handled, see below + + code += "\tcase " + std::to_string(labels[i]) + ": "; + + if (labels[i + 1] == default_label) + { + code += "default: "; + default_label = 0; + } + else + { + for (size_t k = i + 2; k < labels.size(); k += 2) + { + if (labels[k + 1] == 0 || labels[k + 1] != labels[i + 1]) + continue; + + code += "case " + std::to_string(labels[k]) + ": "; + labels[k + 1] = 0; + } + } + + assert(case_blocks[i / 2] != 0); + std::string &case_data = _blocks.at(case_blocks[i / 2]); + + increase_indentation_level(case_data); + + code += "{\n"; + code += case_data; + code += "\t}\n"; + } + + if (default_label != 0 && default_block != _current_block) + { + std::string &default_data = _blocks.at(default_block); + + increase_indentation_level(default_data); + + code += "\tdefault: {\n"; + code += default_data; + code += "\t}\n"; + + _blocks.erase(default_block); + } + + code += "\t}\n"; + } + else // Switch statements do not work correctly in SM3 if a constant is used as selector value (this is a D3DCompiler bug), so replace them with if statements + { + write_location(code, loc); + + code += "\t[unroll] do { "; // This dummy loop makes "break" statements work + + if (flags & 0x1) code += "[flatten] "; + if (flags & 0x2) code += "[branch] "; + + std::vector labels = case_literal_and_labels; + for (size_t i = 0; i < labels.size(); i += 2) + { + if (labels[i + 1] == 0) + continue; // Happens if a case was already handled, see below + + code += "if (" + id_to_name(selector_value) + " == " + std::to_string(labels[i]); + + for (size_t k = i + 2; k < labels.size(); k += 2) + { + if (labels[k + 1] == 0 || labels[k + 1] != labels[i + 1]) + continue; + + code += " || " + id_to_name(selector_value) + " == " + std::to_string(labels[k]); + labels[k + 1] = 0; + } + + assert(case_blocks[i / 2] != 0); + std::string &case_data = _blocks.at(case_blocks[i / 2]); + + increase_indentation_level(case_data); + + code += ")\n\t{\n"; + code += case_data; + code += "\t}\n\telse\n\t"; + } + + code += "{\n"; + + if (default_block != _current_block) + { + std::string &default_data = _blocks.at(default_block); + + increase_indentation_level(default_data); + + code += default_data; + + _blocks.erase(default_block); + } + + code += "\t} } while (false);\n"; + } + + // Remove consumed blocks to save memory + _blocks.erase(selector_block); + for (const id case_block : case_blocks) + _blocks.erase(case_block); + } + + id create_block() override + { + const id res = make_id(); + + std::string &block = _blocks.emplace(res, std::string()).first->second; + // Reserve a decently big enough memory block to avoid frequent reallocations + block.reserve(4096); + + return res; + } + id set_block(id id) override + { + _last_block = _current_block; + _current_block = id; + + return _last_block; + } + void enter_block(id id) override + { + _current_block = id; + } + id leave_block_and_kill() override + { + if (!is_in_block()) + return 0; + + std::string &code = _blocks.at(_current_block); + + code += "\tdiscard;\n"; + + const auto &return_type = _functions.back()->return_type; + if (!return_type.is_void()) + { + // HLSL compiler doesn't handle discard like a shader kill + // Add a return statement to exit functions in case discard is the last control flow statement + // See https://docs.microsoft.com/windows/win32/direct3dhlsl/discard--sm4---asm- + code += "\treturn "; + write_constant(code, return_type, constant()); + code += ";\n"; + } + + return set_block(0); + } + id leave_block_and_return(id value) override + { + if (!is_in_block()) + return 0; + + // Skip implicit return statement + if (!_functions.back()->return_type.is_void() && value == 0) + return set_block(0); + + std::string &code = _blocks.at(_current_block); + + code += "\treturn"; + + if (value != 0) + code += ' ' + id_to_name(value); + + code += ";\n"; + + return set_block(0); + } + id leave_block_and_switch(id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + id leave_block_and_branch(id target, unsigned int loop_flow) override + { + if (!is_in_block()) + return _last_block; + + std::string &code = _blocks.at(_current_block); + + switch (loop_flow) + { + case 1: + code += "\tbreak;\n"; + break; + case 2: // Keep track of continue target block, so we can insert its code here later + code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n"; + break; + } + + return set_block(0); + } + id leave_block_and_branch_conditional(id, id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + void leave_function() override + { + assert(_last_block != 0); + + _blocks.at(0) += "{\n" + _blocks.at(_last_block) + "}\n"; + } +}; + +codegen *reshadefx::create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants) +{ + return new codegen_hlsl(shader_model, debug_info, uniforms_to_spec_constants); +} diff --git a/dep/reshadefx/src/effect_codegen_spirv.cpp b/dep/reshadefx/src/effect_codegen_spirv.cpp new file mode 100644 index 000000000..2695b970a --- /dev/null +++ b/dep/reshadefx/src/effect_codegen_spirv.cpp @@ -0,0 +1,2394 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include +#include // memcmp +#include // std::find_if, std::max +#include + +// Use the C++ variant of the SPIR-V headers +#include +namespace spv { +#include +} + +using namespace reshadefx; + +/// +/// A single instruction in a SPIR-V module +/// +struct spirv_instruction +{ + spv::Op op; + spv::Id type; + spv::Id result; + std::vector operands; + + explicit spirv_instruction(spv::Op op = spv::OpNop) : op(op), type(0), result(0) {} + spirv_instruction(spv::Op op, spv::Id result) : op(op), type(result), result(0) {} + spirv_instruction(spv::Op op, spv::Id type, spv::Id result) : op(op), type(type), result(result) {} + + /// + /// Add a single operand to the instruction. + /// + spirv_instruction &add(spv::Id operand) + { + operands.push_back(operand); + return *this; + } + + /// + /// Add a range of operands to the instruction. + /// + template + spirv_instruction &add(It begin, It end) + { + operands.insert(operands.end(), begin, end); + return *this; + } + + /// + /// Add a null-terminated literal UTF-8 string to the instruction. + /// + spirv_instruction &add_string(const char *string) + { + uint32_t word; + do { + word = 0; + for (uint32_t i = 0; i < 4 && *string; ++i) + reinterpret_cast(&word)[i] = *string++; + add(word); + } while (*string || (word & 0xFF000000)); + return *this; + } + + /// + /// Write this instruction to a SPIR-V module. + /// + /// The output stream to append this instruction to. + void write(std::vector &output) const + { + // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html + // 0 | Opcode: The 16 high-order bits are the WordCount of the instruction. The 16 low-order bits are the opcode enumerant. + // 1 | Optional instruction type + // . | Optional instruction Result + // . | Operand 1 (if needed) + // . | Operand 2 (if needed) + // ... | ... + // WordCount - 1 | Operand N (N is determined by WordCount minus the 1 to 3 words used for the opcode, instruction type , and instruction Result ). + + const uint32_t num_words = 1 + (type != 0) + (result != 0) + static_cast(operands.size()); + output.push_back((num_words << spv::WordCountShift) | op); + + // Optional instruction type ID + if (type != 0) + output.push_back(type); + + // Optional instruction result ID + if (result != 0) + output.push_back(result); + + // Write out the operands + output.insert(output.end(), operands.begin(), operands.end()); + } +}; + +/// +/// A list of instructions forming a basic block in the SPIR-V module +/// +struct spirv_basic_block +{ + std::vector instructions; + + /// + /// Append another basic block the end of this one. + /// + void append(const spirv_basic_block &block) + { + instructions.insert(instructions.end(), block.instructions.begin(), block.instructions.end()); + } +}; + +class codegen_spirv final : public codegen +{ +public: + codegen_spirv(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y) + : _debug_info(debug_info), _vulkan_semantics(vulkan_semantics), _uniforms_to_spec_constants(uniforms_to_spec_constants), _enable_16bit_types(enable_16bit_types), _flip_vert_y(flip_vert_y) + { + _glsl_ext = make_id(); + } + +private: + struct type_lookup + { + reshadefx::type type; + bool is_ptr; + uint32_t array_stride; + std::pair storage; + + friend bool operator==(const type_lookup &lhs, const type_lookup &rhs) + { + return lhs.type == rhs.type && lhs.is_ptr == rhs.is_ptr && lhs.array_stride == rhs.array_stride && lhs.storage == rhs.storage; + } + }; + struct function_blocks + { + spirv_basic_block declaration; + spirv_basic_block variables; + spirv_basic_block definition; + type return_type; + std::vector param_types; + bool is_entry_point = false; + + friend bool operator==(const function_blocks &lhs, const function_blocks &rhs) + { + if (lhs.param_types.size() != rhs.param_types.size()) + return false; + for (size_t i = 0; i < lhs.param_types.size(); ++i) + if (!(lhs.param_types[i] == rhs.param_types[i])) + return false; + return lhs.return_type == rhs.return_type; + } + }; + + spirv_basic_block _entries; + spirv_basic_block _execution_modes; + spirv_basic_block _debug_a; + spirv_basic_block _debug_b; + spirv_basic_block _annotations; + spirv_basic_block _types_and_constants; + spirv_basic_block _variables; + + std::unordered_set _spec_constants; + std::unordered_set _capabilities; + std::vector> _type_lookup; + std::vector> _constant_lookup; + std::vector> _function_type_lookup; + std::unordered_map _string_lookup; + std::unordered_map> _storage_lookup; + std::unordered_map _semantic_to_location; + + std::vector _functions_blocks; + std::unordered_map _block_data; + spirv_basic_block *_current_block_data = nullptr; + + bool _debug_info = false; + bool _vulkan_semantics = false; + bool _uniforms_to_spec_constants = false; + bool _enable_16bit_types = false; + bool _flip_vert_y = false; + id _glsl_ext = 0; + id _global_ubo_type = 0; + id _global_ubo_variable = 0; + std::vector _global_ubo_types; + function_blocks *_current_function = nullptr; + + inline void add_location(const location &loc, spirv_basic_block &block) + { + if (loc.source.empty() || !_debug_info) + return; + + spv::Id file; + + if (const auto it = _string_lookup.find(loc.source); + it != _string_lookup.end()) + file = it->second; + else { + add_instruction(spv::OpString, 0, _debug_a, file) + .add_string(loc.source.c_str()); + _string_lookup.emplace(loc.source, file); + } + + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpLine + add_instruction_without_result(spv::OpLine, block) + .add(file) + .add(loc.line) + .add(loc.column); + } + inline spirv_instruction &add_instruction(spv::Op op, spv::Id type = 0) + { + assert(is_in_function() && is_in_block()); + return add_instruction(op, type, *_current_block_data); + } + inline spirv_instruction &add_instruction(spv::Op op, spv::Id type, spirv_basic_block &block) + { + spirv_instruction &instruction = add_instruction_without_result(op, block); + instruction.type = type; + instruction.result = make_id(); + return instruction; + } + inline spirv_instruction &add_instruction(spv::Op op, spv::Id type, spirv_basic_block &block, spv::Id &result) + { + spirv_instruction &instruction = add_instruction_without_result(op, block); + instruction.type = type; + instruction.result = result = make_id(); + return instruction; + } + inline spirv_instruction &add_instruction_without_result(spv::Op op) + { + assert(is_in_function() && is_in_block()); + return add_instruction_without_result(op, *_current_block_data); + } + inline spirv_instruction &add_instruction_without_result(spv::Op op, spirv_basic_block &block) + { + return block.instructions.emplace_back(op); + } + + void write_result(module &module) override + { + // First initialize the UBO type now that all member types are known + if (_global_ubo_type != 0) + { + spirv_instruction &type_inst = add_instruction_without_result(spv::OpTypeStruct, _types_and_constants); + type_inst.add(_global_ubo_types.begin(), _global_ubo_types.end()); + type_inst.result = _global_ubo_type; + + spirv_instruction &variable_inst = add_instruction_without_result(spv::OpVariable, _variables); + variable_inst.add(spv::StorageClassUniform); + variable_inst.type = convert_type({ type::t_struct, 0, 0, type::q_uniform, 0, _global_ubo_type }, true, spv::StorageClassUniform); + variable_inst.result = _global_ubo_variable; + + add_name(variable_inst.result, "$Globals"); + } + + module = std::move(_module); + + std::vector spirv; + + // Write SPIRV header info + spirv.push_back(spv::MagicNumber); + spirv.push_back(0x10300); // Force SPIR-V 1.3 + spirv.push_back(0u); // Generator magic number, see https://www.khronos.org/registry/spir-v/api/spir-v.xml + spirv.push_back(_next_id); // Maximum ID + spirv.push_back(0u); // Reserved for instruction schema + + // All capabilities + spirv_instruction(spv::OpCapability) + .add(spv::CapabilityShader) // Implicitly declares the Matrix capability too + .write(spirv); + + for (spv::Capability capability : _capabilities) + spirv_instruction(spv::OpCapability) + .add(capability) + .write(spirv); + + // Optional extension instructions + spirv_instruction(spv::OpExtInstImport, _glsl_ext) + .add_string("GLSL.std.450") // Import GLSL extension + .write(spirv); + + // Single required memory model instruction + spirv_instruction(spv::OpMemoryModel) + .add(spv::AddressingModelLogical) + .add(spv::MemoryModelGLSL450) + .write(spirv); + + // All entry point declarations + for (const auto &node : _entries.instructions) + node.write(spirv); + + // All execution mode declarations + for (const auto &node : _execution_modes.instructions) + node.write(spirv); + + spirv_instruction(spv::OpSource) + .add(spv::SourceLanguageUnknown) // ReShade FX is not a reserved token at the moment + .add(0) // Language version, TODO: Maybe fill in ReShade version here? + .write(spirv); + + if (_debug_info) + { + // All debug instructions + for (const auto &node : _debug_a.instructions) + node.write(spirv); + for (const auto &node : _debug_b.instructions) + node.write(spirv); + } + + // All annotation instructions + for (const auto &node : _annotations.instructions) + node.write(spirv); + + // All type declarations + for (const auto &node : _types_and_constants.instructions) + node.write(spirv); + for (const auto &node : _variables.instructions) + node.write(spirv); + + // All function definitions + for (const auto &function : _functions_blocks) + { + if (function.definition.instructions.empty()) + continue; + + for (const auto &node : function.declaration.instructions) + node.write(spirv); + + // Grab first label and move it in front of variable declarations + function.definition.instructions.front().write(spirv); + assert(function.definition.instructions.front().op == spv::OpLabel); + + for (const auto &node : function.variables.instructions) + node.write(spirv); + for (auto it = function.definition.instructions.begin() + 1; it != function.definition.instructions.end(); ++it) + it->write(spirv); + } + + module.code.assign(reinterpret_cast(spirv.data()), reinterpret_cast(spirv.data() + spirv.size())); + } + + spv::Id convert_type(type info, bool is_ptr = false, spv::StorageClass storage = spv::StorageClassFunction, spv::ImageFormat format = spv::ImageFormatUnknown, uint32_t array_stride = 0) + { + assert(array_stride == 0 || info.is_array()); + + // The storage class is only relevant for pointers, so ignore it for other types during lookup + if (is_ptr == false) + storage = spv::StorageClassFunction; + // There cannot be sampler variables that are local to a function, so always assume uniform storage for them + if (info.is_object()) + storage = spv::StorageClassUniformConstant; + else + assert(format == spv::ImageFormatUnknown); + + if (info.is_sampler() || info.is_storage()) + info.rows = info.cols = 1; + + // Fall back to 32-bit types and use relaxed precision decoration instead if 16-bit types are not enabled + if (!_enable_16bit_types && info.is_numeric() && info.precision() < 32) + info.base = static_cast(info.base + 1); // min16int -> int, min16uint -> uint, min16float -> float + + const type_lookup lookup { info, is_ptr, array_stride, { storage, format } }; + + if (const auto it = std::find_if(_type_lookup.begin(), _type_lookup.end(), + [&lookup](const auto &lookup_it) { return lookup_it.first == lookup; }); + it != _type_lookup.end()) + return it->second; + + spv::Id type, elem_type; + if (is_ptr) + { + elem_type = convert_type(info, false, storage, format, array_stride); + + add_instruction(spv::OpTypePointer, 0, _types_and_constants, type) + .add(storage) + .add(elem_type); + } + else if (info.is_array()) + { + auto elem_info = info; + elem_info.array_length = 0; + + // Make sure we don't get any dynamic arrays here + assert(info.array_length > 0); + + elem_type = convert_type(elem_info, false, storage, format); + const spv::Id array_length = emit_constant(info.array_length); + + add_instruction(spv::OpTypeArray, 0, _types_and_constants, type) + .add(elem_type) + .add(array_length); + + if (array_stride != 0) + add_decoration(type, spv::DecorationArrayStride, { array_stride }); + } + else if (info.is_matrix()) + { + // Convert MxN matrix to a SPIR-V matrix with M vectors with N elements + auto elem_info = info; + elem_info.rows = info.cols; + elem_info.cols = 1; + + elem_type = convert_type(elem_info, false, storage, format); + + // Matrix types with just one row are interpreted as if they were a vector type + if (info.rows == 1) + return elem_type; + + add_instruction(spv::OpTypeMatrix, 0, _types_and_constants, type) + .add(elem_type) + .add(info.rows); + } + else if (info.is_vector()) + { + auto elem_info = info; + elem_info.rows = 1; + elem_info.cols = 1; + + elem_type = convert_type(elem_info, false, storage, format); + + add_instruction(spv::OpTypeVector, 0, _types_and_constants, type) + .add(elem_type) + .add(info.rows); + } + else + { + switch (info.base) + { + case type::t_void: + assert(info.rows == 0 && info.cols == 0); + add_instruction(spv::OpTypeVoid, 0, _types_and_constants, type); + break; + case type::t_bool: + assert(info.rows == 1 && info.cols == 1); + add_instruction(spv::OpTypeBool, 0, _types_and_constants, type); + break; + case type::t_min16int: + assert(_enable_16bit_types && info.rows == 1 && info.cols == 1); + add_capability(spv::CapabilityInt16); + if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput) + add_capability(spv::CapabilityStorageInputOutput16); + add_instruction(spv::OpTypeInt, 0, _types_and_constants, type) + .add(16) // Width + .add(1); // Signedness + break; + case type::t_int: + assert(info.rows == 1 && info.cols == 1); + add_instruction(spv::OpTypeInt, 0, _types_and_constants, type) + .add(32) // Width + .add(1); // Signedness + break; + case type::t_min16uint: + assert(_enable_16bit_types && info.rows == 1 && info.cols == 1); + add_capability(spv::CapabilityInt16); + if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput) + add_capability(spv::CapabilityStorageInputOutput16); + add_instruction(spv::OpTypeInt, 0, _types_and_constants, type) + .add(16) // Width + .add(0); // Signedness + break; + case type::t_uint: + assert(info.rows == 1 && info.cols == 1); + add_instruction(spv::OpTypeInt, 0, _types_and_constants, type) + .add(32) // Width + .add(0); // Signedness + break; + case type::t_min16float: + assert(_enable_16bit_types && info.rows == 1 && info.cols == 1); + add_capability(spv::CapabilityFloat16); + if (storage == spv::StorageClassInput || storage == spv::StorageClassOutput) + add_capability(spv::CapabilityStorageInputOutput16); + add_instruction(spv::OpTypeFloat, 0, _types_and_constants, type) + .add(16); // Width + break; + case type::t_float: + assert(info.rows == 1 && info.cols == 1); + add_instruction(spv::OpTypeFloat, 0, _types_and_constants, type) + .add(32); // Width + break; + case type::t_struct: + assert(info.rows == 0 && info.cols == 0 && info.definition != 0); + type = info.definition; + break; + case type::t_sampler1d_int: + case type::t_sampler1d_uint: + case type::t_sampler1d_float: + add_capability(spv::CapabilitySampled1D); + [[fallthrough]]; + case type::t_sampler2d_int: + case type::t_sampler2d_uint: + case type::t_sampler2d_float: + case type::t_sampler3d_int: + case type::t_sampler3d_uint: + case type::t_sampler3d_float: + elem_type = convert_image_type(info, format); + add_instruction(spv::OpTypeSampledImage, 0, _types_and_constants, type) + .add(elem_type); + break; + case type::t_storage1d_int: + case type::t_storage1d_uint: + case type::t_storage1d_float: + add_capability(spv::CapabilityImage1D); + [[fallthrough]]; + case type::t_storage2d_int: + case type::t_storage2d_uint: + case type::t_storage2d_float: + case type::t_storage3d_int: + case type::t_storage3d_uint: + case type::t_storage3d_float: + // No format specified for the storage image + if (format == spv::ImageFormatUnknown) + add_capability(spv::CapabilityStorageImageWriteWithoutFormat); + return convert_image_type(info, format); + default: + return assert(false), 0; + } + } + + _type_lookup.push_back({ lookup, type }); + + return type; + } + spv::Id convert_type(const function_blocks &info) + { + if (const auto it = std::find_if(_function_type_lookup.begin(), _function_type_lookup.end(), + [&lookup = info](const auto &lookup_it) { return lookup_it.first == lookup; }); + it != _function_type_lookup.end()) + return it->second; + + auto return_type = convert_type(info.return_type); + assert(return_type != 0); + + std::vector param_type_ids; + param_type_ids.reserve(info.param_types.size()); + for (const type ¶m_type : info.param_types) + param_type_ids.push_back(convert_type(param_type, true)); + + spirv_instruction &inst = add_instruction(spv::OpTypeFunction, 0, _types_and_constants); + inst.add(return_type); + inst.add(param_type_ids.begin(), param_type_ids.end()); + + _function_type_lookup.push_back({ info, inst.result });; + + return inst.result; + } + spv::Id convert_image_type(type info, spv::ImageFormat format = spv::ImageFormatUnknown) + { + type_lookup lookup { info, false, 0u, { spv::StorageClassUniformConstant, format } }; + + auto elem_info = info; + elem_info.rows = 1; + elem_info.cols = 1; + + if (!info.is_numeric()) + { + if ((info.is_integral() && info.is_signed()) || (format >= spv::ImageFormatRgba32i && format <= spv::ImageFormatR8i)) + elem_info.base = type::t_int; + else if ((info.is_integral() && info.is_unsigned()) || (format >= spv::ImageFormatRgba32ui && format <= spv::ImageFormatR8ui)) + elem_info.base = type::t_uint; + else + elem_info.base = type::t_float; + } + + if (!info.is_storage()) + { + lookup.type = elem_info; + lookup.type.base = static_cast(type::t_texture1d + info.texture_dimension() - 1); + lookup.type.definition = static_cast(elem_info.base); + } + + if (const auto it = std::find_if(_type_lookup.begin(), _type_lookup.end(), + [&lookup](const auto &lookup_it) { return lookup_it.first == lookup; }); + it != _type_lookup.end()) + return it->second; + + spv::Id type, elem_type = convert_type(elem_info, false, spv::StorageClassUniformConstant); + + add_instruction(spv::OpTypeImage, 0, _types_and_constants, type) + .add(elem_type) // Sampled Type (always a scalar type) + .add(spv::Dim1D + info.texture_dimension() - 1) + .add(0) // Not a depth image + .add(0) // Not an array + .add(0) // Not multi-sampled + .add(info.is_storage() ? 2 : 1) // Used with a sampler or as storage + .add(format); + + _type_lookup.push_back({ lookup, type }); + + return type; + } + + uint32_t semantic_to_location(const std::string &semantic, uint32_t max_array_length = 1) + { + if (semantic.compare(0, 5, "COLOR") == 0) + return std::strtoul(semantic.c_str() + 5, nullptr, 10); + if (semantic.compare(0, 9, "SV_TARGET") == 0) + return std::strtoul(semantic.c_str() + 9, nullptr, 10); + + if (const auto it = _semantic_to_location.find(semantic); + it != _semantic_to_location.end()) + return it->second; + + // Extract the semantic index from the semantic name (e.g. 2 for "TEXCOORD2") + size_t digit_index = semantic.size() - 1; + while (digit_index != 0 && semantic[digit_index] >= '0' && semantic[digit_index] <= '9') + digit_index--; + digit_index++; + + const uint32_t semantic_digit = std::strtoul(semantic.c_str() + digit_index, nullptr, 10); + const std::string semantic_base = semantic.substr(0, digit_index); + + uint32_t location = static_cast(_semantic_to_location.size()); + + // Now create adjoining location indices for all possible semantic indices belonging to this semantic name + for (uint32_t a = 0; a < semantic_digit + max_array_length; ++a) + { + const auto insert = _semantic_to_location.emplace(semantic_base + std::to_string(a), location + a); + if (!insert.second) + { + assert(a == 0 || (insert.first->second - a) == location); + + // Semantic was already created with a different location index, so need to remap to that + location = insert.first->second - a; + } + } + + return location + semantic_digit; + } + + const spv::BuiltIn semantic_to_builtin(const std::string &semantic, shader_type stype) const + { + if (semantic == "SV_POSITION") + return stype == shader_type::ps ? spv::BuiltInFragCoord : spv::BuiltInPosition; + if (semantic == "SV_POINTSIZE") + return spv::BuiltInPointSize; + if (semantic == "SV_DEPTH") + return spv::BuiltInFragDepth; + if (semantic == "SV_VERTEXID") + return _vulkan_semantics ? spv::BuiltInVertexIndex : spv::BuiltInVertexId; + if (semantic == "SV_ISFRONTFACE") + return spv::BuiltInFrontFacing; + if (semantic == "SV_GROUPID") + return spv::BuiltInWorkgroupId; + if (semantic == "SV_GROUPINDEX") + return spv::BuiltInLocalInvocationIndex; + if (semantic == "SV_GROUPTHREADID") + return spv::BuiltInLocalInvocationId; + if (semantic == "SV_DISPATCHTHREADID") + return spv::BuiltInGlobalInvocationId; + return spv::BuiltInMax; + } + const spv::ImageFormat format_to_image_format(texture_format format) + { + switch (format) + { + default: + assert(false); + [[fallthrough]]; + case texture_format::unknown: + return spv::ImageFormatUnknown; + case texture_format::r8: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatR8; + case texture_format::r16: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatR16; + case texture_format::r16f: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatR16f; + case texture_format::r32i: + return spv::ImageFormatR32i; + case texture_format::r32u: + return spv::ImageFormatR32ui; + case texture_format::r32f: + return spv::ImageFormatR32f; + case texture_format::rg8: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatRg8; + case texture_format::rg16: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatRg16; + case texture_format::rg16f: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatRg16f; + case texture_format::rg32f: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatRg32f; + case texture_format::rgba8: + return spv::ImageFormatRgba8; + case texture_format::rgba16: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatRgba16; + case texture_format::rgba16f: + return spv::ImageFormatRgba16f; + case texture_format::rgba32f: + return spv::ImageFormatRgba32f; + case texture_format::rgb10a2: + add_capability(spv::CapabilityStorageImageExtendedFormats); + return spv::ImageFormatRgb10A2; + } + } + + inline void add_name(id id, const char *name) + { + if (!_debug_info) + return; + + assert(name != nullptr); + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpName + add_instruction_without_result(spv::OpName, _debug_b) + .add(id) + .add_string(name); + } + inline void add_builtin(id id, spv::BuiltIn builtin) + { + add_instruction_without_result(spv::OpDecorate, _annotations) + .add(id) + .add(spv::DecorationBuiltIn) + .add(builtin); + } + inline void add_decoration(id id, spv::Decoration decoration, std::initializer_list values = {}) + { + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpDecorate + add_instruction_without_result(spv::OpDecorate, _annotations) + .add(id) + .add(decoration) + .add(values.begin(), values.end()); + } + inline void add_member_name(id id, uint32_t member_index, const char *name) + { + if (!_debug_info) + return; + + assert(name != nullptr); + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpMemberName + add_instruction_without_result(spv::OpMemberName, _debug_b) + .add(id) + .add(member_index) + .add_string(name); + } + inline void add_member_builtin(id id, uint32_t member_index, spv::BuiltIn builtin) + { + add_instruction_without_result(spv::OpMemberDecorate, _annotations) + .add(id) + .add(member_index) + .add(spv::DecorationBuiltIn) + .add(builtin); + } + inline void add_member_decoration(id id, uint32_t member_index, spv::Decoration decoration, std::initializer_list values = {}) + { + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpMemberDecorate + add_instruction_without_result(spv::OpMemberDecorate, _annotations) + .add(id) + .add(member_index) + .add(decoration) + .add(values.begin(), values.end()); + } + inline void add_capability(spv::Capability capability) + { + _capabilities.insert(capability); + } + + id define_struct(const location &loc, struct_info &info) override + { + // First define all member types to make sure they are declared before the struct type references them + std::vector member_types; + member_types.reserve(info.member_list.size()); + for (const struct_member_info &member : info.member_list) + member_types.push_back(convert_type(member.type)); + + // Afterwards define the actual struct type + add_location(loc, _types_and_constants); + + add_instruction(spv::OpTypeStruct, 0, _types_and_constants, info.definition) + .add(member_types.begin(), member_types.end()); + + if (!info.unique_name.empty()) + add_name(info.definition, info.unique_name.c_str()); + + for (uint32_t index = 0; index < info.member_list.size(); ++index) + { + const struct_member_info &member = info.member_list[index]; + + add_member_name(info.definition, index, member.name.c_str()); + + if (!_enable_16bit_types && member.type.is_numeric() && member.type.precision() < 32) + add_member_decoration(info.definition, index, spv::DecorationRelaxedPrecision); + } + + _structs.push_back(info); + + return info.definition; + } + id define_texture(const location &, texture_info &info) override + { + info.id = make_id(); // Need to create an unique ID here too, so that the symbol lookup for textures works + info.binding = ~0u; + + _module.textures.push_back(info); + + return info.id; + } + id define_sampler(const location &loc, const texture_info &, sampler_info &info) override + { + info.id = define_variable(loc, info.type, info.unique_name.c_str(), spv::StorageClassUniformConstant); + info.binding = _module.num_sampler_bindings++; + info.texture_binding = ~0u; + + add_decoration(info.id, spv::DecorationBinding, { info.binding }); + add_decoration(info.id, spv::DecorationDescriptorSet, { 1 }); + + _module.samplers.push_back(info); + + return info.id; + } + id define_storage(const location &loc, const texture_info &tex_info, storage_info &info) override + { + info.id = define_variable(loc, info.type, info.unique_name.c_str(), spv::StorageClassUniformConstant, format_to_image_format(tex_info.format)); + info.binding = _module.num_storage_bindings++; + + add_decoration(info.id, spv::DecorationBinding, { info.binding }); + add_decoration(info.id, spv::DecorationDescriptorSet, { 2 }); + + _module.storages.push_back(info); + + return info.id; + } + id define_uniform(const location &, uniform_info &info) override + { + if (_uniforms_to_spec_constants && info.has_initializer_value) + { + const id res = emit_constant(info.type, info.initializer_value, true); + + add_name(res, info.name.c_str()); + + const auto add_spec_constant = [this](const spirv_instruction &inst, const uniform_info &info, const constant &initializer_value, size_t initializer_offset) { + assert(inst.op == spv::OpSpecConstant || inst.op == spv::OpSpecConstantTrue || inst.op == spv::OpSpecConstantFalse); + + const uint32_t spec_id = static_cast(_module.spec_constants.size()); + add_decoration(inst.result, spv::DecorationSpecId, { spec_id }); + + uniform_info scalar_info = info; + scalar_info.type.rows = 1; + scalar_info.type.cols = 1; + scalar_info.size = 4; + scalar_info.offset = static_cast(initializer_offset); + scalar_info.initializer_value = {}; + scalar_info.initializer_value.as_uint[0] = initializer_value.as_uint[initializer_offset]; + + _module.spec_constants.push_back(scalar_info); + }; + + const spirv_instruction &base_inst = _types_and_constants.instructions.back(); + assert(base_inst.result == res); + + // External specialization constants need to be scalars + if (info.type.is_scalar()) + { + add_spec_constant(base_inst, info, info.initializer_value, 0); + } + else + { + assert(base_inst.op == spv::OpSpecConstantComposite); + + // Add each individual scalar component of the constant as a separate external specialization constant + for (size_t i = 0; i < (info.type.is_array() ? base_inst.operands.size() : 1); ++i) + { + constant initializer_value = info.initializer_value; + spirv_instruction elem_inst = base_inst; + + if (info.type.is_array()) + { + elem_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(), + [elem = base_inst.operands[i]](const auto &it) { return it.result == elem; }); + + assert(initializer_value.array_data.size() == base_inst.operands.size()); + initializer_value = initializer_value.array_data[i]; + } + + for (size_t row = 0; row < elem_inst.operands.size(); ++row) + { + const spirv_instruction &row_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(), + [elem = elem_inst.operands[row]](const auto &it) { return it.result == elem; }); + + if (row_inst.op != spv::OpSpecConstantComposite) + { + add_spec_constant(row_inst, info, initializer_value, row); + continue; + } + + for (size_t col = 0; col < row_inst.operands.size(); ++col) + { + const spirv_instruction &col_inst = *std::find_if(_types_and_constants.instructions.rbegin(), _types_and_constants.instructions.rend(), + [elem = row_inst.operands[col]](const auto &it) { return it.result == elem; }); + + add_spec_constant(col_inst, info, initializer_value, row * info.type.cols + col); + } + } + } + } + + return res; + } + else + { + // Create global uniform buffer variable on demand + if (_global_ubo_type == 0) + { + _global_ubo_type = make_id(); + + add_decoration(_global_ubo_type, spv::DecorationBlock); + } + if (_global_ubo_variable == 0) + { + _global_ubo_variable = make_id(); + + add_decoration(_global_ubo_variable, spv::DecorationDescriptorSet, { 0 }); + add_decoration(_global_ubo_variable, spv::DecorationBinding, { 0 }); + } + + uint32_t alignment = (info.type.rows == 3 ? 4 : info.type.rows) * 4; + info.size = info.type.rows * 4; + + uint32_t array_stride = 16; + const uint32_t matrix_stride = 16; + + if (info.type.is_matrix()) + { + alignment = matrix_stride; + info.size = info.type.rows * matrix_stride; + } + if (info.type.is_array()) + { + alignment = array_stride; + array_stride = align_up(info.size, array_stride); + // Uniform block rules do not permit anything in the padding of an array + info.size = array_stride * info.type.array_length; + } + + info.offset = _module.total_uniform_size; + info.offset = align_up(info.offset, alignment); + _module.total_uniform_size = info.offset + info.size; + + type ubo_type = info.type; + // Convert boolean uniform variables to integer type so that they have a defined size + if (info.type.is_boolean()) + ubo_type.base = type::t_uint; + + const uint32_t member_index = static_cast(_global_ubo_types.size()); + + // Composite objects in the uniform storage class must be explicitly laid out, which includes array types requiring a stride decoration + _global_ubo_types.push_back( + convert_type(ubo_type, false, spv::StorageClassUniform, spv::ImageFormatUnknown, info.type.is_array() ? array_stride : 0u)); + + add_member_name(_global_ubo_type, member_index, info.name.c_str()); + + add_member_decoration(_global_ubo_type, member_index, spv::DecorationOffset, { info.offset }); + + if (info.type.is_matrix()) + { + // 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) + // TODO: This technically only works with square matrices + add_member_decoration(_global_ubo_type, member_index, spv::DecorationColMajor); + add_member_decoration(_global_ubo_type, member_index, spv::DecorationMatrixStride, { matrix_stride }); + } + + _module.uniforms.push_back(info); + + return 0xF0000000 | member_index; + } + } + id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override + { + spv::StorageClass storage = spv::StorageClassFunction; + if (type.has(type::q_groupshared)) + storage = spv::StorageClassWorkgroup; + else if (global) + storage = spv::StorageClassPrivate; + + return define_variable(loc, type, name.c_str(), storage, spv::ImageFormatUnknown, initializer_value); + } + id define_variable(const location &loc, const type &type, const char *name, spv::StorageClass storage, spv::ImageFormat format = spv::ImageFormatUnknown, spv::Id initializer_value = 0) + { + assert(storage != spv::StorageClassFunction || _current_function != nullptr); + + spirv_basic_block &block = (storage != spv::StorageClassFunction) ? + _variables : _current_function->variables; + + add_location(loc, block); + + spv::Id res; + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpVariable + spirv_instruction &inst = add_instruction(spv::OpVariable, convert_type(type, true, storage, format), block, res) + .add(storage); + + if (initializer_value != 0) + { + if (storage != spv::StorageClassFunction || _current_function->is_entry_point) + { + // The initializer for variables must be a constant + inst.add(initializer_value); + } + else + { + // 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 declaration + expression variable; + variable.reset_to_lvalue(loc, res, type); + emit_store(variable, initializer_value); + } + } + + if (name != nullptr && *name != '\0') + add_name(res, name); + + if (!_enable_16bit_types && type.is_numeric() && type.precision() < 32) + add_decoration(res, spv::DecorationRelaxedPrecision); + + _storage_lookup[res] = { storage, format }; + + return res; + } + id define_function(const location &loc, function_info &info) override + { + assert(!is_in_function()); + + auto &function = _functions_blocks.emplace_back(); + function.return_type = info.return_type; + + _current_function = &function; + + for (auto ¶m : info.parameter_list) + function.param_types.push_back(param.type); + + add_location(loc, function.declaration); + + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpFunction + add_instruction(spv::OpFunction, convert_type(info.return_type), function.declaration, info.definition) + .add(spv::FunctionControlMaskNone) + .add(convert_type(function)); + + if (!info.name.empty()) + add_name(info.definition, info.name.c_str()); + + for (auto ¶m : info.parameter_list) + { + add_location(param.location, function.declaration); + + param.definition = add_instruction(spv::OpFunctionParameter, convert_type(param.type, true), function.declaration).result; + + add_name(param.definition, param.name.c_str()); + } + + _functions.push_back(std::make_unique(info)); + + return info.definition; + } + + void define_entry_point(function_info &func, shader_type stype, int num_threads[3]) override + { + // Modify entry point name so each thread configuration is made separate + if (stype == shader_type::cs) + func.unique_name = 'E' + func.unique_name + + '_' + std::to_string(num_threads[0]) + + '_' + std::to_string(num_threads[1]) + + '_' + std::to_string(num_threads[2]); + + if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), + [&func](const auto &ep) { return ep.name == func.unique_name; }); + it != _module.entry_points.end()) + return; + + _module.entry_points.push_back({ func.unique_name, stype }); + + spv::Id position_variable = 0, point_size_variable = 0; + std::vector inputs_and_outputs; + std::vector call_params; + + // Generate the glue entry point function + function_info entry_point; + entry_point.return_type = { type::t_void }; + + define_function({}, entry_point); + enter_block(create_block()); + + _current_function->is_entry_point = true; + + const auto create_varying_param = [this, &call_params](const struct_member_info ¶m) { + // Initialize all output variables with zero + const spv::Id variable = define_variable({}, param.type, nullptr, spv::StorageClassFunction, spv::ImageFormatUnknown, emit_constant(param.type, 0u)); + + expression &call_param = call_params.emplace_back(); + call_param.reset_to_lvalue({}, variable, param.type); + + return variable; + }; + + const auto create_varying_variable = [this, &inputs_and_outputs, &position_variable, &point_size_variable, stype](const type ¶m_type, const std::string &semantic, spv::StorageClass storage, int a = 0) { + const spv::Id variable = define_variable({}, param_type, nullptr, storage); + + if (const spv::BuiltIn builtin = semantic_to_builtin(semantic, stype); + builtin != spv::BuiltInMax) + { + assert(a == 0); // Built-in variables cannot be arrays + + add_builtin(variable, builtin); + + if (builtin == spv::BuiltInPosition && storage == spv::StorageClassOutput) + position_variable = variable; + if (builtin == spv::BuiltInPointSize && storage == spv::StorageClassOutput) + point_size_variable = variable; + } + else + { + assert(stype != shader_type::cs); // Compute shaders cannot have custom inputs or outputs + + const uint32_t location = semantic_to_location(semantic, std::max(1, param_type.array_length)); + add_decoration(variable, spv::DecorationLocation, { location + a }); + } + + if (param_type.has(type::q_noperspective)) + add_decoration(variable, spv::DecorationNoPerspective); + if (param_type.has(type::q_centroid)) + add_decoration(variable, spv::DecorationCentroid); + if (param_type.has(type::q_nointerpolation)) + add_decoration(variable, spv::DecorationFlat); + + inputs_and_outputs.push_back(variable); + return variable; + }; + + // Translate function parameters to input/output variables + for (const struct_member_info ¶m : func.parameter_list) + { + spv::Id param_var = create_varying_param(param); + + // Create separate input/output variables for "inout" parameters + if (param.type.has(type::q_in)) + { + spv::Id param_value = 0; + + // Flatten structure parameters + if (param.type.is_struct()) + { + const struct_info &definition = get_struct(param.type.definition); + + type struct_type = param.type; + const int array_length = std::max(1, param.type.array_length); + struct_type.array_length = 0; + + // Struct arrays need to be flattened into individual elements as well + std::vector array_elements; + array_elements.reserve(array_length); + for (int a = 0; a < array_length; a++) + { + std::vector struct_elements; + struct_elements.reserve(definition.member_list.size()); + for (const struct_member_info &member : definition.member_list) + { + spv::Id input_var = create_varying_variable(member.type, member.semantic, spv::StorageClassInput, a); + + param_value = add_instruction(spv::OpLoad, convert_type(member.type)) + .add(input_var).result; + struct_elements.push_back(param_value); + } + + param_value = add_instruction(spv::OpCompositeConstruct, convert_type(struct_type)) + .add(struct_elements.begin(), struct_elements.end()).result; + array_elements.push_back(param_value); + } + + if (param.type.is_array()) + { + // Build the array from all constructed struct elements + param_value = add_instruction(spv::OpCompositeConstruct, convert_type(param.type)) + .add(array_elements.begin(), array_elements.end()).result; + } + } + else + { + spv::Id input_var = create_varying_variable(param.type, param.semantic, spv::StorageClassInput); + + param_value = add_instruction(spv::OpLoad, convert_type(param.type)) + .add(input_var).result; + } + + add_instruction_without_result(spv::OpStore) + .add(param_var) + .add(param_value); + } + + if (param.type.has(type::q_out)) + { + if (param.type.is_struct()) + { + const struct_info &definition = get_struct(param.type.definition); + + for (int a = 0, array_length = std::max(1, param.type.array_length); a < array_length; a++) + { + for (const struct_member_info &member : definition.member_list) + { + create_varying_variable(member.type, member.semantic, spv::StorageClassOutput, a); + } + } + } + else + { + create_varying_variable(param.type, param.semantic, spv::StorageClassOutput); + } + } + } + + const auto call_result = emit_call({}, func.definition, func.return_type, call_params); + + for (size_t i = 0, inputs_and_outputs_index = 0; i < func.parameter_list.size(); ++i) + { + const struct_member_info ¶m = func.parameter_list[i]; + + if (param.type.has(type::q_out)) + { + const spv::Id value = add_instruction(spv::OpLoad, convert_type(param.type)) + .add(call_params[i].base).result; + + if (param.type.is_struct()) + { + const struct_info &definition = get_struct(param.type.definition); + + type struct_type = param.type; + const int array_length = std::max(1, param.type.array_length); + struct_type.array_length = 0; + + // Skip input variables if this is an "inout" parameter + if (param.type.has(type::q_in)) + inputs_and_outputs_index += definition.member_list.size() * array_length; + + // Split up struct array into individual struct elements again + for (int a = 0; a < array_length; a++) + { + spv::Id element_value = value; + if (param.type.is_array()) + { + element_value = add_instruction(spv::OpCompositeExtract, convert_type(struct_type)) + .add(value) + .add(a).result; + } + + // Split out struct fields into separate output variables again + for (uint32_t member_index = 0; member_index < definition.member_list.size(); ++member_index) + { + const struct_member_info &member = definition.member_list[member_index]; + + const spv::Id member_value = add_instruction(spv::OpCompositeExtract, convert_type(member.type)) + .add(element_value) + .add(member_index).result; + + add_instruction_without_result(spv::OpStore) + .add(inputs_and_outputs[inputs_and_outputs_index++]) + .add(member_value); + } + } + } + else + { + // Skip input variable if this is an "inout" parameter (see loop above) + if (param.type.has(type::q_in)) + inputs_and_outputs_index += 1; + + add_instruction_without_result(spv::OpStore) + .add(inputs_and_outputs[inputs_and_outputs_index++]) + .add(value); + } + } + else + { + // Input parameters do not need to store anything, but increase the input/output variable index + if (param.type.is_struct()) + { + const struct_info &definition = get_struct(param.type.definition); + inputs_and_outputs_index += definition.member_list.size() * std::max(1, param.type.array_length); + } + else + { + inputs_and_outputs_index += 1; + } + } + } + + if (func.return_type.is_struct()) + { + const struct_info &definition = get_struct(func.return_type.definition); + + for (uint32_t member_index = 0; member_index < definition.member_list.size(); ++member_index) + { + const struct_member_info &member = definition.member_list[member_index]; + + const spv::Id result = create_varying_variable(member.type, member.semantic, spv::StorageClassOutput); + const spv::Id member_result = add_instruction(spv::OpCompositeExtract, convert_type(member.type)) + .add(call_result) + .add(member_index).result; + + add_instruction_without_result(spv::OpStore) + .add(result) + .add(member_result); + } + } + else if (!func.return_type.is_void()) + { + const spv::Id result = create_varying_variable(func.return_type, func.return_semantic, spv::StorageClassOutput); + + add_instruction_without_result(spv::OpStore) + .add(result) + .add(call_result); + } + + // Add code to flip the output vertically + if (_flip_vert_y && position_variable != 0 && stype == shader_type::vs) + { + expression position; + position.reset_to_lvalue({}, position_variable, { type::t_float, 4, 1 }); + position.add_constant_index_access(1); // Y component + + // gl_Position.y = -gl_Position.y + emit_store(position, + emit_unary_op({}, tokenid::minus, { type::t_float, 1, 1 }, + emit_load(position, false))); + } + + // Add code that sets the point size to a default value (in case this vertex shader is used with point primitives) + if (point_size_variable == 0 && stype == shader_type::vs) + { + create_varying_variable({ type::t_float, 1, 1 }, "SV_POINTSIZE", spv::StorageClassOutput); + + expression point_size; + point_size.reset_to_lvalue({}, point_size_variable, { type::t_float, 1, 1 }); + + // gl_PointSize = 1.0 + emit_store(point_size, emit_constant({ type::t_float, 1, 1 }, 1)); + } + + leave_block_and_return(0); + leave_function(); + + spv::ExecutionModel model; + switch (stype) + { + case shader_type::vs: + model = spv::ExecutionModelVertex; + break; + case shader_type::ps: + model = spv::ExecutionModelFragment; + add_instruction_without_result(spv::OpExecutionMode, _execution_modes) + .add(entry_point.definition) + .add(_vulkan_semantics ? spv::ExecutionModeOriginUpperLeft : spv::ExecutionModeOriginLowerLeft); + break; + case shader_type::cs: + model = spv::ExecutionModelGLCompute; + add_instruction_without_result(spv::OpExecutionMode, _execution_modes) + .add(entry_point.definition) + .add(spv::ExecutionModeLocalSize) + .add(num_threads[0]) + .add(num_threads[1]) + .add(num_threads[2]); + break; + default: + assert(false); + return; + } + + assert(!func.unique_name.empty()); + add_instruction_without_result(spv::OpEntryPoint, _entries) + .add(model) + .add(entry_point.definition) + .add_string(func.unique_name.c_str()) + .add(inputs_and_outputs.begin(), inputs_and_outputs.end()); + } + + id emit_load(const expression &exp, bool) override + { + if (exp.is_constant) // Constant expressions do not have a complex access chain + return emit_constant(exp.type, exp.constant); + + size_t i = 0; + spv::Id result = exp.base; + auto base_type = exp.type; + bool is_uniform_bool = false; + + if (exp.is_lvalue || !exp.chain.empty()) + add_location(exp.location, *_current_block_data); + + // If a variable is referenced, load the value first + if (exp.is_lvalue && _spec_constants.find(exp.base) == _spec_constants.end()) + { + if (!exp.chain.empty()) + base_type = exp.chain[0].from; + + std::pair storage = { spv::StorageClassFunction, spv::ImageFormatUnknown }; + if (const auto it = _storage_lookup.find(exp.base); + it != _storage_lookup.end()) + storage = it->second; + + spirv_instruction *access_chain = nullptr; + + // Check if this is a uniform variable (see 'define_uniform' function above) and dereference it + if (result & 0xF0000000) + { + const uint32_t member_index = result ^ 0xF0000000; + + storage.first = spv::StorageClassUniform; + is_uniform_bool = base_type.is_boolean(); + + if (is_uniform_bool) + base_type.base = type::t_uint; + + access_chain = &add_instruction(spv::OpAccessChain) + .add(_global_ubo_variable) + .add(emit_constant(member_index)); + } + + // Any indexing expressions can be resolved during load with an 'OpAccessChain' already + if (!exp.chain.empty() && ( + exp.chain[0].op == expression::operation::op_member || + exp.chain[0].op == expression::operation::op_dynamic_index || + exp.chain[0].op == expression::operation::op_constant_index)) + { + // Ensure that 'access_chain' cannot get invalidated by calls to 'emit_constant' or 'convert_type' + assert(_current_block_data != &_types_and_constants); + + // Use access chain from uniform if possible, otherwise create new one + if (access_chain == nullptr) access_chain = + &add_instruction(spv::OpAccessChain).add(result); // Base + + // Ignore first index into 1xN matrices, since they were translated to a vector type in SPIR-V + if (exp.chain[0].from.rows == 1 && exp.chain[0].from.cols > 1) + i = 1; + + for (; i < exp.chain.size() && ( + exp.chain[i].op == expression::operation::op_member || + exp.chain[i].op == expression::operation::op_dynamic_index || + exp.chain[i].op == expression::operation::op_constant_index); ++i) + access_chain->add(exp.chain[i].op == expression::operation::op_dynamic_index ? + exp.chain[i].index : + emit_constant(exp.chain[i].index)); // Indexes + + base_type = exp.chain[i - 1].to; + access_chain->type = convert_type(base_type, true, storage.first, storage.second); // Last type is the result + result = access_chain->result; + } + else if (access_chain != nullptr) + { + access_chain->type = convert_type(base_type, true, storage.first, storage.second, base_type.is_array() ? 16u : 0u); + result = access_chain->result; + } + + result = add_instruction(spv::OpLoad, convert_type(base_type, false, spv::StorageClassFunction, storage.second)) + .add(result) // Pointer + .result; + } + + // Need to convert boolean uniforms which are actually integers in SPIR-V + if (is_uniform_bool) + { + base_type.base = type::t_bool; + + result = add_instruction(spv::OpINotEqual, convert_type(base_type)) + .add(result) + .add(emit_constant(0)) + .result; + } + + // Work through all remaining operations in the access chain and apply them to the value + for (; i < exp.chain.size(); ++i) + { + assert(result != 0); + const auto &op = exp.chain[i]; + + switch (op.op) + { + case expression::operation::op_cast: + if (op.from.is_scalar() && !op.to.is_scalar()) + { + type cast_type = op.to; + cast_type.base = op.from.base; + + std::vector args; + for (unsigned int c = 0; c < op.to.components(); ++c) + args.emplace_back().reset_to_rvalue(exp.location, result, op.from); + + result = emit_construct(exp.location, cast_type, args); + } + + if (op.from.is_boolean()) + { + const spv::Id true_constant = emit_constant(op.to, 1); + const spv::Id false_constant = emit_constant(op.to, 0); + + result = add_instruction(spv::OpSelect, convert_type(op.to)) + .add(result) // Condition + .add(true_constant) + .add(false_constant) + .result; + } + else + { + spv::Op spv_op = spv::OpNop; + switch (op.to.base) + { + case type::t_bool: + if (op.from.is_floating_point()) + spv_op = spv::OpFOrdNotEqual; + else + spv_op = spv::OpINotEqual; + // Add instruction to compare value against zero instead of casting + result = add_instruction(spv_op, convert_type(op.to)) + .add(result) + .add(emit_constant(op.from, 0)) + .result; + continue; + case type::t_min16int: + case type::t_int: + if (op.from.is_floating_point()) + spv_op = spv::OpConvertFToS; + else if (op.from.precision() == op.to.precision()) + spv_op = spv::OpBitcast; + else if (_enable_16bit_types) + spv_op = spv::OpSConvert; + else + continue; // Do not have to add conversion instruction between min16int/int if 16-bit types are not enabled + break; + case type::t_min16uint: + case type::t_uint: + if (op.from.is_floating_point()) + spv_op = spv::OpConvertFToU; + else if (op.from.precision() == op.to.precision()) + spv_op = spv::OpBitcast; + else if (_enable_16bit_types) + spv_op = spv::OpUConvert; + else + continue; + break; + case type::t_min16float: + case type::t_float: + if (op.from.is_floating_point() && !_enable_16bit_types) + continue; // Do not have to add conversion instruction between min16float/float if 16-bit types are not enabled + else if (op.from.is_floating_point()) + spv_op = spv::OpFConvert; + else if (op.from.is_signed()) + spv_op = spv::OpConvertSToF; + else + spv_op = spv::OpConvertUToF; + break; + default: + assert(false); + } + + result = add_instruction(spv_op, convert_type(op.to)) + .add(result) + .result; + } + break; + case expression::operation::op_dynamic_index: + assert(op.from.is_vector() && op.to.is_scalar()); + result = add_instruction(spv::OpVectorExtractDynamic, convert_type(op.to)) + .add(result) // Vector + .add(op.index) // Index + .result; + break; + case expression::operation::op_member: // In case of struct return values, which are r-values + case expression::operation::op_constant_index: + assert(op.from.is_vector() || op.from.is_matrix() || op.from.is_struct()); + result = add_instruction(spv::OpCompositeExtract, convert_type(op.to)) + .add(result) + .add(op.index) // Literal Index + .result; + break; + case expression::operation::op_swizzle: + if (op.to.is_vector()) + { + if (op.from.is_matrix()) + { + spv::Id components[4]; + for (unsigned int c = 0; c < 4 && op.swizzle[c] >= 0; ++c) + { + const unsigned int row = op.swizzle[c] / 4; + const unsigned int column = op.swizzle[c] - row * 4; + + type scalar_type = op.to; + scalar_type.rows = 1; + scalar_type.cols = 1; + + spirv_instruction &node = add_instruction(spv::OpCompositeExtract, convert_type(scalar_type)) + .add(result); + + if (op.from.rows > 1) // Matrix types with a single row are actually vectors, so they don't need the extra index + node.add(row); + + node.add(column); + + components[c] = node.result; + } + + spirv_instruction &node = add_instruction(spv::OpCompositeConstruct, convert_type(op.to)); + for (unsigned int c = 0; c < 4 && op.swizzle[c] >= 0; ++c) + node.add(components[c]); + result = node.result; + break; + } + else if (op.from.is_vector()) + { + spirv_instruction &node = add_instruction(spv::OpVectorShuffle, convert_type(op.to)) + .add(result) // Vector 1 + .add(result); // Vector 2 + for (unsigned int c = 0; c < 4 && op.swizzle[c] >= 0; ++c) + node.add(op.swizzle[c]); + result = node.result; + break; + } + else + { + spirv_instruction &node = add_instruction(spv::OpCompositeConstruct, convert_type(op.to)); + for (unsigned int c = 0; c < op.to.rows; ++c) + node.add(result); + result = node.result; + break; + } + } + else if (op.from.is_matrix() && op.to.is_scalar()) + { + assert(op.swizzle[1] < 0); + + spirv_instruction &node = add_instruction(spv::OpCompositeExtract, convert_type(op.to)) + .add(result); // Composite + if (op.from.rows > 1) + { + const unsigned int row = op.swizzle[0] / 4; + const unsigned int column = op.swizzle[0] - row * 4; + node.add(row); + node.add(column); + } + else + { + node.add(op.swizzle[0]); + } + result = node.result; // Result ID + break; + } + assert(false); + break; + } + } + + return result; + } + void emit_store(const expression &exp, id value) override + { + assert(value != 0 && exp.is_lvalue && !exp.is_constant && !exp.type.is_sampler()); + + add_location(exp.location, *_current_block_data); + + size_t i = 0; + // Any indexing expressions can be resolved with an 'OpAccessChain' already + spv::Id target = emit_access_chain(exp, i); + auto base_type = exp.chain.empty() ? exp.type : i == 0 ? exp.chain[0].from : exp.chain[i - 1].to; + + // TODO: Complex access chains like float4x4[0].m00m10[0] = 0; + // Work through all remaining operations in the access chain and apply them to the value + for (; i < exp.chain.size(); ++i) + { + const auto &op = exp.chain[i]; + switch (op.op) + { + case expression::operation::op_cast: + case expression::operation::op_member: + // These should have been handled above already (and casting does not make sense for a store operation) + break; + case expression::operation::op_dynamic_index: + case expression::operation::op_constant_index: + assert(false); + break; + case expression::operation::op_swizzle: + { + spv::Id result = add_instruction(spv::OpLoad, convert_type(base_type)) + .add(target) // Pointer + .result; // Result ID + + if (base_type.is_vector()) + { + spirv_instruction &node = add_instruction(spv::OpVectorShuffle, convert_type(base_type)) + .add(result) // Vector 1 + .add(value); // Vector 2 + + unsigned int shuffle[4] = { 0, 1, 2, 3 }; + for (unsigned int c = 0; c < base_type.rows; ++c) + if (op.swizzle[c] >= 0) + shuffle[op.swizzle[c]] = base_type.rows + c; + for (unsigned int c = 0; c < base_type.rows; ++c) + node.add(shuffle[c]); + + value = node.result; + } + else if (op.to.is_scalar()) + { + assert(op.swizzle[1] < 0); + + spirv_instruction &node = add_instruction(spv::OpCompositeInsert, convert_type(base_type)) + .add(value) // Object + .add(result); // Composite + + if (op.from.is_matrix() && op.from.rows > 1) + { + const unsigned int row = op.swizzle[0] / 4; + const unsigned int column = op.swizzle[0] - row * 4; + node.add(row); + node.add(column); + } + else + { + node.add(op.swizzle[0]); + } + + value = node.result; // Result ID + } + else + { + // TODO: Implement matrix to vector swizzles + assert(false); + } + break; + } + } + } + + add_instruction_without_result(spv::OpStore) + .add(target) + .add(value); + } + id emit_access_chain(const expression &exp, size_t &i) override + { + // This function cannot create access chains for uniform variables + assert((exp.base & 0xF0000000) == 0); + + i = 0; + if (exp.chain.empty() || ( + exp.chain[0].op != expression::operation::op_member && + exp.chain[0].op != expression::operation::op_dynamic_index && + exp.chain[0].op != expression::operation::op_constant_index)) + return exp.base; + + std::pair storage = { spv::StorageClassFunction, spv::ImageFormatUnknown }; + if (const auto it = _storage_lookup.find(exp.base); + it != _storage_lookup.end()) + storage = it->second; + + // Ensure that 'access_chain' cannot get invalidated by calls to 'emit_constant' or 'convert_type' + assert(_current_block_data != &_types_and_constants); + + spirv_instruction *access_chain = + &add_instruction(spv::OpAccessChain).add(exp.base); // Base + + // Ignore first index into 1xN matrices, since they were translated to a vector type in SPIR-V + if (exp.chain[0].from.rows == 1 && exp.chain[0].from.cols > 1) + i = 1; + + for (; i < exp.chain.size() && ( + exp.chain[i].op == expression::operation::op_member || + exp.chain[i].op == expression::operation::op_dynamic_index || + exp.chain[i].op == expression::operation::op_constant_index); ++i) + access_chain->add(exp.chain[i].op == expression::operation::op_dynamic_index ? + exp.chain[i].index : + emit_constant(exp.chain[i].index)); // Indexes + + access_chain->type = convert_type(exp.chain[i - 1].to, true, storage.first, storage.second); // Last type is the result + return access_chain->result; + } + + id emit_constant(uint32_t value) + { + return emit_constant({ type::t_uint, 1, 1 }, value); + } + id emit_constant(const type &type, uint32_t value) + { + // Create a constant value of the specified type + constant data = {}; // Initialize to zero, so that components not set below still have a defined value for the lookup via std::memcmp + for (unsigned int i = 0; i < type.components(); ++i) + if (type.is_integral()) + data.as_uint[i] = value; + else + data.as_float[i] = static_cast(value); + + return emit_constant(type, data, false); + } + id emit_constant(const type &type, const constant &data) override + { + return emit_constant(type, data, false); + } + id emit_constant(const type &type, const constant &data, bool spec_constant) + { + if (!spec_constant) // Specialization constants cannot reuse other constants + { + if (const auto it = std::find_if(_constant_lookup.begin(), _constant_lookup.end(), + [&type, &data](auto &x) { + if (!(std::get<0>(x) == 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())) + return false; + for (size_t i = 0; i < data.array_data.size(); ++i) + if (std::memcmp(&std::get<1>(x).array_data[i].as_uint[0], &data.array_data[i].as_uint[0], sizeof(uint32_t) * 16) != 0) + return false; + return true; + }); + it != _constant_lookup.end()) + return std::get<2>(*it); // Re-use existing constant instead of duplicating the definition + } + + spv::Id result; + if (type.is_array()) + { + assert(type.array_length > 0); // Unsized arrays cannot be constants + + auto elem_type = type; + elem_type.array_length = 0; + + std::vector elements; + elements.reserve(type.array_length); + + // Fill up elements with constant array data + for (const constant &elem : data.array_data) + elements.push_back(emit_constant(elem_type, elem, spec_constant)); + // Fill up any remaining elements with a default value (when the array data did not specify them) + for (size_t i = elements.size(); i < static_cast(type.array_length); ++i) + elements.push_back(emit_constant(elem_type, {}, spec_constant)); + + result = add_instruction(spec_constant ? spv::OpSpecConstantComposite : spv::OpConstantComposite, convert_type(type), _types_and_constants) + .add(elements.begin(), elements.end()) + .result; + } + else if (type.is_struct()) + { + assert(!spec_constant); // Structures cannot be specialization constants + + result = add_instruction(spv::OpConstantNull, convert_type(type), _types_and_constants) + .result; + } + else if (type.is_vector() || type.is_matrix()) + { + auto elem_type = type; + elem_type.rows = type.cols; + elem_type.cols = 1; + + spv::Id rows[4] = {}; + + // Construct matrix constant out of row vector constants + // Construct vector constant out of scalar constants for each element + for (unsigned int i = 0; i < type.rows; ++i) + { + constant row_data = {}; + for (unsigned int k = 0; k < type.cols; ++k) + row_data.as_uint[k] = data.as_uint[i * type.cols + k]; + + rows[i] = emit_constant(elem_type, row_data, spec_constant); + } + + if (type.rows == 1) + { + result = rows[0]; + } + else + { + spirv_instruction &node = add_instruction(spec_constant ? spv::OpSpecConstantComposite : spv::OpConstantComposite, convert_type(type), _types_and_constants); + for (unsigned int i = 0; i < type.rows; ++i) + node.add(rows[i]); + + result = node.result; + } + } + else if (type.is_boolean()) + { + result = add_instruction(data.as_uint[0] ? + (spec_constant ? spv::OpSpecConstantTrue : spv::OpConstantTrue) : + (spec_constant ? spv::OpSpecConstantFalse : spv::OpConstantFalse), convert_type(type), _types_and_constants) + .result; + } + else + { + assert(type.is_scalar()); + + result = add_instruction(spec_constant ? spv::OpSpecConstant : spv::OpConstant, convert_type(type), _types_and_constants) + .add(data.as_uint[0]) + .result; + } + + if (spec_constant) // Keep track of all specialization constants + _spec_constants.insert(result); + else + _constant_lookup.push_back({ type, data, result }); + + return result; + } + + id emit_unary_op(const location &loc, tokenid op, const type &type, id val) override + { + spv::Op spv_op = spv::OpNop; + + switch (op) + { + case tokenid::minus: + spv_op = type.is_floating_point() ? spv::OpFNegate : spv::OpSNegate; + break; + case tokenid::tilde: + spv_op = spv::OpNot; + break; + case tokenid::exclaim: + spv_op = spv::OpLogicalNot; + break; + default: + return assert(false), 0; + } + + add_location(loc, *_current_block_data); + + spirv_instruction &inst = add_instruction(spv_op, convert_type(type)); + inst.add(val); // Operand + + return inst.result; + } + id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) override + { + spv::Op spv_op = spv::OpNop; + + switch (op) + { + case tokenid::plus: + case tokenid::plus_plus: + case tokenid::plus_equal: + spv_op = type.is_floating_point() ? spv::OpFAdd : spv::OpIAdd; + break; + case tokenid::minus: + case tokenid::minus_minus: + case tokenid::minus_equal: + spv_op = type.is_floating_point() ? spv::OpFSub : spv::OpISub; + break; + case tokenid::star: + case tokenid::star_equal: + spv_op = type.is_floating_point() ? spv::OpFMul : spv::OpIMul; + break; + case tokenid::slash: + case tokenid::slash_equal: + spv_op = type.is_floating_point() ? spv::OpFDiv : type.is_signed() ? spv::OpSDiv : spv::OpUDiv; + break; + case tokenid::percent: + case tokenid::percent_equal: + spv_op = type.is_floating_point() ? spv::OpFRem : type.is_signed() ? spv::OpSRem : spv::OpUMod; + break; + case tokenid::caret: + case tokenid::caret_equal: + spv_op = spv::OpBitwiseXor; + break; + case tokenid::pipe: + case tokenid::pipe_equal: + spv_op = spv::OpBitwiseOr; + break; + case tokenid::ampersand: + case tokenid::ampersand_equal: + spv_op = spv::OpBitwiseAnd; + break; + case tokenid::less_less: + case tokenid::less_less_equal: + spv_op = spv::OpShiftLeftLogical; + break; + case tokenid::greater_greater: + case tokenid::greater_greater_equal: + spv_op = type.is_signed() ? spv::OpShiftRightArithmetic : spv::OpShiftRightLogical; + break; + case tokenid::pipe_pipe: + spv_op = spv::OpLogicalOr; + break; + case tokenid::ampersand_ampersand: + spv_op = spv::OpLogicalAnd; + break; + case tokenid::less: + spv_op = type.is_floating_point() ? spv::OpFOrdLessThan : + type.is_signed() ? spv::OpSLessThan : spv::OpULessThan; + break; + case tokenid::less_equal: + spv_op = type.is_floating_point() ? spv::OpFOrdLessThanEqual : + type.is_signed() ? spv::OpSLessThanEqual : spv::OpULessThanEqual; + break; + case tokenid::greater: + spv_op = type.is_floating_point() ? spv::OpFOrdGreaterThan : + type.is_signed() ? spv::OpSGreaterThan : spv::OpUGreaterThan; + break; + case tokenid::greater_equal: + spv_op = type.is_floating_point() ? spv::OpFOrdGreaterThanEqual : + type.is_signed() ? spv::OpSGreaterThanEqual : spv::OpUGreaterThanEqual; + break; + case tokenid::equal_equal: + spv_op = type.is_floating_point() ? spv::OpFOrdEqual : + type.is_boolean() ? spv::OpLogicalEqual : spv::OpIEqual; + break; + case tokenid::exclaim_equal: + spv_op = type.is_floating_point() ? spv::OpFOrdNotEqual : + type.is_boolean() ? spv::OpLogicalNotEqual : spv::OpINotEqual; + break; + default: + return assert(false), 0; + } + + add_location(loc, *_current_block_data); + + // Binary operators generally only work on scalars and vectors in SPIR-V, so need to apply them to matrices component-wise + if (type.is_matrix() && type.rows != 1) + { + std::vector ids; + ids.reserve(type.cols); + + auto vector_type = type; + vector_type.rows = type.cols; + vector_type.cols = 1; + + for (unsigned int row = 0; row < type.rows; ++row) + { + const spv::Id lhs_elem = add_instruction(spv::OpCompositeExtract, convert_type(vector_type)) + .add(lhs) + .add(row) + .result; + const spv::Id rhs_elem = add_instruction(spv::OpCompositeExtract, convert_type(vector_type)) + .add(rhs) + .add(row) + .result; + + spirv_instruction &inst = add_instruction(spv_op, convert_type(vector_type)); + inst.add(lhs_elem); // Operand 1 + inst.add(rhs_elem); // Operand 2 + + if (res_type.has(type::q_precise)) + add_decoration(inst.result, spv::DecorationNoContraction); + if (!_enable_16bit_types && res_type.precision() < 32) + add_decoration(inst.result, spv::DecorationRelaxedPrecision); + + ids.push_back(inst.result); + } + + spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(res_type)); + inst.add(ids.begin(), ids.end()); + + return inst.result; + } + else + { + spirv_instruction &inst = add_instruction(spv_op, convert_type(res_type)); + inst.add(lhs); // Operand 1 + inst.add(rhs); // Operand 2 + + if (res_type.has(type::q_precise)) + add_decoration(inst.result, spv::DecorationNoContraction); + if (!_enable_16bit_types && res_type.precision() < 32) + add_decoration(inst.result, spv::DecorationRelaxedPrecision); + + return inst.result; + } + } + id emit_ternary_op(const location &loc, tokenid op, const type &type, id condition, id true_value, id false_value) override + { + if (op != tokenid::question) + return assert(false), 0; + + add_location(loc, *_current_block_data); + + spirv_instruction &inst = add_instruction(spv::OpSelect, convert_type(type)); + inst.add(condition); // Condition + inst.add(true_value); // Object 1 + inst.add(false_value); // Object 2 + + return inst.result; + } + id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert(arg.chain.empty() && arg.base != 0); +#endif + add_location(loc, *_current_block_data); + + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpFunctionCall + spirv_instruction &inst = add_instruction(spv::OpFunctionCall, convert_type(res_type)); + inst.add(function); // Function + for (const expression &arg : args) + inst.add(arg.base); // Arguments + + return inst.result; + } + id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert(arg.chain.empty() && arg.base != 0); +#endif + add_location(loc, *_current_block_data); + + enum + { + #define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) name##i, + #include "effect_symbol_table_intrinsics.inl" + }; + + switch (intrinsic) + { + #define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) case name##i: code + #include "effect_symbol_table_intrinsics.inl" + default: + return assert(false), 0; + } + } + id emit_construct(const location &loc, const type &type, const std::vector &args) override + { +#ifndef NDEBUG + for (const expression &arg : args) + assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); +#endif + add_location(loc, *_current_block_data); + + std::vector ids; + ids.reserve(args.size()); + + // There must be exactly one constituent for each top-level component of the result + if (type.is_matrix()) + { + auto vector_type = type; + vector_type.rows = type.cols; + vector_type.cols = 1; + + // Turn the list of scalar arguments into a list of column vectors + for (size_t arg = 0; arg < args.size(); arg += vector_type.rows) + { + spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(vector_type)); + for (unsigned row = 0; row < vector_type.rows; ++row) + inst.add(args[arg + row].base); + + ids.push_back(inst.result); + } + } + else + { + assert(type.is_vector() || type.is_array()); + + // The exception is that for constructing a vector, a contiguous subset of the scalars consumed can be represented by a vector operand instead + for (const expression &arg : args) + ids.push_back(arg.base); + } + + spirv_instruction &inst = add_instruction(spv::OpCompositeConstruct, convert_type(type)); + inst.add(ids.begin(), ids.end()); + + return inst.result; + } + + void emit_if(const location &loc, id, id condition_block, id true_statement_block, id false_statement_block, unsigned int selection_control) override + { + spirv_instruction merge_label = _current_block_data->instructions.back(); + assert(merge_label.op == spv::OpLabel); + _current_block_data->instructions.pop_back(); + + // Add previous block containing the condition value first + _current_block_data->append(_block_data[condition_block]); + + spirv_instruction branch_inst = _current_block_data->instructions.back(); + assert(branch_inst.op == spv::OpBranchConditional); + _current_block_data->instructions.pop_back(); + + // Add structured control flow instruction + add_location(loc, *_current_block_data); + add_instruction_without_result(spv::OpSelectionMerge) + .add(merge_label.result) + .add(selection_control & 0x3); // 'SelectionControl' happens to match the flags produced by the parser + + // Append all blocks belonging to the branch + _current_block_data->instructions.push_back(branch_inst); + _current_block_data->append(_block_data[true_statement_block]); + _current_block_data->append(_block_data[false_statement_block]); + + _current_block_data->instructions.push_back(merge_label); + } + id 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 &type) override + { + spirv_instruction merge_label = _current_block_data->instructions.back(); + assert(merge_label.op == spv::OpLabel); + _current_block_data->instructions.pop_back(); + + // Add previous block containing the condition value first + _current_block_data->append(_block_data[condition_block]); + + if (true_statement_block != condition_block) + _current_block_data->append(_block_data[true_statement_block]); + if (false_statement_block != condition_block) + _current_block_data->append(_block_data[false_statement_block]); + + _current_block_data->instructions.push_back(merge_label); + + add_location(loc, *_current_block_data); + + // https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpPhi + spirv_instruction &inst = add_instruction(spv::OpPhi, convert_type(type)) + .add(true_value) // Variable 0 + .add(true_statement_block) // Parent 0 + .add(false_value) // Variable 1 + .add(false_statement_block); // Parent 1 + + return inst.result; + } + void 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) override + { + spirv_instruction merge_label = _current_block_data->instructions.back(); + assert(merge_label.op == spv::OpLabel); + _current_block_data->instructions.pop_back(); + + // Add previous block first + _current_block_data->append(_block_data[prev_block]); + + // Fill header block + assert(_block_data[header_block].instructions.size() == 2); + _current_block_data->instructions.push_back(_block_data[header_block].instructions[0]); + assert(_current_block_data->instructions.back().op == spv::OpLabel); + + // Add structured control flow instruction + add_location(loc, *_current_block_data); + add_instruction_without_result(spv::OpLoopMerge) + .add(merge_label.result) + .add(continue_block) + .add(loop_control & 0x3); // 'LoopControl' happens to match the flags produced by the parser + + _current_block_data->instructions.push_back(_block_data[header_block].instructions[1]); + assert(_current_block_data->instructions.back().op == spv::OpBranch); + + // Add condition block if it exists + if (condition_block != 0) + _current_block_data->append(_block_data[condition_block]); + + // Append loop body block before continue block + _current_block_data->append(_block_data[loop_block]); + _current_block_data->append(_block_data[continue_block]); + + _current_block_data->instructions.push_back(merge_label); + } + void emit_switch(const location &loc, id, id selector_block, id default_label, id default_block, const std::vector &case_literal_and_labels, const std::vector &case_blocks, unsigned int selection_control) override + { + assert(case_blocks.size() == case_literal_and_labels.size() / 2); + + spirv_instruction merge_label = _current_block_data->instructions.back(); + assert(merge_label.op == spv::OpLabel); + _current_block_data->instructions.pop_back(); + + // Add previous block containing the selector value first + _current_block_data->append(_block_data[selector_block]); + + spirv_instruction switch_inst = _current_block_data->instructions.back(); + assert(switch_inst.op == spv::OpSwitch); + _current_block_data->instructions.pop_back(); + + // Add structured control flow instruction + add_location(loc, *_current_block_data); + add_instruction_without_result(spv::OpSelectionMerge) + .add(merge_label.result) + .add(selection_control & 0x3); // 'SelectionControl' happens to match the flags produced by the parser + + // Update switch instruction to contain all case labels + switch_inst.operands[1] = default_label; + switch_inst.add(case_literal_and_labels.begin(), case_literal_and_labels.end()); + + // Append all blocks belonging to the switch + _current_block_data->instructions.push_back(switch_inst); + + std::vector blocks = case_blocks; + if (default_label != merge_label.result) + blocks.push_back(default_block); + // Eliminate duplicates (because of multiple case labels pointing to the same block) + std::sort(blocks.begin(), blocks.end()); + blocks.erase(std::unique(blocks.begin(), blocks.end()), blocks.end()); + for (const id case_block : blocks) + _current_block_data->append(_block_data[case_block]); + + _current_block_data->instructions.push_back(merge_label); + } + + bool is_in_function() const override { return _current_function != nullptr; } + + id set_block(id id) override + { + _last_block = _current_block; + _current_block = id; + _current_block_data = &_block_data[id]; + + return _last_block; + } + void enter_block(id id) override + { + assert(id != 0); + // Can only use labels inside functions and should never be in another basic block if creating a new one + assert(is_in_function() && !is_in_block()); + + set_block(id); + + add_instruction_without_result(spv::OpLabel) + .result = id; + } + id leave_block_and_kill() override + { + assert(is_in_function()); // Can only discard inside functions + + if (!is_in_block()) + return 0; + + add_instruction_without_result(spv::OpKill); + + return set_block(0); + } + id leave_block_and_return(id value) override + { + assert(is_in_function()); // Can only return from inside functions + + if (!is_in_block()) // Might already have left the last block in which case this has to be ignored + return 0; + + if (_current_function->return_type.is_void()) + { + add_instruction_without_result(spv::OpReturn); + } + else + { + if (0 == value) // The implicit return statement needs this + value = add_instruction(spv::OpUndef, convert_type(_current_function->return_type), _types_and_constants).result; + + add_instruction_without_result(spv::OpReturnValue) + .add(value); + } + + return set_block(0); + } + id leave_block_and_switch(id value, id default_target) override + { + assert(value != 0 && default_target != 0); + assert(is_in_function()); // Can only switch inside functions + + if (!is_in_block()) + return _last_block; + + add_instruction_without_result(spv::OpSwitch) + .add(value) + .add(default_target); + + return set_block(0); + } + id leave_block_and_branch(id target, unsigned int) override + { + assert(target != 0); + assert(is_in_function()); // Can only branch inside functions + + if (!is_in_block()) + return _last_block; + + add_instruction_without_result(spv::OpBranch) + .add(target); + + return set_block(0); + } + id leave_block_and_branch_conditional(id condition, id true_target, id false_target) override + { + assert(condition != 0 && true_target != 0 && false_target != 0); + assert(is_in_function()); // Can only branch inside functions + + if (!is_in_block()) + return _last_block; + + add_instruction_without_result(spv::OpBranchConditional) + .add(condition) + .add(true_target) + .add(false_target); + + return set_block(0); + } + void leave_function() override + { + assert(is_in_function()); // Can only leave if there was a function to begin with + + _current_function->definition = _block_data[_last_block]; + + // Append function end instruction + add_instruction_without_result(spv::OpFunctionEnd, _current_function->definition); + + _current_function = nullptr; + } +}; + +codegen *reshadefx::create_codegen_spirv(bool vulkan_semantics, bool debug_info, bool uniforms_to_spec_constants, bool enable_16bit_types, bool flip_vert_y) +{ + return new codegen_spirv(vulkan_semantics, debug_info, uniforms_to_spec_constants, enable_16bit_types, flip_vert_y); +} diff --git a/dep/reshadefx/src/effect_expression.cpp b/dep/reshadefx/src/effect_expression.cpp new file mode 100644 index 000000000..1073c7000 --- /dev/null +++ b/dep/reshadefx/src/effect_expression.cpp @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_lexer.hpp" +#include "effect_codegen.hpp" +#include // fmod +#include +#include // memcpy, memset +#include // std::min, std::max + +reshadefx::type reshadefx::type::merge(const type &lhs, const type &rhs) +{ + type result = { std::max(lhs.base, rhs.base) }; + + // Non-numeric types cannot be vectors or matrices + if (!result.is_numeric()) + { + result.rows = 0; + result.cols = 0; + } + // If one side of the expression is scalar, it needs to be promoted to the same dimension as the other side + else if ((lhs.rows == 1 && lhs.cols == 1) || (rhs.rows == 1 && rhs.cols == 1)) + { + result.rows = std::max(lhs.rows, rhs.rows); + result.cols = std::max(lhs.cols, rhs.cols); + } + else // Otherwise dimensions match or one side is truncated to match the other one + { + result.rows = std::min(lhs.rows, rhs.rows); + result.cols = std::min(lhs.cols, rhs.cols); + } + + // Some qualifiers propagate to the result + result.qualifiers = (lhs.qualifiers & type::q_precise) | (rhs.qualifiers & type::q_precise); + + // In case this is a structure, assume they are the same + result.definition = rhs.definition; + assert(lhs.definition == rhs.definition || lhs.definition == 0); + assert(lhs.array_length == 0 && rhs.array_length == 0); + + return result; +} + +std::string reshadefx::type::description() const +{ + std::string result; + switch (base) + { + case reshadefx::type::t_void: + result = "void"; + break; + case reshadefx::type::t_bool: + result = "bool"; + break; + case reshadefx::type::t_min16int: + result = "min16int"; + break; + case reshadefx::type::t_int: + result = "int"; + break; + case reshadefx::type::t_min16uint: + result = "min16uint"; + break; + case reshadefx::type::t_uint: + result = "uint"; + break; + case reshadefx::type::t_min16float: + result = "min16float"; + break; + case reshadefx::type::t_float: + result = "float"; + break; + case reshadefx::type::t_string: + result = "string"; + break; + case reshadefx::type::t_struct: + result = "struct"; + break; + case reshadefx::type::t_texture1d: + result = "texture1D"; + break; + case reshadefx::type::t_texture2d: + result = "texture2D"; + break; + case reshadefx::type::t_texture3d: + result = "texture3D"; + break; + case reshadefx::type::t_sampler1d_int: + result = "sampler1D'; + break; + case reshadefx::type::t_sampler2d_int: + result = "sampler2D'; + break; + case reshadefx::type::t_sampler3d_int: + result = "sampler3D'; + break; + case reshadefx::type::t_sampler1d_uint: + result = "sampler1D'; + break; + case reshadefx::type::t_sampler2d_uint: + result = "sampler2D'; + break; + case reshadefx::type::t_sampler3d_uint: + result = "sampler3D'; + break; + case reshadefx::type::t_sampler1d_float: + result = "sampler1D'; + break; + case reshadefx::type::t_sampler2d_float: + result = "sampler2D'; + break; + case reshadefx::type::t_sampler3d_float: + result = "sampler3D'; + break; + case reshadefx::type::t_storage1d_int: + result = "storage1D'; + break; + case reshadefx::type::t_storage2d_int: + result = "storage2D'; + break; + case reshadefx::type::t_storage3d_int: + result = "storage3D'; + break; + case reshadefx::type::t_storage1d_uint: + result = "storage1D'; + break; + case reshadefx::type::t_storage2d_uint: + result = "storage2D'; + break; + case reshadefx::type::t_storage3d_uint: + result = "storage3D'; + break; + case reshadefx::type::t_storage1d_float: + result = "storage1D'; + break; + case reshadefx::type::t_storage2d_float: + result = "storage2D'; + break; + case reshadefx::type::t_storage3d_float: + result = "storage3D'; + break; + case reshadefx::type::t_function: + result = "function"; + break; + } + + if (is_numeric()) + { + if (rows > 1 || cols > 1) + result += std::to_string(rows); + if (cols > 1) + result += 'x' + std::to_string(cols); + } + + if (is_array()) + { + result += '['; + if (array_length > 0) + result += std::to_string(array_length); + result += ']'; + } + + return result; +} + +void reshadefx::expression::reset_to_lvalue(const reshadefx::location &loc, uint32_t in_base, const reshadefx::type &in_type) +{ + type = in_type; + base = in_base; + location = loc; + is_lvalue = true; + is_constant = false; + chain.clear(); + + // Make sure uniform l-values cannot be assigned to by making them constant + if (in_type.has(type::q_uniform)) + type.qualifiers |= type::q_const; + + // Strip away global variable qualifiers + type.qualifiers &= ~(reshadefx::type::q_extern | reshadefx::type::q_static | reshadefx::type::q_uniform | reshadefx::type::q_groupshared); +} +void reshadefx::expression::reset_to_rvalue(const reshadefx::location &loc, uint32_t in_base, const reshadefx::type &in_type) +{ + type = in_type; + type.qualifiers |= type::q_const; + base = in_base; + location = loc; + is_lvalue = false; + is_constant = false; + chain.clear(); + + // Strip away global variable qualifiers + type.qualifiers &= ~(reshadefx::type::q_extern | reshadefx::type::q_static | reshadefx::type::q_uniform | reshadefx::type::q_groupshared); +} + +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, bool data) +{ + type = { type::t_bool, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_uint[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, float data) +{ + type = { type::t_float, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_float[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, int32_t data) +{ + type = { type::t_int, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_int[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, uint32_t data) +{ + type = { type::t_uint, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_uint[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, std::string data) +{ + type = { type::t_string, 0, 0, type::q_const }; + base = 0; constant = {}; constant.string_data = std::move(data); + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, reshadefx::constant data, const reshadefx::type &in_type) +{ + type = in_type; + type.qualifiers |= type::q_const; + base = 0; constant = std::move(data); + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} + +void reshadefx::expression::add_cast_operation(const reshadefx::type &cast_type) +{ + // First try to simplify the cast with a swizzle operation (only works with scalars and vectors) + if (type.cols == 1 && cast_type.cols == 1 && type.rows != cast_type.rows) + { + signed char swizzle[] = { 0, 1, 2, 3 }; + // Ignore components in a demotion cast + for (unsigned int i = cast_type.rows; i < 4; ++i) + swizzle[i] = -1; + // Use the last component to fill in a promotion cast + for (unsigned int i = type.rows; i < cast_type.rows; ++i) + swizzle[i] = swizzle[type.rows - 1]; + + add_swizzle_access(swizzle, cast_type.rows); + } + + if (type == cast_type) + return; // There is nothing more to do if the expression is already of the target type at this point + + if (is_constant) + { + const auto cast_constant = [](reshadefx::constant &constant, const reshadefx::type &from, const reshadefx::type &to) { + // Handle scalar to vector promotion first + if (from.is_scalar() && !to.is_scalar()) + for (unsigned int i = 1; i < to.components(); ++i) + constant.as_uint[i] = constant.as_uint[0]; + + // Next check whether the type needs casting as well (and don't convert between signed/unsigned, since that is handled by the union) + if (from.base == to.base || from.is_floating_point() == to.is_floating_point()) + return; + + if (!to.is_floating_point()) + for (unsigned int i = 0; i < to.components(); ++i) + constant.as_uint[i] = static_cast(constant.as_float[i]); + else + for (unsigned int i = 0; i < to.components(); ++i) + constant.as_float[i] = static_cast(constant.as_int[i]); + }; + + for (auto &element : constant.array_data) + cast_constant(element, type, cast_type); + + cast_constant(constant, type, cast_type); + } + else + { + assert(!type.is_array() && !cast_type.is_array()); + + chain.push_back({ operation::op_cast, type, cast_type }); + } + + type = cast_type; + type.qualifiers |= type::q_const; // Casting always makes expression not modifiable +} +void reshadefx::expression::add_member_access(unsigned int index, const reshadefx::type &in_type) +{ + assert(type.is_struct()); + + chain.push_back({ operation::op_member, type, in_type, index }); + + // The type is now the type of the member that was accessed + type = in_type; + is_constant = false; +} +void reshadefx::expression::add_dynamic_index_access(uint32_t index_expression) +{ + assert(!is_constant); // Cannot have dynamic indexing into constant in SPIR-V + assert(type.is_array() || (type.is_numeric() && !type.is_scalar())); + + auto prev_type = type; + + if (type.is_array()) + { + type.array_length = 0; + } + else if (type.is_matrix()) + { + type.rows = type.cols; + type.cols = 1; + } + else if (type.is_vector()) + { + type.rows = 1; + } + + chain.push_back({ operation::op_dynamic_index, prev_type, type, index_expression }); +} +void reshadefx::expression::add_constant_index_access(unsigned int index) +{ + assert(type.is_array() || (type.is_numeric() && !type.is_scalar())); + + auto prev_type = type; + + if (type.is_array()) + { + assert(type.array_length < 0 || index < static_cast(type.array_length)); + + type.array_length = 0; + } + else if (type.is_matrix()) + { + assert(index < type.components()); + + type.rows = type.cols; + type.cols = 1; + } + else if (type.is_vector()) + { + assert(index < type.components()); + + type.rows = 1; + } + + if (is_constant) + { + if (prev_type.is_array()) + { + constant = constant.array_data[index]; + } + else if (prev_type.is_matrix()) // Indexing into a matrix returns a row of it as a vector + { + for (unsigned int i = 0; i < prev_type.cols; ++i) + constant.as_uint[i] = constant.as_uint[index * prev_type.cols + i]; + } + else // Indexing into a vector returns the element as a scalar + { + constant.as_uint[0] = constant.as_uint[index]; + } + } + else + { + chain.push_back({ operation::op_constant_index, prev_type, type, index }); + } +} +void reshadefx::expression::add_swizzle_access(const signed char swizzle[4], unsigned int length) +{ + assert(type.is_numeric() && !type.is_array()); + + const auto prev_type = type; + + type.rows = length; + type.cols = 1; + + if (is_constant) + { + assert(constant.array_data.empty()); + + uint32_t data[16]; + std::memcpy(data, &constant.as_uint[0], sizeof(data)); + for (unsigned int i = 0; i < length; ++i) + constant.as_uint[i] = data[swizzle[i]]; + std::memset(&constant.as_uint[length], 0, sizeof(uint32_t) * (16 - length)); // Clear the rest of the constant + } + else if (length == 1 && prev_type.is_vector()) // Use indexing when possible since the code generation logic is simpler in SPIR-V + { + chain.push_back({ operation::op_constant_index, prev_type, type, static_cast(swizzle[0]) }); + } + else + { + chain.push_back({ operation::op_swizzle, prev_type, type, 0, { swizzle[0], swizzle[1], swizzle[2], swizzle[3] } }); + } +} + +bool reshadefx::expression::evaluate_constant_expression(reshadefx::tokenid op) +{ + if (!is_constant) + return false; + + switch (op) + { + case tokenid::exclaim: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = !constant.as_uint[i]; + break; + case tokenid::minus: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] = -constant.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_int[i] = -constant.as_int[i]; + break; + case tokenid::tilde: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = ~constant.as_uint[i]; + break; + default: + // Unknown operator token, so nothing to do + break; + } + + return true; +} +bool reshadefx::expression::evaluate_constant_expression(reshadefx::tokenid op, const reshadefx::constant &rhs) +{ + if (!is_constant) + return false; + + switch (op) + { + case tokenid::percent: + if (type.is_floating_point()) { + for (unsigned int i = 0; i < type.components(); ++i) + // Floating point modulo with zero is defined and results in NaN + if (rhs.as_float[i] == 0) + constant.as_float[i] = std::numeric_limits::quiet_NaN(); + else + constant.as_float[i] = std::fmod(constant.as_float[i], rhs.as_float[i]); + } + else if (type.is_signed()) { + for (unsigned int i = 0; i < type.components(); ++i) + // Integer modulo with zero on the other hand is not defined, so do not fold this expression in that case + if (rhs.as_int[i] == 0) + return false; + else + constant.as_int[i] %= rhs.as_int[i]; + } + else { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_uint[i] == 0) + return false; + else + constant.as_uint[i] %= rhs.as_uint[i]; + } + break; + case tokenid::star: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] *= rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] *= rhs.as_uint[i]; + break; + case tokenid::plus: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] += rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] += rhs.as_uint[i]; + break; + case tokenid::minus: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] -= rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] -= rhs.as_uint[i]; + break; + case tokenid::slash: + if (type.is_floating_point()) { + for (unsigned int i = 0; i < type.components(); ++i) + // Floating point division by zero is well defined and results in infinity or NaN + constant.as_float[i] /= rhs.as_float[i]; + } + else if (type.is_signed()) { + for (unsigned int i = 0; i < type.components(); ++i) + // Integer division by zero on the other hand is not defined, so do not fold this expression in that case + if (rhs.as_int[i] == 0) + return false; + else + constant.as_int[i] /= rhs.as_int[i]; + } + else { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_uint[i] == 0) + return false; + else + constant.as_uint[i] /= rhs.as_uint[i]; + } + break; + case tokenid::ampersand: + case tokenid::ampersand_ampersand: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] &= rhs.as_uint[i]; + break; + case tokenid::pipe: + case tokenid::pipe_pipe: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] |= rhs.as_uint[i]; + break; + case tokenid::caret: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] ^= rhs.as_uint[i]; + break; + case tokenid::less: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] < rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] < rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] < rhs.as_uint[i]; + type.base = type::t_bool; // Logic operations change the type to boolean + break; + case tokenid::less_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] <= rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] <= rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] <= rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::greater: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] > rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] > rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] > rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::greater_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] >= rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] >= rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] >= rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::equal_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] == rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] == rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::exclaim_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] != rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] != rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::less_less: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] <<= rhs.as_uint[i]; + break; + case tokenid::greater_greater: + if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_int[i] >>= rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] >>= rhs.as_uint[i]; + break; + default: + // Unknown operator token, so nothing to do + break; + } + + return true; +} diff --git a/dep/reshadefx/src/effect_lexer.cpp b/dep/reshadefx/src/effect_lexer.cpp new file mode 100644 index 000000000..796cc1bd9 --- /dev/null +++ b/dep/reshadefx/src/effect_lexer.cpp @@ -0,0 +1,1166 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_lexer.hpp" +#include +#include +#include // Used for static lookup tables + +using namespace reshadefx; + +enum token_type +{ + DIGIT = '0', + IDENT = 'A', + SPACE = ' ', +}; + +// Lookup table which translates a given char to a token type +static const unsigned type_lookup[256] = { + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, SPACE, + '\n', SPACE, SPACE, SPACE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, SPACE, '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', DIGIT, DIGIT, + DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, ':', ';', + '<', '=', '>', '?', '@', IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, '[', '\\', ']', '^', IDENT, 0x00, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, '{', '|', '}', '~', 0x00, 0x00, 0x00, +}; + +// Lookup tables which translate a given string literal to a token and backwards +static const std::unordered_map token_lookup = { + { tokenid::end_of_file, "end of file" }, + { tokenid::exclaim, "!" }, + { tokenid::hash, "#" }, + { tokenid::dollar, "$" }, + { tokenid::percent, "%" }, + { tokenid::ampersand, "&" }, + { tokenid::parenthesis_open, "(" }, + { tokenid::parenthesis_close, ")" }, + { tokenid::star, "*" }, + { tokenid::plus, "+" }, + { tokenid::comma, "," }, + { tokenid::minus, "-" }, + { tokenid::dot, "." }, + { tokenid::slash, "/" }, + { tokenid::colon, ":" }, + { tokenid::semicolon, ";" }, + { tokenid::less, "<" }, + { tokenid::equal, "=" }, + { tokenid::greater, ">" }, + { tokenid::question, "?" }, + { tokenid::at, "@" }, + { tokenid::bracket_open, "[" }, + { tokenid::backslash, "\\" }, + { tokenid::bracket_close, "]" }, + { tokenid::caret, "^" }, + { tokenid::brace_open, "{" }, + { tokenid::pipe, "|" }, + { tokenid::brace_close, "}" }, + { tokenid::tilde, "~" }, + { tokenid::exclaim_equal, "!=" }, + { tokenid::percent_equal, "%=" }, + { tokenid::ampersand_ampersand, "&&" }, + { tokenid::ampersand_equal, "&=" }, + { tokenid::star_equal, "*=" }, + { tokenid::plus_plus, "++" }, + { tokenid::plus_equal, "+=" }, + { tokenid::minus_minus, "--" }, + { tokenid::minus_equal, "-=" }, + { tokenid::arrow, "->" }, + { tokenid::ellipsis, "..." }, + { tokenid::slash_equal, "|=" }, + { tokenid::colon_colon, "::" }, + { tokenid::less_less_equal, "<<=" }, + { tokenid::less_less, "<<" }, + { tokenid::less_equal, "<=" }, + { tokenid::equal_equal, "==" }, + { tokenid::greater_greater_equal, ">>=" }, + { tokenid::greater_greater, ">>" }, + { tokenid::greater_equal, ">=" }, + { tokenid::caret_equal, "^=" }, + { tokenid::pipe_equal, "|=" }, + { tokenid::pipe_pipe, "||" }, + { tokenid::identifier, "identifier" }, + { tokenid::reserved, "reserved word" }, + { tokenid::true_literal, "true" }, + { tokenid::false_literal, "false" }, + { tokenid::int_literal, "integral literal" }, + { tokenid::uint_literal, "integral literal" }, + { tokenid::float_literal, "floating point literal" }, + { tokenid::double_literal, "floating point literal" }, + { tokenid::string_literal, "string literal" }, + { tokenid::namespace_, "namespace" }, + { tokenid::struct_, "struct" }, + { tokenid::technique, "technique" }, + { tokenid::pass, "pass" }, + { tokenid::for_, "for" }, + { tokenid::while_, "while" }, + { tokenid::do_, "do" }, + { tokenid::if_, "if" }, + { tokenid::else_, "else" }, + { tokenid::switch_, "switch" }, + { tokenid::case_, "case" }, + { tokenid::default_, "default" }, + { tokenid::break_, "break" }, + { tokenid::continue_, "continue" }, + { tokenid::return_, "return" }, + { tokenid::discard_, "discard" }, + { tokenid::extern_, "extern" }, + { tokenid::static_, "static" }, + { tokenid::uniform_, "uniform" }, + { tokenid::volatile_, "volatile" }, + { tokenid::precise, "precise" }, + { tokenid::groupshared, "groupshared" }, + { tokenid::in, "in" }, + { tokenid::out, "out" }, + { tokenid::inout, "inout" }, + { tokenid::const_, "const" }, + { tokenid::linear, "linear" }, + { tokenid::noperspective, "noperspective" }, + { tokenid::centroid, "centroid" }, + { tokenid::nointerpolation, "nointerpolation" }, + { tokenid::void_, "void" }, + { tokenid::bool_, "bool" }, + { tokenid::bool2, "bool2" }, + { tokenid::bool3, "bool3" }, + { tokenid::bool4, "bool4" }, + { tokenid::bool2x2, "bool2x2" }, + { tokenid::bool2x3, "bool2x3" }, + { tokenid::bool2x4, "bool2x4" }, + { tokenid::bool3x2, "bool3x2" }, + { tokenid::bool3x3, "bool3x3" }, + { tokenid::bool3x4, "bool3x4" }, + { tokenid::bool4x2, "bool4x2" }, + { tokenid::bool4x3, "bool4x3" }, + { tokenid::bool4x4, "bool4x4" }, + { tokenid::int_, "int" }, + { tokenid::int2, "int2" }, + { tokenid::int3, "int3" }, + { tokenid::int4, "int4" }, + { tokenid::int2x2, "int2x2" }, + { tokenid::int2x3, "int2x3" }, + { tokenid::int2x4, "int2x4" }, + { tokenid::int3x2, "int3x2" }, + { tokenid::int3x3, "int3x3" }, + { tokenid::int3x4, "int3x4" }, + { tokenid::int4x2, "int4x2" }, + { tokenid::int4x3, "int4x3" }, + { tokenid::int4x4, "int4x4" }, + { tokenid::min16int, "min16int" }, + { tokenid::min16int2, "min16int2" }, + { tokenid::min16int3, "min16int3" }, + { tokenid::min16int4, "min16int4" }, + { tokenid::uint_, "uint" }, + { tokenid::uint2, "uint2" }, + { tokenid::uint3, "uint3" }, + { tokenid::uint4, "uint4" }, + { tokenid::uint2x2, "uint2x2" }, + { tokenid::uint2x3, "uint2x3" }, + { tokenid::uint2x4, "uint2x4" }, + { tokenid::uint3x2, "uint3x2" }, + { tokenid::uint3x3, "uint3x3" }, + { tokenid::uint3x4, "uint3x4" }, + { tokenid::uint4x2, "uint4x2" }, + { tokenid::uint4x3, "uint4x3" }, + { tokenid::uint4x4, "uint4x4" }, + { tokenid::min16uint, "min16uint" }, + { tokenid::min16uint2, "min16uint2" }, + { tokenid::min16uint3, "min16uint3" }, + { tokenid::min16uint4, "min16uint4" }, + { tokenid::float_, "float" }, + { tokenid::float2, "float2" }, + { tokenid::float3, "float3" }, + { tokenid::float4, "float4" }, + { tokenid::float2x2, "float2x2" }, + { tokenid::float2x3, "float2x3" }, + { tokenid::float2x4, "float2x4" }, + { tokenid::float3x2, "float3x2" }, + { tokenid::float3x3, "float3x3" }, + { tokenid::float3x4, "float3x4" }, + { tokenid::float4x2, "float4x2" }, + { tokenid::float4x3, "float4x3" }, + { tokenid::float4x4, "float4x4" }, + { tokenid::min16float, "min16float" }, + { tokenid::min16float2, "min16float2" }, + { tokenid::min16float3, "min16float3" }, + { tokenid::min16float4, "min16float4" }, + { tokenid::vector, "vector" }, + { tokenid::matrix, "matrix" }, + { tokenid::string_, "string" }, + { tokenid::texture1d, "texture1D" }, + { tokenid::texture2d, "texture2D" }, + { tokenid::texture3d, "texture3D" }, + { tokenid::sampler1d, "sampler1D" }, + { tokenid::sampler2d, "sampler2D" }, + { tokenid::sampler3d, "sampler3D" }, + { tokenid::storage1d, "storage1D" }, + { tokenid::storage2d, "storage2D" }, + { tokenid::storage3d, "storage3D" }, +}; +static const std::unordered_map keyword_lookup = { + { "asm", tokenid::reserved }, + { "asm_fragment", tokenid::reserved }, + { "auto", tokenid::reserved }, + { "bool", tokenid::bool_ }, + { "bool2", tokenid::bool2 }, + { "bool2x1", tokenid::bool2 }, + { "bool2x2", tokenid::bool2x2 }, + { "bool2x3", tokenid::bool2x3 }, + { "bool2x4", tokenid::bool2x4 }, + { "bool3", tokenid::bool3 }, + { "bool3x1", tokenid::bool3 }, + { "bool3x2", tokenid::bool3x2 }, + { "bool3x3", tokenid::bool3x3 }, + { "bool3x4", tokenid::bool3x4 }, + { "bool4", tokenid::bool4 }, + { "bool4x1", tokenid::bool4 }, + { "bool4x2", tokenid::bool4x2 }, + { "bool4x3", tokenid::bool4x3 }, + { "bool4x4", tokenid::bool4x4 }, + { "break", tokenid::break_ }, + { "case", tokenid::case_ }, + { "cast", tokenid::reserved }, + { "catch", tokenid::reserved }, + { "centroid", tokenid::reserved }, + { "char", tokenid::reserved }, + { "class", tokenid::reserved }, + { "column_major", tokenid::reserved }, + { "compile", tokenid::reserved }, + { "const", tokenid::const_ }, + { "const_cast", tokenid::reserved }, + { "continue", tokenid::continue_ }, + { "default", tokenid::default_ }, + { "delete", tokenid::reserved }, + { "discard", tokenid::discard_ }, + { "do", tokenid::do_ }, + { "double", tokenid::reserved }, + { "dword", tokenid::uint_ }, + { "dword2", tokenid::uint2 }, + { "dword2x1", tokenid::uint2 }, + { "dword2x2", tokenid::uint2x2 }, + { "dword2x3", tokenid::uint2x3 }, + { "dword2x4", tokenid::uint2x4 }, + { "dword3", tokenid::uint3, }, + { "dword3x1", tokenid::uint3 }, + { "dword3x2", tokenid::uint3x2 }, + { "dword3x3", tokenid::uint3x3 }, + { "dword3x4", tokenid::uint3x4 }, + { "dword4", tokenid::uint4 }, + { "dword4x1", tokenid::uint4 }, + { "dword4x2", tokenid::uint4x2 }, + { "dword4x3", tokenid::uint4x3 }, + { "dword4x4", tokenid::uint4x4 }, + { "dynamic_cast", tokenid::reserved }, + { "else", tokenid::else_ }, + { "enum", tokenid::reserved }, + { "explicit", tokenid::reserved }, + { "extern", tokenid::extern_ }, + { "external", tokenid::reserved }, + { "false", tokenid::false_literal }, + { "FALSE", tokenid::false_literal }, + { "float", tokenid::float_ }, + { "float2", tokenid::float2 }, + { "float2x1", tokenid::float2 }, + { "float2x2", tokenid::float2x2 }, + { "float2x3", tokenid::float2x3 }, + { "float2x4", tokenid::float2x4 }, + { "float3", tokenid::float3 }, + { "float3x1", tokenid::float3 }, + { "float3x2", tokenid::float3x2 }, + { "float3x3", tokenid::float3x3 }, + { "float3x4", tokenid::float3x4 }, + { "float4", tokenid::float4 }, + { "float4x1", tokenid::float4 }, + { "float4x2", tokenid::float4x2 }, + { "float4x3", tokenid::float4x3 }, + { "float4x4", tokenid::float4x4 }, + { "for", tokenid::for_ }, + { "foreach", tokenid::reserved }, + { "friend", tokenid::reserved }, + { "globallycoherent", tokenid::reserved }, + { "goto", tokenid::reserved }, + { "groupshared", tokenid::groupshared }, + { "half", tokenid::reserved }, + { "half2", tokenid::reserved }, + { "half2x1", tokenid::reserved }, + { "half2x2", tokenid::reserved }, + { "half2x3", tokenid::reserved }, + { "half2x4", tokenid::reserved }, + { "half3", tokenid::reserved }, + { "half3x1", tokenid::reserved }, + { "half3x2", tokenid::reserved }, + { "half3x3", tokenid::reserved }, + { "half3x4", tokenid::reserved }, + { "half4", tokenid::reserved }, + { "half4x1", tokenid::reserved }, + { "half4x2", tokenid::reserved }, + { "half4x3", tokenid::reserved }, + { "half4x4", tokenid::reserved }, + { "if", tokenid::if_ }, + { "in", tokenid::in }, + { "inline", tokenid::reserved }, + { "inout", tokenid::inout }, + { "int", tokenid::int_ }, + { "int2", tokenid::int2 }, + { "int2x1", tokenid::int2 }, + { "int2x2", tokenid::int2x2 }, + { "int2x3", tokenid::int2x3 }, + { "int2x4", tokenid::int2x4 }, + { "int3", tokenid::int3 }, + { "int3x1", tokenid::int3 }, + { "int3x2", tokenid::int3x2 }, + { "int3x3", tokenid::int3x3 }, + { "int3x4", tokenid::int3x4 }, + { "int4", tokenid::int4 }, + { "int4x1", tokenid::int4 }, + { "int4x2", tokenid::int4x2 }, + { "int4x3", tokenid::int4x3 }, + { "int4x4", tokenid::int4x4 }, + { "interface", tokenid::reserved }, + { "linear", tokenid::linear }, + { "long", tokenid::reserved }, + { "matrix", tokenid::matrix }, + { "min16float", tokenid::min16float }, + { "min16float2", tokenid::min16float2 }, + { "min16float3", tokenid::min16float3 }, + { "min16float4", tokenid::min16float4 }, + { "min16int", tokenid::min16int }, + { "min16int2", tokenid::min16int2 }, + { "min16int3", tokenid::min16int3 }, + { "min16int4", tokenid::min16int4 }, + { "min16uint", tokenid::min16uint }, + { "min16uint2", tokenid::min16uint2 }, + { "min16uint3", tokenid::min16uint3 }, + { "min16uint4", tokenid::min16uint4 }, + { "mutable", tokenid::reserved }, + { "namespace", tokenid::namespace_ }, + { "new", tokenid::reserved }, + { "noinline", tokenid::reserved }, + { "nointerpolation", tokenid::nointerpolation }, + { "noperspective", tokenid::noperspective }, + { "operator", tokenid::reserved }, + { "out", tokenid::out }, + { "packed", tokenid::reserved }, + { "packoffset", tokenid::reserved }, + { "pass", tokenid::pass }, + { "precise", tokenid::precise }, + { "private", tokenid::reserved }, + { "protected", tokenid::reserved }, + { "public", tokenid::reserved }, + { "register", tokenid::reserved }, + { "reinterpret_cast", tokenid::reserved }, + { "restrict", tokenid::reserved }, + { "return", tokenid::return_ }, + { "row_major", tokenid::reserved }, + { "sample", tokenid::reserved }, + { "sampler", tokenid::sampler2d }, + { "sampler1D", tokenid::sampler1d }, + { "sampler1DArray", tokenid::reserved }, + { "sampler2D", tokenid::sampler2d }, + { "sampler2DArray", tokenid::reserved }, + { "sampler2DMS", tokenid::reserved }, + { "sampler2DMSArray", tokenid::reserved }, + { "sampler3D", tokenid::sampler3d }, + { "sampler_state", tokenid::reserved }, + { "samplerCube", tokenid::reserved }, + { "samplerCubeArray", tokenid::reserved }, + { "samplerCUBE", tokenid::reserved }, + { "samplerRect", tokenid::reserved }, + { "samplerRECT", tokenid::reserved }, + { "SamplerState", tokenid::reserved }, + { "storage", tokenid::storage2d }, + { "storage1D", tokenid::storage1d }, + { "storage2D", tokenid::storage2d }, + { "storage3D", tokenid::storage3d }, + { "shared", tokenid::reserved }, + { "short", tokenid::reserved }, + { "signed", tokenid::reserved }, + { "sizeof", tokenid::reserved }, + { "snorm", tokenid::reserved }, + { "static", tokenid::static_ }, + { "static_cast", tokenid::reserved }, + { "string", tokenid::string_ }, + { "struct", tokenid::struct_ }, + { "switch", tokenid::switch_ }, + { "technique", tokenid::technique }, + { "template", tokenid::reserved }, + { "texture", tokenid::texture2d }, + { "Texture1D", tokenid::reserved }, + { "texture1D", tokenid::texture1d }, + { "Texture1DArray", tokenid::reserved }, + { "Texture2D", tokenid::reserved }, + { "texture2D", tokenid::texture2d }, + { "Texture2DArray", tokenid::reserved }, + { "Texture2DMS", tokenid::reserved }, + { "Texture2DMSArray", tokenid::reserved }, + { "Texture3D", tokenid::reserved }, + { "texture3D", tokenid::texture3d }, + { "textureCUBE", tokenid::reserved }, + { "TextureCube", tokenid::reserved }, + { "TextureCubeArray", tokenid::reserved }, + { "textureRECT", tokenid::reserved }, + { "this", tokenid::reserved }, + { "true", tokenid::true_literal }, + { "TRUE", tokenid::true_literal }, + { "try", tokenid::reserved }, + { "typedef", tokenid::reserved }, + { "uint", tokenid::uint_ }, + { "uint2", tokenid::uint2 }, + { "uint2x1", tokenid::uint2 }, + { "uint2x2", tokenid::uint2x2 }, + { "uint2x3", tokenid::uint2x3 }, + { "uint2x4", tokenid::uint2x4 }, + { "uint3", tokenid::uint3 }, + { "uint3x1", tokenid::uint3 }, + { "uint3x2", tokenid::uint3x2 }, + { "uint3x3", tokenid::uint3x3 }, + { "uint3x4", tokenid::uint3x4 }, + { "uint4", tokenid::uint4 }, + { "uint4x1", tokenid::uint4 }, + { "uint4x2", tokenid::uint4x2 }, + { "uint4x3", tokenid::uint4x3 }, + { "uint4x4", tokenid::uint4x4 }, + { "uniform", tokenid::uniform_ }, + { "union", tokenid::reserved }, + { "unorm", tokenid::reserved }, + { "unsigned", tokenid::reserved }, + { "using", tokenid::reserved }, + { "vector", tokenid::vector }, + { "virtual", tokenid::reserved }, + { "void", tokenid::void_ }, + { "volatile", tokenid::volatile_ }, + { "while", tokenid::while_ } +}; +static const std::unordered_map pp_directive_lookup = { + { "define", tokenid::hash_def }, + { "undef", tokenid::hash_undef }, + { "if", tokenid::hash_if }, + { "ifdef", tokenid::hash_ifdef }, + { "ifndef", tokenid::hash_ifndef }, + { "else", tokenid::hash_else }, + { "elif", tokenid::hash_elif }, + { "endif", tokenid::hash_endif }, + { "error", tokenid::hash_error }, + { "warning", tokenid::hash_warning }, + { "pragma", tokenid::hash_pragma }, + { "include", tokenid::hash_include }, +}; + +static inline bool is_octal_digit(char c) +{ + return static_cast(c - '0') < 8; +} +static inline bool is_decimal_digit(char c) +{ + return static_cast(c - '0') < 10; +} +static inline bool is_hexadecimal_digit(char c) +{ + return is_decimal_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +static bool is_digit(char c, int radix) +{ + switch (radix) + { + case 8: + return is_octal_digit(c); + case 10: + return is_decimal_digit(c); + case 16: + return is_hexadecimal_digit(c); + } + + return false; +} +static long long octal_to_decimal(long long n) +{ + long long m = 0; + + while (n != 0) + { + m *= 8; + m += n & 7; + n >>= 3; + } + + while (m != 0) + { + n *= 10; + n += m & 7; + m >>= 3; + } + + return n; +} + +std::string reshadefx::token::id_to_name(tokenid id) +{ + const auto it = token_lookup.find(id); + if (it != token_lookup.end()) + return std::string(it->second); + return "unknown"; +} + +reshadefx::token reshadefx::lexer::lex() +{ + bool is_at_line_begin = _cur_location.column <= 1; + + token tok; +next_token: + // Reset token data + tok.location = _cur_location; + tok.offset = input_offset(); + tok.length = 1; + tok.literal_as_double = 0; + tok.literal_as_string.clear(); + + assert(_cur <= _end); + + // Do a character type lookup for the current character + switch (type_lookup[uint8_t(*_cur)]) + { + case 0xFF: // EOF + tok.id = tokenid::end_of_file; + return tok; + case SPACE: + skip_space(); + if (_ignore_whitespace || is_at_line_begin || *_cur == '\n') + goto next_token; + tok.id = tokenid::space; + tok.length = input_offset() - tok.offset; + return tok; + case '\n': + _cur++; + _cur_location.line++; + _cur_location.column = 1; + is_at_line_begin = true; + if (_ignore_whitespace) + goto next_token; + tok.id = tokenid::end_of_line; + return tok; + case DIGIT: + parse_numeric_literal(tok); + break; + case IDENT: + parse_identifier(tok); + break; + case '!': + if (_cur[1] == '=') + tok.id = tokenid::exclaim_equal, + tok.length = 2; + else + tok.id = tokenid::exclaim; + break; + case '"': + parse_string_literal(tok, _escape_string_literals); + break; + case '#': + if (is_at_line_begin) + { + if (!parse_pp_directive(tok) || _ignore_pp_directives) + { + skip_to_next_line(); + goto next_token; + } + } // These braces are important so the 'else' is matched to the right 'if' statement + else + tok.id = tokenid::hash; + break; + case '$': + tok.id = tokenid::dollar; + break; + case '%': + if (_cur[1] == '=') + tok.id = tokenid::percent_equal, + tok.length = 2; + else + tok.id = tokenid::percent; + break; + case '&': + if (_cur[1] == '&') + tok.id = tokenid::ampersand_ampersand, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::ampersand_equal, + tok.length = 2; + else + tok.id = tokenid::ampersand; + break; + case '(': + tok.id = tokenid::parenthesis_open; + break; + case ')': + tok.id = tokenid::parenthesis_close; + break; + case '*': + if (_cur[1] == '=') + tok.id = tokenid::star_equal, + tok.length = 2; + else + tok.id = tokenid::star; + break; + case '+': + if (_cur[1] == '+') + tok.id = tokenid::plus_plus, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::plus_equal, + tok.length = 2; + else + tok.id = tokenid::plus; + break; + case ',': + tok.id = tokenid::comma; + break; + case '-': + if (_cur[1] == '-') + tok.id = tokenid::minus_minus, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::minus_equal, + tok.length = 2; + else if (_cur[1] == '>') + tok.id = tokenid::arrow, + tok.length = 2; + else + tok.id = tokenid::minus; + break; + case '.': + if (type_lookup[uint8_t(_cur[1])] == DIGIT) + parse_numeric_literal(tok); + else if (_cur[1] == '.' && _cur[2] == '.') + tok.id = tokenid::ellipsis, + tok.length = 3; + else + tok.id = tokenid::dot; + break; + case '/': + if (_cur[1] == '/') + { + skip_to_next_line(); + if (_ignore_comments) + goto next_token; + tok.id = tokenid::single_line_comment; + tok.length = input_offset() - tok.offset; + return tok; + } + else if (_cur[1] == '*') + { + while (_cur < _end) + { + if (*_cur == '\n') + { + _cur_location.line++; + _cur_location.column = 1; + } + else if (_cur[0] == '*' && _cur[1] == '/') + { + skip(2); + break; + } + skip(1); + } + if (_ignore_comments) + goto next_token; + tok.id = tokenid::multi_line_comment; + tok.length = input_offset() - tok.offset; + return tok; + } + else if (_cur[1] == '=') + tok.id = tokenid::slash_equal, + tok.length = 2; + else + tok.id = tokenid::slash; + break; + case ':': + if (_cur[1] == ':') + tok.id = tokenid::colon_colon, + tok.length = 2; + else + tok.id = tokenid::colon; + break; + case ';': + tok.id = tokenid::semicolon; + break; + case '<': + if (_cur[1] == '<') + if (_cur[2] == '=') + tok.id = tokenid::less_less_equal, + tok.length = 3; + else + tok.id = tokenid::less_less, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::less_equal, + tok.length = 2; + else + tok.id = tokenid::less; + break; + case '=': + if (_cur[1] == '=') + tok.id = tokenid::equal_equal, + tok.length = 2; + else + tok.id = tokenid::equal; + break; + case '>': + if (_cur[1] == '>') + if (_cur[2] == '=') + tok.id = tokenid::greater_greater_equal, + tok.length = 3; + else + tok.id = tokenid::greater_greater, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::greater_equal, + tok.length = 2; + else + tok.id = tokenid::greater; + break; + case '?': + tok.id = tokenid::question; + break; + case '@': + tok.id = tokenid::at; + break; + case '[': + tok.id = tokenid::bracket_open; + break; + case '\\': + if (_cur[1] == '\n' || (_cur[1] == '\r' && _cur[2] == '\n')) + { + // Skip to next line if current line ends with a backslash + skip_space(); + if (_ignore_whitespace) + goto next_token; + tok.id = tokenid::space; + tok.length = input_offset() - tok.offset; + return tok; + } + tok.id = tokenid::backslash; + break; + case ']': + tok.id = tokenid::bracket_close; + break; + case '^': + if (_cur[1] == '=') + tok.id = tokenid::caret_equal, + tok.length = 2; + else + tok.id = tokenid::caret; + break; + case '{': + tok.id = tokenid::brace_open; + break; + case '|': + if (_cur[1] == '=') + tok.id = tokenid::pipe_equal, + tok.length = 2; + else if (_cur[1] == '|') + tok.id = tokenid::pipe_pipe, + tok.length = 2; + else + tok.id = tokenid::pipe; + break; + case '}': + tok.id = tokenid::brace_close; + break; + case '~': + tok.id = tokenid::tilde; + break; + default: + tok.id = tokenid::unknown; + break; + } + + skip(tok.length); + + return tok; +} + +void reshadefx::lexer::skip(size_t length) +{ + _cur += length; + _cur_location.column += static_cast(length); +} +void reshadefx::lexer::skip_space() +{ + // Skip each character until a space is found + while (_cur < _end) + { + if (_cur[0] == '\\' && (_cur[1] == '\n' || (_cur[1] == '\r' && _cur[2] == '\n'))) + { + skip(_cur[1] == '\r' ? 3 : 2); + _cur_location.line++; + _cur_location.column = 1; + continue; + } + + if (type_lookup[uint8_t(*_cur)] == SPACE) + skip(1); + else + break; + } +} +void reshadefx::lexer::skip_to_next_line() +{ + // Skip each character until a new line feed is found + while (*_cur != '\n' && _cur < _end) + { +#if 0 + if (_cur[0] == '\\' && (_cur[1] == '\n' || (_cur[1] == '\r' && _cur[2] == '\n'))) + { + skip(_cur[1] == '\r' ? 3 : 2); + _cur_location.line++; + _cur_location.column = 1; + continue; + } +#endif + + skip(1); + } +} + +void reshadefx::lexer::reset_to_offset(size_t offset) +{ + assert(offset < _input.size()); + _cur = _input.data() + offset; +} + +void reshadefx::lexer::parse_identifier(token &tok) const +{ + auto *const begin = _cur, *end = begin; + + // Skip to the end of the identifier sequence + while (type_lookup[uint8_t(*end)] == IDENT || type_lookup[uint8_t(*end)] == DIGIT) + end++; + + tok.id = tokenid::identifier; + tok.offset = input_offset(); + tok.length = end - begin; + tok.literal_as_string.assign(begin, end); + + if (_ignore_keywords) + return; + + if (const auto it = keyword_lookup.find(tok.literal_as_string); + it != keyword_lookup.end()) + tok.id = it->second; +} +bool reshadefx::lexer::parse_pp_directive(token &tok) +{ + skip(1); // Skip the '#' + skip_space(); // Skip any space between the '#' and directive + parse_identifier(tok); + + if (const auto it = pp_directive_lookup.find(tok.literal_as_string); + it != pp_directive_lookup.end()) + { + tok.id = it->second; + return true; + } + else if (!_ignore_line_directives && tok.literal_as_string == "line") // The #line directive needs special handling + { + skip(tok.length); // The 'parse_identifier' does not update the pointer to the current character, so do that now + skip_space(); + parse_numeric_literal(tok); + skip(tok.length); + + _cur_location.line = tok.literal_as_int; + + // Need to subtract one since the line containing #line does not count into the statistics + if (_cur_location.line != 0) + _cur_location.line--; + + skip_space(); + + // Check if this #line directive has an file name attached to it + if (_cur[0] == '"') + { + token temptok; + parse_string_literal(temptok, false); + + _cur_location.source = std::move(temptok.literal_as_string); + } + + // Do not return the #line directive as token to the caller + return false; + } + + tok.id = tokenid::hash_unknown; + + return true; +} +void reshadefx::lexer::parse_string_literal(token &tok, bool escape) +{ + auto *const begin = _cur, *end = begin + 1; // Skip first quote character right away + + for (auto c = *end; c != '"'; c = *++end) + { + if (c == '\n' || end >= _end) + { + // Line feed reached, the string literal is done (technically this should be an error, but the lexer does not report errors, so ignore it) + end--; + if (end[0] == '\r') end--; + break; + } + + if (c == '\r') + { + // Silently ignore carriage return characters + continue; + } + + if (unsigned int n = (end[1] == '\r' && end + 2 < _end) ? 2 : 1; + c == '\\' && end[n] == '\n') + { + // Escape character found at end of line, the string literal continues on to the next line + end += n; + _cur_location.line++; + continue; + } + + // Handle escape sequences + if (c == '\\' && escape) + { + unsigned int n = 0; + + // Any character following the '\' is not parsed as usual, so increment pointer here (this makes sure '\"' does not abort the outer loop as well) + switch (c = *++end) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + for (unsigned int i = 0; i < 3 && is_octal_digit(*end) && end < _end; i++) + { + c = *end++; + n = (n << 3) | (c - '0'); + } + // For simplicity the number is limited to what fits in a single character + c = n & 0xFF; + // The octal parsing loop above incremented one pass the escape sequence, so step back + end--; + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + if (is_hexadecimal_digit(*++end)) + { + while (is_hexadecimal_digit(*end) && end < _end) + { + c = *end++; + n = (n << 4) | (is_decimal_digit(c) ? (c - '0') : (c - 55 - 32 * (c & 0x20))); + } + + // For simplicity the number is limited to what fits in a single character + c = n & 0xFF; + } + // The hexadecimal parsing loop and check above incremented one pass the escape sequence, so step back + end--; + break; + } + } + + tok.literal_as_string += c; + } + + tok.id = tokenid::string_literal; + tok.length = end - begin + 1; +} +void reshadefx::lexer::parse_numeric_literal(token &tok) const +{ + // This routine handles both integer and floating point numbers + auto *const begin = _cur, *end = _cur; + int mantissa_size = 0, decimal_location = -1, radix = 10; + long long fraction = 0, exponent = 0; + + // If a literal starts with '0' it is either an octal or hexadecimal ('0x') value + if (begin[0] == '0') + { + if (begin[1] == 'x' || begin[1] == 'X') + { + end = begin + 2; + radix = 16; + } + else + { + radix = 8; + } + } + + for (; mantissa_size <= 18; mantissa_size++, end++) + { + auto c = *end; + + if (is_decimal_digit(c)) + { + c -= '0'; + + if (c >= radix) + break; + } + else if (radix == 16) + { + // Hexadecimal values can contain the letters A to F + if (c >= 'A' && c <= 'F') + c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else + break; + } + else + { + if (c != '.' || decimal_location >= 0) + break; + + // Found a decimal character, as such convert current values + if (radix == 8) + { + radix = 10; + fraction = octal_to_decimal(fraction); + } + + decimal_location = mantissa_size; + continue; + } + + fraction *= radix; + fraction += c; + } + + // Ignore additional digits that cannot affect the value + while (is_digit(*end, radix)) + end++; + + // If a decimal character was found, this is a floating point value, otherwise an integer one + if (decimal_location < 0) + { + tok.id = tokenid::int_literal; + decimal_location = mantissa_size; + } + else + { + tok.id = tokenid::float_literal; + mantissa_size -= 1; + } + + // Literals can be followed by an exponent + if (*end == 'E' || *end == 'e') + { + auto tmp = end + 1; + const bool negative = *tmp == '-'; + + if (negative || *tmp == '+') + tmp++; + + if (is_decimal_digit(*tmp)) + { + end = tmp; + + tok.id = tokenid::float_literal; + + do { + exponent *= 10; + exponent += (*end++) - '0'; + } while (is_decimal_digit(*end)); + + if (negative) + exponent = -exponent; + } + } + + // Various suffixes force specific literal types + if (*end == 'F' || *end == 'f') + { + end++; // Consume the suffix + tok.id = tokenid::float_literal; + } + else if (*end == 'L' || *end == 'l') + { + end++; // Consume the suffix + tok.id = tokenid::double_literal; + } + else if (tok.id == tokenid::int_literal && (*end == 'U' || *end == 'u')) // The 'u' suffix is only valid on integers and needs to be ignored otherwise + { + end++; // Consume the suffix + tok.id = tokenid::uint_literal; + } + + if (tok.id == tokenid::float_literal || tok.id == tokenid::double_literal) + { + exponent += decimal_location - mantissa_size; + + const bool exponent_negative = exponent < 0; + + if (exponent_negative) + exponent = -exponent; + + // Limit exponent + if (exponent > 511) + exponent = 511; + + // Quick exponent calculation + double e = 1.0; + const double powers_of_10[] = { + 10., + 100., + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + + for (auto d = powers_of_10; exponent != 0; exponent >>= 1, d++) + if (exponent & 1) + e *= *d; + + if (tok.id == tokenid::float_literal) + tok.literal_as_float = exponent_negative ? fraction / static_cast(e) : fraction * static_cast(e); + else + tok.literal_as_double = exponent_negative ? fraction / e : fraction * e; + } + else + { + // Limit the maximum value to what fits into our token structure + tok.literal_as_uint = static_cast(fraction & 0xFFFFFFFF); + } + + tok.length = end - begin; +} diff --git a/dep/reshadefx/src/effect_parser_exp.cpp b/dep/reshadefx/src/effect_parser_exp.cpp new file mode 100644 index 000000000..7fe436467 --- /dev/null +++ b/dep/reshadefx/src/effect_parser_exp.cpp @@ -0,0 +1,1538 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_lexer.hpp" +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include + +#define RESHADEFX_SHORT_CIRCUIT 0 + +reshadefx::parser::parser() +{ +} +reshadefx::parser::~parser() +{ +} + +void reshadefx::parser::error(const location &location, unsigned int code, const std::string &message) +{ + _errors += location.source; + _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": error"; + _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; + _errors += message; + _errors += '\n'; +} +void reshadefx::parser::warning(const location &location, unsigned int code, const std::string &message) +{ + _errors += location.source; + _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": warning"; + _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; + _errors += message; + _errors += '\n'; +} + +void reshadefx::parser::backup() +{ + _token_backup = _token_next; + _lexer_backup_offset = _lexer->input_offset(); +} +void reshadefx::parser::restore() +{ + _lexer->reset_to_offset(_lexer_backup_offset); + _token_next = _token_backup; // Copy instead of move here, since restore may be called twice (from 'accept_type_class' and then again from 'parse_expression_unary') +} + +void reshadefx::parser::consume() +{ + _token = std::move(_token_next); + _token_next = _lexer->lex(); +} +void reshadefx::parser::consume_until(tokenid tokid) +{ + while (!accept(tokid) && !peek(tokenid::end_of_file)) + { + consume(); + } +} + +bool reshadefx::parser::accept(tokenid tokid) +{ + if (peek(tokid)) + { + consume(); + return true; + } + + return false; +} +bool reshadefx::parser::expect(tokenid tokid) +{ + if (!accept(tokid)) + { + error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected '" + token::id_to_name(tokid) + '\''); + return false; + } + + return true; +} + +bool reshadefx::parser::accept_symbol(std::string &identifier, scoped_symbol &symbol) +{ + // Starting an identifier with '::' restricts the symbol search to the global namespace level + const bool exclusive = accept(tokenid::colon_colon); + + if (exclusive ? !expect(tokenid::identifier) : !accept(tokenid::identifier)) + { + // No token should come through here, since all possible prefix expressions should have been handled above, so this is an error in the syntax + if (!exclusive) + error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + '\''); + return false; + } + + identifier = std::move(_token.literal_as_string); + + // Can concatenate multiple '::' to force symbol search for a specific namespace level + while (accept(tokenid::colon_colon)) + { + if (!expect(tokenid::identifier)) + return false; + identifier += "::" + std::move(_token.literal_as_string); + } + + // Figure out which scope to start searching in + struct scope scope = { "::", 0, 0 }; + if (!exclusive) scope = current_scope(); + + // Lookup name in the symbol table + symbol = find_symbol(identifier, scope, exclusive); + + return true; +} +bool reshadefx::parser::accept_type_class(type &type) +{ + type.rows = type.cols = 0; + + if (peek(tokenid::identifier) || peek(tokenid::colon_colon)) + { + type.base = type::t_struct; + + backup(); // Need to restore if this identifier does not turn out to be a structure + + std::string identifier; + scoped_symbol symbol; + if (accept_symbol(identifier, symbol)) + { + if (symbol.id && symbol.op == symbol_type::structure) + { + type.definition = symbol.id; + return true; + } + } + + restore(); + + return false; + } + + if (accept(tokenid::vector)) + { + type.base = type::t_float; // Default to float4 unless a type is specified (see below) + type.rows = 4, type.cols = 1; + + if (accept('<')) + { + if (!accept_type_class(type)) // This overwrites the base type again + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected vector element type"), false; + else if (!type.is_scalar()) + return error(_token.location, 3122, "vector element type must be a scalar type"), false; + + if (!expect(',') || !expect(tokenid::int_literal)) + return false; + else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) + return error(_token.location, 3052, "vector dimension must be between 1 and 4"), false; + + type.rows = static_cast(_token.literal_as_int); + + if (!expect('>')) + return false; + } + + return true; + } + if (accept(tokenid::matrix)) + { + type.base = type::t_float; // Default to float4x4 unless a type is specified (see below) + type.rows = 4, type.cols = 4; + + if (accept('<')) + { + if (!accept_type_class(type)) // This overwrites the base type again + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected matrix element type"), false; + else if (!type.is_scalar()) + return error(_token.location, 3123, "matrix element type must be a scalar type"), false; + + if (!expect(',') || !expect(tokenid::int_literal)) + return false; + else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) + return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; + + type.rows = static_cast(_token.literal_as_int); + + if (!expect(',') || !expect(tokenid::int_literal)) + return false; + else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) + return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; + + type.cols = static_cast(_token.literal_as_int); + + if (!expect('>')) + return false; + } + + return true; + } + + if (accept(tokenid::sampler1d) || accept(tokenid::sampler2d) || accept(tokenid::sampler3d)) + { + const unsigned int texture_dimension = static_cast(_token.id) - static_cast(tokenid::sampler1d); + + if (accept('<')) + { + if (!accept_type_class(type)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected sampler element type"), false; + if (type.is_object()) + return error(_token.location, 3124, "object element type cannot be an object type"), false; + if (!type.is_numeric() || type.is_matrix()) + return error(_token.location, 3521, "sampler element type must fit in four 32-bit quantities"), false; + + if (type.is_integral() && type.is_signed()) + type.base = static_cast(type::t_sampler1d_int + texture_dimension); + else if (type.is_integral() && type.is_unsigned()) + type.base = static_cast(type::t_sampler1d_uint + texture_dimension); + else + type.base = static_cast(type::t_sampler1d_float + texture_dimension); + + if (!expect('>')) + return false; + } + else + { + type.base = static_cast(type::t_sampler1d_float + texture_dimension); + type.rows = 4; + type.cols = 1; + } + + return true; + } + if (accept(tokenid::storage1d) || accept(tokenid::storage2d) || accept(tokenid::storage3d)) + { + const unsigned int texture_dimension = static_cast(_token.id) - static_cast(tokenid::storage1d); + + if (accept('<')) + { + if (!accept_type_class(type)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected storage element type"), false; + if (type.is_object()) + return error(_token.location, 3124, "object element type cannot be an object type"), false; + if (!type.is_numeric() || type.is_matrix()) + return error(_token.location, 3521, "storage element type must fit in four 32-bit quantities"), false; + + if (type.is_integral() && type.is_signed()) + type.base = static_cast(type::t_storage1d_int + texture_dimension); + else if (type.is_integral() && type.is_unsigned()) + type.base = static_cast(type::t_storage1d_uint + texture_dimension); + else + type.base = static_cast(type::t_storage1d_float + texture_dimension); + + if (!expect('>')) + return false; + } + else + { + type.base = static_cast(type::t_storage1d_float + texture_dimension); + type.rows = 4; + type.cols = 1; + } + + return true; + } + + switch (_token_next.id) + { + case tokenid::void_: + type.base = type::t_void; + break; + case tokenid::bool_: + case tokenid::bool2: + case tokenid::bool3: + case tokenid::bool4: + type.base = type::t_bool; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::bool_)); + type.cols = 1; + break; + case tokenid::bool2x2: + case tokenid::bool2x3: + case tokenid::bool2x4: + case tokenid::bool3x2: + case tokenid::bool3x3: + case tokenid::bool3x4: + case tokenid::bool4x2: + case tokenid::bool4x3: + case tokenid::bool4x4: + type.base = type::t_bool; + type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::bool2x2)) / 3; + type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::bool2x2)) % 3; + break; + case tokenid::int_: + case tokenid::int2: + case tokenid::int3: + case tokenid::int4: + type.base = type::t_int; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::int_)); + type.cols = 1; + break; + case tokenid::int2x2: + case tokenid::int2x3: + case tokenid::int2x4: + case tokenid::int3x2: + case tokenid::int3x3: + case tokenid::int3x4: + case tokenid::int4x2: + case tokenid::int4x3: + case tokenid::int4x4: + type.base = type::t_int; + type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::int2x2)) / 3; + type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::int2x2)) % 3; + break; + case tokenid::min16int: + case tokenid::min16int2: + case tokenid::min16int3: + case tokenid::min16int4: + type.base = type::t_min16int; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::min16int)); + type.cols = 1; + break; + case tokenid::uint_: + case tokenid::uint2: + case tokenid::uint3: + case tokenid::uint4: + type.base = type::t_uint; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::uint_)); + type.cols = 1; + break; + case tokenid::uint2x2: + case tokenid::uint2x3: + case tokenid::uint2x4: + case tokenid::uint3x2: + case tokenid::uint3x3: + case tokenid::uint3x4: + case tokenid::uint4x2: + case tokenid::uint4x3: + case tokenid::uint4x4: + type.base = type::t_uint; + type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::uint2x2)) / 3; + type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::uint2x2)) % 3; + break; + case tokenid::min16uint: + case tokenid::min16uint2: + case tokenid::min16uint3: + case tokenid::min16uint4: + type.base = type::t_min16uint; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::min16uint)); + type.cols = 1; + break; + case tokenid::float_: + case tokenid::float2: + case tokenid::float3: + case tokenid::float4: + type.base = type::t_float; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::float_)); + type.cols = 1; + break; + case tokenid::float2x2: + case tokenid::float2x3: + case tokenid::float2x4: + case tokenid::float3x2: + case tokenid::float3x3: + case tokenid::float3x4: + case tokenid::float4x2: + case tokenid::float4x3: + case tokenid::float4x4: + type.base = type::t_float; + type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::float2x2)) / 3; + type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::float2x2)) % 3; + break; + case tokenid::min16float: + case tokenid::min16float2: + case tokenid::min16float3: + case tokenid::min16float4: + type.base = type::t_min16float; + type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::min16float)); + type.cols = 1; + break; + case tokenid::string_: + type.base = type::t_string; + break; + case tokenid::texture1d: + type.base = type::t_texture1d; + break; + case tokenid::texture2d: + type.base = type::t_texture2d; + break; + case tokenid::texture3d: + type.base = type::t_texture3d; + break; + default: + return false; + } + + consume(); + + return true; +} +bool reshadefx::parser::accept_type_qualifiers(type &type) +{ + unsigned int qualifiers = 0; + + // Storage + if (accept(tokenid::extern_)) + qualifiers |= type::q_extern; + if (accept(tokenid::static_)) + qualifiers |= type::q_static; + if (accept(tokenid::uniform_)) + qualifiers |= type::q_uniform; + if (accept(tokenid::volatile_)) + qualifiers |= type::q_volatile; + if (accept(tokenid::precise)) + qualifiers |= type::q_precise; + if (accept(tokenid::groupshared)) + qualifiers |= type::q_groupshared; + + if (accept(tokenid::in)) + qualifiers |= type::q_in; + if (accept(tokenid::out)) + qualifiers |= type::q_out; + if (accept(tokenid::inout)) + qualifiers |= type::q_inout; + + // Modifiers + if (accept(tokenid::const_)) + qualifiers |= type::q_const; + + // Interpolation + if (accept(tokenid::linear)) + qualifiers |= type::q_linear; + if (accept(tokenid::noperspective)) + qualifiers |= type::q_noperspective; + if (accept(tokenid::centroid)) + qualifiers |= type::q_centroid; + if (accept(tokenid::nointerpolation)) + qualifiers |= type::q_nointerpolation; + + if (qualifiers == 0) + return false; + if ((type.qualifiers & qualifiers) == qualifiers) + warning(_token.location, 3048, "duplicate usages specified"); + + type.qualifiers |= qualifiers; + + // Continue parsing potential additional qualifiers until no more are found + accept_type_qualifiers(type); + + return true; +} + +bool reshadefx::parser::accept_unary_op() +{ + switch (_token_next.id) + { + case tokenid::exclaim: // !x (logical not) + case tokenid::plus: // +x + case tokenid::minus: // -x (negate) + case tokenid::tilde: // ~x (bitwise not) + case tokenid::plus_plus: // ++x + case tokenid::minus_minus: // --x + break; + default: + return false; + } + + consume(); + + return true; +} +bool reshadefx::parser::accept_postfix_op() +{ + switch (_token_next.id) + { + case tokenid::plus_plus: // ++x + case tokenid::minus_minus: // --x + break; + default: + return false; + } + + consume(); + + return true; +} +bool reshadefx::parser::peek_multary_op(unsigned int &precedence) const +{ + // Precedence values taken from https://cppreference.com/w/cpp/language/operator_precedence + switch (_token_next.id) + { + case tokenid::question: precedence = 1; break; // x ? a : b + case tokenid::pipe_pipe: precedence = 2; break; // a || b (logical or) + case tokenid::ampersand_ampersand: precedence = 3; break; // a && b (logical and) + case tokenid::pipe: precedence = 4; break; // a | b (bitwise or) + case tokenid::caret: precedence = 5; break; // a ^ b (bitwise xor) + case tokenid::ampersand: precedence = 6; break; // a & b (bitwise and) + case tokenid::equal_equal: precedence = 7; break; // a == b (equal) + case tokenid::exclaim_equal: precedence = 7; break; // a != b (not equal) + case tokenid::less: precedence = 8; break; // a < b + case tokenid::greater: precedence = 8; break; // a > b + case tokenid::less_equal: precedence = 8; break; // a <= b + case tokenid::greater_equal: precedence = 8; break; // a >= b + case tokenid::less_less: precedence = 9; break; // a << b (left shift) + case tokenid::greater_greater: precedence = 9; break; // a >> b (right shift) + case tokenid::plus: precedence = 10; break; // a + b (add) + case tokenid::minus: precedence = 10; break; // a - b (subtract) + case tokenid::star: precedence = 11; break; // a * b (multiply) + case tokenid::slash: precedence = 11; break; // a / b (divide) + case tokenid::percent: precedence = 11; break; // a % b (modulo) + default: + return false; + } + + // Do not consume token yet since the expression may be skipped due to precedence + return true; +} +bool reshadefx::parser::accept_assignment_op() +{ + switch (_token_next.id) + { + case tokenid::equal: // a = b + case tokenid::percent_equal: // a %= b + case tokenid::ampersand_equal: // a &= b + case tokenid::star_equal: // a *= b + case tokenid::plus_equal: // a += b + case tokenid::minus_equal: // a -= b + case tokenid::slash_equal: // a /= b + case tokenid::less_less_equal: // a <<= b + case tokenid::greater_greater_equal: // a >>= b + case tokenid::caret_equal: // a ^= b + case tokenid::pipe_equal: // a |= b + break; + default: + return false; + } + + consume(); + + return true; +} + +bool reshadefx::parser::parse_expression(expression &exp) +{ + // Parse first expression + if (!parse_expression_assignment(exp)) + return false; + + // Continue parsing if an expression sequence is next (in the form "a, b, c, ...") + while (accept(',')) + // Overwrite 'exp' since conveniently the last expression in the sequence is the result + if (!parse_expression_assignment(exp)) + return false; + + return true; +} + +bool reshadefx::parser::parse_expression_unary(expression &exp) +{ + auto location = _token_next.location; + + // Check if a prefix operator exists + if (accept_unary_op()) + { + // Remember the operator token before parsing the expression that follows it + const tokenid op = _token.id; + + // Parse the actual expression + if (!parse_expression_unary(exp)) + return false; + + // Unary operators are only valid on basic types + if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) + return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; + + // Special handling for the "++" and "--" operators + if (op == tokenid::plus_plus || op == tokenid::minus_minus) + { + if (exp.type.has(type::q_const) || !exp.is_lvalue) + return error(location, 3025, "l-value specifies const object"), false; + + // Create a constant one in the type of the expression + constant one = {}; + for (unsigned int i = 0; i < exp.type.components(); ++i) + if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; + + const auto value = _codegen->emit_load(exp); + const auto result = _codegen->emit_binary_op(location, op, exp.type, value, + _codegen->emit_constant(exp.type, one)); + + // The "++" and "--" operands modify the source variable, so store result back into it + _codegen->emit_store(exp, result); + } + else if (op != tokenid::plus) // Ignore "+" operator since it does not actually do anything + { + // The "~" bitwise operator is only valid on integral types + if (op == tokenid::tilde && !exp.type.is_integral()) + return error(exp.location, 3082, "int or unsigned int type required"), false; + // The logical not operator expects a boolean type as input, so perform cast if necessary + if (op == tokenid::exclaim && !exp.type.is_boolean()) + exp.add_cast_operation({ type::t_bool, exp.type.rows, exp.type.cols }); // Note: The result will be boolean as well + + // Constant expressions can be evaluated at compile time + if (!exp.evaluate_constant_expression(op)) + { + const auto value = _codegen->emit_load(exp); + const auto result = _codegen->emit_unary_op(location, op, exp.type, value); + + exp.reset_to_rvalue(location, result, exp.type); + } + } + } + else if (accept('(')) + { + // Note: This backup may get overridden in 'accept_type_class', but should point to the same token still + backup(); + + // Check if this is a C-style cast expression + if (type cast_type; accept_type_class(cast_type)) + { + if (peek('(')) + { + // This is not a C-style cast but a constructor call, so need to roll-back and parse that instead + restore(); + } + else if (expect(')')) + { + // Parse the expression behind cast operator + if (!parse_expression_unary(exp)) + return false; + + // Check if the types already match, in which case there is nothing to do + if (exp.type == cast_type) + return true; + + // Check if a cast between these types is valid + if (!type::rank(exp.type, cast_type)) + return error(location, 3017, "cannot convert these types (from " + exp.type.description() + " to " + cast_type.description() + ')'), false; + + exp.add_cast_operation(cast_type); + return true; + } + else + { + // Type name was not followed by a closing parenthesis + return false; + } + } + + // Parse expression between the parentheses + if (!parse_expression(exp) || !expect(')')) + return false; + } + else if (accept('{')) + { + bool is_constant = true; + std::vector elements; + type composite_type = { type::t_void, 1, 1 }; + + while (!peek('}')) + { + // There should be a comma between arguments + if (!elements.empty() && !expect(',')) + return consume_until('}'), false; + + // Initializer lists might contain a comma at the end, so break out of the loop if nothing follows afterwards + if (peek('}')) + break; + + expression &element = elements.emplace_back(); + + // Parse the argument expression + if (!parse_expression_assignment(element)) + return consume_until('}'), false; + + if (element.type.is_array()) + return error(element.location, 3119, "arrays cannot be multi-dimensional"), consume_until('}'), false; + if (composite_type.base != type::t_void && element.type.definition != composite_type.definition) + return error(element.location, 3017, "cannot convert these types (from " + element.type.description() + " to " + composite_type.description() + ')'), false; + + is_constant &= element.is_constant; // Result is only constant if all arguments are constant + composite_type = type::merge(composite_type, element.type); + } + + // Constant arrays can be constructed at compile time + if (is_constant) + { + constant res = {}; + for (expression &element : elements) + { + element.add_cast_operation(composite_type); + res.array_data.push_back(element.constant); + } + + composite_type.array_length = static_cast(elements.size()); + + exp.reset_to_rvalue_constant(location, std::move(res), composite_type); + } + else + { + // Resolve all access chains + for (expression &element : elements) + { + element.add_cast_operation(composite_type); + element.reset_to_rvalue(element.location, _codegen->emit_load(element), composite_type); + } + + composite_type.array_length = static_cast(elements.size()); + + const auto result = _codegen->emit_construct(location, composite_type, elements); + + exp.reset_to_rvalue(location, result, composite_type); + } + + return expect('}'); + } + else if (accept(tokenid::true_literal)) + { + exp.reset_to_rvalue_constant(location, true); + } + else if (accept(tokenid::false_literal)) + { + exp.reset_to_rvalue_constant(location, false); + } + else if (accept(tokenid::int_literal)) + { + exp.reset_to_rvalue_constant(location, _token.literal_as_int); + } + else if (accept(tokenid::uint_literal)) + { + exp.reset_to_rvalue_constant(location, _token.literal_as_uint); + } + else if (accept(tokenid::float_literal)) + { + exp.reset_to_rvalue_constant(location, _token.literal_as_float); + } + else if (accept(tokenid::double_literal)) + { + // Convert double literal to float literal for now + warning(location, 5000, "double literal truncated to float literal"); + + exp.reset_to_rvalue_constant(location, static_cast(_token.literal_as_double)); + } + else if (accept(tokenid::string_literal)) + { + std::string value = std::move(_token.literal_as_string); + + // Multiple string literals in sequence are concatenated into a single string literal + while (accept(tokenid::string_literal)) + value += _token.literal_as_string; + + exp.reset_to_rvalue_constant(location, std::move(value)); + } + else if (type type; accept_type_class(type)) // Check if this is a constructor call expression + { + if (!expect('(')) + return false; + if (!type.is_numeric()) + return error(location, 3037, "constructors only defined for numeric base types"), false; + + // Empty constructors do not exist + if (accept(')')) + return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; + + // Parse entire argument expression list + bool is_constant = true; + unsigned int num_components = 0; + std::vector arguments; + + while (!peek(')')) + { + // There should be a comma between arguments + if (!arguments.empty() && !expect(',')) + return false; + + expression &argument = arguments.emplace_back(); + + // Parse the argument expression + if (!parse_expression_assignment(argument)) + return false; + + // Constructors are only defined for numeric base types + if (!argument.type.is_numeric()) + return error(argument.location, 3017, "cannot convert non-numeric types"), false; + + is_constant &= argument.is_constant; // Result is only constant if all arguments are constant + num_components += argument.type.components(); + } + + // The list should be terminated with a parenthesis + if (!expect(')')) + return false; + + // The total number of argument elements needs to match the number of elements in the result type + if (num_components != type.components()) + return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; + + assert(num_components > 0 && num_components <= 16 && !type.is_array()); + + if (is_constant) // Constants can be converted at compile time + { + constant res = {}; + unsigned int i = 0; + for (expression &argument : arguments) + { + argument.add_cast_operation({ type.base, argument.type.rows, argument.type.cols }); + for (unsigned int k = 0; k < argument.type.components(); ++k) + res.as_uint[i++] = argument.constant.as_uint[k]; + } + + exp.reset_to_rvalue_constant(location, std::move(res), type); + } + else if (arguments.size() > 1) + { + // Flatten all arguments to a list of scalars + for (auto it = arguments.begin(); it != arguments.end();) + { + // Argument is a scalar already, so only need to cast it + if (it->type.is_scalar()) + { + expression &argument = *it++; + + auto scalar_type = argument.type; + scalar_type.base = type.base; + argument.add_cast_operation(scalar_type); + + argument.reset_to_rvalue(argument.location, _codegen->emit_load(argument), scalar_type); + } + else + { + const expression argument = *it; + it = arguments.erase(it); + + // Convert to a scalar value and re-enter the loop in the next iteration (in case a cast is necessary too) + for (unsigned int i = argument.type.components(); i > 0; --i) + { + expression scalar = argument; + scalar.add_constant_index_access(i - 1); + + it = arguments.insert(it, scalar); + } + } + } + + const auto result = _codegen->emit_construct(location, type, arguments); + + exp.reset_to_rvalue(location, result, type); + } + else // A constructor call with a single argument is identical to a cast + { + assert(!arguments.empty()); + + // Reset expression to only argument and add cast to expression access chain + exp = std::move(arguments[0]); exp.add_cast_operation(type); + } + } + // At this point only identifiers are left to check and resolve + else + { + std::string identifier; + scoped_symbol symbol; + if (!accept_symbol(identifier, symbol)) + return false; + + // Check if this is a function call or variable reference + if (accept('(')) + { + // Can only call symbols that are functions, but do not abort yet if no symbol was found since the identifier may reference an intrinsic + if (symbol.id && symbol.op != symbol_type::function) + return error(location, 3005, "identifier '" + identifier + "' represents a variable, not a function"), false; + + // Parse entire argument expression list + std::vector arguments; + + while (!peek(')')) + { + // There should be a comma between arguments + if (!arguments.empty() && !expect(',')) + return false; + + expression &argument = arguments.emplace_back(); + + // Parse the argument expression + if (!parse_expression_assignment(argument)) + return false; + } + + // The list should be terminated with a parenthesis + if (!expect(')')) + return false; + + // Function calls can only be made from within functions + if (!_codegen->is_in_function()) + return error(location, 3005, "invalid function call outside of a function"), false; + + // Try to resolve the call by searching through both function symbols and intrinsics + bool undeclared = !symbol.id, ambiguous = false; + + if (!resolve_function_call(identifier, arguments, symbol.scope, symbol, ambiguous)) + { + if (undeclared) + error(location, 3004, "undeclared identifier or no matching intrinsic overload for '" + identifier + '\''); + else if (ambiguous) + error(location, 3067, "ambiguous function call to '" + identifier + '\''); + else + error(location, 3013, "no matching function overload for '" + identifier + '\''); + return false; + } + + assert(symbol.function != nullptr); + + std::vector parameters(arguments.size()); + + // We need to allocate some temporary variables to pass in and load results from pointer parameters + for (size_t i = 0; i < arguments.size(); ++i) + { + const auto ¶m_type = symbol.function->parameter_list[i].type; + + if (param_type.has(type::q_out) && (!arguments[i].is_lvalue || (arguments[i].type.has(type::q_const) && !arguments[i].type.is_object()))) + return error(arguments[i].location, 3025, "l-value specifies const object for an 'out' parameter"), false; + + if (arguments[i].type.components() > param_type.components()) + warning(arguments[i].location, 3206, "implicit truncation of vector type"); + + if (symbol.op == symbol_type::function || param_type.has(type::q_out)) + { + if (param_type.is_object() || param_type.has(type::q_groupshared) /* Special case for atomic intrinsics */) + { + if (arguments[i].type != param_type) + return error(location, 3004, "no matching intrinsic overload for '" + identifier + '\''), false; + + assert(arguments[i].is_lvalue); + + // Do not shadow object or pointer parameters to function calls + size_t chain_index = 0; + const auto access_chain = _codegen->emit_access_chain(arguments[i], chain_index); + parameters[i].reset_to_lvalue(arguments[i].location, access_chain, param_type); + assert(chain_index == arguments[i].chain.size()); + + // This is referencing a l-value, but want to avoid copying below + parameters[i].is_lvalue = false; + } + else + { + // All user-defined functions actually accept pointers as arguments, same applies to intrinsics with 'out' parameters + const auto temp_variable = _codegen->define_variable(arguments[i].location, param_type); + parameters[i].reset_to_lvalue(arguments[i].location, temp_variable, param_type); + } + } + else + { + expression arg = arguments[i]; + arg.add_cast_operation(param_type); + parameters[i].reset_to_rvalue(arg.location, _codegen->emit_load(arg), param_type); + + // Keep track of whether the parameter is a constant for code generation (this makes the expression invalid for all other uses) + parameters[i].is_constant = arg.is_constant; + } + } + + // Copy in parameters from the argument access chains to parameter variables + for (size_t i = 0; i < arguments.size(); ++i) + { + // Only do this for pointer parameters as discovered above + if (parameters[i].is_lvalue && parameters[i].type.has(type::q_in) && !parameters[i].type.is_object()) + { + expression arg = arguments[i]; + arg.add_cast_operation(parameters[i].type); + _codegen->emit_store(parameters[i], _codegen->emit_load(arg)); + } + } + + // Check if the call resolving found an intrinsic or function and invoke the corresponding code + const auto result = symbol.op == symbol_type::function ? + _codegen->emit_call(location, symbol.id, symbol.type, parameters) : + _codegen->emit_call_intrinsic(location, symbol.id, symbol.type, parameters); + + exp.reset_to_rvalue(location, result, symbol.type); + + // Copy out parameters from parameter variables back to the argument access chains + for (size_t i = 0; i < arguments.size(); ++i) + { + // Only do this for pointer parameters as discovered above + if (parameters[i].is_lvalue && parameters[i].type.has(type::q_out) && !parameters[i].type.is_object()) + { + expression arg = parameters[i]; + arg.add_cast_operation(arguments[i].type); + _codegen->emit_store(arguments[i], _codegen->emit_load(arg)); + } + } + + if (_current_function != nullptr) + { + // Calling a function makes the caller inherit all sampler and storage object references from the callee + _current_function->referenced_samplers.insert(symbol.function->referenced_samplers.begin(), symbol.function->referenced_samplers.end()); + _current_function->referenced_storages.insert(symbol.function->referenced_storages.begin(), symbol.function->referenced_storages.end()); + } + } + else if (symbol.op == symbol_type::invalid) + { + // Show error if no symbol matching the identifier was found + return error(location, 3004, "undeclared identifier '" + identifier + '\''), false; + } + else if (symbol.op == symbol_type::variable) + { + assert(symbol.id != 0); + // Simply return the pointer to the variable, dereferencing is done on site where necessary + exp.reset_to_lvalue(location, symbol.id, symbol.type); + + if (_current_function != nullptr && + symbol.scope.level == symbol.scope.namespace_level && symbol.id != 0xFFFFFFFF) // Ignore invalid symbols that were added during error recovery + { + // Keep track of any global sampler or storage objects referenced in the current function + if (symbol.type.is_sampler()) + _current_function->referenced_samplers.insert(symbol.id); + if (symbol.type.is_storage()) + _current_function->referenced_storages.insert(symbol.id); + } + } + else if (symbol.op == symbol_type::constant) + { + // Constants are loaded into the access chain + exp.reset_to_rvalue_constant(location, symbol.constant, symbol.type); + } + else + { + // Can only reference variables and constants by name, functions need to be called + return error(location, 3005, "identifier '" + identifier + "' represents a function, not a variable"), false; + } + } + + while (!peek(tokenid::end_of_file)) + { + location = _token_next.location; + + // Check if a postfix operator exists + if (accept_postfix_op()) + { + // Unary operators are only valid on basic types + if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) + return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; + if (exp.type.has(type::q_const) || !exp.is_lvalue) + return error(exp.location, 3025, "l-value specifies const object"), false; + + // Create a constant one in the type of the expression + constant one = {}; + for (unsigned int i = 0; i < exp.type.components(); ++i) + if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; + + const auto value = _codegen->emit_load(exp, true); + const auto result = _codegen->emit_binary_op(location, _token.id, exp.type, value, _codegen->emit_constant(exp.type, one)); + + // The "++" and "--" operands modify the source variable, so store result back into it + _codegen->emit_store(exp, result); + + // All postfix operators return a r-value rather than a l-value to the variable + exp.reset_to_rvalue(location, value, exp.type); + } + else if (accept('.')) + { + if (!expect(tokenid::identifier)) + return false; + + location = std::move(_token.location); + const auto subscript = std::move(_token.literal_as_string); + + if (accept('(')) // Methods (function calls on types) are not supported right now + { + if (!exp.type.is_struct() || exp.type.is_array()) + error(location, 3087, "object does not have methods"); + else + error(location, 3088, "structures do not have methods"); + return false; + } + else if (exp.type.is_array()) // Arrays do not have subscripts + { + error(location, 3018, "invalid subscript on array"); + return false; + } + else if (exp.type.is_vector()) + { + const size_t length = subscript.size(); + if (length > 4) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; + + bool is_const = false; + signed char offsets[4] = { -1, -1, -1, -1 }; + enum { xyzw, rgba, stpq } set[4]; + + for (size_t i = 0; i < length; ++i) + { + switch (subscript[i]) + { + case 'x': offsets[i] = 0, set[i] = xyzw; break; + case 'y': offsets[i] = 1, set[i] = xyzw; break; + case 'z': offsets[i] = 2, set[i] = xyzw; break; + case 'w': offsets[i] = 3, set[i] = xyzw; break; + case 'r': offsets[i] = 0, set[i] = rgba; break; + case 'g': offsets[i] = 1, set[i] = rgba; break; + case 'b': offsets[i] = 2, set[i] = rgba; break; + case 'a': offsets[i] = 3, set[i] = rgba; break; + case 's': offsets[i] = 0, set[i] = stpq; break; + case 't': offsets[i] = 1, set[i] = stpq; break; + case 'p': offsets[i] = 2, set[i] = stpq; break; + case 'q': offsets[i] = 3, set[i] = stpq; break; + default: + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + } + + if (i > 0 && (set[i] != set[i - 1])) + return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; + if (static_cast(offsets[i]) >= exp.type.rows) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; + + // The result is not modifiable if a swizzle appears multiple times + for (size_t k = 0; k < i; ++k) + if (offsets[k] == offsets[i]) { + is_const = true; + break; + } + } + + // Add swizzle to current access chain + exp.add_swizzle_access(offsets, static_cast(length)); + + if (is_const) + exp.type.qualifiers |= type::q_const; + } + else if (exp.type.is_matrix()) + { + const size_t length = subscript.size(); + if (length < 3) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + + bool is_const = false; + signed char offsets[4] = { -1, -1, -1, -1 }; + const unsigned int set = subscript[1] == 'm'; + const int coefficient = !set; + + for (size_t i = 0, j = 0; i < length; i += 3 + set, ++j) + { + if (subscript[i] != '_' || + subscript[i + set + 1] < '0' + coefficient || + subscript[i + set + 1] > '3' + coefficient || + subscript[i + set + 2] < '0' + coefficient || + subscript[i + set + 2] > '3' + coefficient) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + if (set && subscript[i + 1] != 'm') + return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; + + const unsigned int row = static_cast((subscript[i + set + 1] - '0') - coefficient); + const unsigned int col = static_cast((subscript[i + set + 2] - '0') - coefficient); + + if ((row >= exp.type.rows || col >= exp.type.cols) || j > 3) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; + + offsets[j] = static_cast(row * 4 + col); + + // The result is not modifiable if a swizzle appears multiple times + for (size_t k = 0; k < j; ++k) + if (offsets[k] == offsets[j]) { + is_const = true; + break; + } + } + + // Add swizzle to current access chain + exp.add_swizzle_access(offsets, static_cast(length / (3 + set))); + + if (is_const) + exp.type.qualifiers |= type::q_const; + } + else if (exp.type.is_struct()) + { + const auto &member_list = _codegen->get_struct(exp.type.definition).member_list; + + // Find member with matching name is structure definition + uint32_t member_index = 0; + for (const struct_member_info &member : member_list) { + if (member.name == subscript) + break; + ++member_index; + } + + if (member_index >= member_list.size()) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + + // Add field index to current access chain + exp.add_member_access(member_index, member_list[member_index].type); + } + else if (exp.type.is_scalar()) + { + const size_t length = subscript.size(); + if (length > 4) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; + + for (size_t i = 0; i < length; ++i) + if ((subscript[i] != 'x' && subscript[i] != 'r' && subscript[i] != 's') || i > 3) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + + // Promote scalar to vector type using cast + auto target_type = exp.type; + target_type.rows = static_cast(length); + + exp.add_cast_operation(target_type); + + if (length > 1) + exp.type.qualifiers |= type::q_const; + } + else + { + error(location, 3018, "invalid subscript '" + subscript + '\''); + return false; + } + } + else if (accept('[')) + { + if (!exp.type.is_array() && !exp.type.is_vector() && !exp.type.is_matrix()) + return error(_token.location, 3121, "array, matrix, vector, or indexable object type expected in index expression"), false; + + // Parse index expression + expression index; + if (!parse_expression(index) || !expect(']')) + return false; + else if (!index.type.is_scalar() || !index.type.is_integral()) + return error(index.location, 3120, "invalid type for index - index must be an integer scalar"), false; + + // Add index expression to current access chain + if (index.is_constant) + { + // Check array bounds if known + if (exp.type.array_length > 0 && index.constant.as_uint[0] >= static_cast(exp.type.array_length)) + return error(index.location, 3504, "array index out of bounds"), false; + + exp.add_constant_index_access(index.constant.as_uint[0]); + } + else + { + if (exp.is_constant) + { + // To handle a dynamic index into a constant means we need to create a local variable first or else any of the indexing instructions do not work + const auto temp_variable = _codegen->define_variable(location, exp.type, std::string(), false, _codegen->emit_constant(exp.type, exp.constant)); + exp.reset_to_lvalue(exp.location, temp_variable, exp.type); + } + + exp.add_dynamic_index_access(_codegen->emit_load(index)); + } + } + else + { + break; + } + } + + return true; +} + +bool reshadefx::parser::parse_expression_multary(expression &lhs, unsigned int left_precedence) +{ + // Parse left hand side of the expression + if (!parse_expression_unary(lhs)) + return false; + + // Check if an operator exists so that this is a binary or ternary expression + unsigned int right_precedence; + + while (peek_multary_op(right_precedence)) + { + // Only process this operator if it has a lower precedence than the current operation, otherwise leave it for later and abort + if (right_precedence <= left_precedence) + break; + + // Finally consume the operator token + consume(); + + const tokenid op = _token.id; + + // Check if this is a binary or ternary operation + if (op != tokenid::question) + { +#if RESHADEFX_SHORT_CIRCUIT + codegen::id lhs_block = 0; + codegen::id rhs_block = 0; + codegen::id merge_block = 0; + + // Switch block to a new one before parsing right-hand side value in case it needs to be skipped during short-circuiting + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + { + lhs_block = _codegen->set_block(0); + rhs_block = _codegen->create_block(); + merge_block = _codegen->create_block(); + + _codegen->enter_block(rhs_block); + } +#endif + // Parse the right hand side of the binary operation + expression rhs; + if (!parse_expression_multary(rhs, right_precedence)) + return false; + + // Deduce the result base type based on implicit conversion rules + type type = type::merge(lhs.type, rhs.type); + bool is_bool_result = false; + + // Do some error checking depending on the operator + if (op == tokenid::equal_equal || op == tokenid::exclaim_equal) + { + // Equality checks return a boolean value + is_bool_result = true; + + // Cannot check equality between incompatible types + if (lhs.type.is_array() || rhs.type.is_array() || lhs.type.definition != rhs.type.definition) + return error(rhs.location, 3020, "type mismatch"), false; + } + else if (op == tokenid::ampersand || op == tokenid::pipe || op == tokenid::caret) + { + if (type.is_boolean()) + type.base = type::t_int; + + // Cannot perform bitwise operations on non-integral types + if (!lhs.type.is_integral()) + return error(lhs.location, 3082, "int or unsigned int type required"), false; + if (!rhs.type.is_integral()) + return error(rhs.location, 3082, "int or unsigned int type required"), false; + } + else + { + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + type.base = type::t_bool; + else if (op == tokenid::less || op == tokenid::less_equal || op == tokenid::greater || op == tokenid::greater_equal) + is_bool_result = true; // Logical operations return a boolean value + else if (type.is_boolean()) + type.base = type::t_int; // Arithmetic with boolean values treats the operands as integers + + // Cannot perform arithmetic operations on non-basic types + if (!lhs.type.is_scalar() && !lhs.type.is_vector() && !lhs.type.is_matrix()) + return error(lhs.location, 3022, "scalar, vector, or matrix expected"), false; + if (!rhs.type.is_scalar() && !rhs.type.is_vector() && !rhs.type.is_matrix()) + return error(rhs.location, 3022, "scalar, vector, or matrix expected"), false; + } + + // Perform implicit type conversion + if (lhs.type.components() > type.components()) + warning(lhs.location, 3206, "implicit truncation of vector type"); + if (rhs.type.components() > type.components()) + warning(rhs.location, 3206, "implicit truncation of vector type"); + + lhs.add_cast_operation(type); + rhs.add_cast_operation(type); + +#if RESHADEFX_SHORT_CIRCUIT + // Reset block to left-hand side since the load of the left-hand side value has to happen in there + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + _codegen->set_block(lhs_block); +#endif + + // Constant expressions can be evaluated at compile time + if (rhs.is_constant && lhs.evaluate_constant_expression(op, rhs.constant)) + continue; + + const auto lhs_value = _codegen->emit_load(lhs); + +#if RESHADEFX_SHORT_CIRCUIT + // Short circuit for logical && and || operators + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + { + // Emit "if ( lhs) result = rhs" for && expression + codegen::id condition_value = lhs_value; + // Emit "if (!lhs) result = rhs" for || expression + if (op == tokenid::pipe_pipe) + condition_value = _codegen->emit_unary_op(lhs.location, tokenid::exclaim, type, lhs_value); + + _codegen->leave_block_and_branch_conditional(condition_value, rhs_block, merge_block); + + _codegen->set_block(rhs_block); + // Only load value of right hand side expression after entering the second block + const auto rhs_value = _codegen->emit_load(rhs); + _codegen->leave_block_and_branch(merge_block); + + _codegen->enter_block(merge_block); + + const auto result_value = _codegen->emit_phi(lhs.location, condition_value, lhs_block, rhs_value, rhs_block, lhs_value, lhs_block, type); + + lhs.reset_to_rvalue(lhs.location, result_value, type); + continue; + } +#endif + const auto rhs_value = _codegen->emit_load(rhs); + + // Certain operations return a boolean type instead of the type of the input expressions + if (is_bool_result) + type = { type::t_bool, type.rows, type.cols }; + + const auto result_value = _codegen->emit_binary_op(lhs.location, op, type, lhs.type, lhs_value, rhs_value); + + lhs.reset_to_rvalue(lhs.location, result_value, type); + } + else + { + // A conditional expression needs a scalar or vector type condition + if (!lhs.type.is_scalar() && !lhs.type.is_vector()) + return error(lhs.location, 3022, "boolean or vector expression expected"), false; + +#if RESHADEFX_SHORT_CIRCUIT + // Switch block to a new one before parsing first part in case it needs to be skipped during short-circuiting + const codegen::id merge_block = _codegen->create_block(); + const codegen::id condition_block = _codegen->set_block(0); + codegen::id true_block = _codegen->create_block(); + codegen::id false_block = _codegen->create_block(); + + _codegen->enter_block(true_block); +#endif + // Parse the first part of the right hand side of the ternary operation + expression true_exp; + if (!parse_expression(true_exp)) + return false; + + if (!expect(':')) + return false; + +#if RESHADEFX_SHORT_CIRCUIT + // Switch block to a new one before parsing second part in case it needs to be skipped during short-circuiting + _codegen->set_block(0); + _codegen->enter_block(false_block); +#endif + // Parse the second part of the right hand side of the ternary operation + expression false_exp; + if (!parse_expression_assignment(false_exp)) + return false; + + // Check that the condition dimension matches that of at least one side + if (lhs.type.rows != true_exp.type.rows && lhs.type.cols != true_exp.type.cols) + return error(lhs.location, 3020, "dimension of conditional does not match value"), false; + + // Check that the two value expressions can be converted between each other + if (true_exp.type.array_length != false_exp.type.array_length || true_exp.type.definition != false_exp.type.definition) + return error(false_exp.location, 3020, "type mismatch between conditional values"), false; + + // Deduce the result base type based on implicit conversion rules + const type type = type::merge(true_exp.type, false_exp.type); + + if (true_exp.type.components() > type.components()) + warning(true_exp.location, 3206, "implicit truncation of vector type"); + if (false_exp.type.components() > type.components()) + warning(false_exp.location, 3206, "implicit truncation of vector type"); + +#if RESHADEFX_SHORT_CIRCUIT + // Reset block to left-hand side since the load of the condition value has to happen in there + _codegen->set_block(condition_block); +#else + // The conditional operator instruction expects the condition to be a boolean type + lhs.add_cast_operation({ type::t_bool, type.rows, 1 }); +#endif + true_exp.add_cast_operation(type); + false_exp.add_cast_operation(type); + + // Load condition value from expression + const auto condition_value = _codegen->emit_load(lhs); + +#if RESHADEFX_SHORT_CIRCUIT + _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); + + _codegen->set_block(true_block); + // Only load true expression value after entering the first block + const auto true_value = _codegen->emit_load(true_exp); + true_block = _codegen->leave_block_and_branch(merge_block); + + _codegen->set_block(false_block); + // Only load false expression value after entering the second block + const auto false_value = _codegen->emit_load(false_exp); + false_block = _codegen->leave_block_and_branch(merge_block); + + _codegen->enter_block(merge_block); + + const auto result_value = _codegen->emit_phi(lhs.location, condition_value, condition_block, true_value, true_block, false_value, false_block, type); +#else + const auto true_value = _codegen->emit_load(true_exp); + const auto false_value = _codegen->emit_load(false_exp); + + const auto result_value = _codegen->emit_ternary_op(lhs.location, op, type, condition_value, true_value, false_value); +#endif + lhs.reset_to_rvalue(lhs.location, result_value, type); + } + } + + return true; +} + +bool reshadefx::parser::parse_expression_assignment(expression &lhs) +{ + // Parse left hand side of the expression + if (!parse_expression_multary(lhs)) + return false; + + // Check if an operator exists so that this is an assignment + if (accept_assignment_op()) + { + // Remember the operator token before parsing the expression that follows it + const tokenid op = _token.id; + + // Parse right hand side of the assignment expression + // This may be another assignment expression to support chains like "a = b = c = 0;" + expression rhs; + if (!parse_expression_assignment(rhs)) + return false; + + // Check if the assignment is valid + if (lhs.type.has(type::q_const) || !lhs.is_lvalue) + return error(lhs.location, 3025, "l-value specifies const object"), false; + if (!type::rank(lhs.type, rhs.type)) + return error(rhs.location, 3020, "cannot convert these types (from " + rhs.type.description() + " to " + lhs.type.description() + ')'), false; + + // Cannot perform bitwise operations on non-integral types + if (!lhs.type.is_integral() && (op == tokenid::ampersand_equal || op == tokenid::pipe_equal || op == tokenid::caret_equal)) + return error(lhs.location, 3082, "int or unsigned int type required"), false; + + // Perform implicit type conversion of right hand side value + if (rhs.type.components() > lhs.type.components()) + warning(rhs.location, 3206, "implicit truncation of vector type"); + + rhs.add_cast_operation(lhs.type); + + auto result = _codegen->emit_load(rhs); + + // Check if this is an assignment with an additional arithmetic instruction + if (op != tokenid::equal) + { + // Load value for modification + const auto value = _codegen->emit_load(lhs); + + // Handle arithmetic assignment operation + result = _codegen->emit_binary_op(lhs.location, op, lhs.type, value, result); + } + + // Write result back to variable + _codegen->emit_store(lhs, result); + + // Return the result value since you can write assignments within expressions + lhs.reset_to_rvalue(lhs.location, result, lhs.type); + } + + return true; +} diff --git a/dep/reshadefx/src/effect_parser_stmt.cpp b/dep/reshadefx/src/effect_parser_stmt.cpp new file mode 100644 index 000000000..782972954 --- /dev/null +++ b/dep/reshadefx/src/effect_parser_stmt.cpp @@ -0,0 +1,2003 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_lexer.hpp" +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include // std::toupper +#include +#include +#include + +struct on_scope_exit +{ + template + explicit on_scope_exit(F lambda) : leave(lambda) { } + ~on_scope_exit() { leave(); } + + std::function leave; +}; + +bool reshadefx::parser::parse(std::string input, codegen *backend) +{ + _lexer.reset(new lexer(std::move(input))); + + // Set backend for subsequent code-generation + _codegen = backend; + assert(backend != nullptr); + + consume(); + + bool parse_success = true; + bool current_success = true; + + while (!peek(tokenid::end_of_file)) + { + parse_top(current_success); + if (!current_success) + parse_success = false; + } + + return parse_success; +} + +void reshadefx::parser::parse_top(bool &parse_success) +{ + if (accept(tokenid::namespace_)) + { + // Anonymous namespaces are not supported right now, so an identifier is a must + if (!expect(tokenid::identifier)) + { + parse_success = false; + return; + } + + const auto name = std::move(_token.literal_as_string); + + if (!expect('{')) + { + parse_success = false; + return; + } + + enter_namespace(name); + + bool current_success = true; + bool parse_success_namespace = true; + + // Recursively parse top level statements until the namespace is closed again + while (!peek('}')) // Empty namespaces are valid + { + parse_top(current_success); + if (!current_success) + parse_success_namespace = false; + } + + leave_namespace(); + + parse_success = expect('}') && parse_success_namespace; + } + else if (accept(tokenid::struct_)) // Structure keyword found, parse the structure definition + { + // Structure definitions are terminated with a semicolon + parse_success = parse_struct() && expect(';'); + } + else if (accept(tokenid::technique)) // Technique keyword found, parse the technique definition + { + parse_success = parse_technique(); + } + else + { + if (type type; parse_type(type)) // Type found, this can be either a variable or a function declaration + { + parse_success = expect(tokenid::identifier); + if (!parse_success) + return; + + if (peek('(')) + { + const auto name = std::move(_token.literal_as_string); + // This is definitely a function declaration, so parse it + if (!parse_function(type, name)) + { + // Insert dummy function into symbol table, so later references can be resolved despite the error + insert_symbol(name, { symbol_type::function, ~0u, { type::t_function } }, true); + parse_success = false; + return; + } + } + else + { + // There may be multiple variable names after the type, handle them all + unsigned int count = 0; + do { + if (count++ > 0 && !(expect(',') && expect(tokenid::identifier))) + { + parse_success = false; + return; + } + const auto name = std::move(_token.literal_as_string); + if (!parse_variable(type, name, true)) + { + // Insert dummy variable into symbol table, so later references can be resolved despite the error + insert_symbol(name, { symbol_type::variable, ~0u, type }, true); + // Skip the rest of the statement + consume_until(';'); + parse_success = false; + return; + } + } while (!peek(';')); + + // Variable declarations are terminated with a semicolon + parse_success = expect(';'); + } + } + else if (accept(';')) // Ignore single semicolons in the source + { + parse_success = true; + } + else + { + // Unexpected token in source stream, consume and report an error about it + consume(); + // Only add another error message if succeeded parsing previously + // This is done to avoid walls of error messages because of consequential errors following a top-level syntax mistake + if (parse_success) + error(_token.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token.id) + '\''); + parse_success = false; + } + } +} + +bool reshadefx::parser::parse_statement(bool scoped) +{ + if (!_codegen->is_in_block()) + return error(_token_next.location, 0, "unreachable code"), false; + + unsigned int loop_control = 0; + unsigned int selection_control = 0; + + // Read any loop and branch control attributes first + while (accept('[')) + { + enum control_mask + { + unroll = 0x1, + dont_unroll = 0x2, + flatten = (0x1 << 4), + dont_flatten = (0x2 << 4), + switch_force_case = (0x4 << 4), + switch_call = (0x8 << 4) + }; + + const auto attribute = std::move(_token_next.literal_as_string); + + if (!expect(tokenid::identifier) || !expect(']')) + return false; + + if (attribute == "unroll") + loop_control |= unroll; + else if (attribute == "loop" || attribute == "fastopt") + loop_control |= dont_unroll; + else if (attribute == "flatten") + selection_control |= flatten; + else if (attribute == "branch") + selection_control |= dont_flatten; + else if (attribute == "forcecase") + selection_control |= switch_force_case; + else if (attribute == "call") + selection_control |= switch_call; + else + warning(_token.location, 0, "unknown attribute"); + + if ((loop_control & (unroll | dont_unroll)) == (unroll | dont_unroll)) + return error(_token.location, 3524, "can't use loop and unroll attributes together"), false; + if ((selection_control & (flatten | dont_flatten)) == (flatten | dont_flatten)) + return error(_token.location, 3524, "can't use branch and flatten attributes together"), false; + } + + // Shift by two so that the possible values are 0x01 for 'flatten' and 0x02 for 'dont_flatten', equivalent to 'unroll' and 'dont_unroll' + selection_control >>= 4; + + if (peek('{')) // Parse statement block + return parse_statement_block(scoped); + else if (accept(';')) // Ignore empty statements + return true; + + // Most statements with the exception of declarations are only valid inside functions + if (_codegen->is_in_function()) + { + assert(_current_function != nullptr); + + const auto location = _token_next.location; + + if (accept(tokenid::if_)) + { + codegen::id true_block = _codegen->create_block(); // Block which contains the statements executed when the condition is true + codegen::id false_block = _codegen->create_block(); // Block which contains the statements executed when the condition is false + const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the branch re-merged with the current control flow + + expression condition; + if (!expect('(') || !parse_expression(condition) || !expect(')')) + return false; + else if (!condition.type.is_scalar()) + return error(condition.location, 3019, "if statement conditional expressions must evaluate to a scalar"), false; + + // Load condition and convert to boolean value as required by 'OpBranchConditional' + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + const codegen::id condition_value = _codegen->emit_load(condition); + const codegen::id condition_block = _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); + + { // Then block of the if statement + _codegen->enter_block(true_block); + + if (!parse_statement(true)) + return false; + + true_block = _codegen->leave_block_and_branch(merge_block); + } + { // Else block of the if statement + _codegen->enter_block(false_block); + + if (accept(tokenid::else_) && !parse_statement(true)) + return false; + + false_block = _codegen->leave_block_and_branch(merge_block); + } + + _codegen->enter_block(merge_block); + + // Emit structured control flow for an if statement and connect all basic blocks + _codegen->emit_if(location, condition_value, condition_block, true_block, false_block, selection_control); + + return true; + } + + if (accept(tokenid::switch_)) + { + const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the switch re-merged with the current control flow + + expression selector_exp; + if (!expect('(') || !parse_expression(selector_exp) || !expect(')')) + return false; + else if (!selector_exp.type.is_scalar()) + return error(selector_exp.location, 3019, "switch statement expression must evaluate to a scalar"), false; + + // Load selector and convert to integral value as required by switch instruction + selector_exp.add_cast_operation({ type::t_int, 1, 1 }); + + const auto selector_value = _codegen->emit_load(selector_exp); + const auto selector_block = _codegen->leave_block_and_switch(selector_value, merge_block); + + if (!expect('{')) + return false; + + _loop_break_target_stack.push_back(merge_block); + on_scope_exit _([this]() { _loop_break_target_stack.pop_back(); }); + + bool parse_success = true; + // The default case jumps to the end of the switch statement if not overwritten + codegen::id default_label = merge_block, default_block = merge_block; + codegen::id current_label = _codegen->create_block(); + std::vector case_literal_and_labels, case_blocks; + size_t last_case_label_index = 0; + + // Enter first switch statement body block + _codegen->enter_block(current_label); + + while (!peek(tokenid::end_of_file)) + { + while (accept(tokenid::case_) || accept(tokenid::default_)) + { + if (_token.id == tokenid::case_) + { + expression case_label; + if (!parse_expression(case_label)) + return consume_until('}'), false; + else if (!case_label.type.is_scalar() || !case_label.type.is_integral() || !case_label.is_constant) + return error(case_label.location, 3020, "invalid type for case expression - value must be an integer scalar"), consume_until('}'), false; + + // Check for duplicate case values + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + { + if (case_literal_and_labels[i] == case_label.constant.as_uint[0]) + { + parse_success = false; + error(case_label.location, 3532, "duplicate case " + std::to_string(case_label.constant.as_uint[0])); + break; + } + } + + case_blocks.emplace_back(); // This is set to the actual block below + case_literal_and_labels.push_back(case_label.constant.as_uint[0]); + case_literal_and_labels.push_back(current_label); + } + else + { + // Check if the default label was already changed by a previous 'default' statement + if (default_label != merge_block) + { + parse_success = false; + error(_token.location, 3532, "duplicate default in switch statement"); + } + + default_label = current_label; + default_block = 0; // This is set to the actual block below + } + + if (!expect(':')) + return consume_until('}'), false; + } + + // It is valid for no statement to follow if this is the last label in the switch body + const bool end_of_switch = peek('}'); + + if (!end_of_switch && !parse_statement(true)) + return consume_until('}'), false; + + // Handle fall-through case and end of switch statement + if (peek(tokenid::case_) || peek(tokenid::default_) || end_of_switch) + { + if (_codegen->is_in_block()) // Disallow fall-through for now + { + parse_success = false; + error(_token_next.location, 3533, "non-empty case statements must have break or return"); + } + + const codegen::id next_label = end_of_switch ? merge_block : _codegen->create_block(); + // This is different from 'current_label', since there may have been branching logic inside the case, which would have changed the active block + const codegen::id current_block = _codegen->leave_block_and_branch(next_label); + + if (0 == default_block) + default_block = current_block; + for (size_t i = last_case_label_index; i < case_blocks.size(); ++i) + // Need to use the initial label for the switch table, but the current block to merge all the block data + case_blocks[i] = current_block; + + current_label = next_label; + _codegen->enter_block(current_label); + + if (end_of_switch) // We reached the end, nothing more to do + break; + + last_case_label_index = case_blocks.size(); + } + } + + if (case_literal_and_labels.empty() && default_label == merge_block) + warning(location, 5002, "switch statement contains no 'case' or 'default' labels"); + + // Emit structured control flow for a switch statement and connect all basic blocks + _codegen->emit_switch(location, selector_value, selector_block, default_label, default_block, case_literal_and_labels, case_blocks, selection_control); + + return expect('}') && parse_success; + } + + if (accept(tokenid::for_)) + { + if (!expect('(')) + return false; + + enter_scope(); + on_scope_exit _([this]() { leave_scope(); }); + + // Parse initializer first + if (type type; parse_type(type)) + { + unsigned int count = 0; + do { // There may be multiple declarations behind a type, so loop through them + if (count++ > 0 && !expect(',')) + return false; + if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string))) + return false; + } while (!peek(';')); + } + else + { + // Initializer can also contain an expression if not a variable declaration list and not empty + if (!peek(';')) + { + expression expression; + if (!parse_expression(expression)) + return false; + } + } + + if (!expect(';')) + return false; + + const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the loop + const codegen::id header_label = _codegen->create_block(); // Pointer to the loop merge instruction + const codegen::id continue_label = _codegen->create_block(); // Pointer to the continue block + codegen::id loop_block = _codegen->create_block(); // Pointer to the main loop body block + codegen::id condition_block = _codegen->create_block(); // Pointer to the condition check + codegen::id condition_value = 0; + + // End current block by branching to the next label + const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); + + { // Begin loop block (this header is used for explicit structured control flow) + _codegen->enter_block(header_label); + + _codegen->leave_block_and_branch(condition_block); + } + + { // Parse condition block + _codegen->enter_block(condition_block); + + if (!peek(';')) + { + expression condition; + if (!parse_expression(condition)) + return false; + + if (!condition.type.is_scalar()) + return error(condition.location, 3019, "scalar value expected"), false; + + // Evaluate condition and branch to the right target + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + condition_value = _codegen->emit_load(condition); + + condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block); + } + else // It is valid for there to be no condition expression + { + condition_block = _codegen->leave_block_and_branch(loop_block); + } + + if (!expect(';')) + return false; + } + + { // Parse loop continue block into separate block so it can be appended to the end down the line + _codegen->enter_block(continue_label); + + if (!peek(')')) + { + expression continue_exp; + if (!parse_expression(continue_exp)) + return false; + } + + if (!expect(')')) + return false; + + // Branch back to the loop header at the end of the continue block + _codegen->leave_block_and_branch(header_label); + } + + { // Parse loop body block + _codegen->enter_block(loop_block); + + _loop_break_target_stack.push_back(merge_block); + _loop_continue_target_stack.push_back(continue_label); + + const bool parse_success = parse_statement(false); + + _loop_break_target_stack.pop_back(); + _loop_continue_target_stack.pop_back(); + + if (!parse_success) + return false; + + loop_block = _codegen->leave_block_and_branch(continue_label); + } + + // Add merge block label to the end of the loop + _codegen->enter_block(merge_block); + + // Emit structured control flow for a loop statement and connect all basic blocks + _codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control); + + return true; + } + + if (accept(tokenid::while_)) + { + enter_scope(); + on_scope_exit _([this]() { leave_scope(); }); + + const codegen::id merge_block = _codegen->create_block(); + const codegen::id header_label = _codegen->create_block(); + const codegen::id continue_label = _codegen->create_block(); + codegen::id loop_block = _codegen->create_block(); + codegen::id condition_block = _codegen->create_block(); + codegen::id condition_value = 0; + + // End current block by branching to the next label + const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); + + { // Begin loop block + _codegen->enter_block(header_label); + + _codegen->leave_block_and_branch(condition_block); + } + + { // Parse condition block + _codegen->enter_block(condition_block); + + expression condition; + if (!expect('(') || !parse_expression(condition) || !expect(')')) + return false; + else if (!condition.type.is_scalar()) + return error(condition.location, 3019, "scalar value expected"), false; + + // Evaluate condition and branch to the right target + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + condition_value = _codegen->emit_load(condition); + + condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block); + } + + { // Parse loop body block + _codegen->enter_block(loop_block); + + _loop_break_target_stack.push_back(merge_block); + _loop_continue_target_stack.push_back(continue_label); + + const bool parse_success = parse_statement(false); + + _loop_break_target_stack.pop_back(); + _loop_continue_target_stack.pop_back(); + + if (!parse_success) + return false; + + loop_block = _codegen->leave_block_and_branch(continue_label); + } + + { // Branch back to the loop header in empty continue block + _codegen->enter_block(continue_label); + + _codegen->leave_block_and_branch(header_label); + } + + // Add merge block label to the end of the loop + _codegen->enter_block(merge_block); + + // Emit structured control flow for a loop statement and connect all basic blocks + _codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control); + + return true; + } + + if (accept(tokenid::do_)) + { + const codegen::id merge_block = _codegen->create_block(); + const codegen::id header_label = _codegen->create_block(); + const codegen::id continue_label = _codegen->create_block(); + codegen::id loop_block = _codegen->create_block(); + codegen::id condition_value = 0; + + // End current block by branching to the next label + const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); + + { // Begin loop block + _codegen->enter_block(header_label); + + _codegen->leave_block_and_branch(loop_block); + } + + { // Parse loop body block + _codegen->enter_block(loop_block); + + _loop_break_target_stack.push_back(merge_block); + _loop_continue_target_stack.push_back(continue_label); + + const bool parse_success = parse_statement(true); + + _loop_break_target_stack.pop_back(); + _loop_continue_target_stack.pop_back(); + + if (!parse_success) + return false; + + loop_block = _codegen->leave_block_and_branch(continue_label); + } + + { // Continue block does the condition evaluation + _codegen->enter_block(continue_label); + + expression condition; + if (!expect(tokenid::while_) || !expect('(') || !parse_expression(condition) || !expect(')') || !expect(';')) + return false; + else if (!condition.type.is_scalar()) + return error(condition.location, 3019, "scalar value expected"), false; + + // Evaluate condition and branch to the right target + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + condition_value = _codegen->emit_load(condition); + + _codegen->leave_block_and_branch_conditional(condition_value, header_label, merge_block); + } + + // Add merge block label to the end of the loop + _codegen->enter_block(merge_block); + + // Emit structured control flow for a loop statement and connect all basic blocks + _codegen->emit_loop(location, condition_value, prev_block, header_label, 0, loop_block, continue_label, loop_control); + + return true; + } + + if (accept(tokenid::break_)) + { + if (_loop_break_target_stack.empty()) + return error(location, 3518, "break must be inside loop"), false; + + // Branch to the break target of the inner most loop on the stack + _codegen->leave_block_and_branch(_loop_break_target_stack.back(), 1); + + return expect(';'); + } + + if (accept(tokenid::continue_)) + { + if (_loop_continue_target_stack.empty()) + return error(location, 3519, "continue must be inside loop"), false; + + // Branch to the continue target of the inner most loop on the stack + _codegen->leave_block_and_branch(_loop_continue_target_stack.back(), 2); + + return expect(';'); + } + + if (accept(tokenid::return_)) + { + const type &ret_type = _current_function->return_type; + + if (!peek(';')) + { + expression expression; + if (!parse_expression(expression)) + return consume_until(';'), false; + + // Cannot return to void + if (ret_type.is_void()) + // Consume the semicolon that follows the return expression so that parsing may continue + return error(location, 3079, "void functions cannot return a value"), accept(';'), false; + + // Cannot return arrays from a function + if (expression.type.is_array() || !type::rank(expression.type, ret_type)) + return error(location, 3017, "expression (" + expression.type.description() + ") does not match function return type (" + ret_type.description() + ')'), accept(';'), false; + + // Load return value and perform implicit cast to function return type + if (expression.type.components() > ret_type.components()) + warning(expression.location, 3206, "implicit truncation of vector type"); + + expression.add_cast_operation(ret_type); + + const auto return_value = _codegen->emit_load(expression); + + _codegen->leave_block_and_return(return_value); + } + else if (!ret_type.is_void()) + { + // No return value was found, but the function expects one + error(location, 3080, "function must return a value"); + + // Consume the semicolon that follows the return expression so that parsing may continue + accept(';'); + + return false; + } + else + { + _codegen->leave_block_and_return(); + } + + return expect(';'); + } + + if (accept(tokenid::discard_)) + { + // Leave the current function block + _codegen->leave_block_and_kill(); + + return expect(';'); + } + } + + // Handle variable declarations + if (type type; parse_type(type)) + { + unsigned int count = 0; + do { // There may be multiple declarations behind a type, so loop through them + if (count++ > 0 && !expect(',')) + // Try to consume the rest of the declaration so that parsing may continue despite the error + return consume_until(';'), false; + if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string))) + return consume_until(';'), false; + } while (!peek(';')); + + return expect(';'); + } + + // Handle expression statements + if (expression expression; parse_expression(expression)) + return expect(';'); // A statement has to be terminated with a semicolon + + // Gracefully consume any remaining characters until the statement would usually end, so that parsing may continue despite the error + consume_until(';'); + + return false; +} +bool reshadefx::parser::parse_statement_block(bool scoped) +{ + if (!expect('{')) + return false; + + if (scoped) + enter_scope(); + + // Parse statements until the end of the block is reached + while (!peek('}') && !peek(tokenid::end_of_file)) + { + if (!parse_statement(true)) + { + if (scoped) + leave_scope(); + + // Ignore the rest of this block + unsigned int level = 0; + + while (!peek(tokenid::end_of_file)) + { + if (accept('{')) + { + ++level; + } + else if (accept('}')) + { + if (level-- == 0) + break; + } // These braces are necessary to match the 'else' to the correct 'if' statement + else + { + consume(); + } + } + + return false; + } + } + + if (scoped) + leave_scope(); + + return expect('}'); +} + +bool reshadefx::parser::parse_type(type &type) +{ + type.qualifiers = 0; + + accept_type_qualifiers(type); + + if (!accept_type_class(type)) + return false; + + if (type.is_integral() && (type.has(type::q_centroid) || type.has(type::q_noperspective))) + return error(_token.location, 4576, "signature specifies invalid interpolation mode for integer component type"), false; + else if (type.has(type::q_centroid) && !type.has(type::q_noperspective)) + type.qualifiers |= type::q_linear; + + return true; +} +bool reshadefx::parser::parse_array_size(type &type) +{ + // Reset array length to zero before checking if one exists + type.array_length = 0; + + if (accept('[')) + { + if (accept(']')) + { + // No length expression, so this is an unsized array + type.array_length = -1; + } + else if (expression expression; parse_expression(expression) && expect(']')) + { + if (!expression.is_constant || !(expression.type.is_scalar() && expression.type.is_integral())) + return error(expression.location, 3058, "array dimensions must be literal scalar expressions"), false; + + type.array_length = expression.constant.as_uint[0]; + + if (type.array_length < 1 || type.array_length > 65536) + return error(expression.location, 3059, "array dimension must be between 1 and 65536"), false; + } + else + { + return false; + } + } + + // Multi-dimensional arrays are not supported + if (peek('[')) + return error(_token_next.location, 3119, "arrays cannot be multi-dimensional"), false; + + return true; +} + +bool reshadefx::parser::parse_annotations(std::vector &annotations) +{ + // Check if annotations exist and return early if none do + if (!accept('<')) + return true; + + bool parse_success = true; + + while (!peek('>')) + { + if (type type; accept_type_class(type)) + warning(_token.location, 4717, "type prefixes for annotations are deprecated and ignored"); + + if (!expect(tokenid::identifier)) + return consume_until('>'), false; + + auto name = std::move(_token.literal_as_string); + + if (expression expression; !expect('=') || !parse_expression_multary(expression) || !expect(';')) + return consume_until('>'), false; + else if (expression.is_constant) + annotations.push_back({ expression.type, std::move(name), std::move(expression.constant) }); + else // Continue parsing annotations despite this not being a constant, since the syntax is still correct + parse_success = false, + error(expression.location, 3011, "value must be a literal expression"); + } + + return expect('>') && parse_success; +} + +bool reshadefx::parser::parse_struct() +{ + const auto location = std::move(_token.location); + + struct_info info; + // The structure name is optional + if (accept(tokenid::identifier)) + info.name = std::move(_token.literal_as_string); + else + info.name = "_anonymous_struct_" + std::to_string(location.line) + '_' + std::to_string(location.column); + + info.unique_name = 'S' + current_scope().name + info.name; + std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_'); + + if (!expect('{')) + return false; + + bool parse_success = true; + + while (!peek('}')) // Empty structures are possible + { + struct_member_info member; + + if (!parse_type(member.type)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected struct member type"), consume_until('}'), accept(';'), false; + + unsigned int count = 0; + do { + if (count++ > 0 && !expect(',')) + return consume_until('}'), accept(';'), false; + + if (!expect(tokenid::identifier)) + return consume_until('}'), accept(';'), false; + + member.name = std::move(_token.literal_as_string); + member.location = std::move(_token.location); + + if (member.type.is_void()) + parse_success = false, + error(member.location, 3038, '\'' + member.name + "': struct members cannot be void"); + if (member.type.is_struct()) // Nesting structures would make input/output argument flattening more complicated, so prevent it for now + parse_success = false, + error(member.location, 3090, '\'' + member.name + "': nested struct members are not supported"); + + if (member.type.has(type::q_in) || member.type.has(type::q_out)) + parse_success = false, + error(member.location, 3055, '\'' + member.name + "': struct members cannot be declared 'in' or 'out'"); + if (member.type.has(type::q_const)) + parse_success = false, + error(member.location, 3035, '\'' + member.name + "': struct members cannot be declared 'const'"); + if (member.type.has(type::q_extern)) + parse_success = false, + error(member.location, 3006, '\'' + member.name + "': struct members cannot be declared 'extern'"); + if (member.type.has(type::q_static)) + parse_success = false, + error(member.location, 3007, '\'' + member.name + "': struct members cannot be declared 'static'"); + if (member.type.has(type::q_uniform)) + parse_success = false, + error(member.location, 3047, '\'' + member.name + "': struct members cannot be declared 'uniform'"); + if (member.type.has(type::q_groupshared)) + parse_success = false, + error(member.location, 3010, '\'' + member.name + "': struct members cannot be declared 'groupshared'"); + + // Modify member specific type, so that following members in the declaration list are not affected by this + if (!parse_array_size(member.type)) + return consume_until('}'), accept(';'), false; + else if (member.type.array_length < 0) + parse_success = false, + error(member.location, 3072, '\'' + member.name + "': array dimensions of struct members must be explicit"); + + // Structure members may have semantics to use them as input/output types + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return consume_until('}'), accept(';'), false; + + member.semantic = std::move(_token.literal_as_string); + // Make semantic upper case to simplify comparison later on + std::transform(member.semantic.begin(), member.semantic.end(), member.semantic.begin(), + [](std::string::value_type c) { + return static_cast(std::toupper(c)); + }); + + if (member.semantic.compare(0, 3, "SV_") != 0) + { + // Always numerate semantics, so that e.g. TEXCOORD and TEXCOORD0 point to the same location + if (const char c = member.semantic.back(); c < '0' || c > '9') + member.semantic += '0'; + + if (member.type.is_integral() && !member.type.has(type::q_nointerpolation)) + { + member.type.qualifiers |= type::q_nointerpolation; // Integer fields do not interpolate, so make this explicit (to avoid issues with GLSL) + warning(member.location, 4568, '\'' + member.name + "': integer fields have the 'nointerpolation' qualifier by default"); + } + } + else + { + // Remove optional trailing zero from system value semantics, so that e.g. SV_POSITION and SV_POSITION0 mean the same thing + if (member.semantic.back() == '0' && (member.semantic[member.semantic.size() - 2] < '0' || member.semantic[member.semantic.size() - 2] > '9')) + member.semantic.pop_back(); + } + } + + // Save member name and type for book keeping + info.member_list.push_back(member); + } while (!peek(';')); + + if (!expect(';')) + return consume_until('}'), accept(';'), false; + } + + // Empty structures are valid, but not usually intended, so emit a warning + if (info.member_list.empty()) + warning(location, 5001, "struct has no members"); + + // Define the structure now that information about all the member types was gathered + const auto id = _codegen->define_struct(location, info); + + // Insert the symbol into the symbol table + const symbol symbol = { symbol_type::structure, id }; + + if (!insert_symbol(info.name, symbol, true)) + return error(location, 3003, "redefinition of '" + info.name + '\''), false; + + return expect('}') && parse_success; +} + +bool reshadefx::parser::parse_function(type type, std::string name) +{ + const auto location = std::move(_token.location); + + if (!expect('(')) // Functions always have a parameter list + return false; + if (type.qualifiers != 0) + return error(location, 3047, '\'' + name + "': function return type cannot have any qualifiers"), false; + + function_info info; + info.name = name; + info.unique_name = 'F' + current_scope().name + name; + std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_'); + + info.return_type = type; + _current_function = &info; + + bool parse_success = true; + bool expect_parenthesis = true; + + // Enter function scope (and leave it again when finished parsing this function) + enter_scope(); + on_scope_exit _([this]() { + leave_scope(); + _codegen->leave_function(); + _current_function = nullptr; + }); + + while (!peek(')')) + { + if (!info.parameter_list.empty() && !expect(',')) + { + parse_success = false; + expect_parenthesis = false; + consume_until(')'); + break; + } + + struct_member_info param; + + if (!parse_type(param.type)) + { + error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected parameter type"); + parse_success = false; + expect_parenthesis = false; + consume_until(')'); + break; + } + + if (!expect(tokenid::identifier)) + { + parse_success = false; + expect_parenthesis = false; + consume_until(')'); + break; + } + + param.name = std::move(_token.literal_as_string); + param.location = std::move(_token.location); + + if (param.type.is_void()) + parse_success = false, + error(param.location, 3038, '\'' + param.name + "': function parameters cannot be void"); + + if (param.type.has(type::q_extern)) + parse_success = false, + error(param.location, 3006, '\'' + param.name + "': function parameters cannot be declared 'extern'"); + if (param.type.has(type::q_static)) + parse_success = false, + error(param.location, 3007, '\'' + param.name + "': function parameters cannot be declared 'static'"); + if (param.type.has(type::q_uniform)) + parse_success = false, + error(param.location, 3047, '\'' + param.name + "': function parameters cannot be declared 'uniform', consider placing in global scope instead"); + if (param.type.has(type::q_groupshared)) + parse_success = false, + error(param.location, 3010, '\'' + param.name + "': function parameters cannot be declared 'groupshared'"); + + if (param.type.has(type::q_out) && param.type.has(type::q_const)) + parse_success = false, + error(param.location, 3046, '\'' + param.name + "': output parameters cannot be declared 'const'"); + else if (!param.type.has(type::q_out)) + param.type.qualifiers |= type::q_in; // Function parameters are implicitly 'in' if not explicitly defined as 'out' + + if (!parse_array_size(param.type)) + { + parse_success = false; + expect_parenthesis = false; + consume_until(')'); + break; + } + else if (param.type.array_length < 0) + { + parse_success = false; + error(param.location, 3072, '\'' + param.name + "': array dimensions of function parameters must be explicit"); + } + + // Handle parameter type semantic + if (accept(':')) + { + if (!expect(tokenid::identifier)) + { + parse_success = false; + expect_parenthesis = false; + consume_until(')'); + break; + } + + param.semantic = std::move(_token.literal_as_string); + // Make semantic upper case to simplify comparison later on + std::transform(param.semantic.begin(), param.semantic.end(), param.semantic.begin(), + [](std::string::value_type c) { + return static_cast(std::toupper(c)); + }); + + if (param.semantic.compare(0, 3, "SV_") != 0) + { + // Always numerate semantics, so that e.g. TEXCOORD and TEXCOORD0 point to the same location + if (const char c = param.semantic.back(); c < '0' || c > '9') + param.semantic += '0'; + + if (param.type.is_integral() && !param.type.has(type::q_nointerpolation)) + { + param.type.qualifiers |= type::q_nointerpolation; // Integer parameters do not interpolate, so make this explicit (to avoid issues with GLSL) + warning(param.location, 4568, '\'' + param.name + "': integer parameters have the 'nointerpolation' qualifier by default"); + } + } + else + { + // Remove optional trailing zero from system value semantics, so that e.g. SV_POSITION and SV_POSITION0 mean the same thing + if (param.semantic.back() == '0' && (param.semantic[param.semantic.size() - 2] < '0' || param.semantic[param.semantic.size() - 2] > '9')) + param.semantic.pop_back(); + } + } + + info.parameter_list.push_back(std::move(param)); + } + + if (expect_parenthesis && !expect(')')) + return false; + + // Handle return type semantic + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return false; + if (type.is_void()) + return error(_token.location, 3076, '\'' + name + "': void function cannot have a semantic"), false; + + info.return_semantic = std::move(_token.literal_as_string); + // Make semantic upper case to simplify comparison later on + std::transform(info.return_semantic.begin(), info.return_semantic.end(), info.return_semantic.begin(), + [](std::string::value_type c) { + return static_cast(std::toupper(c)); + }); + } + + // Check if this is a function declaration without a body + if (accept(';')) + return error(location, 3510, '\'' + name + "': function is missing an implementation"), false; + + // Define the function now that information about the declaration was gathered + const auto id = _codegen->define_function(location, info); + + // Insert the function and parameter symbols into the symbol table and update current function pointer to the permanent one + symbol symbol = { symbol_type::function, id, { type::t_function } }; + symbol.function = _current_function = &_codegen->get_function(id); + + if (!insert_symbol(name, symbol, true)) + return error(location, 3003, "redefinition of '" + name + '\''), false; + + for (const struct_member_info ¶m : info.parameter_list) + if (!insert_symbol(param.name, { symbol_type::variable, param.definition, param.type })) + return error(param.location, 3003, "redefinition of '" + param.name + '\''), false; + + // A function has to start with a new block + _codegen->enter_block(_codegen->create_block()); + + if (!parse_statement_block(false)) + parse_success = false; + + // Add implicit return statement to the end of functions + if (_codegen->is_in_block()) + _codegen->leave_block_and_return(); + + return parse_success; +} + +bool reshadefx::parser::parse_variable(type type, std::string name, bool global) +{ + const auto location = std::move(_token.location); + + if (type.is_void()) + return error(location, 3038, '\'' + name + "': variables cannot be void"), false; + if (type.has(type::q_in) || type.has(type::q_out)) + return error(location, 3055, '\'' + name + "': variables cannot be declared 'in' or 'out'"), false; + + // Local and global variables have different requirements + if (global) + { + // Check that type qualifier combinations are valid + if (type.has(type::q_static)) + { + // Global variables that are 'static' cannot be of another storage class + if (type.has(type::q_uniform)) + return error(location, 3007, '\'' + name + "': uniform global variables cannot be declared 'static'"), false; + // The 'volatile' qualifier is only valid memory object declarations that are storage images or uniform blocks + if (type.has(type::q_volatile)) + return error(location, 3008, '\'' + name + "': global variables cannot be declared 'volatile'"), false; + } + else if (!type.has(type::q_groupshared)) + { + // Make all global variables 'uniform' by default, since they should be externally visible without the 'static' keyword + if (!type.has(type::q_uniform) && !type.is_object()) + warning(location, 5000, '\'' + name + "': global variables are considered 'uniform' by default"); + + // Global variables that are not 'static' are always 'extern' and 'uniform' + type.qualifiers |= type::q_extern | type::q_uniform; + + // It is invalid to make 'uniform' variables constant, since they can be modified externally + if (type.has(type::q_const)) + return error(location, 3035, '\'' + name + "': variables which are 'uniform' cannot be declared 'const'"), false; + } + } + else + { + // Static does not really have meaning on local variables + if (type.has(type::q_static)) + type.qualifiers &= ~type::q_static; + + if (type.has(type::q_extern)) + return error(location, 3006, '\'' + name + "': local variables cannot be declared 'extern'"), false; + if (type.has(type::q_uniform)) + return error(location, 3047, '\'' + name + "': local variables cannot be declared 'uniform'"), false; + if (type.has(type::q_groupshared)) + return error(location, 3010, '\'' + name + "': local variables cannot be declared 'groupshared'"), false; + + if (type.is_object()) + return error(location, 3038, '\'' + name + "': local variables cannot be texture, sampler or storage objects"), false; + } + + // The variable name may be followed by an optional array size expression + if (!parse_array_size(type)) + return false; + + bool parse_success = true; + expression initializer; + texture_info texture_info; + sampler_info sampler_info; + storage_info storage_info; + + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return false; + else if (!global) // Only global variables can have a semantic + return error(_token.location, 3043, '\'' + name + "': local variables cannot have semantics"), false; + + std::string &semantic = texture_info.semantic; + semantic = std::move(_token.literal_as_string); + + // Make semantic upper case to simplify comparison later on + std::transform(semantic.begin(), semantic.end(), semantic.begin(), + [](std::string::value_type c) { + return static_cast(std::toupper(c)); + }); + } + else + { + // Global variables can have optional annotations + if (global && !parse_annotations(sampler_info.annotations)) + parse_success = false; + + // Variables without a semantic may have an optional initializer + if (accept('=')) + { + if (!parse_expression_assignment(initializer)) + return false; + + if (type.has(type::q_groupshared)) + return error(initializer.location, 3009, '\'' + name + "': variables declared 'groupshared' cannot have an initializer"), false; + // TODO: This could be resolved by initializing these at the beginning of the entry point + if (global && !initializer.is_constant) + return error(initializer.location, 3011, '\'' + name + "': initial value must be a literal expression"), false; + + // Check type compatibility + if ((type.array_length >= 0 && initializer.type.array_length != type.array_length) || !type::rank(initializer.type, type)) + return error(initializer.location, 3017, '\'' + name + "': initial value (" + initializer.type.description() + ") does not match variable type (" + type.description() + ')'), false; + if ((initializer.type.rows < type.rows || initializer.type.cols < type.cols) && !initializer.type.is_scalar()) + return error(initializer.location, 3017, '\'' + name + "': cannot implicitly convert these vector types (from " + initializer.type.description() + " to " + type.description() + ')'), false; + + // Deduce array size from the initializer expression + if (initializer.type.is_array()) + type.array_length = initializer.type.array_length; + + // Perform implicit cast from initializer expression to variable type + if (initializer.type.components() > type.components()) + warning(initializer.location, 3206, "implicit truncation of vector type"); + + initializer.add_cast_operation(type); + + if (type.has(type::q_static)) + initializer.type.qualifiers |= type::q_static; + } + else if (type.is_numeric() || type.is_struct()) // Numeric variables without an initializer need special handling + { + if (type.has(type::q_const)) // Constants have to have an initial value + return error(location, 3012, '\'' + name + "': missing initial value"), false; + else if (!type.has(type::q_uniform)) // Zero initialize all global variables + initializer.reset_to_rvalue_constant(location, {}, type); + } + else if (global && accept('{')) // Textures and samplers can have a property block attached to their declaration + { + // Non-numeric variables cannot be constants + if (type.has(type::q_const)) + return error(location, 3035, '\'' + name + "': this variable type cannot be declared 'const'"), false; + + while (!peek('}')) + { + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + const auto property_name = std::move(_token.literal_as_string); + const auto property_location = std::move(_token.location); + + if (!expect('=')) + return consume_until('}'), false; + + backup(); + + expression expression; + + if (accept(tokenid::identifier)) // Handle special enumeration names for property values + { + // Transform identifier to uppercase to do case-insensitive comparison + std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(), + [](std::string::value_type c) { + return static_cast(std::toupper(c)); + }); + + static const std::unordered_map s_enum_values = { + { "NONE", 0 }, { "POINT", 0 }, + { "LINEAR", 1 }, + { "WRAP", uint32_t(texture_address_mode::wrap) }, { "REPEAT", uint32_t(texture_address_mode::wrap) }, + { "MIRROR", uint32_t(texture_address_mode::mirror) }, + { "CLAMP", uint32_t(texture_address_mode::clamp) }, + { "BORDER", uint32_t(texture_address_mode::border) }, + { "R8", uint32_t(texture_format::r8) }, + { "R16", uint32_t(texture_format::r16) }, + { "R16F", uint32_t(texture_format::r16f) }, + { "R32I", uint32_t(texture_format::r32i) }, + { "R32U", uint32_t(texture_format::r32u) }, + { "R32F", uint32_t(texture_format::r32f) }, + { "RG8", uint32_t(texture_format::rg8) }, { "R8G8", uint32_t(texture_format::rg8) }, + { "RG16", uint32_t(texture_format::rg16) }, { "R16G16", uint32_t(texture_format::rg16) }, + { "RG16F", uint32_t(texture_format::rg16f) }, { "R16G16F", uint32_t(texture_format::rg16f) }, + { "RG32F", uint32_t(texture_format::rg32f) }, { "R32G32F", uint32_t(texture_format::rg32f) }, + { "RGBA8", uint32_t(texture_format::rgba8) }, { "R8G8B8A8", uint32_t(texture_format::rgba8) }, + { "RGBA16", uint32_t(texture_format::rgba16) }, { "R16G16B16A16", uint32_t(texture_format::rgba16) }, + { "RGBA16F", uint32_t(texture_format::rgba16f) }, { "R16G16B16A16F", uint32_t(texture_format::rgba16f) }, + { "RGBA32F", uint32_t(texture_format::rgba32f) }, { "R32G32B32A32F", uint32_t(texture_format::rgba32f) }, + { "RGB10A2", uint32_t(texture_format::rgb10a2) }, { "R10G10B10A2", uint32_t(texture_format::rgb10a2) }, + }; + + // Look up identifier in list of possible enumeration names + if (const auto it = s_enum_values.find(_token.literal_as_string); + it != s_enum_values.end()) + expression.reset_to_rvalue_constant(_token.location, it->second); + else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression + restore(); + } + + // Parse right hand side as normal expression if no special enumeration name was matched already + if (!expression.is_constant && !parse_expression_multary(expression)) + return consume_until('}'), false; + + if (property_name == "Texture") + { + // Ignore invalid symbols that were added during error recovery + if (expression.base == 0xFFFFFFFF) + return consume_until('}'), false; + + if (!expression.type.is_texture()) + return error(expression.location, 3020, "type mismatch, expected texture name"), consume_until('}'), false; + + if (type.is_sampler() || type.is_storage()) + { + reshadefx::texture_info &target_info = _codegen->get_texture(expression.base); + if (type.is_storage()) + // Texture is used as storage + target_info.storage_access = true; + + texture_info = target_info; + sampler_info.texture_name = target_info.unique_name; + storage_info.texture_name = target_info.unique_name; + } + } + else + { + if (!expression.is_constant || !expression.type.is_scalar()) + return error(expression.location, 3538, "value must be a literal scalar expression"), consume_until('}'), false; + + // All states below expect the value to be of an integer type + expression.add_cast_operation({ type::t_int, 1, 1 }); + const int value = expression.constant.as_int[0]; + + if (value < 0) // There is little use for negative values, so warn in those cases + warning(expression.location, 3571, "negative value specified for property '" + property_name + '\''); + + if (type.is_texture()) + { + if (property_name == "Width") + texture_info.width = value > 0 ? value : 1; + else if (type.texture_dimension() >= 2 && property_name == "Height") + texture_info.height = value > 0 ? value : 1; + else if (type.texture_dimension() >= 3 && property_name == "Depth") + texture_info.depth = value > 0 && value <= std::numeric_limits::max() ? static_cast(value) : 1; + else if (property_name == "MipLevels") + // Also ensures negative values do not cause problems + texture_info.levels = value > 0 && value <= std::numeric_limits::max() ? static_cast(value) : 1; + else if (property_name == "Format") + texture_info.format = static_cast(value); + else + return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false; + } + else if (type.is_sampler()) + { + if (property_name == "SRGBTexture" || property_name == "SRGBReadEnable") + sampler_info.srgb = value != 0; + else if (property_name == "AddressU") + sampler_info.address_u = static_cast(value); + else if (property_name == "AddressV") + sampler_info.address_v = static_cast(value); + else if (property_name == "AddressW") + sampler_info.address_w = static_cast(value); + else if (property_name == "MinFilter") + sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x0F) | ((value << 4) & 0x30)); // Combine sampler filter components into a single filter enumeration value + else if (property_name == "MagFilter") + sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x33) | ((value << 2) & 0x0C)); + else if (property_name == "MipFilter") + sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x3C) | (value & 0x03)); + else if (property_name == "MinLOD" || property_name == "MaxMipLevel") + sampler_info.min_lod = static_cast(value); + else if (property_name == "MaxLOD") + sampler_info.max_lod = static_cast(value); + else if (property_name == "MipLODBias" || property_name == "MipMapLodBias") + sampler_info.lod_bias = static_cast(value); + else + return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false; + } + else if (type.is_storage()) + { + if (property_name == "MipLOD" || property_name == "MipLevel") + storage_info.level = value > 0 && value < std::numeric_limits::max() ? static_cast(value) : 0; + else + return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false; + } + } + + if (!expect(';')) + return consume_until('}'), false; + } + + if (!expect('}')) + return false; + } + } + + // At this point the array size should be known (either from the declaration or the initializer) + if (type.array_length < 0) + return error(location, 3074, '\'' + name + "': implicit array missing initial value"), false; + + symbol symbol; + + // Variables with a constant initializer and constant type are named constants + // Skip this for very large arrays though, to avoid large amounts of duplicated values when that array constant is accessed with a dynamic index + if (type.is_numeric() && type.has(type::q_const) && initializer.is_constant && type.array_length < 100) + { + // Named constants are special symbols + symbol = { symbol_type::constant, 0, type, initializer.constant }; + } + else if (type.is_texture()) + { + assert(global); + + texture_info.name = name; + texture_info.type = static_cast(type.texture_dimension()); + + // Add namespace scope to avoid name clashes + texture_info.unique_name = 'V' + current_scope().name + name; + std::replace(texture_info.unique_name.begin(), texture_info.unique_name.end(), ':', '_'); + + texture_info.annotations = std::move(sampler_info.annotations); + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_texture(location, texture_info); + } + // Samplers are actually combined image samplers + else if (type.is_sampler()) + { + assert(global); + + if (sampler_info.texture_name.empty()) + return error(location, 3012, '\'' + name + "': missing 'Texture' property"), false; + if (type.texture_dimension() != static_cast(texture_info.type)) + return error(location, 3521, '\'' + name + "': type mismatch between texture and sampler type"), false; + if (sampler_info.srgb && texture_info.format != texture_format::rgba8) + return error(location, 4582, '\'' + name + "': texture does not support sRGB sampling (only textures with RGBA8 format do)"), false; + + if (texture_info.format == texture_format::r32i ? + !type.is_integral() || !type.is_signed() : + texture_info.format == texture_format::r32u ? + !type.is_integral() || !type.is_unsigned() : + !type.is_floating_point()) + return error(location, 4582, '\'' + name + "': type mismatch between texture format and sampler element type"), false; + + sampler_info.name = name; + sampler_info.type = type; + + // Add namespace scope to avoid name clashes + sampler_info.unique_name = 'V' + current_scope().name + name; + std::replace(sampler_info.unique_name.begin(), sampler_info.unique_name.end(), ':', '_'); + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_sampler(location, texture_info, sampler_info); + } + else if (type.is_storage()) + { + assert(global); + + if (storage_info.texture_name.empty()) + return error(location, 3012, '\'' + name + "': missing 'Texture' property"), false; + if (type.texture_dimension() != static_cast(texture_info.type)) + return error(location, 3521, '\'' + name + "': type mismatch between texture and storage type"), false; + + if (texture_info.format == texture_format::r32i ? + !type.is_integral() || !type.is_signed() : + texture_info.format == texture_format::r32u ? + !type.is_integral() || !type.is_unsigned() : + !type.is_floating_point()) + return error(location, 4582, '\'' + name + "': type mismatch between texture format and storage element type"), false; + + storage_info.name = name; + storage_info.type = type; + + // Add namespace scope to avoid name clashes + storage_info.unique_name = 'V' + current_scope().name + name; + std::replace(storage_info.unique_name.begin(), storage_info.unique_name.end(), ':', '_'); + + if (storage_info.level > texture_info.levels - 1) + storage_info.level = texture_info.levels - 1; + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_storage(location, texture_info, storage_info); + } + // Uniform variables are put into a global uniform buffer structure + else if (type.has(type::q_uniform)) + { + assert(global); + + uniform_info uniform_info; + uniform_info.name = name; + uniform_info.type = type; + + uniform_info.annotations = std::move(sampler_info.annotations); + + uniform_info.initializer_value = std::move(initializer.constant); + uniform_info.has_initializer_value = initializer.is_constant; + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_uniform(location, uniform_info); + } + // All other variables are separate entities + else + { + // Update global variable names to contain the namespace scope to avoid name clashes + std::string unique_name = global ? 'V' + current_scope().name + name : name; + std::replace(unique_name.begin(), unique_name.end(), ':', '_'); + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_variable(location, type, std::move(unique_name), global, + // Shared variables cannot have an initializer + type.has(type::q_groupshared) ? 0 : _codegen->emit_load(initializer)); + } + + // Insert the symbol into the symbol table + if (!insert_symbol(name, symbol, global)) + return error(location, 3003, "redefinition of '" + name + '\''), false; + + return parse_success; +} + +bool reshadefx::parser::parse_technique() +{ + if (!expect(tokenid::identifier)) + return false; + + technique_info info; + info.name = std::move(_token.literal_as_string); + + bool parse_success = parse_annotations(info.annotations); + + if (!expect('{')) + return false; + + while (!peek('}')) + { + if (pass_info pass; parse_technique_pass(pass)) + info.passes.push_back(std::move(pass)); + else { + parse_success = false; + if (!peek(tokenid::pass) && !peek('}')) // If there is another pass definition following, try to parse that despite the error + return consume_until('}'), false; + } + } + + _codegen->define_technique(std::move(info)); + + return expect('}') && parse_success; +} +bool reshadefx::parser::parse_technique_pass(pass_info &info) +{ + if (!expect(tokenid::pass)) + return false; + + const auto pass_location = std::move(_token.location); + + // Passes can have an optional name + if (accept(tokenid::identifier)) + info.name = std::move(_token.literal_as_string); + + bool parse_success = true; + bool targets_support_srgb = true; + function_info vs_info, ps_info, cs_info; + + if (!expect('{')) + return false; + + while (!peek('}')) + { + // Parse pass states + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + auto location = std::move(_token.location); + const auto state = std::move(_token.literal_as_string); + + if (!expect('=')) + return consume_until('}'), false; + + const bool is_shader_state = state == "VertexShader" || state == "PixelShader" || state == "ComputeShader"; + const bool is_texture_state = state.compare(0, 12, "RenderTarget") == 0 && (state.size() == 12 || (state[12] >= '0' && state[12] < '8')); + + // Shader and render target assignment looks up values in the symbol table, so handle those separately from the other states + if (is_shader_state || is_texture_state) + { + std::string identifier; + scoped_symbol symbol; + if (!accept_symbol(identifier, symbol)) + return consume_until('}'), false; + + location = std::move(_token.location); + + int num_threads[3] = { 1, 1, 1 }; + if (accept('<')) + { + expression x, y, z; + if (!parse_expression_multary(x, 8) || !expect(',') || !parse_expression_multary(y, 8)) + return consume_until('}'), false; + + // Parse optional third dimension (defaults to 1) + z.reset_to_rvalue_constant({}, 1); + if (accept(',') && !parse_expression_multary(z, 8)) + return consume_until('}'), false; + + if (!x.is_constant) + return error(x.location, 3011, "value must be a literal expression"), consume_until('}'), false; + if (!y.is_constant) + return error(y.location, 3011, "value must be a literal expression"), consume_until('}'), false; + if (!z.is_constant) + return error(z.location, 3011, "value must be a literal expression"), consume_until('}'), false; + x.add_cast_operation({ type::t_int, 1, 1 }); + y.add_cast_operation({ type::t_int, 1, 1 }); + z.add_cast_operation({ type::t_int, 1, 1 }); + num_threads[0] = x.constant.as_int[0]; + num_threads[1] = y.constant.as_int[0]; + num_threads[2] = z.constant.as_int[0]; + + if (!expect('>')) + return consume_until('}'), false; + } + + // Ignore invalid symbols that were added during error recovery + if (symbol.id != 0xFFFFFFFF) + { + if (is_shader_state) + { + if (!symbol.id) + parse_success = false, + error(location, 3501, "undeclared identifier '" + identifier + "', expected function name"); + else if (!symbol.type.is_function()) + parse_success = false, + error(location, 3020, "type mismatch, expected function name"); + else { + // Look up the matching function info for this function definition + function_info &function_info = _codegen->get_function(symbol.id); + + // We potentially need to generate a special entry point function which translates between function parameters and input/output variables + switch (state[0]) + { + case 'V': + vs_info = function_info; + _codegen->define_entry_point(vs_info, shader_type::vs); + info.vs_entry_point = vs_info.unique_name; + break; + case 'P': + ps_info = function_info; + _codegen->define_entry_point(ps_info, shader_type::ps); + info.ps_entry_point = ps_info.unique_name; + break; + case 'C': + cs_info = function_info; + _codegen->define_entry_point(cs_info, shader_type::cs, num_threads); + info.cs_entry_point = cs_info.unique_name; + break; + } + } + } + else + { + assert(is_texture_state); + + if (!symbol.id) + parse_success = false, + error(location, 3004, "undeclared identifier '" + identifier + "', expected texture name"); + else if (!symbol.type.is_texture()) + parse_success = false, + error(location, 3020, "type mismatch, expected texture name"); + else if (symbol.type.texture_dimension() != 2) + parse_success = false, + error(location, 3020, "cannot use texture" + std::to_string(symbol.type.texture_dimension()) + "D as render target"); + else { + reshadefx::texture_info &target_info = _codegen->get_texture(symbol.id); + // Texture is used as a render target + target_info.render_target = true; + + // Verify that all render targets in this pass have the same dimensions + if (info.viewport_width != 0 && info.viewport_height != 0 && (target_info.width != info.viewport_width || target_info.height != info.viewport_height)) + parse_success = false, + error(location, 4545, "cannot use multiple render targets with different texture dimensions (is " + std::to_string(target_info.width) + 'x' + std::to_string(target_info.height) + ", but expected " + std::to_string(info.viewport_width) + 'x' + std::to_string(info.viewport_height) + ')'); + + info.viewport_width = target_info.width; + info.viewport_height = target_info.height; + + const auto target_index = state.size() > 12 ? (state[12] - '0') : 0; + info.render_target_names[target_index] = target_info.unique_name; + + // Only RGBA8 format supports sRGB writes across all APIs + if (target_info.format != texture_format::rgba8) + targets_support_srgb = false; + } + } + } + else + { + parse_success = false; + } + } + else // Handle the rest of the pass states + { + backup(); + + expression expression; + + if (accept(tokenid::identifier)) // Handle special enumeration names for pass states + { + // Transform identifier to uppercase to do case-insensitive comparison + std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(), + [](std::string::value_type c) { + return static_cast(std::toupper(c)); + }); + + static const std::unordered_map s_enum_values = { + { "NONE", 0 }, { "ZERO", 0 }, { "ONE", 1 }, + { "ADD", uint32_t(pass_blend_op::add) }, + { "SUBTRACT", uint32_t(pass_blend_op::subtract) }, + { "REVSUBTRACT", uint32_t(pass_blend_op::reverse_subtract) }, + { "MIN", uint32_t(pass_blend_op::min) }, + { "MAX", uint32_t(pass_blend_op::max) }, + { "SRCCOLOR", uint32_t(pass_blend_factor::source_color) }, + { "INVSRCCOLOR", uint32_t(pass_blend_factor::one_minus_source_color) }, + { "DESTCOLOR", uint32_t(pass_blend_factor::dest_color) }, + { "INVDESTCOLOR", uint32_t(pass_blend_factor::one_minus_dest_color) }, + { "SRCALPHA", uint32_t(pass_blend_factor::source_alpha) }, + { "INVSRCALPHA", uint32_t(pass_blend_factor::one_minus_source_alpha) }, + { "DESTALPHA", uint32_t(pass_blend_factor::dest_alpha) }, + { "INVDESTALPHA", uint32_t(pass_blend_factor::one_minus_dest_alpha) }, + { "KEEP", uint32_t(pass_stencil_op::keep) }, + { "REPLACE", uint32_t(pass_stencil_op::replace) }, + { "INVERT", uint32_t(pass_stencil_op::invert) }, + { "INCR", uint32_t(pass_stencil_op::increment) }, + { "INCRSAT", uint32_t(pass_stencil_op::increment_saturate) }, + { "DECR", uint32_t(pass_stencil_op::decrement) }, + { "DECRSAT", uint32_t(pass_stencil_op::decrement_saturate) }, + { "NEVER", uint32_t(pass_stencil_func::never) }, + { "EQUAL", uint32_t(pass_stencil_func::equal) }, + { "NEQUAL", uint32_t(pass_stencil_func::not_equal) }, { "NOTEQUAL", uint32_t(pass_stencil_func::not_equal) }, + { "LESS", uint32_t(pass_stencil_func::less) }, + { "GREATER", uint32_t(pass_stencil_func::greater) }, + { "LEQUAL", uint32_t(pass_stencil_func::less_equal) }, { "LESSEQUAL", uint32_t(pass_stencil_func::less_equal) }, + { "GEQUAL", uint32_t(pass_stencil_func::greater_equal) }, { "GREATEREQUAL", uint32_t(pass_stencil_func::greater_equal) }, + { "ALWAYS", uint32_t(pass_stencil_func::always) }, + { "POINTS", uint32_t(primitive_topology::point_list) }, + { "POINTLIST", uint32_t(primitive_topology::point_list) }, + { "LINES", uint32_t(primitive_topology::line_list) }, + { "LINELIST", uint32_t(primitive_topology::line_list) }, + { "LINESTRIP", uint32_t(primitive_topology::line_strip) }, + { "TRIANGLES", uint32_t(primitive_topology::triangle_list) }, + { "TRIANGLELIST", uint32_t(primitive_topology::triangle_list) }, + { "TRIANGLESTRIP", uint32_t(primitive_topology::triangle_strip) }, + }; + + // Look up identifier in list of possible enumeration names + if (const auto it = s_enum_values.find(_token.literal_as_string); + it != s_enum_values.end()) + expression.reset_to_rvalue_constant(_token.location, it->second); + else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression + restore(); + } + + // Parse right hand side as normal expression if no special enumeration name was matched already + if (!expression.is_constant && !parse_expression_multary(expression)) + return consume_until('}'), false; + else if (!expression.is_constant || !expression.type.is_scalar()) + parse_success = false, + error(expression.location, 3011, "pass state value must be a literal scalar expression"); + + // All states below expect the value to be of an unsigned integer type + expression.add_cast_operation({ type::t_uint, 1, 1 }); + const unsigned int value = expression.constant.as_uint[0]; + +#define SET_STATE_VALUE_INDEXED(name, info_name, value) \ + else if (constexpr size_t name##_len = sizeof(#name) - 1; state.compare(0, name##_len, #name) == 0 && (state.size() == name##_len || (state[name##_len] >= '0' && state[name##_len] < ('0' + static_cast(std::size(info.info_name)))))) \ + { \ + if (state.size() != name##_len) \ + info.info_name[state[name##_len] - '0'] = (value); \ + else \ + for (int i = 0; i < static_cast(std::size(info.info_name)); ++i) \ + info.info_name[i] = (value); \ + } + + if (state == "SRGBWriteEnable") + info.srgb_write_enable = (value != 0); + SET_STATE_VALUE_INDEXED(BlendEnable, blend_enable, value != 0) + else if (state == "StencilEnable") + info.stencil_enable = (value != 0); + else if (state == "ClearRenderTargets") + info.clear_render_targets = (value != 0); + SET_STATE_VALUE_INDEXED(ColorWriteMask, color_write_mask, value & 0xFF) + SET_STATE_VALUE_INDEXED(RenderTargetWriteMask, color_write_mask, value & 0xFF) + else if (state == "StencilReadMask" || state == "StencilMask") + info.stencil_read_mask = value & 0xFF; + else if (state == "StencilWriteMask") + info.stencil_write_mask = value & 0xFF; + SET_STATE_VALUE_INDEXED(BlendOp, blend_op, static_cast(value)) + SET_STATE_VALUE_INDEXED(BlendOpAlpha, blend_op_alpha, static_cast(value)) + SET_STATE_VALUE_INDEXED(SrcBlend, src_blend, static_cast(value)) + SET_STATE_VALUE_INDEXED(SrcBlendAlpha, src_blend_alpha, static_cast(value)) + SET_STATE_VALUE_INDEXED(DestBlend, dest_blend, static_cast(value)) + SET_STATE_VALUE_INDEXED(DestBlendAlpha, dest_blend_alpha, static_cast(value)) + else if (state == "StencilFunc") + info.stencil_comparison_func = static_cast(value); + else if (state == "StencilRef") + info.stencil_reference_value = value; + else if (state == "StencilPass" || state == "StencilPassOp") + info.stencil_op_pass = static_cast(value); + else if (state == "StencilFail" || state == "StencilFailOp") + info.stencil_op_fail = static_cast(value); + else if (state == "StencilZFail" || state == "StencilDepthFail" || state == "StencilDepthFailOp") + info.stencil_op_depth_fail = static_cast(value); + else if (state == "VertexCount") + info.num_vertices = value; + else if (state == "PrimitiveType" || state == "PrimitiveTopology") + info.topology = static_cast(value); + else if (state == "DispatchSizeX") + info.viewport_width = value; + else if (state == "DispatchSizeY") + info.viewport_height = value; + else if (state == "DispatchSizeZ") + info.viewport_dispatch_z = value; + else if (state == "GenerateMipmaps" || state == "GenerateMipMaps") + info.generate_mipmaps = (value != 0); + else + parse_success = false, + error(location, 3004, "unrecognized pass state '" + state + '\''); + +#undef SET_STATE_VALUE_INDEXED + } + + if (!expect(';')) + return consume_until('}'), false; + } + + if (parse_success) + { + if (!info.cs_entry_point.empty()) + { + if (info.viewport_width == 0 || info.viewport_height == 0) + { + parse_success = false; + error(pass_location, 3012, "pass is missing 'DispatchSizeX' or 'DispatchSizeY' property"); + } + + if (!info.vs_entry_point.empty()) + warning(pass_location, 3089, "pass is specifying both 'VertexShader' and 'ComputeShader' which cannot be used together"); + if (!info.ps_entry_point.empty()) + warning(pass_location, 3089, "pass is specifying both 'PixelShader' and 'ComputeShader' which cannot be used together"); + + for (codegen::id id : cs_info.referenced_samplers) + info.samplers.push_back(_codegen->get_sampler(id)); + for (codegen::id id : cs_info.referenced_storages) + info.storages.push_back(_codegen->get_storage(id)); + } + else if (info.vs_entry_point.empty() || info.ps_entry_point.empty()) + { + parse_success = false; + + if (info.vs_entry_point.empty()) + error(pass_location, 3012, "pass is missing 'VertexShader' property"); + if (info.ps_entry_point.empty()) + error(pass_location, 3012, "pass is missing 'PixelShader' property"); + } + else + { + // Verify that shader signatures between VS and PS match (both semantics and interpolation qualifiers) + std::unordered_map vs_semantic_mapping; + if (vs_info.return_semantic.empty()) + { + if (!vs_info.return_type.is_void() && !vs_info.return_type.is_struct()) + { + parse_success = false; + error(pass_location, 3503, '\'' + vs_info.name + "': function return value is missing semantics"); + } + } + else + { + vs_semantic_mapping[vs_info.return_semantic] = vs_info.return_type; + } + + for (const struct_member_info ¶m : vs_info.parameter_list) + { + if (param.semantic.empty()) + { + if (!param.type.is_struct()) + { + parse_success = false; + if (param.type.has(type::q_in)) + error(pass_location, 3502, '\'' + vs_info.name + "': input parameter '" + param.name + "' is missing semantics"); + else + error(pass_location, 3503, '\'' + vs_info.name + "': output parameter '" + param.name + "' is missing semantics"); + } + } + else if (param.type.has(type::q_out)) + { + vs_semantic_mapping[param.semantic] = param.type; + } + } + + if (ps_info.return_semantic.empty()) + { + if (!ps_info.return_type.is_void() && !ps_info.return_type.is_struct()) + { + parse_success = false; + error(pass_location, 3503, '\'' + ps_info.name + "': function return value is missing semantics"); + } + } + + for (const struct_member_info ¶m : ps_info.parameter_list) + { + if (param.semantic.empty()) + { + if (!param.type.is_struct()) + { + parse_success = false; + if (param.type.has(type::q_in)) + error(pass_location, 3502, '\'' + ps_info.name + "': input parameter '" + param.name + "' is missing semantics"); + else + error(pass_location, 3503, '\'' + ps_info.name + "': output parameter '" + param.name + "' is missing semantics"); + } + } + else if (param.type.has(type::q_in)) + { + if (const auto it = vs_semantic_mapping.find(param.semantic); + it == vs_semantic_mapping.end() || it->second != param.type) + warning(pass_location, 4576, '\'' + ps_info.name + "': input parameter '" + param.name + "' semantic does not match vertex shader one"); + else if (((it->second.qualifiers ^ param.type.qualifiers) & (type::q_linear | type::q_noperspective | type::q_centroid | type::q_nointerpolation)) != 0) + parse_success = false, + error( pass_location, 4568, '\'' + ps_info.name + "': input parameter '" + param.name + "' interpolation qualifiers do not match vertex shader ones"); + } + } + + for (codegen::id id : vs_info.referenced_samplers) + info.samplers.push_back(_codegen->get_sampler(id)); + for (codegen::id id : ps_info.referenced_samplers) + info.samplers.push_back(_codegen->get_sampler(id)); + if (!vs_info.referenced_storages.empty() || !ps_info.referenced_storages.empty()) + { + parse_success = false; + error(pass_location, 3667, "storage writes are only valid in compute shaders"); + } + } + + // Verify render target format supports sRGB writes if enabled + if (info.srgb_write_enable && !targets_support_srgb) + parse_success = false, + error(pass_location, 4582, "one or more render target(s) do not support sRGB writes (only textures with RGBA8 format do)"); + } + + return expect('}') && parse_success; +} diff --git a/dep/reshadefx/src/effect_preprocessor.cpp b/dep/reshadefx/src/effect_preprocessor.cpp new file mode 100644 index 000000000..ba491f098 --- /dev/null +++ b/dep/reshadefx/src/effect_preprocessor.cpp @@ -0,0 +1,1295 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_lexer.hpp" +#include "effect_preprocessor.hpp" +#include +#include +#include // std::find_if + +#ifndef _WIN32 + // On Linux systems the native path encoding is UTF-8 already, so no conversion necessary + #define u8path(p) path(p) + #define u8string() string() +#endif + +enum op_type +{ + op_none = -1, + + op_or, + op_and, + op_bitor, + op_bitxor, + op_bitand, + op_not_equal, + op_equal, + op_less, + op_greater, + op_less_equal, + op_greater_equal, + op_leftshift, + op_rightshift, + op_add, + op_subtract, + op_modulo, + op_divide, + op_multiply, + op_plus, + op_negate, + op_not, + op_bitnot, + op_parentheses +}; + +enum macro_replacement +{ + macro_replacement_start = '\x00', + macro_replacement_argument = '\xFD', + macro_replacement_concat = '\xFF', + macro_replacement_stringize = '\xFE', +}; + +static const int precedence_lookup[] = { + 0, 1, 2, 3, 4, // bitwise operators + 5, 6, 7, 7, 7, 7, // logical operators + 8, 8, // left shift, right shift + 9, 9, // add, subtract + 10, 10, 10, // modulo, divide, multiply + 11, 11, 11, 11 // unary operators +}; + +static bool read_file(const std::filesystem::path &path, std::string &data) +{ + std::ifstream file(path, std::ios::binary); + if (!file) + return false; + + // Read file contents into memory + std::error_code ec; + const uintmax_t file_size = std::filesystem::file_size(path, ec); + if (ec) + return false; + + std::string file_data(static_cast(file_size + 1), '\0'); + if (!file.read(file_data.data(), file_size)) + return false; + + // No longer need to have a handle open to the file, since all data was read, so can safely close it + file.close(); + + // Append a new line feed to the end of the input string to avoid issues with parsing + file_data.back() = '\n'; + + // Remove BOM (0xefbbbf means 0xfeff) + if (file_data.size() >= 3 && + static_cast(file_data[0]) == 0xef && + static_cast(file_data[1]) == 0xbb && + static_cast(file_data[2]) == 0xbf) + file_data.erase(0, 3); + + data = std::move(file_data); + return true; +} + +template +static std::string escape_string(std::string s) +{ + for (size_t offset = 0; (offset = s.find(ESCAPE_CHAR, offset)) != std::string::npos; offset += 2) + s.insert(offset, "\\", 1); + return '\"' + s + '\"'; +} + +reshadefx::preprocessor::preprocessor() +{ +} +reshadefx::preprocessor::~preprocessor() +{ +} + +void reshadefx::preprocessor::add_include_path(const std::filesystem::path &path) +{ + assert(!path.empty()); + _include_paths.push_back(path); +} +bool reshadefx::preprocessor::add_macro_definition(const std::string &name, const macro ¯o) +{ + assert(!name.empty()); + return _macros.emplace(name, macro).second; +} + +bool reshadefx::preprocessor::append_file(const std::filesystem::path &path) +{ + std::string source_code; + if (!read_file(path, source_code)) + return false; + + return append_string(std::move(source_code), path); +} +bool reshadefx::preprocessor::append_string(std::string source_code, const std::filesystem::path &path) +{ + // Enforce all input strings to end with a line feed + assert(!source_code.empty() && source_code.back() == '\n'); + + _success = true; // Clear success flag before parsing a new string + + // Give this push a name, so that lexer location starts at a new line + // This is necessary in case this string starts with a preprocessor directive, since the lexer only reports those as such if they appear at the beginning of a new line + // But without a name, the lexer location is set to the last token location, which most likely will not be at the start of the line + push(std::move(source_code), path.empty() ? "unknown" : path.u8string()); + parse(); + + return _success; +} + +std::vector reshadefx::preprocessor::included_files() const +{ + std::vector files; + files.reserve(_file_cache.size()); + for (const auto &it : _file_cache) + files.push_back(std::filesystem::u8path(it.first)); + return files; +} +std::vector> reshadefx::preprocessor::used_macro_definitions() const +{ + std::vector> defines; + defines.reserve(_used_macros.size()); + for (const std::string &name : _used_macros) + if (const auto it = _macros.find(name); + // Do not include function-like macros, since they are more likely to contain a complex replacement list + it != _macros.end() && !it->second.is_function_like) + defines.emplace_back(name, it->second.replacement_list); + return defines; +} + +void reshadefx::preprocessor::error(const location &location, const std::string &message) +{ + _errors += location.source + '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": preprocessor error: " + message + '\n'; + _success = false; // Unset success flag +} +void reshadefx::preprocessor::warning(const location &location, const std::string &message) +{ + _errors += location.source + '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": preprocessor warning: " + message + '\n'; +} + +void reshadefx::preprocessor::push(std::string input, const std::string &name) +{ + location start_location = !name.empty() ? + // Start at the beginning of the file when pushing a new file + location(name, 1) : + // Start with last known token location when pushing an unnamed string + _token.location; + + input_level level = { name }; + level.lexer.reset(new lexer( + std::move(input), + true /* ignore_comments */, + false /* ignore_whitespace */, + false /* ignore_pp_directives */, + false /* ignore_line_directives */, + true /* ignore_keywords */, + false /* escape_string_literals */, + start_location)); + level.next_token.id = tokenid::unknown; + level.next_token.location = start_location; // This is used in 'consume' to initialize the output location + + // Inherit hidden macros from parent + if (!_input_stack.empty()) + level.hidden_macros = _input_stack.back().hidden_macros; + + _input_stack.push_back(std::move(level)); + _next_input_index = _input_stack.size() - 1; + + // Advance into the input stack to update next token + consume(); +} + +bool reshadefx::preprocessor::peek(tokenid tokid) const +{ + if (_input_stack.empty()) + return tokid == tokenid::end_of_file; + + return _input_stack[_next_input_index].next_token == tokid; +} +void reshadefx::preprocessor::consume() +{ + _current_input_index = _next_input_index; + + if (_input_stack.empty()) + { + // End of input has been reached already (this can happen when the input text is not terminated with a new line) + assert(_current_input_index == 0); + return; + } + + // Clear out input stack, now that the current token is overwritten + while (_input_stack.size() > (_current_input_index + 1)) + _input_stack.pop_back(); + + // Update location information after switching input levels + input_level &input = _input_stack[_current_input_index]; + if (!input.name.empty() && input.name != _output_location.source) + { + _output += "#line " + std::to_string(input.next_token.location.line) + " \"" + input.name + "\"\n"; + // Line number is increased before checking against next token in 'tokenid::end_of_line' handling in 'parse' function below, so compensate for that here + _output_location.line = input.next_token.location.line - 1; + _output_location.source = input.name; + } + + // Set current token + _token = std::move(input.next_token); + _current_token_raw_data = input.lexer->input_string().substr(_token.offset, _token.length); + + // Get the next token + input.next_token = input.lexer->lex(); + + // Verify string literals (since the lexer cannot throw errors itself) + if (_token == tokenid::string_literal && _current_token_raw_data.back() != '\"') + error(_token.location, "unterminated string literal"); + + // Pop input level if lexical analysis has reached the end of it + // This ensures the EOF token is not consumed until the very last file + while (peek(tokenid::end_of_file)) + { + // Remove any unterminated blocks from the stack + for (; !_if_stack.empty() && _if_stack.back().input_index >= _next_input_index; _if_stack.pop_back()) + error(_if_stack.back().pp_token.location, "unterminated #if"); + + if (_next_input_index == 0) + { + // End of input has been reached, so cannot pop further and this is the last token + _input_stack.pop_back(); + return; + } + else + { + _next_input_index -= 1; + } + } +} +void reshadefx::preprocessor::consume_until(tokenid tokid) +{ + while (!accept(tokid) && !peek(tokenid::end_of_file)) + { + consume(); + } +} + +bool reshadefx::preprocessor::accept(tokenid tokid, bool ignore_whitespace) +{ + if (ignore_whitespace) + { + while (peek(tokenid::space)) + { + consume(); + } + } + + if (peek(tokid)) + { + consume(); + return true; + } + + return false; +} +bool reshadefx::preprocessor::expect(tokenid tokid) +{ + if (!accept(tokid)) + { + if (_input_stack.empty()) + return tokid == tokenid::end_of_line || tokid == tokenid::end_of_file; + + token actual_token = _input_stack[_next_input_index].next_token; + actual_token.location.source = _output_location.source; + + if (actual_token == tokenid::end_of_line) + error(actual_token.location, "syntax error: unexpected new line"); + else + error(actual_token.location, "syntax error: unexpected token '" + + _input_stack[_next_input_index].lexer->input_string().substr(actual_token.offset, actual_token.length) + '\''); + + return false; + } + + return true; +} + +void reshadefx::preprocessor::parse() +{ + std::string line; + + // Consume all tokens in the input + while (!peek(tokenid::end_of_file)) + { + consume(); + + _recursion_count = 0; + + const bool skip = !_if_stack.empty() && _if_stack.back().skipping; + + switch (_token) + { + case tokenid::hash_if: + parse_if(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_ifdef: + parse_ifdef(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_ifndef: + parse_ifndef(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_else: + parse_else(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_elif: + parse_elif(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_endif: + parse_endif(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + default: + // All other tokens are handled below + break; + } + + if (skip) + // Ignore token since the current section is disabled + continue; + + switch (_token) + { + case tokenid::hash_def: + parse_def(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_undef: + parse_undef(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_error: + parse_error(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_warning: + parse_warning(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_pragma: + parse_pragma(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_include: + parse_include(); + continue; + case tokenid::hash_unknown: + // Standalone "#" is valid and should be ignored + if (_token.length != 0) + error(_token.location, "unrecognized preprocessing directive '" + _token.literal_as_string + '\''); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::end_of_line: + if (line.empty()) + continue; // Do not append empty lines to output, instead emit "#line" statements + _output_location.line++; + if (_token.location.line != _output_location.line) + { + _output += "#line " + std::to_string(_token.location.line) + '\n'; + _output_location.line = _token.location.line; + } + _output += line; + _output += '\n'; + line.clear(); + continue; + case tokenid::identifier: + if (evaluate_identifier_as_macro()) + continue; + [[fallthrough]]; + default: + line += _current_token_raw_data; + break; + } + } + + // Append the last line after the EOF token was reached to the output + _output += line; + _output += '\n'; +} + +void reshadefx::preprocessor::parse_def() +{ + if (!expect(tokenid::identifier)) + return; + if (_token.literal_as_string == "defined") + return warning(_token.location, "macro name 'defined' is reserved"); + + macro m; + const location location = std::move(_token.location); + const std::string macro_name = std::move(_token.literal_as_string); + + // Only create function-like macro if the parenthesis follows the macro name without any whitespace between + if (accept(tokenid::parenthesis_open, false)) + { + m.is_function_like = true; + + while (accept(tokenid::identifier)) + { + m.parameters.push_back(_token.literal_as_string); + + if (!accept(tokenid::comma)) + break; + } + + if (accept(tokenid::ellipsis)) + m.is_variadic = true; + + if (!expect(tokenid::parenthesis_close)) + return; + } + + create_macro_replacement_list(m); + + if (!add_macro_definition(macro_name, m)) + return error(location, "redefinition of '" + macro_name + "'"); +} +void reshadefx::preprocessor::parse_undef() +{ + if (!expect(tokenid::identifier)) + return; + if (_token.literal_as_string == "defined") + return warning(_token.location, "macro name 'defined' is reserved"); + + _macros.erase(_token.literal_as_string); +} + +void reshadefx::preprocessor::parse_if() +{ + if_level level; + level.pp_token = _token; + level.input_index = _current_input_index; + + // Evaluate expression after updating 'pp_token', so that it points at the beginning # token + level.value = evaluate_expression(); + + const bool parent_skipping = !_if_stack.empty() && _if_stack.back().skipping; + level.skipping = parent_skipping || !level.value; + + _if_stack.push_back(std::move(level)); +} +void reshadefx::preprocessor::parse_ifdef() +{ + if_level level; + level.pp_token = _token; + level.input_index = _current_input_index; + + if (!expect(tokenid::identifier)) + return; + + level.value = is_defined(_token.literal_as_string); + + const bool parent_skipping = !_if_stack.empty() && _if_stack.back().skipping; + level.skipping = parent_skipping || !level.value; + + _if_stack.push_back(std::move(level)); + // Only add to used macro list if this #ifdef is active and the macro was not defined before + if (!parent_skipping) + if (const auto it = _macros.find(_token.literal_as_string); it == _macros.end() || it->second.is_predefined) + _used_macros.emplace(_token.literal_as_string); +} +void reshadefx::preprocessor::parse_ifndef() +{ + if_level level; + level.pp_token = _token; + level.input_index = _current_input_index; + + if (!expect(tokenid::identifier)) + return; + + level.value = !is_defined(_token.literal_as_string); + + const bool parent_skipping = !_if_stack.empty() && _if_stack.back().skipping; + level.skipping = parent_skipping || !level.value; + + _if_stack.push_back(std::move(level)); + // Only add to used macro list if this #ifndef is active and the macro was not defined before + if (!parent_skipping) + if (const auto it = _macros.find(_token.literal_as_string); it == _macros.end() || it->second.is_predefined) + _used_macros.emplace(_token.literal_as_string); +} +void reshadefx::preprocessor::parse_elif() +{ + if (_if_stack.empty()) + return error(_token.location, "missing #if for #elif"); + + if_level &level = _if_stack.back(); + if (level.pp_token == tokenid::hash_else) + return error(_token.location, "#elif is not allowed after #else"); + + // Update 'pp_token' before evaluating expression, so that it points at the beginning # token + level.pp_token = _token; + level.input_index = _current_input_index; + + const bool parent_skipping = _if_stack.size() > 1 && _if_stack[_if_stack.size() - 2].skipping; + const bool condition_result = evaluate_expression(); + level.skipping = parent_skipping || level.value || !condition_result; + + if (!level.value) level.value = condition_result; +} +void reshadefx::preprocessor::parse_else() +{ + if (_if_stack.empty()) + return error(_token.location, "missing #if for #else"); + + if_level &level = _if_stack.back(); + if (level.pp_token == tokenid::hash_else) + return error(_token.location, "#else is not allowed after #else"); + + level.pp_token = _token; + level.input_index = _current_input_index; + + const bool parent_skipping = _if_stack.size() > 1 && _if_stack[_if_stack.size() - 2].skipping; + level.skipping = parent_skipping || level.value; + + if (!level.value) level.value = true; +} +void reshadefx::preprocessor::parse_endif() +{ + if (_if_stack.empty()) + error(_token.location, "missing #if for #endif"); + else + _if_stack.pop_back(); +} + +void reshadefx::preprocessor::parse_error() +{ + const location keyword_location = std::move(_token.location); + + if (!expect(tokenid::string_literal)) + return; + + error(keyword_location, _token.literal_as_string); +} +void reshadefx::preprocessor::parse_warning() +{ + const location keyword_location = std::move(_token.location); + + if (!expect(tokenid::string_literal)) + return; + + warning(keyword_location, _token.literal_as_string); +} + +void reshadefx::preprocessor::parse_pragma() +{ + const location keyword_location = std::move(_token.location); + + if (!expect(tokenid::identifier)) + return; + + std::string pragma = std::move(_token.literal_as_string); + std::string pragma_args; + + // Ignore whitespace preceding the argument list + accept(tokenid::space); + + while (!peek(tokenid::end_of_line) && !peek(tokenid::end_of_file)) + { + consume(); + + if (_token == tokenid::identifier && evaluate_identifier_as_macro()) + continue; + + // Collapse all whitespace down to a single space + if (_token == tokenid::space) + pragma_args += ' '; + else + pragma_args += _current_token_raw_data; + } + + if (pragma == "once") + { + // Clear file contents, so that future include statements simply push an empty string instead of these file contents again + if (const auto it = _file_cache.find(_output_location.source); it != _file_cache.end()) + it->second.clear(); + return; + } + + if (pragma == "warning" || pragma == "reshade") + { + _used_pragmas.emplace_back(std::move(pragma), std::move(pragma_args)); + return; + } + + warning(keyword_location, "unknown pragma ignored"); +} + +void reshadefx::preprocessor::parse_include() +{ + const location keyword_location = std::move(_token.location); + + while (accept(tokenid::identifier)) + { + if (!evaluate_identifier_as_macro()) + { + error(_token.location, "syntax error: unexpected identifier in #include"); + consume_until(tokenid::end_of_line); + return; + } + } + + if (!expect(tokenid::string_literal)) + { + consume_until(tokenid::end_of_line); + return; + } + + std::filesystem::path file_name = std::filesystem::u8path(_token.literal_as_string); + std::filesystem::path file_path = std::filesystem::u8path(_output_location.source); + file_path.replace_filename(file_name); + + std::error_code ec; + if (!std::filesystem::exists(file_path, ec)) + for (const std::filesystem::path &include_path : _include_paths) + if (std::filesystem::exists(file_path = include_path / file_name, ec)) + break; + + const std::string file_path_string = file_path.u8string(); + + // Detect recursive include and abort to avoid infinite loop + if (std::find_if(_input_stack.begin(), _input_stack.end(), + [&file_path_string](const input_level &level) { return level.name == file_path_string; }) != _input_stack.end()) + return error(_token.location, "recursive #include"); + + std::string input; + if (const auto it = _file_cache.find(file_path_string); it != _file_cache.end()) + { + input = it->second; + } + else + { + if (!read_file(file_path, input)) + return error(keyword_location, "could not open included file '" + file_name.u8string() + '\''); + + _file_cache.emplace(file_path_string, input); + } + + // Skip end of line character following the include statement before pushing, so that the line number is already pointing to the next line when popping out of it again + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + + // Clear out input stack before pushing include, so that hidden macros do not bleed into the include + while (_input_stack.size() > (_next_input_index + 1)) + _input_stack.pop_back(); + + push(std::move(input), file_path_string); +} + +bool reshadefx::preprocessor::evaluate_expression() +{ + struct rpn_token + { + int value; + bool is_op; + }; + + size_t rpn_index = 0; + size_t stack_index = 0; + const size_t STACK_SIZE = 128; + rpn_token rpn[STACK_SIZE]; + int stack[STACK_SIZE]; + + // Keep track of previous token to figure out data type of expression + tokenid previous_token = _token; + + // Run shunting-yard algorithm + while (!peek(tokenid::end_of_line) && !peek(tokenid::end_of_file)) + { + if (stack_index >= STACK_SIZE || rpn_index >= STACK_SIZE) + return error(_token.location, "expression evaluator ran out of stack space"), false; + + consume(); + + auto op = op_none; + bool left_associative = true; + bool parenthesis_matched = false; + + switch (_token) + { + case tokenid::space: + continue; + case tokenid::exclaim: + op = op_not; + left_associative = false; + break; + case tokenid::percent: + op = op_modulo; + break; + case tokenid::ampersand: + op = op_bitand; + break; + case tokenid::star: + op = op_multiply; + break; + case tokenid::plus: + left_associative = + previous_token == tokenid::int_literal || + previous_token == tokenid::uint_literal || + previous_token == tokenid::identifier || + previous_token == tokenid::parenthesis_close; + op = left_associative ? op_add : op_plus; + break; + case tokenid::minus: + left_associative = + previous_token == tokenid::int_literal || + previous_token == tokenid::uint_literal || + previous_token == tokenid::identifier || + previous_token == tokenid::parenthesis_close; + op = left_associative ? op_subtract : op_negate; + break; + case tokenid::slash: + op = op_divide; + break; + case tokenid::less: + op = op_less; + break; + case tokenid::greater: + op = op_greater; + break; + case tokenid::caret: + op = op_bitxor; + break; + case tokenid::pipe: + op = op_bitor; + break; + case tokenid::tilde: + op = op_bitnot; + left_associative = false; + break; + case tokenid::exclaim_equal: + op = op_not_equal; + break; + case tokenid::ampersand_ampersand: + op = op_and; + break; + case tokenid::less_less: + op = op_leftshift; + break; + case tokenid::less_equal: + op = op_less_equal; + break; + case tokenid::equal_equal: + op = op_equal; + break; + case tokenid::greater_greater: + op = op_rightshift; + break; + case tokenid::greater_equal: + op = op_greater_equal; + break; + case tokenid::pipe_pipe: + op = op_or; + break; + default: + // This is not an operator token + break; + } + + switch (_token) + { + case tokenid::parenthesis_open: + stack[stack_index++] = op_parentheses; + break; + case tokenid::parenthesis_close: + parenthesis_matched = false; + while (stack_index > 0) + { + const int op2 = stack[--stack_index]; + if (op2 == op_parentheses) + { + parenthesis_matched = true; + break; + } + + rpn[rpn_index++] = { op2, true }; + } + + if (!parenthesis_matched) + return error(_token.location, "unmatched ')'"), false; + break; + case tokenid::identifier: + if (evaluate_identifier_as_macro()) + continue; + + if (_token.literal_as_string == "exists") + { + const bool has_parentheses = accept(tokenid::parenthesis_open); + + while (accept(tokenid::identifier)) + { + if (!evaluate_identifier_as_macro()) + { + error(_token.location, "syntax error: unexpected identifier after 'exists'"); + return false; + } + } + + if (!expect(tokenid::string_literal)) + return false; + + std::filesystem::path file_name = std::filesystem::u8path(_token.literal_as_string); + std::filesystem::path file_path = std::filesystem::u8path(_output_location.source); + file_path.replace_filename(file_name); + + if (has_parentheses && !expect(tokenid::parenthesis_close)) + return false; + + std::error_code ec; + if (!std::filesystem::exists(file_path, ec)) + for (const std::filesystem::path &include_path : _include_paths) + if (std::filesystem::exists(file_path = include_path / file_name, ec)) + break; + + rpn[rpn_index++] = { std::filesystem::exists(file_path, ec) ? 1 : 0, false }; + continue; + } + if (_token.literal_as_string == "defined") + { + const bool has_parentheses = accept(tokenid::parenthesis_open); + + if (!expect(tokenid::identifier)) + return false; + + const std::string macro_name = std::move(_token.literal_as_string); + + if (has_parentheses && !expect(tokenid::parenthesis_close)) + return false; + + rpn[rpn_index++] = { is_defined(macro_name) ? 1 : 0, false }; + continue; + } + + // An identifier that cannot be replaced with a number becomes zero + rpn[rpn_index++] = { 0, false }; + break; + case tokenid::int_literal: + case tokenid::uint_literal: + rpn[rpn_index++] = { _token.literal_as_int, false }; + break; + default: + if (op == op_none) + return error(_token.location, "invalid expression"), false; + + while (stack_index > 0) + { + const int prev_op = stack[stack_index - 1]; + if (prev_op == op_parentheses) + break; + + if (left_associative ? + (precedence_lookup[op] > precedence_lookup[prev_op]) : + (precedence_lookup[op] >= precedence_lookup[prev_op])) + break; + + stack_index--; + rpn[rpn_index++] = { prev_op, true }; + } + + stack[stack_index++] = op; + break; + } + + previous_token = _token; + } + + while (stack_index > 0) + { + const int op = stack[--stack_index]; + if (op == op_parentheses) + return error(_token.location, "unmatched ')'"), false; + + rpn[rpn_index++] = { op, true }; + } + +#define UNARY_OPERATION(op) { \ + if (stack_index < 1) \ + return error(_token.location, "invalid expression"), 0; \ + stack[stack_index - 1] = op stack[stack_index - 1]; \ + } +#define BINARY_OPERATION(op) { \ + if (stack_index < 2) \ + return error(_token.location, "invalid expression"), 0; \ + stack[stack_index - 2] = stack[stack_index - 2] op stack[stack_index - 1]; \ + stack_index--; \ + } + + // Evaluate reverse polish notation output + for (rpn_token *token = rpn; rpn_index--; token++) + { + if (token->is_op) + { + switch (token->value) + { + case op_or: + BINARY_OPERATION(||); + break; + case op_and: + BINARY_OPERATION(&&); + break; + case op_bitor: + BINARY_OPERATION(|); + break; + case op_bitxor: + BINARY_OPERATION(^); + break; + case op_bitand: + BINARY_OPERATION(&); + break; + case op_not_equal: + BINARY_OPERATION(!=); + break; + case op_equal: + BINARY_OPERATION(==); + break; + case op_less: + BINARY_OPERATION(<); + break; + case op_greater: + BINARY_OPERATION(>); + break; + case op_less_equal: + BINARY_OPERATION(<=); + break; + case op_greater_equal: + BINARY_OPERATION(>=); + break; + case op_leftshift: + BINARY_OPERATION(<<); + break; + case op_rightshift: + BINARY_OPERATION(>>); + break; + case op_add: + BINARY_OPERATION(+); + break; + case op_subtract: + BINARY_OPERATION(-); + break; + case op_modulo: + BINARY_OPERATION(%); + break; + case op_divide: + BINARY_OPERATION(/); + break; + case op_multiply: + BINARY_OPERATION(*); + break; + case op_plus: + UNARY_OPERATION(+); + break; + case op_negate: + UNARY_OPERATION(-); + break; + case op_not: + UNARY_OPERATION(!); + break; + case op_bitnot: + UNARY_OPERATION(~); + break; + } + } + else + { + stack[stack_index++] = token->value; + } + } + + if (stack_index != 1) + return error(_token.location, "invalid expression"), false; + + return stack[0] != 0; +} + +bool reshadefx::preprocessor::evaluate_identifier_as_macro() +{ + if (_token.literal_as_string == "__LINE__") + { + push(std::to_string(_token.location.line)); + return true; + } + if (_token.literal_as_string == "__FILE__") + { + push(escape_string(_token.location.source)); + return true; + } + if (_token.literal_as_string == "__FILE_STEM__") + { + const std::filesystem::path file_stem = std::filesystem::u8path(_token.location.source).stem(); + push(escape_string(file_stem.u8string())); + return true; + } + if (_token.literal_as_string == "__FILE_NAME__") + { + const std::filesystem::path file_name = std::filesystem::u8path(_token.location.source).filename(); + push(escape_string(file_name.u8string())); + return true; + } + + const auto it = _macros.find(_token.literal_as_string); + if (it == _macros.end()) + return false; + + if (!_input_stack.empty()) + { + const std::unordered_set &hidden_macros = _input_stack[_current_input_index].hidden_macros; + if (hidden_macros.find(_token.literal_as_string) != hidden_macros.end()) + return false; + } + + const location macro_location = _token.location; + if (_recursion_count++ >= 256) + return error(macro_location, "macro recursion too high"), false; + + std::vector arguments; + if (it->second.is_function_like) + { + if (!accept(tokenid::parenthesis_open)) + return false; // Function like macro used without arguments, handle that like a normal identifier instead + + while (true) + { + int parentheses_level = 0; + std::string argument; + + // Ignore whitespace preceding the argument + accept(tokenid::space); + + if (accept(tokenid::parenthesis_close)) + break; // Special case for when there are no arguments + + while (true) + { + if (peek(tokenid::end_of_file)) + return error(macro_location, "unexpected end of file in macro expansion"), false; + + // Consume all tokens of the argument + consume(); + + if (_token == tokenid::comma && parentheses_level == 0 && !(it->second.is_variadic && arguments.size() == it->second.parameters.size())) + break; // Comma marks end of an argument (unless this is the last argument in a variadic macro invocation) + if (_token == tokenid::parenthesis_open) + parentheses_level++; + if (_token == tokenid::parenthesis_close && --parentheses_level < 0) + break; + + // Collapse all whitespace down to a single space + if (_token == tokenid::space) + argument += ' '; + else + argument += _current_token_raw_data; + } + + // Trim whitespace following the argument + if (argument.size() && argument.back() == ' ') + argument.pop_back(); + + arguments.push_back(std::move(argument)); + + if (parentheses_level < 0) + break; + } + } + + expand_macro(it->first, it->second, arguments); + + return true; +} + +bool reshadefx::preprocessor::is_defined(const std::string &name) const +{ + return _macros.find(name) != _macros.end() || + // Check built-in macros as well + name == "__LINE__" || + name == "__FILE__" || + name == "__FILE_NAME__" || + name == "__FILE_STEM__"; +} + +void reshadefx::preprocessor::expand_macro(const std::string &name, const macro ¯o, const std::vector &arguments) +{ + if (macro.replacement_list.empty()) + return; + + // Verify argument count for function-like macros + if (arguments.size() < macro.parameters.size()) + return warning(_token.location, "not enough arguments for function-like macro invocation '" + name + "'"); + if (arguments.size() > macro.parameters.size() && !macro.is_variadic) + return warning(_token.location, "too many arguments for function-like macro invocation '" + name + "'"); + + std::string input; + input.reserve(macro.replacement_list.size()); + + for (size_t offset = 0; offset < macro.replacement_list.size(); ++offset) + { + if (macro.replacement_list[offset] != macro_replacement_start) + { + input += macro.replacement_list[offset]; + continue; + } + + // This is a special replacement sequence + const char type = macro.replacement_list[++offset]; + const char index = macro.replacement_list[++offset]; + if (static_cast(index) >= arguments.size()) + { + if (macro.is_variadic) + { + // The concatenation operator has a special meaning when placed between a comma and a variable argument, deleting the preceding comma + if (type == macro_replacement_concat && input.back() == ',') + input.pop_back(); + if (type == macro_replacement_stringize) + input += "\"\""; + } + continue; + } + + switch (type) + { + case macro_replacement_argument: + // Argument prescan + push(arguments[index] + static_cast(macro_replacement_argument)); + while (true) + { + // Consume all tokens of the argument (until the end marker is reached) + consume(); + + if (_token == tokenid::unknown) // 'macro_replacement_argument' is 'tokenid::unknown' + break; + if (_token == tokenid::identifier && evaluate_identifier_as_macro()) + continue; + + input += _current_token_raw_data; + } + assert(_current_token_raw_data[0] == macro_replacement_argument); + break; + case macro_replacement_concat: + input += arguments[index]; + break; + case macro_replacement_stringize: + // Adds backslashes to escape quotes + input += escape_string<'\"'>(arguments[index]); + break; + } + } + + push(std::move(input)); + + // Avoid expanding macros again that are referencing themselves + _input_stack[_current_input_index].hidden_macros.insert(name); +} + +void reshadefx::preprocessor::create_macro_replacement_list(macro ¯o) +{ + // Since the number of parameters is encoded in the string, it may not exceed the available size of a char + if (macro.parameters.size() >= std::numeric_limits::max()) + return error(_token.location, "too many macro parameters"); + + // Ignore whitespace preceding the replacement list + accept(tokenid::space); + + bool next_concat = false; + + while (!peek(tokenid::end_of_line) && !peek(tokenid::end_of_file)) + { + consume(); + + switch (_token) + { + case tokenid::hash: + if (accept(tokenid::hash, false)) + { + if (macro.replacement_list.empty()) + return error(_token.location, "## cannot appear at start of macro expansion"); + if (peek(tokenid::end_of_line)) + return error(_token.location, "## cannot appear at end of macro expansion"); + + // Remove any whitespace preceding or following the concatenation operator (so "a ## b" becomes "ab") + if (macro.replacement_list.back() == ' ') + macro.replacement_list.pop_back(); + accept(tokenid::space); + + // Disable macro expansion for any argument preceding or following the ## token concatenation operator + if (macro.replacement_list.size() > 2 && macro.replacement_list[macro.replacement_list.size() - 2] == macro_replacement_argument) + macro.replacement_list[macro.replacement_list.size() - 2] = macro_replacement_concat; + next_concat = true; + continue; + } + if (macro.is_function_like) + { + if (!expect(tokenid::identifier)) + return; + + const auto it = std::find(macro.parameters.begin(), macro.parameters.end(), _token.literal_as_string); + if (it == macro.parameters.end() && !(macro.is_variadic && _token.literal_as_string == "__VA_ARGS__")) + return error(_token.location, "# must be followed by parameter name"); + + // Start a # stringize operator + macro.replacement_list += macro_replacement_start; + macro.replacement_list += macro_replacement_stringize; + macro.replacement_list += static_cast(std::distance(macro.parameters.begin(), it)); + next_concat = false; + continue; + } + break; + case tokenid::space: + // Collapse all whitespace down to a single space + macro.replacement_list += ' '; + continue; + case tokenid::minus: + // Special case to handle things like "#define NUM -1\n -NUM", which would otherwise result in "--1", making parsing fail + if (macro.replacement_list.empty()) + macro.replacement_list += ' '; + break; + case tokenid::identifier: + if (const auto it = std::find(macro.parameters.begin(), macro.parameters.end(), _token.literal_as_string); + it != macro.parameters.end() || (macro.is_variadic && _token.literal_as_string == "__VA_ARGS__")) + { + macro.replacement_list += macro_replacement_start; + macro.replacement_list += static_cast(next_concat ? macro_replacement_concat : macro_replacement_argument); + macro.replacement_list += static_cast(std::distance(macro.parameters.begin(), it)); + next_concat = false; + continue; + } + break; + default: + // Token needs no special handling, raw data is added to macro below + break; + } + + macro.replacement_list += _current_token_raw_data; + next_concat = false; + } + + // Trim whitespace following the replacement list + if (macro.replacement_list.size() && macro.replacement_list.back() == ' ') + macro.replacement_list.pop_back(); +} diff --git a/dep/reshadefx/src/effect_symbol_table.cpp b/dep/reshadefx/src/effect_symbol_table.cpp new file mode 100644 index 000000000..291d55396 --- /dev/null +++ b/dep/reshadefx/src/effect_symbol_table.cpp @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "effect_symbol_table.hpp" +#include +#include // alloca +#include // std::upper_bound, std::sort +#include // std::greater + +enum class intrinsic_id : uint32_t +{ +#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) name##i, + #include "effect_symbol_table_intrinsics.inl" +}; + +struct intrinsic +{ + intrinsic(const char *name, intrinsic_id id, const reshadefx::type &ret_type, std::initializer_list arg_types) : id(id) + { + function.name = name; + function.return_type = ret_type; + function.parameter_list.reserve(arg_types.size()); + for (const reshadefx::type &arg_type : arg_types) + function.parameter_list.push_back({ arg_type }); + } + + intrinsic_id id; + reshadefx::function_info function; +}; + +#define void { reshadefx::type::t_void } +#define bool { reshadefx::type::t_bool, 1, 1 } +#define bool2 { reshadefx::type::t_bool, 2, 1 } +#define bool3 { reshadefx::type::t_bool, 3, 1 } +#define bool4 { reshadefx::type::t_bool, 4, 1 } +#define int { reshadefx::type::t_int, 1, 1 } +#define int2 { reshadefx::type::t_int, 2, 1 } +#define int3 { reshadefx::type::t_int, 3, 1 } +#define int4 { reshadefx::type::t_int, 4, 1 } +#define int2x3 { reshadefx::type::t_int, 2, 3 } +#define int2x2 { reshadefx::type::t_int, 2, 2 } +#define int2x4 { reshadefx::type::t_int, 2, 4 } +#define int3x2 { reshadefx::type::t_int, 3, 2 } +#define int3x3 { reshadefx::type::t_int, 3, 3 } +#define int3x4 { reshadefx::type::t_int, 3, 4 } +#define int4x2 { reshadefx::type::t_int, 4, 2 } +#define int4x3 { reshadefx::type::t_int, 4, 3 } +#define int4x4 { reshadefx::type::t_int, 4, 4 } +#define out_int { reshadefx::type::t_int, 1, 1, reshadefx::type::q_out } +#define out_int2 { reshadefx::type::t_int, 2, 1, reshadefx::type::q_out } +#define out_int3 { reshadefx::type::t_int, 3, 1, reshadefx::type::q_out } +#define out_int4 { reshadefx::type::t_int, 4, 1, reshadefx::type::q_out } +#define inout_int { reshadefx::type::t_int, 1, 1, reshadefx::type::q_inout | reshadefx::type::q_groupshared } +#define uint { reshadefx::type::t_uint, 1, 1 } +#define uint2 { reshadefx::type::t_uint, 2, 1 } +#define uint3 { reshadefx::type::t_uint, 3, 1 } +#define uint4 { reshadefx::type::t_uint, 4, 1 } +#define inout_uint { reshadefx::type::t_uint, 1, 1, reshadefx::type::q_inout | reshadefx::type::q_groupshared } +#define float { reshadefx::type::t_float, 1, 1 } +#define float2 { reshadefx::type::t_float, 2, 1 } +#define float3 { reshadefx::type::t_float, 3, 1 } +#define float4 { reshadefx::type::t_float, 4, 1 } +#define float2x3 { reshadefx::type::t_float, 2, 3 } +#define float2x2 { reshadefx::type::t_float, 2, 2 } +#define float2x4 { reshadefx::type::t_float, 2, 4 } +#define float3x2 { reshadefx::type::t_float, 3, 2 } +#define float3x3 { reshadefx::type::t_float, 3, 3 } +#define float3x4 { reshadefx::type::t_float, 3, 4 } +#define float4x2 { reshadefx::type::t_float, 4, 2 } +#define float4x3 { reshadefx::type::t_float, 4, 3 } +#define float4x4 { reshadefx::type::t_float, 4, 4 } +#define out_float { reshadefx::type::t_float, 1, 1, reshadefx::type::q_out } +#define out_float2 { reshadefx::type::t_float, 2, 1, reshadefx::type::q_out } +#define out_float3 { reshadefx::type::t_float, 3, 1, reshadefx::type::q_out } +#define out_float4 { reshadefx::type::t_float, 4, 1, reshadefx::type::q_out } +#define sampler1d_int { reshadefx::type::t_sampler1d_int, 1, 1 } +#define sampler2d_int { reshadefx::type::t_sampler2d_int, 1, 1 } +#define sampler3d_int { reshadefx::type::t_sampler3d_int, 1, 1 } +#define sampler1d_uint { reshadefx::type::t_sampler1d_uint, 1, 1 } +#define sampler2d_uint { reshadefx::type::t_sampler2d_uint, 1, 1 } +#define sampler3d_uint { reshadefx::type::t_sampler3d_uint, 1, 1 } +#define sampler1d_float { reshadefx::type::t_sampler1d_float, 1, 1 } +#define sampler2d_float { reshadefx::type::t_sampler2d_float, 1, 1 } +#define sampler3d_float { reshadefx::type::t_sampler3d_float, 1, 1 } +#define sampler1d_float4 { reshadefx::type::t_sampler1d_float, 4, 1 } +#define sampler2d_float4 { reshadefx::type::t_sampler2d_float, 4, 1 } +#define sampler3d_float4 { reshadefx::type::t_sampler3d_float, 4, 1 } +#define storage1d_int { reshadefx::type::t_storage1d_int, 1, 1 } +#define storage2d_int { reshadefx::type::t_storage2d_int, 1, 1 } +#define storage3d_int { reshadefx::type::t_storage3d_int, 1, 1 } +#define storage1d_uint { reshadefx::type::t_storage1d_uint, 1, 1 } +#define storage2d_uint { reshadefx::type::t_storage2d_uint, 1, 1 } +#define storage3d_uint { reshadefx::type::t_storage3d_uint, 1, 1 } +#define storage1d_float { reshadefx::type::t_storage1d_float, 1, 1 } +#define storage2d_float { reshadefx::type::t_storage2d_float, 1, 1 } +#define storage3d_float { reshadefx::type::t_storage3d_float, 1, 1 } +#define storage1d_float4 { reshadefx::type::t_storage1d_float, 4, 1 } +#define storage2d_float4 { reshadefx::type::t_storage2d_float, 4, 1 } +#define storage3d_float4 { reshadefx::type::t_storage3d_float, 4, 1 } +#define inout_storage1d_int { reshadefx::type::t_storage1d_int, 1, 1, reshadefx::type::q_inout } +#define inout_storage2d_int { reshadefx::type::t_storage2d_int, 1, 1, reshadefx::type::q_inout } +#define inout_storage3d_int { reshadefx::type::t_storage3d_int, 1, 1, reshadefx::type::q_inout } +#define inout_storage1d_uint { reshadefx::type::t_storage1d_uint, 1, 1, reshadefx::type::q_inout } +#define inout_storage2d_uint { reshadefx::type::t_storage2d_uint, 1, 1, reshadefx::type::q_inout } +#define inout_storage3d_uint { reshadefx::type::t_storage3d_uint, 1, 1, reshadefx::type::q_inout } + +// Import intrinsic function definitions +static const intrinsic s_intrinsics[] = +{ +#define DEFINE_INTRINSIC(name, i, ret_type, ...) intrinsic(#name, intrinsic_id::name##i, ret_type, { __VA_ARGS__ }), + #include "effect_symbol_table_intrinsics.inl" +}; + +#undef void +#undef bool +#undef bool2 +#undef bool3 +#undef bool4 +#undef int +#undef int2 +#undef int3 +#undef int4 +#undef uint +#undef uint2 +#undef uint3 +#undef uint4 +#undef float1 +#undef float2 +#undef float3 +#undef float4 +#undef float2x2 +#undef float3x3 +#undef float4x4 +#undef out_float +#undef out_float2 +#undef out_float3 +#undef out_float4 +#undef sampler1d_int +#undef sampler2d_int +#undef sampler3d_int +#undef sampler1d_uint +#undef sampler2d_uint +#undef sampler3d_uint +#undef sampler1d_float4 +#undef sampler2d_float4 +#undef sampler3d_float4 +#undef storage1d_int +#undef storage2d_int +#undef storage3d_int +#undef storage1d_uint +#undef storage2d_uint +#undef storage3d_uint +#undef storage1d_float4 +#undef storage2d_float4 +#undef storage3d_float4 +#undef inout_storage1d_int +#undef inout_storage2d_int +#undef inout_storage3d_int +#undef inout_storage1d_uint +#undef inout_storage2d_uint +#undef inout_storage3d_uint + +unsigned int reshadefx::type::rank(const type &src, const type &dst) +{ + if (src.is_array() != dst.is_array() || (src.array_length != dst.array_length && src.array_length > 0 && dst.array_length > 0)) + return 0; // Arrays of different sizes are not compatible + if (src.is_struct() || dst.is_struct()) + return src.definition == dst.definition ? 32 : 0; // Structs are only compatible if they are the same type + if (!src.is_numeric() || !dst.is_numeric()) + return src.base == dst.base && src.rows == dst.rows && src.cols == dst.cols ? 32 : 0; // Numeric values are not compatible with other types + if (src.is_matrix() && (!dst.is_matrix() || src.rows != dst.rows || src.cols != dst.cols)) + return 0; // Matrix truncation or dimensions do not match + + // This table is based on the following rules: + // - Floating point has a higher rank than integer types + // - Integer to floating point promotion has a higher rank than floating point to integer conversion + // - Signed to unsigned integer conversion has a higher rank than unsigned to signed integer conversion + static const int ranks[7][7] = { + { 5, 4, 4, 4, 4, 4, 4 }, // bool + { 3, 5, 5, 2, 2, 4, 4 }, // min16int + { 3, 5, 5, 2, 2, 4, 4 }, // int + { 3, 1, 1, 5, 5, 4, 4 }, // min16uint + { 3, 1, 1, 5, 5, 4, 4 }, // uint + { 3, 3, 3, 3, 3, 6, 6 }, // min16float + { 3, 3, 3, 3, 3, 6, 6 } // float + }; + + assert(src.base > 0 && src.base <= 7); // bool - float + assert(dst.base > 0 && dst.base <= 7); + + const int rank = ranks[src.base - 1][dst.base - 1] << 2; + + if ((src.is_scalar() && dst.is_vector())) + return rank >> 1; // Scalar to vector promotion has a lower rank + if ((src.is_vector() && dst.is_scalar()) || (src.is_vector() == dst.is_vector() && src.rows > dst.rows && src.cols >= dst.cols)) + return rank >> 2; // Vector to scalar conversion has an even lower rank + if ((src.is_vector() != dst.is_vector()) || src.is_matrix() != dst.is_matrix() || src.components() != dst.components()) + return 0; // If components weren't converted at this point, the types are not compatible + + return rank * src.components(); // More components causes a higher rank +} + +reshadefx::symbol_table::symbol_table() +{ + _current_scope.name = "::"; + _current_scope.level = 0; + _current_scope.namespace_level = 0; +} + +void reshadefx::symbol_table::enter_scope() +{ + _current_scope.level++; +} +void reshadefx::symbol_table::enter_namespace(const std::string &name) +{ + _current_scope.name += name + "::"; + _current_scope.level++; + _current_scope.namespace_level++; +} +void reshadefx::symbol_table::leave_scope() +{ + assert(_current_scope.level > 0); + + for (auto &symbol : _symbol_stack) + { + std::vector &scope_list = symbol.second; + + for (auto scope_it = scope_list.begin(); scope_it != scope_list.end();) + { + if (scope_it->scope.level > scope_it->scope.namespace_level && + scope_it->scope.level >= _current_scope.level) + { + scope_it = scope_list.erase(scope_it); + } + else + { + ++scope_it; + } + } + } + + _current_scope.level--; +} +void reshadefx::symbol_table::leave_namespace() +{ + assert(_current_scope.level > 0); + assert(_current_scope.namespace_level > 0); + + _current_scope.name.erase(_current_scope.name.substr(0, _current_scope.name.size() - 2).rfind("::") + 2); + _current_scope.level--; + _current_scope.namespace_level--; +} + +bool reshadefx::symbol_table::insert_symbol(const std::string &name, const symbol &symbol, bool global) +{ + assert(symbol.id != 0 || symbol.op == symbol_type::constant); + + // Make sure the symbol does not exist yet + if (symbol.op != symbol_type::function && find_symbol(name, _current_scope, true).id != 0) + return false; + + // Insertion routine which keeps the symbol stack sorted by namespace level + const auto insert_sorted = [](auto &vec, const auto &item) { + return vec.insert( + std::upper_bound(vec.begin(), vec.end(), item, + [](auto lhs, auto rhs) { + return lhs.scope.namespace_level < rhs.scope.namespace_level; + }), item); + }; + + // Global symbols are accessible from every scope + if (global) + { + scope scope = { "", 0, 0 }; + + // Walk scope chain from global scope back to current one + for (size_t pos = 0; pos != std::string::npos; pos = _current_scope.name.find("::", pos)) + { + // Extract scope name + scope.name = _current_scope.name.substr(0, pos += 2); + const auto previous_scope_name = _current_scope.name.substr(pos); + + // Insert symbol into this scope + insert_sorted(_symbol_stack[previous_scope_name + name], scoped_symbol { symbol, scope }); + + // Continue walking up the scope chain + scope.level = ++scope.namespace_level; + } + } + else + { + // This is a local symbol so it's sufficient to update the symbol stack with just the current scope + insert_sorted(_symbol_stack[name], scoped_symbol { symbol, _current_scope }); + } + + return true; +} + +reshadefx::scoped_symbol reshadefx::symbol_table::find_symbol(const std::string &name) const +{ + // Default to start search with current scope and walk back the scope chain + return find_symbol(name, _current_scope, false); +} +reshadefx::scoped_symbol reshadefx::symbol_table::find_symbol(const std::string &name, const scope &scope, bool exclusive) const +{ + const auto stack_it = _symbol_stack.find(name); + + // Check if symbol does exist + if (stack_it == _symbol_stack.end() || stack_it->second.empty()) + return {}; + + // Walk up the scope chain starting at the requested scope level and find a matching symbol + scoped_symbol result = {}; + + for (auto it = stack_it->second.rbegin(), end = stack_it->second.rend(); it != end; ++it) + { + if (it->scope.level > scope.level || + it->scope.namespace_level > scope.namespace_level || (it->scope.namespace_level == scope.namespace_level && it->scope.name != scope.name)) + continue; + if (exclusive && it->scope.level < scope.level) + continue; + + if (it->op == symbol_type::constant || it->op == symbol_type::variable || it->op == symbol_type::structure) + return *it; // Variables and structures have the highest priority and are always picked immediately + else if (result.id == 0) + result = *it; // Function names have a lower priority, so continue searching in case a variable with the same name exists + } + + return result; +} + +static int compare_functions(const std::vector &arguments, const reshadefx::function_info *function1, const reshadefx::function_info *function2) +{ + const size_t num_arguments = arguments.size(); + + // Check if the first function matches the argument types + bool function1_viable = true; + const auto function1_ranks = static_cast(alloca(num_arguments * sizeof(unsigned int))); + for (size_t i = 0; i < num_arguments; ++i) + { + if ((function1_ranks[i] = reshadefx::type::rank(arguments[i].type, function1->parameter_list[i].type)) == 0) + { + function1_viable = false; + break; + } + } + + // Catch case where the second function does not exist + if (function2 == nullptr) + return function1_viable ? -1 : 1; // If the first function is not viable, this compare fails + + // Check if the second function matches the argument types + bool function2_viable = true; + const auto function2_ranks = static_cast(alloca(num_arguments * sizeof(unsigned int))); + for (size_t i = 0; i < num_arguments; ++i) + { + if ((function2_ranks[i] = reshadefx::type::rank(arguments[i].type, function2->parameter_list[i].type)) == 0) + { + function2_viable = false; + break; + } + } + + // If one of the functions is not viable, then the other one automatically wins + if (!function1_viable || !function2_viable) + return function2_viable - function1_viable; + + // Both functions are possible, so find the one with the higher ranking + std::sort(function1_ranks, function1_ranks + num_arguments, std::greater()); + std::sort(function2_ranks, function2_ranks + num_arguments, std::greater()); + + for (size_t i = 0; i < num_arguments; ++i) + if (function1_ranks[i] > function2_ranks[i]) + return -1; // Left function wins + else if (function2_ranks[i] > function1_ranks[i]) + return +1; // Right function wins + + return 0; // Both functions are equally viable +} + +bool reshadefx::symbol_table::resolve_function_call(const std::string &name, const std::vector &arguments, const scope &scope, symbol &out_data, bool &is_ambiguous) const +{ + out_data.op = symbol_type::function; + + const function_info *result = nullptr; + unsigned int num_overloads = 0; + unsigned int overload_namespace = scope.namespace_level; + + // Look up function name in the symbol stack and loop through the associated symbols + const auto stack_it = _symbol_stack.find(name); + + if (stack_it != _symbol_stack.end() && !stack_it->second.empty()) + { + for (auto it = stack_it->second.rbegin(), end = stack_it->second.rend(); it != end; ++it) + { + if (it->op != symbol_type::function) + continue; + if (it->scope.level > scope.level || + it->scope.namespace_level > scope.namespace_level || (it->scope.namespace_level == scope.namespace_level && it->scope.name != scope.name)) + continue; + + const function_info *const function = it->function; + + if (function == nullptr) + continue; + + if (function->parameter_list.empty()) + { + if (arguments.empty()) + { + out_data.id = it->id; + out_data.type = function->return_type; + out_data.function = result = function; + num_overloads = 1; + break; + } + else + { + continue; + } + } + else if (arguments.size() != function->parameter_list.size()) + { + continue; + } + + // A new possibly-matching function was found, compare it against the current result + const int comparison = compare_functions(arguments, function, result); + + if (comparison < 0) // The new function is a better match + { + out_data.id = it->id; + out_data.type = function->return_type; + out_data.function = result = function; + num_overloads = 1; + overload_namespace = it->scope.namespace_level; + } + else if (comparison == 0 && overload_namespace == it->scope.namespace_level) // Both functions are equally viable, so the call is ambiguous + { + ++num_overloads; + } + } + } + + // Try matching against intrinsic functions if no matching user-defined function was found up to this point + if (num_overloads == 0) + { + for (const intrinsic &intrinsic : s_intrinsics) + { + if (intrinsic.function.name != name || intrinsic.function.parameter_list.size() != arguments.size()) + continue; + + // A new possibly-matching intrinsic function was found, compare it against the current result + const int comparison = compare_functions(arguments, &intrinsic.function, result); + + if (comparison < 0) // The new function is a better match + { + out_data.op = symbol_type::intrinsic; + out_data.id = static_cast(intrinsic.id); + out_data.type = intrinsic.function.return_type; + out_data.function = &intrinsic.function; + result = out_data.function; + num_overloads = 1; + } + else if (comparison == 0 && overload_namespace == 0) // Both functions are equally viable, so the call is ambiguous (intrinsics are always in the global namespace) + { + ++num_overloads; + } + } + } + + is_ambiguous = num_overloads > 1; + + return num_overloads == 1; +} diff --git a/dep/reshadefx/src/effect_symbol_table_intrinsics.inl b/dep/reshadefx/src/effect_symbol_table_intrinsics.inl new file mode 100644 index 000000000..c15937e84 --- /dev/null +++ b/dep/reshadefx/src/effect_symbol_table_intrinsics.inl @@ -0,0 +1,4196 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#if defined(__INTELLISENSE__) || !defined(DEFINE_INTRINSIC) +#define DEFINE_INTRINSIC(name, i, ret_type, ...) +#endif +#if defined(__INTELLISENSE__) || !defined(IMPLEMENT_INTRINSIC_GLSL) +#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) +#endif +#if defined(__INTELLISENSE__) || !defined(IMPLEMENT_INTRINSIC_HLSL) +#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) +#endif +#if defined(__INTELLISENSE__) || !defined(IMPLEMENT_INTRINSIC_SPIRV) +#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) +#endif + +// ret abs(x) +DEFINE_INTRINSIC(abs, 0, int, int) +DEFINE_INTRINSIC(abs, 0, int2, int2) +DEFINE_INTRINSIC(abs, 0, int3, int3) +DEFINE_INTRINSIC(abs, 0, int4, int4) +DEFINE_INTRINSIC(abs, 1, float, float) +DEFINE_INTRINSIC(abs, 1, float2, float2) +DEFINE_INTRINSIC(abs, 1, float3, float3) +DEFINE_INTRINSIC(abs, 1, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(abs, 0, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(abs, 1, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(abs, 0, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(abs, 1, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(abs, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SAbs) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(abs, 1, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FAbs) + .add(args[0].base) + .result; + }) + +// ret all(x) +DEFINE_INTRINSIC(all, 0, bool, bool) +DEFINE_INTRINSIC(all, 1, bool, bool2) +DEFINE_INTRINSIC(all, 1, bool, bool3) +DEFINE_INTRINSIC(all, 1, bool, bool4) +IMPLEMENT_INTRINSIC_GLSL(all, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_GLSL(all, 1, { + code += "all(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(all, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_HLSL(all, 1, { + code += "all(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(all, 0, { + return args[0].base; + }) +IMPLEMENT_INTRINSIC_SPIRV(all, 1, { + return + add_instruction(spv::OpAll, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret any(x) +DEFINE_INTRINSIC(any, 0, bool, bool) +DEFINE_INTRINSIC(any, 1, bool, bool2) +DEFINE_INTRINSIC(any, 1, bool, bool3) +DEFINE_INTRINSIC(any, 1, bool, bool4) +IMPLEMENT_INTRINSIC_GLSL(any, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_GLSL(any, 1, { + code += "any(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(any, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_HLSL(any, 1, { + code += "any(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(any, 0, { + return args[0].base; + }) +IMPLEMENT_INTRINSIC_SPIRV(any, 1, { + return + add_instruction(spv::OpAny, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret asin(x) +DEFINE_INTRINSIC(asin, 0, float, float) +DEFINE_INTRINSIC(asin, 0, float2, float2) +DEFINE_INTRINSIC(asin, 0, float3, float3) +DEFINE_INTRINSIC(asin, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(asin, 0, { + code += "asin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asin, 0, { + code += "asin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asin, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Asin) + .add(args[0].base) + .result; + }) + +// ret acos(x) +DEFINE_INTRINSIC(acos, 0, float, float) +DEFINE_INTRINSIC(acos, 0, float2, float2) +DEFINE_INTRINSIC(acos, 0, float3, float3) +DEFINE_INTRINSIC(acos, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(acos, 0, { + code += "acos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(acos, 0, { + code += "acos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(acos, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Acos) + .add(args[0].base) + .result; + }) + +// ret atan(x) +DEFINE_INTRINSIC(atan, 0, float, float) +DEFINE_INTRINSIC(atan, 0, float2, float2) +DEFINE_INTRINSIC(atan, 0, float3, float3) +DEFINE_INTRINSIC(atan, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(atan, 0, { + code += "atan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atan, 0, { + code += "atan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atan, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Atan) + .add(args[0].base) + .result; + }) + +// ret atan2(x, y) +DEFINE_INTRINSIC(atan2, 0, float, float, float) +DEFINE_INTRINSIC(atan2, 0, float2, float2, float2) +DEFINE_INTRINSIC(atan2, 0, float3, float3, float3) +DEFINE_INTRINSIC(atan2, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(atan2, 0, { + code += "atan(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atan2, 0, { + code += "atan2(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atan2, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Atan2) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret sin(x) +DEFINE_INTRINSIC(sin, 0, float, float) +DEFINE_INTRINSIC(sin, 0, float2, float2) +DEFINE_INTRINSIC(sin, 0, float3, float3) +DEFINE_INTRINSIC(sin, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sin, 0, { + code += "sin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sin, 0, { + code += "sin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sin, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sin) + .add(args[0].base) + .result; + }) + +// ret sinh(x) +DEFINE_INTRINSIC(sinh, 0, float, float) +DEFINE_INTRINSIC(sinh, 0, float2, float2) +DEFINE_INTRINSIC(sinh, 0, float3, float3) +DEFINE_INTRINSIC(sinh, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sinh, 0, { + code += "sinh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sinh, 0, { + code += "sinh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sinh, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sinh) + .add(args[0].base) + .result; + }) + +// ret cos(x) +DEFINE_INTRINSIC(cos, 0, float, float) +DEFINE_INTRINSIC(cos, 0, float2, float2) +DEFINE_INTRINSIC(cos, 0, float3, float3) +DEFINE_INTRINSIC(cos, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(cos, 0, { + code += "cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(cos, 0, { + code += "cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(cos, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cos) + .add(args[0].base) + .result; + }) + +// ret cosh(x) +DEFINE_INTRINSIC(cosh, 0, float, float) +DEFINE_INTRINSIC(cosh, 0, float2, float2) +DEFINE_INTRINSIC(cosh, 0, float3, float3) +DEFINE_INTRINSIC(cosh, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(cosh, 0, { + code += "cosh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(cosh, 0, { + code += "cosh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(cosh, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cosh) + .add(args[0].base) + .result; + }) + +// ret tan(x) +DEFINE_INTRINSIC(tan, 0, float, float) +DEFINE_INTRINSIC(tan, 0, float2, float2) +DEFINE_INTRINSIC(tan, 0, float3, float3) +DEFINE_INTRINSIC(tan, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(tan, 0, { + code += "tan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tan, 0, { + code += "tan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tan, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Tan) + .add(args[0].base) + .result; + }) + +// ret tanh(x) +DEFINE_INTRINSIC(tanh, 0, float, float) +DEFINE_INTRINSIC(tanh, 0, float2, float2) +DEFINE_INTRINSIC(tanh, 0, float3, float3) +DEFINE_INTRINSIC(tanh, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(tanh, 0, { + code += "tanh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tanh, 0, { + code += "tanh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tanh, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Tanh) + .add(args[0].base) + .result; + }) + +// sincos(x, out s, out c) +DEFINE_INTRINSIC(sincos, 0, void, float, out_float, out_float) +DEFINE_INTRINSIC(sincos, 0, void, float2, out_float2, out_float2) +DEFINE_INTRINSIC(sincos, 0, void, float3, out_float3, out_float3) +DEFINE_INTRINSIC(sincos, 0, void, float4, out_float4, out_float4) +IMPLEMENT_INTRINSIC_GLSL(sincos, 0, { + code += id_to_name(args[1].base) + " = sin(" + id_to_name(args[0].base) + "), " + id_to_name(args[2].base) + " = cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sincos, 0, { + code += "sincos(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sincos, 0, { + const spv::Id sin_result = add_instruction(spv::OpExtInst, convert_type(args[0].type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sin) + .add(args[0].base) + .result; + const spv::Id cos_result = add_instruction(spv::OpExtInst, convert_type(args[0].type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cos) + .add(args[0].base) + .result; + + add_instruction_without_result(spv::OpStore) + .add(args[1].base) + .add(sin_result); + add_instruction_without_result(spv::OpStore) + .add(args[2].base) + .add(cos_result); + + return 0; + }) + +// ret asint(x) +DEFINE_INTRINSIC(asint, 0, int, float) +DEFINE_INTRINSIC(asint, 0, int2, float2) +DEFINE_INTRINSIC(asint, 0, int3, float3) +DEFINE_INTRINSIC(asint, 0, int4, float4) +IMPLEMENT_INTRINSIC_GLSL(asint, 0, { + code += "floatBitsToInt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asint, 0, { + _uses_bitwise_cast = true; + if (_shader_model < 40) + code += "__"; + code += "asint(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asint, 0, { + return + add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret asuint(x) +DEFINE_INTRINSIC(asuint, 0, uint, float) +DEFINE_INTRINSIC(asuint, 0, uint2, float2) +DEFINE_INTRINSIC(asuint, 0, uint3, float3) +DEFINE_INTRINSIC(asuint, 0, uint4, float4) +IMPLEMENT_INTRINSIC_GLSL(asuint, 0, { + code += "floatBitsToUint(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asuint, 0, { + _uses_bitwise_cast = true; + if (_shader_model < 40) + code += "__"; + code += "asuint(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asuint, 0, { + return + add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret asfloat(x) +DEFINE_INTRINSIC(asfloat, 0, float, int) +DEFINE_INTRINSIC(asfloat, 0, float2, int2) +DEFINE_INTRINSIC(asfloat, 0, float3, int3) +DEFINE_INTRINSIC(asfloat, 0, float4, int4) +DEFINE_INTRINSIC(asfloat, 1, float, uint) +DEFINE_INTRINSIC(asfloat, 1, float2, uint2) +DEFINE_INTRINSIC(asfloat, 1, float3, uint3) +DEFINE_INTRINSIC(asfloat, 1, float4, uint4) +IMPLEMENT_INTRINSIC_GLSL(asfloat, 0, { + code += "intBitsToFloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(asfloat, 1, { + code += "uintBitsToFloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asfloat, 0, { + _uses_bitwise_cast = true; + if (_shader_model < 40) + code += "__"; + code += "asfloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asfloat, 1, { + _uses_bitwise_cast = true; + if (_shader_model < 40) + code += "__"; + code += "asfloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asfloat, 0, { + return + add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(asfloat, 1, { + return + add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret firstbitlow +DEFINE_INTRINSIC(firstbitlow, 0, uint, uint) +DEFINE_INTRINSIC(firstbitlow, 0, uint2, uint2) +DEFINE_INTRINSIC(firstbitlow, 0, uint3, uint3) +DEFINE_INTRINSIC(firstbitlow, 0, uint4, uint4) +IMPLEMENT_INTRINSIC_GLSL(firstbitlow, 0, { + code += "findLSB(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(firstbitlow, 0, { + if (_shader_model < 50) + code += "__"; + code += "firstbitlow(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(firstbitlow, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FindILsb) + .add(args[0].base) + .result; + }) + +// ret firstbithigh +DEFINE_INTRINSIC(firstbithigh, 0, int, int) +DEFINE_INTRINSIC(firstbithigh, 0, int2, int2) +DEFINE_INTRINSIC(firstbithigh, 0, int3, int3) +DEFINE_INTRINSIC(firstbithigh, 0, int4, int4) +DEFINE_INTRINSIC(firstbithigh, 1, uint, uint) +DEFINE_INTRINSIC(firstbithigh, 1, uint2, uint2) +DEFINE_INTRINSIC(firstbithigh, 1, uint3, uint3) +DEFINE_INTRINSIC(firstbithigh, 1, uint4, uint4) +IMPLEMENT_INTRINSIC_GLSL(firstbithigh, 0, { + code += "findMSB(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(firstbithigh, 1, { + code += "findMSB(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(firstbithigh, 0, { + if (_shader_model < 50) + code += "__"; + code += "firstbithigh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(firstbithigh, 1, { + if (_shader_model < 50) + code += "__"; + code += "firstbithigh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(firstbithigh, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FindSMsb) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(firstbithigh, 1, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FindUMsb) + .add(args[0].base) + .result; + }) + +// ret countbits +DEFINE_INTRINSIC(countbits, 0, uint, uint) +DEFINE_INTRINSIC(countbits, 0, uint2, uint2) +DEFINE_INTRINSIC(countbits, 0, uint3, uint3) +DEFINE_INTRINSIC(countbits, 0, uint4, uint4) +IMPLEMENT_INTRINSIC_GLSL(countbits, 0, { + code += "bitCount(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(countbits, 0, { + if (_shader_model < 50) + code += "__"; + code += "countbits(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(countbits, 0, { + return + add_instruction(spv::OpBitCount, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret reversebits +DEFINE_INTRINSIC(reversebits, 0, uint, uint) +DEFINE_INTRINSIC(reversebits, 0, uint2, uint2) +DEFINE_INTRINSIC(reversebits, 0, uint3, uint3) +DEFINE_INTRINSIC(reversebits, 0, uint4, uint4) +IMPLEMENT_INTRINSIC_GLSL(reversebits, 0, { + code += "bitfieldReverse(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(reversebits, 0, { + if (_shader_model < 50) + code += "__"; + code += "reversebits(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(reversebits, 0, { + return + add_instruction(spv::OpBitReverse, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret ceil(x) +DEFINE_INTRINSIC(ceil, 0, float, float) +DEFINE_INTRINSIC(ceil, 0, float2, float2) +DEFINE_INTRINSIC(ceil, 0, float3, float3) +DEFINE_INTRINSIC(ceil, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(ceil, 0, { + code += "ceil(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ceil, 0, { + code += "ceil(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ceil, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Ceil) + .add(args[0].base) + .result; + }) + +// ret floor(x) +DEFINE_INTRINSIC(floor, 0, float, float) +DEFINE_INTRINSIC(floor, 0, float2, float2) +DEFINE_INTRINSIC(floor, 0, float3, float3) +DEFINE_INTRINSIC(floor, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(floor, 0, { + code += "floor(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(floor, 0, { + if (_shader_model >= 40) + code += "floor(" + id_to_name(args[0].base) + ')'; + else // Using the floor intrinsic sometimes causes the SM3 D3DCompiler to generate wrong code, so replace it with a custom implementation + code += id_to_name(args[0].base) + " - frac(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(floor, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Floor) + .add(args[0].base) + .result; + }) + +// ret clamp(x, min, max) +DEFINE_INTRINSIC(clamp, 0, int, int, int, int) +DEFINE_INTRINSIC(clamp, 0, int2, int2, int2, int2) +DEFINE_INTRINSIC(clamp, 0, int3, int3, int3, int3) +DEFINE_INTRINSIC(clamp, 0, int4, int4, int4, int4) +DEFINE_INTRINSIC(clamp, 1, uint, uint, uint, uint) +DEFINE_INTRINSIC(clamp, 1, uint2, uint2, uint2, uint2) +DEFINE_INTRINSIC(clamp, 1, uint3, uint3, uint3, uint3) +DEFINE_INTRINSIC(clamp, 1, uint4, uint4, uint4, uint4) +DEFINE_INTRINSIC(clamp, 2, float, float, float, float) +DEFINE_INTRINSIC(clamp, 2, float2, float2, float2, float2) +DEFINE_INTRINSIC(clamp, 2, float3, float3, float3, float3) +DEFINE_INTRINSIC(clamp, 2, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(clamp, 0, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(clamp, 1, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(clamp, 2, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(clamp, 0, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(clamp, 1, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(clamp, 2, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(clamp, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SClamp) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(clamp, 1, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450UClamp) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(clamp, 2, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FClamp) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret saturate(x) +DEFINE_INTRINSIC(saturate, 0, float, float) +DEFINE_INTRINSIC(saturate, 0, float2, float2) +DEFINE_INTRINSIC(saturate, 0, float3, float3) +DEFINE_INTRINSIC(saturate, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(saturate, 0, { + code += "clamp(" + id_to_name(args[0].base) + ", 0.0, 1.0)"; + }) +IMPLEMENT_INTRINSIC_HLSL(saturate, 0, { + code += "saturate(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(saturate, 0, { + const spv::Id constant_one = emit_constant(args[0].type, 1u); + const spv::Id constant_zero = emit_constant(args[0].type, 0u); + + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FClamp) + .add(args[0].base) + .add(constant_zero) + .add(constant_one) + .result; + }) + +// ret mad(mvalue, avalue, bvalue) +DEFINE_INTRINSIC(mad, 0, float, float, float, float) +DEFINE_INTRINSIC(mad, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(mad, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(mad, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(mad, 0, { + code += "fma(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mad, 0, { + if (_shader_model >= 50) + code += "mad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else + code += id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(mad, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Fma) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret rcp(x) +DEFINE_INTRINSIC(rcp, 0, float, float) +DEFINE_INTRINSIC(rcp, 0, float2, float2) +DEFINE_INTRINSIC(rcp, 0, float3, float3) +DEFINE_INTRINSIC(rcp, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(rcp, 0, { + code += "1.0 / " + id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_HLSL(rcp, 0, { + if (_shader_model >= 50) + code += "rcp(" + id_to_name(args[0].base) + ')'; + else + code += "1.0 / " + id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(rcp, 0, { + const spv::Id constant_one = emit_constant(args[0].type, 1u); + + return + add_instruction(spv::OpFDiv, convert_type(res_type)) + .add(constant_one) + .add(args[0].base) + .result; + }) + +// ret pow(x, y) +DEFINE_INTRINSIC(pow, 0, float, float, float) +DEFINE_INTRINSIC(pow, 0, float2, float2, float2) +DEFINE_INTRINSIC(pow, 0, float3, float3, float3) +DEFINE_INTRINSIC(pow, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(pow, 0, { + code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(pow, 0, { + code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(pow, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Pow) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret exp(x) +DEFINE_INTRINSIC(exp, 0, float, float) +DEFINE_INTRINSIC(exp, 0, float2, float2) +DEFINE_INTRINSIC(exp, 0, float3, float3) +DEFINE_INTRINSIC(exp, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(exp, 0, { + code += "exp(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(exp, 0, { + code += "exp(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(exp, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Exp) + .add(args[0].base) + .result; + }) + +// ret exp2(x) +DEFINE_INTRINSIC(exp2, 0, float, float) +DEFINE_INTRINSIC(exp2, 0, float2, float2) +DEFINE_INTRINSIC(exp2, 0, float3, float3) +DEFINE_INTRINSIC(exp2, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(exp2, 0, { + code += "exp2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(exp2, 0, { + code += "exp2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(exp2, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Exp2) + .add(args[0].base) + .result; + }) + +// ret log(x) +DEFINE_INTRINSIC(log, 0, float, float) +DEFINE_INTRINSIC(log, 0, float2, float2) +DEFINE_INTRINSIC(log, 0, float3, float3) +DEFINE_INTRINSIC(log, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(log, 0, { + code += "log(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(log, 0, { + code += "log(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(log, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Log) + .add(args[0].base) + .result; + }) + +// ret log2(x) +DEFINE_INTRINSIC(log2, 0, float, float) +DEFINE_INTRINSIC(log2, 0, float2, float2) +DEFINE_INTRINSIC(log2, 0, float3, float3) +DEFINE_INTRINSIC(log2, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(log2, 0, { + code += "log2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(log2, 0, { + code += "log2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(log2, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Log2) + .add(args[0].base) + .result; + }) + +// ret log10(x) +DEFINE_INTRINSIC(log10, 0, float, float) +DEFINE_INTRINSIC(log10, 0, float2, float2) +DEFINE_INTRINSIC(log10, 0, float3, float3) +DEFINE_INTRINSIC(log10, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(log10, 0, { + code += "(log2(" + id_to_name(args[0].base) + ") / log2(10.0))"; + }) +IMPLEMENT_INTRINSIC_HLSL(log10, 0, { + code += "(log2(" + id_to_name(args[0].base) + ") / log2(10.0))"; + }) +IMPLEMENT_INTRINSIC_SPIRV(log10, 0, { + const spv::Id log2 = add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Log2) + .add(args[0].base) + .result; + + const spv::Id log10 = emit_constant(args[0].type, /* log2(10) */ + constant { { 3.321928f, 3.321928f, 3.321928f, 3.321928f } }); + + return + add_instruction(spv::OpFDiv, convert_type(res_type)) + .add(log2) + .add(log10) + .result; }) + +// ret sign(x) +DEFINE_INTRINSIC(sign, 0, int, int) +DEFINE_INTRINSIC(sign, 0, int2, int2) +DEFINE_INTRINSIC(sign, 0, int3, int3) +DEFINE_INTRINSIC(sign, 0, int4, int4) +DEFINE_INTRINSIC(sign, 1, float, float) +DEFINE_INTRINSIC(sign, 1, float2, float2) +DEFINE_INTRINSIC(sign, 1, float3, float3) +DEFINE_INTRINSIC(sign, 1, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sign, 0, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(sign, 1, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sign, 0, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sign, 1, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sign, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SSign) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(sign, 1, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FSign) + .add(args[0].base) + .result; + }) + +// ret sqrt(x) +DEFINE_INTRINSIC(sqrt, 0, float, float) +DEFINE_INTRINSIC(sqrt, 0, float2, float2) +DEFINE_INTRINSIC(sqrt, 0, float3, float3) +DEFINE_INTRINSIC(sqrt, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sqrt, 0, { + code += "sqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sqrt, 0, { + code += "sqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sqrt, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sqrt) + .add(args[0].base) + .result; + }) + +// ret rsqrt(x) +DEFINE_INTRINSIC(rsqrt, 0, float, float) +DEFINE_INTRINSIC(rsqrt, 0, float2, float2) +DEFINE_INTRINSIC(rsqrt, 0, float3, float3) +DEFINE_INTRINSIC(rsqrt, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(rsqrt, 0, { + code += "inversesqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(rsqrt, 0, { + code += "rsqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(rsqrt, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450InverseSqrt) + .add(args[0].base) + .result; + }) + +// ret lerp(x, y, s) +DEFINE_INTRINSIC(lerp, 0, float, float, float, float) +DEFINE_INTRINSIC(lerp, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(lerp, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(lerp, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(lerp, 0, { + code += "mix(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(lerp, 0, { + code += "lerp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(lerp, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FMix) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret step(y, x) +DEFINE_INTRINSIC(step, 0, float, float, float) +DEFINE_INTRINSIC(step, 0, float2, float2, float2) +DEFINE_INTRINSIC(step, 0, float3, float3, float3) +DEFINE_INTRINSIC(step, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(step, 0, { + code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(step, 0, { + code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(step, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Step) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret smoothstep(min, max, x) +DEFINE_INTRINSIC(smoothstep, 0, float, float, float, float) +DEFINE_INTRINSIC(smoothstep, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(smoothstep, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(smoothstep, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(smoothstep, 0, { + code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(smoothstep, 0, { + code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(smoothstep, 0, { + return + add_instruction(spv::OpExtInst, convert_type(args[2].type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SmoothStep) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret frac(x) +DEFINE_INTRINSIC(frac, 0, float, float) +DEFINE_INTRINSIC(frac, 0, float2, float2) +DEFINE_INTRINSIC(frac, 0, float3, float3) +DEFINE_INTRINSIC(frac, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(frac, 0, { + code += "fract(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(frac, 0, { + code += "frac(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(frac, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Fract) + .add(args[0].base) + .result; + }) + +// ret ldexp(x, exp) +DEFINE_INTRINSIC(ldexp, 0, float, float, int) +DEFINE_INTRINSIC(ldexp, 0, float2, float2, int2) +DEFINE_INTRINSIC(ldexp, 0, float3, float3, int3) +DEFINE_INTRINSIC(ldexp, 0, float4, float4, int4) +IMPLEMENT_INTRINSIC_GLSL(ldexp, 0, { + code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ldexp, 0, { + code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ldexp, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Ldexp) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret modf(x, out ip) +DEFINE_INTRINSIC(modf, 0, float, float, out_float) +DEFINE_INTRINSIC(modf, 0, float2, float2, out_float2) +DEFINE_INTRINSIC(modf, 0, float3, float3, out_float3) +DEFINE_INTRINSIC(modf, 0, float4, float4, out_float4) +IMPLEMENT_INTRINSIC_GLSL(modf, 0, { + code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(modf, 0, { + code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(modf, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Modf) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret frexp(x, out exp) +DEFINE_INTRINSIC(frexp, 0, float, float, out_int) +DEFINE_INTRINSIC(frexp, 0, float2, float2, out_int2) +DEFINE_INTRINSIC(frexp, 0, float3, float3, out_int3) +DEFINE_INTRINSIC(frexp, 0, float4, float4, out_int4) +IMPLEMENT_INTRINSIC_GLSL(frexp, 0, { + code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(frexp, 0, { + code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(frexp, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Frexp) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret trunc(x) +DEFINE_INTRINSIC(trunc, 0, float, float) +DEFINE_INTRINSIC(trunc, 0, float2, float2) +DEFINE_INTRINSIC(trunc, 0, float3, float3) +DEFINE_INTRINSIC(trunc, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(trunc, 0, { + code += "trunc(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(trunc, 0, { + code += "trunc(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(trunc, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Trunc) + .add(args[0].base) + .result; + }) + +// ret round(x) +DEFINE_INTRINSIC(round, 0, float, float) +DEFINE_INTRINSIC(round, 0, float2, float2) +DEFINE_INTRINSIC(round, 0, float3, float3) +DEFINE_INTRINSIC(round, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(round, 0, { + code += "round(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(round, 0, { + code += "round(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(round, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Round) + .add(args[0].base) + .result; + }) + +// ret min(x, y) +DEFINE_INTRINSIC(min, 0, int, int, int) +DEFINE_INTRINSIC(min, 0, int2, int2, int2) +DEFINE_INTRINSIC(min, 0, int3, int3, int3) +DEFINE_INTRINSIC(min, 0, int4, int4, int4) +DEFINE_INTRINSIC(min, 1, float, float, float) +DEFINE_INTRINSIC(min, 1, float2, float2, float2) +DEFINE_INTRINSIC(min, 1, float3, float3, float3) +DEFINE_INTRINSIC(min, 1, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(min, 0, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(min, 1, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(min, 0, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(min, 1, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(min, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SMin) + .add(args[0].base) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(min, 1, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FMin) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret max(x, y) +DEFINE_INTRINSIC(max, 0, int, int, int) +DEFINE_INTRINSIC(max, 0, int2, int2, int2) +DEFINE_INTRINSIC(max, 0, int3, int3, int3) +DEFINE_INTRINSIC(max, 0, int4, int4, int4) +DEFINE_INTRINSIC(max, 1, float, float, float) +DEFINE_INTRINSIC(max, 1, float2, float2, float2) +DEFINE_INTRINSIC(max, 1, float3, float3, float3) +DEFINE_INTRINSIC(max, 1, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(max, 0, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(max, 1, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(max, 0, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(max, 1, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(max, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SMax) + .add(args[0].base) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(max, 1, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FMax) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret degree(x) +DEFINE_INTRINSIC(degrees, 0, float, float) +DEFINE_INTRINSIC(degrees, 0, float2, float2) +DEFINE_INTRINSIC(degrees, 0, float3, float3) +DEFINE_INTRINSIC(degrees, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(degrees, 0, { + code += "degrees(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(degrees, 0, { + code += "degrees(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(degrees, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Degrees) + .add(args[0].base) + .result; + }) + +// ret radians(x) +DEFINE_INTRINSIC(radians, 0, float, float) +DEFINE_INTRINSIC(radians, 0, float2, float2) +DEFINE_INTRINSIC(radians, 0, float3, float3) +DEFINE_INTRINSIC(radians, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(radians, 0, { + code += "radians(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(radians, 0, { + code += "radians(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(radians, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Radians) + .add(args[0].base) + .result; + }) + +// ret ddx(x) +DEFINE_INTRINSIC(ddx, 0, float, float) +DEFINE_INTRINSIC(ddx, 0, float2, float2) +DEFINE_INTRINSIC(ddx, 0, float3, float3) +DEFINE_INTRINSIC(ddx, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(ddx, 0, { + code += "dFdx(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ddx, 0, { + code += "ddx(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ddx, 0, { + return + add_instruction(spv::OpDPdx, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret ddy(x) +DEFINE_INTRINSIC(ddy, 0, float, float) +DEFINE_INTRINSIC(ddy, 0, float2, float2) +DEFINE_INTRINSIC(ddy, 0, float3, float3) +DEFINE_INTRINSIC(ddy, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(ddy, 0, { + code += "dFdy(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ddy, 0, { + code += "ddy(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ddy, 0, { + return + add_instruction(spv::OpDPdy, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret fwidth(x) +DEFINE_INTRINSIC(fwidth, 0, float, float) +DEFINE_INTRINSIC(fwidth, 0, float2, float2) +DEFINE_INTRINSIC(fwidth, 0, float3, float3) +DEFINE_INTRINSIC(fwidth, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(fwidth, 0, { + code += "fwidth(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(fwidth, 0, { + code += "fwidth(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(fwidth, 0, { + return + add_instruction(spv::OpFwidth, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret dot(x, y) +DEFINE_INTRINSIC(dot, 0, float, float, float) +DEFINE_INTRINSIC(dot, 1, float, float2, float2) +DEFINE_INTRINSIC(dot, 1, float, float3, float3) +DEFINE_INTRINSIC(dot, 1, float, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(dot, 0, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(dot, 1, { + code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(dot, 0, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(dot, 1, { + code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(dot, 0, { + return + add_instruction(spv::OpFMul, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(dot, 1, { + return + add_instruction(spv::OpDot, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret cross(x, y) +DEFINE_INTRINSIC(cross, 0, float3, float3, float3) +IMPLEMENT_INTRINSIC_GLSL(cross, 0, { + code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(cross, 0, { + code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(cross, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cross) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret length(x) +DEFINE_INTRINSIC(length, 0, float, float) +DEFINE_INTRINSIC(length, 0, float, float2) +DEFINE_INTRINSIC(length, 0, float, float3) +DEFINE_INTRINSIC(length, 0, float, float4) +IMPLEMENT_INTRINSIC_GLSL(length, 0, { + code += "length(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(length, 0, { + code += "length(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(length, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Length) + .add(args[0].base) + .result; + }) + +// ret distance(x, y) +DEFINE_INTRINSIC(distance, 0, float, float, float) +DEFINE_INTRINSIC(distance, 0, float, float2, float2) +DEFINE_INTRINSIC(distance, 0, float, float3, float3) +DEFINE_INTRINSIC(distance, 0, float, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(distance, 0, { + code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(distance, 0, { + code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(distance, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Distance) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret normalize(x) +DEFINE_INTRINSIC(normalize, 0, float2, float2) +DEFINE_INTRINSIC(normalize, 0, float3, float3) +DEFINE_INTRINSIC(normalize, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(normalize, 0, { + code += "normalize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(normalize, 0, { + code += "normalize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(normalize, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Normalize) + .add(args[0].base) + .result; + }) + +// ret transpose(x) +DEFINE_INTRINSIC(transpose, 0, float2x2, float2x2) +DEFINE_INTRINSIC(transpose, 0, float2x3, float3x2) +DEFINE_INTRINSIC(transpose, 0, float2x4, float4x2) +DEFINE_INTRINSIC(transpose, 0, float3x2, float2x3) +DEFINE_INTRINSIC(transpose, 0, float3x3, float3x3) +DEFINE_INTRINSIC(transpose, 0, float3x4, float4x3) +DEFINE_INTRINSIC(transpose, 0, float4x2, float2x4) +DEFINE_INTRINSIC(transpose, 0, float4x3, float3x4) +DEFINE_INTRINSIC(transpose, 0, float4x4, float4x4) +IMPLEMENT_INTRINSIC_GLSL(transpose, 0, { + code += "transpose(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(transpose, 0, { + code += "transpose(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(transpose, 0, { + return + add_instruction(spv::OpTranspose, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret determinant(m) +DEFINE_INTRINSIC(determinant, 0, float, float2x2) +DEFINE_INTRINSIC(determinant, 0, float, float3x3) +DEFINE_INTRINSIC(determinant, 0, float, float4x4) +IMPLEMENT_INTRINSIC_GLSL(determinant, 0, { + code += "determinant(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(determinant, 0, { + code += "determinant(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(determinant, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Determinant) + .add(args[0].base) + .result; + }) + +// ret reflect(i, n) +DEFINE_INTRINSIC(reflect, 0, float2, float2, float2) +DEFINE_INTRINSIC(reflect, 0, float3, float3, float3) +DEFINE_INTRINSIC(reflect, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(reflect, 0, { + code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(reflect, 0, { + code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(reflect, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Reflect) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret refract(i, n, eta) +DEFINE_INTRINSIC(refract, 0, float2, float2, float2, float) +DEFINE_INTRINSIC(refract, 0, float3, float3, float3, float) +DEFINE_INTRINSIC(refract, 0, float4, float4, float4, float) +IMPLEMENT_INTRINSIC_GLSL(refract, 0, { + code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(refract, 0, { + code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(refract, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Refract) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret faceforward(n, i, ng) +DEFINE_INTRINSIC(faceforward, 0, float, float, float, float) +DEFINE_INTRINSIC(faceforward, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(faceforward, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(faceforward, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(faceforward, 0, { + code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(faceforward, 0, { + code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(faceforward, 0, { + return + add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FaceForward) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret mul(x, y) +DEFINE_INTRINSIC(mul, 0, int2, int, int2) +DEFINE_INTRINSIC(mul, 0, int3, int, int3) +DEFINE_INTRINSIC(mul, 0, int4, int, int4) +DEFINE_INTRINSIC(mul, 0, float2, float, float2) +DEFINE_INTRINSIC(mul, 0, float3, float, float3) +DEFINE_INTRINSIC(mul, 0, float4, float, float4) +IMPLEMENT_INTRINSIC_GLSL(mul, 0, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 0, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 0, { + return + add_instruction(spv::OpVectorTimesScalar, convert_type(res_type)) + .add(args[1].base) + .add(args[0].base) + .result; + }) +DEFINE_INTRINSIC(mul, 1, int2, int2, int) +DEFINE_INTRINSIC(mul, 1, int3, int3, int) +DEFINE_INTRINSIC(mul, 1, int4, int4, int) +DEFINE_INTRINSIC(mul, 1, float2, float2, float) +DEFINE_INTRINSIC(mul, 1, float3, float3, float) +DEFINE_INTRINSIC(mul, 1, float4, float4, float) +IMPLEMENT_INTRINSIC_GLSL(mul, 1, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 1, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 1, { + return + add_instruction(spv::OpVectorTimesScalar, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +DEFINE_INTRINSIC(mul, 2, int2x2, int, int2x2) +DEFINE_INTRINSIC(mul, 2, int2x3, int, int2x3) +DEFINE_INTRINSIC(mul, 2, int2x4, int, int2x4) +DEFINE_INTRINSIC(mul, 2, int3x2, int, int3x2) +DEFINE_INTRINSIC(mul, 2, int3x3, int, int3x3) +DEFINE_INTRINSIC(mul, 2, int3x4, int, int3x4) +DEFINE_INTRINSIC(mul, 2, int4x2, int, int4x2) +DEFINE_INTRINSIC(mul, 2, int4x3, int, int4x3) +DEFINE_INTRINSIC(mul, 2, int4x4, int, int4x4) +DEFINE_INTRINSIC(mul, 2, float2x2, float, float2x2) +DEFINE_INTRINSIC(mul, 2, float2x3, float, float2x3) +DEFINE_INTRINSIC(mul, 2, float2x4, float, float2x4) +DEFINE_INTRINSIC(mul, 2, float3x2, float, float3x2) +DEFINE_INTRINSIC(mul, 2, float3x3, float, float3x3) +DEFINE_INTRINSIC(mul, 2, float3x4, float, float3x4) +DEFINE_INTRINSIC(mul, 2, float4x2, float, float4x2) +DEFINE_INTRINSIC(mul, 2, float4x3, float, float4x3) +DEFINE_INTRINSIC(mul, 2, float4x4, float, float4x4) +IMPLEMENT_INTRINSIC_GLSL(mul, 2, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 2, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 2, { + return + add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type)) + .add(args[1].base) + .add(args[0].base) + .result; + }) +DEFINE_INTRINSIC(mul, 3, int2x2, int2x2, int) +DEFINE_INTRINSIC(mul, 3, int2x3, int2x3, int) +DEFINE_INTRINSIC(mul, 3, int2x4, int2x4, int) +DEFINE_INTRINSIC(mul, 3, int3x2, int3x2, int) +DEFINE_INTRINSIC(mul, 3, int3x3, int3x3, int) +DEFINE_INTRINSIC(mul, 3, int3x4, int3x4, int) +DEFINE_INTRINSIC(mul, 3, int4x2, int4x2, int) +DEFINE_INTRINSIC(mul, 3, int4x3, int4x3, int) +DEFINE_INTRINSIC(mul, 3, int4x4, int4x4, int) +DEFINE_INTRINSIC(mul, 3, float2x2, float2x2, float) +DEFINE_INTRINSIC(mul, 3, float2x3, float2x3, float) +DEFINE_INTRINSIC(mul, 3, float2x4, float2x4, float) +DEFINE_INTRINSIC(mul, 3, float3x2, float3x2, float) +DEFINE_INTRINSIC(mul, 3, float3x3, float3x3, float) +DEFINE_INTRINSIC(mul, 3, float3x4, float3x4, float) +DEFINE_INTRINSIC(mul, 3, float4x2, float4x2, float) +DEFINE_INTRINSIC(mul, 3, float4x3, float4x3, float) +DEFINE_INTRINSIC(mul, 3, float4x4, float4x4, float) +IMPLEMENT_INTRINSIC_GLSL(mul, 3, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 3, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 3, { + return + add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +DEFINE_INTRINSIC(mul, 4, int2, int2, int2x2) +DEFINE_INTRINSIC(mul, 4, int3, int2, int2x3) +DEFINE_INTRINSIC(mul, 4, int4, int2, int2x4) +DEFINE_INTRINSIC(mul, 4, int2, int3, int3x2) +DEFINE_INTRINSIC(mul, 4, int3, int3, int3x3) +DEFINE_INTRINSIC(mul, 4, int4, int3, int3x4) +DEFINE_INTRINSIC(mul, 4, int2, int4, int4x2) +DEFINE_INTRINSIC(mul, 4, int3, int4, int4x3) +DEFINE_INTRINSIC(mul, 4, int4, int4, int4x4) +DEFINE_INTRINSIC(mul, 4, float2, float2, float2x2) +DEFINE_INTRINSIC(mul, 4, float3, float2, float2x3) +DEFINE_INTRINSIC(mul, 4, float4, float2, float2x4) +DEFINE_INTRINSIC(mul, 4, float2, float3, float3x2) +DEFINE_INTRINSIC(mul, 4, float3, float3, float3x3) +DEFINE_INTRINSIC(mul, 4, float4, float3, float3x4) +DEFINE_INTRINSIC(mul, 4, float2, float4, float4x2) +DEFINE_INTRINSIC(mul, 4, float3, float4, float4x3) +DEFINE_INTRINSIC(mul, 4, float4, float4, float4x4) +IMPLEMENT_INTRINSIC_GLSL(mul, 4, { + // Flip inputs because matrices are column-wise + code += '(' + id_to_name(args[1].base) + " * " + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 4, { + code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 4, { + return + add_instruction(spv::OpMatrixTimesVector, convert_type(res_type)) + .add(args[1].base) // Flip inputs because matrices are column-wise + .add(args[0].base) + .result; + }) +DEFINE_INTRINSIC(mul, 5, int2, int2x2, int2) +DEFINE_INTRINSIC(mul, 5, int2, int2x3, int3) +DEFINE_INTRINSIC(mul, 5, int2, int2x4, int4) +DEFINE_INTRINSIC(mul, 5, int3, int3x2, int2) +DEFINE_INTRINSIC(mul, 5, int3, int3x3, int3) +DEFINE_INTRINSIC(mul, 5, int3, int3x4, int4) +DEFINE_INTRINSIC(mul, 5, int4, int4x2, int2) +DEFINE_INTRINSIC(mul, 5, int4, int4x3, int3) +DEFINE_INTRINSIC(mul, 5, int4, int4x4, int4) +DEFINE_INTRINSIC(mul, 5, float2, float2x2, float2) +DEFINE_INTRINSIC(mul, 5, float2, float2x3, float3) +DEFINE_INTRINSIC(mul, 5, float2, float2x4, float4) +DEFINE_INTRINSIC(mul, 5, float3, float3x2, float2) +DEFINE_INTRINSIC(mul, 5, float3, float3x3, float3) +DEFINE_INTRINSIC(mul, 5, float3, float3x4, float4) +DEFINE_INTRINSIC(mul, 5, float4, float4x2, float2) +DEFINE_INTRINSIC(mul, 5, float4, float4x3, float3) +DEFINE_INTRINSIC(mul, 5, float4, float4x4, float4) +IMPLEMENT_INTRINSIC_GLSL(mul, 5, { + // Flip inputs because matrices are column-wise + code += '(' + id_to_name(args[1].base) + " * " + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 5, { + code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 5, { + return + add_instruction(spv::OpVectorTimesMatrix, convert_type(res_type)) + .add(args[1].base) // Flip inputs because matrices are column-wise + .add(args[0].base) + .result; + }) + +DEFINE_INTRINSIC(mul, 6, int2x2, int2x2, int2x2) +DEFINE_INTRINSIC(mul, 6, int2x3, int2x2, int2x3) +DEFINE_INTRINSIC(mul, 6, int2x4, int2x2, int2x4) +DEFINE_INTRINSIC(mul, 6, int2x2, int2x3, int3x2) +DEFINE_INTRINSIC(mul, 6, int2x3, int2x3, int3x3) +DEFINE_INTRINSIC(mul, 6, int2x4, int2x3, int3x4) +DEFINE_INTRINSIC(mul, 6, int2x2, int2x4, int4x2) +DEFINE_INTRINSIC(mul, 6, int2x3, int2x4, int4x3) +DEFINE_INTRINSIC(mul, 6, int2x4, int2x4, int4x4) +DEFINE_INTRINSIC(mul, 6, int3x2, int3x2, int2x2) +DEFINE_INTRINSIC(mul, 6, int3x3, int3x2, int2x3) +DEFINE_INTRINSIC(mul, 6, int3x4, int3x2, int2x4) +DEFINE_INTRINSIC(mul, 6, int3x2, int3x3, int3x2) +DEFINE_INTRINSIC(mul, 6, int3x3, int3x3, int3x3) +DEFINE_INTRINSIC(mul, 6, int3x4, int3x3, int3x4) +DEFINE_INTRINSIC(mul, 6, int3x2, int3x4, int4x2) +DEFINE_INTRINSIC(mul, 6, int3x3, int3x4, int4x3) +DEFINE_INTRINSIC(mul, 6, int3x4, int3x4, int4x4) +DEFINE_INTRINSIC(mul, 6, int4x2, int4x2, int2x2) +DEFINE_INTRINSIC(mul, 6, int4x3, int4x2, int2x3) +DEFINE_INTRINSIC(mul, 6, int4x4, int4x2, int2x4) +DEFINE_INTRINSIC(mul, 6, int4x2, int4x3, int3x2) +DEFINE_INTRINSIC(mul, 6, int4x3, int4x3, int3x3) +DEFINE_INTRINSIC(mul, 6, int4x4, int4x3, int3x4) +DEFINE_INTRINSIC(mul, 6, int4x2, int4x4, int4x2) +DEFINE_INTRINSIC(mul, 6, int4x3, int4x4, int4x3) +DEFINE_INTRINSIC(mul, 6, int4x4, int4x4, int4x4) +DEFINE_INTRINSIC(mul, 6, float2x2, float2x2, float2x2) +DEFINE_INTRINSIC(mul, 6, float2x3, float2x2, float2x3) +DEFINE_INTRINSIC(mul, 6, float2x4, float2x2, float2x4) +DEFINE_INTRINSIC(mul, 6, float2x2, float2x3, float3x2) +DEFINE_INTRINSIC(mul, 6, float2x3, float2x3, float3x3) +DEFINE_INTRINSIC(mul, 6, float2x4, float2x3, float3x4) +DEFINE_INTRINSIC(mul, 6, float2x2, float2x4, float4x2) +DEFINE_INTRINSIC(mul, 6, float2x3, float2x4, float4x3) +DEFINE_INTRINSIC(mul, 6, float2x4, float2x4, float4x4) +DEFINE_INTRINSIC(mul, 6, float3x2, float3x2, float2x2) +DEFINE_INTRINSIC(mul, 6, float3x3, float3x2, float2x3) +DEFINE_INTRINSIC(mul, 6, float3x4, float3x2, float2x4) +DEFINE_INTRINSIC(mul, 6, float3x2, float3x3, float3x2) +DEFINE_INTRINSIC(mul, 6, float3x3, float3x3, float3x3) +DEFINE_INTRINSIC(mul, 6, float3x4, float3x3, float3x4) +DEFINE_INTRINSIC(mul, 6, float3x2, float3x4, float4x2) +DEFINE_INTRINSIC(mul, 6, float3x3, float3x4, float4x3) +DEFINE_INTRINSIC(mul, 6, float3x4, float3x4, float4x4) +DEFINE_INTRINSIC(mul, 6, float4x2, float4x2, float2x2) +DEFINE_INTRINSIC(mul, 6, float4x3, float4x2, float2x3) +DEFINE_INTRINSIC(mul, 6, float4x4, float4x2, float2x4) +DEFINE_INTRINSIC(mul, 6, float4x2, float4x3, float3x2) +DEFINE_INTRINSIC(mul, 6, float4x3, float4x3, float3x3) +DEFINE_INTRINSIC(mul, 6, float4x4, float4x3, float3x4) +DEFINE_INTRINSIC(mul, 6, float4x2, float4x4, float4x2) +DEFINE_INTRINSIC(mul, 6, float4x3, float4x4, float4x3) +DEFINE_INTRINSIC(mul, 6, float4x4, float4x4, float4x4) +IMPLEMENT_INTRINSIC_GLSL(mul, 6, { + // Flip inputs because matrices are column-wise + code += '(' + id_to_name(args[1].base) + " * " + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 6, { + code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 6, { + return + add_instruction(spv::OpMatrixTimesMatrix, convert_type(res_type)) + .add(args[1].base) // Flip inputs because matrices are column-wise + .add(args[0].base) + .result; + }) + +// ret isinf(x) +DEFINE_INTRINSIC(isinf, 0, bool, float) +DEFINE_INTRINSIC(isinf, 0, bool2, float2) +DEFINE_INTRINSIC(isinf, 0, bool3, float3) +DEFINE_INTRINSIC(isinf, 0, bool4, float4) +IMPLEMENT_INTRINSIC_GLSL(isinf, 0, { + code += "isinf(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(isinf, 0, { + code += "isinf(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(isinf, 0, { + return + add_instruction(spv::OpIsInf, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret isnan(x) +DEFINE_INTRINSIC(isnan, 0, bool, float) +DEFINE_INTRINSIC(isnan, 0, bool2, float2) +DEFINE_INTRINSIC(isnan, 0, bool3, float3) +DEFINE_INTRINSIC(isnan, 0, bool4, float4) +IMPLEMENT_INTRINSIC_GLSL(isnan, 0, { + code += "isnan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(isnan, 0, { + code += "isnan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(isnan, 0, { + return + add_instruction(spv::OpIsNan, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret tex1D(s, coords) +// ret tex1D(s, coords, offset) +DEFINE_INTRINSIC(tex1D, 0, int, sampler1d_int, float) +DEFINE_INTRINSIC(tex1D, 0, uint, sampler1d_uint, float) +DEFINE_INTRINSIC(tex1D, 0, float, sampler1d_float, float) +DEFINE_INTRINSIC(tex1D, 0, float4, sampler1d_float4, float) +DEFINE_INTRINSIC(tex1D, 1, int, sampler1d_int, float, int) +DEFINE_INTRINSIC(tex1D, 1, uint, sampler1d_uint, float, int) +DEFINE_INTRINSIC(tex1D, 1, float, sampler1d_float, float, int) +DEFINE_INTRINSIC(tex1D, 1, float4, sampler1d_float4, float, int) +IMPLEMENT_INTRINSIC_GLSL(tex1D, 0, { + code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex1D, 1, { + code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1D, 0, { + if (_shader_model >= 40) { // SM4 and higher use a more object-oriented programming model for textures + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch + code += "uint temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + "); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0))"; + } + else { + code += "tex1D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex1D, 1, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else + code += "uint temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + "); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0), " + id_to_name(args[2].base) + ')'; + } + else { + code += "tex1D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1D, 0, { + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .add(spv::ImageOperandsMaskNone) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1D, 1, { + // Non-constant offset operand needs extended capability + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex2D(s, coords) +// ret tex2D(s, coords, offset) +DEFINE_INTRINSIC(tex2D, 0, int, sampler2d_int, float2) +DEFINE_INTRINSIC(tex2D, 0, uint, sampler2d_uint, float2) +DEFINE_INTRINSIC(tex2D, 0, float, sampler2d_float, float2) +DEFINE_INTRINSIC(tex2D, 0, float4, sampler2d_float4, float2) +DEFINE_INTRINSIC(tex2D, 1, int, sampler2d_int, float2, int2) +DEFINE_INTRINSIC(tex2D, 1, uint, sampler2d_uint, float2, int2) +DEFINE_INTRINSIC(tex2D, 1, float, sampler2d_float, float2, int2) +DEFINE_INTRINSIC(tex2D, 1, float4, sampler2d_float4, float2, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2D, 0, { + code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex2D, 1, { + code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2D, 0, { + if (_shader_model >= 40) { // SM4 and higher use a more object-oriented programming model for textures + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch + code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0))"; + } + else { + code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex2D, 1, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else + code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0), " + id_to_name(args[2].base) + ')'; + } + else { + code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2D, 0, { + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .add(spv::ImageOperandsMaskNone) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2D, 1, { + // Non-constant offset operand needs extended capability + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex3D(s, coords) +// ret tex3D(s, coords, offset) +DEFINE_INTRINSIC(tex3D, 0, int, sampler3d_int, float3) +DEFINE_INTRINSIC(tex3D, 0, uint, sampler3d_uint, float3) +DEFINE_INTRINSIC(tex3D, 0, float, sampler3d_float, float3) +DEFINE_INTRINSIC(tex3D, 0, float4, sampler3d_float4, float3) +DEFINE_INTRINSIC(tex3D, 1, int, sampler3d_int, float3, int3) +DEFINE_INTRINSIC(tex3D, 1, uint, sampler3d_uint, float3, int3) +DEFINE_INTRINSIC(tex3D, 1, float, sampler3d_float, float3, int3) +DEFINE_INTRINSIC(tex3D, 1, float4, sampler3d_float4, float3, int3) +IMPLEMENT_INTRINSIC_GLSL(tex3D, 0, { + code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex3D, 1, { + code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3D, 0, { + if (_shader_model >= 40) { // SM4 and higher use a more object-oriented programming model for textures + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch + code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0))"; + } + else { + code += "tex3D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex3D, 1, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else + code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0), " + id_to_name(args[2].base) + ')'; + } + else { + code += "tex3D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3D, 0, { + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .add(spv::ImageOperandsMaskNone) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3D, 1, { + // Non-constant offset operand needs extended capability + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex1Dlod(s, coords) +// ret tex1Dlod(s, coords, offset) +DEFINE_INTRINSIC(tex1Dlod, 0, int, sampler1d_int, float4) +DEFINE_INTRINSIC(tex1Dlod, 0, uint, sampler1d_uint, float4) +DEFINE_INTRINSIC(tex1Dlod, 0, float, sampler1d_float, float4) +DEFINE_INTRINSIC(tex1Dlod, 0, float4, sampler1d_float4, float4) +DEFINE_INTRINSIC(tex1Dlod, 1, int, sampler1d_int, float4, int) +DEFINE_INTRINSIC(tex1Dlod, 1, uint, sampler1d_uint, float4, int) +DEFINE_INTRINSIC(tex1Dlod, 1, float, sampler1d_float, float4, int) +DEFINE_INTRINSIC(tex1Dlod, 1, float4, sampler1d_float4, float4, int) +IMPLEMENT_INTRINSIC_GLSL(tex1Dlod, 0, { + code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w)"; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex1Dlod, 1, { + code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dlod, 0, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w)"; + else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch + code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ".x * temp" + std::to_string(res) + ".x, (int)" + id_to_name(args[1].base) + ".w))"; + } + else { + code += "tex1Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dlod, 1, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + else + code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ".x * temp" + std::to_string(res) + ".x, (int)" + id_to_name(args[1].base) + ".w))" + id_to_name(args[2].base) + ')'; + } + else { + code += "tex1Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0, 0, 0))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dlod, 0, { + const spv::Id x = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(0) // .x + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(x) + .add(spv::ImageOperandsLodMask) + .add(lod) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dlod, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id x = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(0) // .x + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(x) + .add(spv::ImageOperandsLodMask | (args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)) + .add(lod) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex2Dlod(s, coords) +// ret tex2Dlod(s, coords, offset) +DEFINE_INTRINSIC(tex2Dlod, 0, int, sampler2d_int, float4) +DEFINE_INTRINSIC(tex2Dlod, 0, uint, sampler2d_uint, float4) +DEFINE_INTRINSIC(tex2Dlod, 0, float, sampler2d_float, float4) +DEFINE_INTRINSIC(tex2Dlod, 0, float4, sampler2d_float4, float4) +DEFINE_INTRINSIC(tex2Dlod, 1, int, sampler2d_int, float4, int2) +DEFINE_INTRINSIC(tex2Dlod, 1, uint, sampler2d_uint, float4, int2) +DEFINE_INTRINSIC(tex2Dlod, 1, float, sampler2d_float, float4, int2) +DEFINE_INTRINSIC(tex2Dlod, 1, float4, sampler2d_float4, float4, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2Dlod, 0, { + code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w)"; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex2Dlod, 1, { + code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dlod, 0, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w)"; + else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch + code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ".xy * temp" + std::to_string(res) + ".xy, (int)" + id_to_name(args[1].base) + ".w))"; + } + else { + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dlod, 1, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + else + code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ".xy * temp" + std::to_string(res) + ".xy, (int)" + id_to_name(args[1].base) + ".w))" + id_to_name(args[2].base) + ')'; + } + else { + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0, 0))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dlod, 0, { + const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(xy) + .add(spv::ImageOperandsLodMask) + .add(lod) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dlod, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(xy) + .add(spv::ImageOperandsLodMask | (args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)) + .add(lod) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex3Dlod(s, coords) +// ret tex3Dlod(s, coords, offset) +DEFINE_INTRINSIC(tex3Dlod, 0, int, sampler3d_int, float4) +DEFINE_INTRINSIC(tex3Dlod, 0, uint, sampler3d_uint, float4) +DEFINE_INTRINSIC(tex3Dlod, 0, float, sampler3d_float, float4) +DEFINE_INTRINSIC(tex3Dlod, 0, float4, sampler3d_float4, float4) +DEFINE_INTRINSIC(tex3Dlod, 1, int, sampler3d_int, float4, int2) +DEFINE_INTRINSIC(tex3Dlod, 1, uint, sampler3d_uint, float4, int2) +DEFINE_INTRINSIC(tex3Dlod, 1, float, sampler3d_float, float4, int2) +DEFINE_INTRINSIC(tex3Dlod, 1, float4, sampler3d_float4, float4, int2) +IMPLEMENT_INTRINSIC_GLSL(tex3Dlod, 0, { + code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w)"; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex3Dlod, 1, { + code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dlod, 0, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w)"; + else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch + code += "uint4 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z, temp" + std::to_string(res) + ".w); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ".xyz * temp" + std::to_string(res) + ".xyz, (int)" + id_to_name(args[1].base) + ".w))"; + } + else { + code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dlod, 1, { + if (_shader_model >= 40) { + if (res_type.is_floating_point() || _shader_model >= 67) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + else + code += "uint4 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z, temp" + std::to_string(res) + ".w); " + + id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ".xyz * temp" + std::to_string(res) + ".xyz, (int)" + id_to_name(args[1].base) + ".w))" + id_to_name(args[2].base) + ')'; + } + else { + code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dlod, 0, { + const spv::Id xyz = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 3, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .add(2) // .z + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(xyz) + .add(spv::ImageOperandsLodMask) + .add(lod) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dlod, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id xyz = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 3, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .add(2) // .z + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type)) + .add(args[0].base) + .add(xyz) + .add(spv::ImageOperandsLodMask | (args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)) + .add(lod) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex1Dfetch(s, coords) +// ret tex1Dfetch(s, coords, lod) +DEFINE_INTRINSIC(tex1Dfetch, 0, int, sampler1d_int, int) +DEFINE_INTRINSIC(tex1Dfetch, 0, uint, sampler1d_uint, int) +DEFINE_INTRINSIC(tex1Dfetch, 0, float, sampler1d_float, int) +DEFINE_INTRINSIC(tex1Dfetch, 0, float4, sampler1d_float4, int) +DEFINE_INTRINSIC(tex1Dfetch, 1, int, sampler1d_int, int, int) +DEFINE_INTRINSIC(tex1Dfetch, 1, uint, sampler1d_uint, int, int) +DEFINE_INTRINSIC(tex1Dfetch, 1, float, sampler1d_float, int, int) +DEFINE_INTRINSIC(tex1Dfetch, 1, float4, sampler1d_float4, int, int) +DEFINE_INTRINSIC(tex1Dfetch, 2, int, storage1d_int, int) +DEFINE_INTRINSIC(tex1Dfetch, 2, uint, storage1d_uint, int) +DEFINE_INTRINSIC(tex1Dfetch, 2, float, storage1d_float, int) +DEFINE_INTRINSIC(tex1Dfetch, 2, float4, storage1d_float4, int) +IMPLEMENT_INTRINSIC_GLSL(tex1Dfetch, 0, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", 0)"; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex1Dfetch, 1, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex1Dfetch, 2, { + code += "imageLoad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ")"; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dfetch, 0, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ", 0))"; + else { + // SM3 does not have a fetch intrinsic, so emulate it by transforming coordinates into texture space ones + // Also add a half-pixel offset to align texels with pixels + // (coords + 0.5) / size + code += "tex1Dlod(" + id_to_name(args[0].base) + ".s, float4((" + + id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize, 0, 0, 0))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dfetch, 1, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + "))"; + else { + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" + + id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize * exp2(" + id_to_name(args[2].base) + "), 0, 0, " + + id_to_name(args[2].base) + "))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dfetch, 2, { + code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dfetch, 0, { + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type)) + .add(image) + .add(args[1].base) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dfetch, 1, { + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type)) + .add(image) + .add(args[1].base) + .add(spv::ImageOperandsLodMask) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dfetch, 2, { + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageRead, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex2Dfetch(s, coords) +// ret tex2Dfetch(s, coords, lod) +DEFINE_INTRINSIC(tex2Dfetch, 0, int, sampler2d_int, int2) +DEFINE_INTRINSIC(tex2Dfetch, 0, uint, sampler2d_uint, int2) +DEFINE_INTRINSIC(tex2Dfetch, 0, float, sampler2d_float, int2) +DEFINE_INTRINSIC(tex2Dfetch, 0, float4, sampler2d_float4, int2) +DEFINE_INTRINSIC(tex2Dfetch, 1, int, sampler2d_int, int2, int) +DEFINE_INTRINSIC(tex2Dfetch, 1, uint, sampler2d_uint, int2, int) +DEFINE_INTRINSIC(tex2Dfetch, 1, float, sampler2d_float, int2, int) +DEFINE_INTRINSIC(tex2Dfetch, 1, float4, sampler2d_float4, int2, int) +DEFINE_INTRINSIC(tex2Dfetch, 2, int, storage2d_int, int2) +DEFINE_INTRINSIC(tex2Dfetch, 2, uint, storage2d_uint, int2) +DEFINE_INTRINSIC(tex2Dfetch, 2, float, storage2d_float, int2) +DEFINE_INTRINSIC(tex2Dfetch, 2, float4, storage2d_float4, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 0, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", 0)"; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 1, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 2, { + code += "imageLoad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 0, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ", 0))"; + else { + // SM3 does not have a fetch intrinsic, so emulate it by transforming coordinates into texture space ones + // Also add a half-pixel offset to align texels with pixels + // (coords + 0.5) / size + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" + + id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize, 0, 0))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 1, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + "))"; + else { + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" + + id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize * exp2(" + id_to_name(args[2].base) + "), 0, " + + id_to_name(args[2].base) + "))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 2, { + if (_shader_model >= 50) + code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']'; + else + code += "{}"; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 0, { + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type)) + .add(image) + .add(args[1].base) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 1, { + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type)) + .add(image) + .add(args[1].base) + .add(spv::ImageOperandsLodMask) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 2, { + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageRead, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex3Dfetch(s, coords) +// ret tex3Dfetch(s, coords, lod) +DEFINE_INTRINSIC(tex3Dfetch, 0, int, sampler3d_int, int3) +DEFINE_INTRINSIC(tex3Dfetch, 0, uint, sampler3d_uint, int3) +DEFINE_INTRINSIC(tex3Dfetch, 0, float, sampler3d_float, int3) +DEFINE_INTRINSIC(tex3Dfetch, 0, float4, sampler3d_float4, int3) +DEFINE_INTRINSIC(tex3Dfetch, 1, int, sampler3d_int, int3, int) +DEFINE_INTRINSIC(tex3Dfetch, 1, uint, sampler3d_uint, int3, int) +DEFINE_INTRINSIC(tex3Dfetch, 1, float, sampler3d_float, int3, int) +DEFINE_INTRINSIC(tex3Dfetch, 1, float4, sampler3d_float4, int3, int) +DEFINE_INTRINSIC(tex3Dfetch, 2, int, storage3d_int, int3) +DEFINE_INTRINSIC(tex3Dfetch, 2, uint, storage3d_uint, int3) +DEFINE_INTRINSIC(tex3Dfetch, 2, float, storage3d_float, int3) +DEFINE_INTRINSIC(tex3Dfetch, 2, float4, storage3d_float4, int3) +IMPLEMENT_INTRINSIC_GLSL(tex3Dfetch, 0, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", 0)"; + if (res_type.rows == 1) + code += ".x"; // Collapse last argument from a 4-component vector + }) +IMPLEMENT_INTRINSIC_GLSL(tex3Dfetch, 1, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex3Dfetch, 2, { + code += "imageLoad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ")"; + if (res_type.rows == 1) + code += ".x"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dfetch, 0, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ", 0))"; + else { + // SM3 does not have a fetch intrinsic, so emulate it by transforming coordinates into texture space ones + // Also add a half-pixel offset to align texels with pixels + // (coords + 0.5) / size + code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, float4((" + + id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize, 0))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dfetch, 1, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + "))"; + else { + code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, float4((" + + id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize * exp2(" + id_to_name(args[2].base) + "), " + + id_to_name(args[2].base) + "))"; + if (res_type.rows == 1) + code += ".x"; + } + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dfetch, 2, { + code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dfetch, 0, { + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type)) + .add(image) + .add(args[1].base) + .result; + if (res_type.rows == 1) + // Collapse last argument from a 4-component vector + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dfetch, 1, { + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type)) + .add(image) + .add(args[1].base) + .add(spv::ImageOperandsLodMask) + .add(args[2].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dfetch, 2, { + type res_vector_type = res_type; + res_vector_type.rows = 4; + + const spv::Id res = add_instruction(spv::OpImageRead, convert_type(res_vector_type)) + .add(args[0].base) + .add(args[1].base) + .result; + if (res_type.rows == 1) + return + add_instruction(spv::OpCompositeExtract, convert_type(res_type)) + .add(res) + .add(0u) + .result; + else + return res; + }) + +// ret tex2DgatherR(s, coords) +// ret tex2DgatherR(s, coords, offset) +// ret tex2DgatherR(s, coords, offset0, offset1, offset2, offset3) +DEFINE_INTRINSIC(tex2DgatherR, 0, float4, sampler2d_float4, float2) +DEFINE_INTRINSIC(tex2DgatherR, 1, float4, sampler2d_float4, float2, int2) +DEFINE_INTRINSIC(tex2DgatherR, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherR, 0, { + code += "textureGather(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", 0)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherR, 1, { + code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + ", 0)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherR, 2, { + code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + + "ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 0)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherR, 0, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherRed(" + s + ".s, " + id_to_name(args[1].base) + ')'; + else if (_shader_model >= 40) // Emulate texture gather intrinsic by sampling each location separately (SM41 has 'Gather', but that only works on single component texture formats) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1))." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1))." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0))." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0))." + 'r' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0))." + 'r' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherR, 1, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherRed(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1))." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1))." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0))." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0))." + 'r' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0))." + 'r' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherR, 2, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherRed(" + s + ".s, " + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + " - int2(0, 1), " + + id_to_name(args[3].base) + " - int2(1, 1), " + + id_to_name(args[4].base) + " - int2(1, 0), " + + id_to_name(args[5].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ")." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ")." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ")." + 'r' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ")." + 'r' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0))." + 'r' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0))." + 'r' + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherR, 0, { + const spv::Id comp = emit_constant(0u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsMaskNone) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherR, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(0u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherR, 2, { + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(0u); + const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants) + .add(args[2].base) + .add(args[3].base) + .add(args[4].base) + .add(args[5].base) + .result; + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsConstOffsetsMask) + .add(offsets) + .result; + }) +// ret tex2DgatherG(s, coords) +// ret tex2DgatherG(s, coords, offset) +// ret tex2DgatherG(s, coords, offset0, offset1, offset2, offset3) +DEFINE_INTRINSIC(tex2DgatherG, 0, float4, sampler2d_float4, float2) +DEFINE_INTRINSIC(tex2DgatherG, 1, float4, sampler2d_float4, float2, int2) +DEFINE_INTRINSIC(tex2DgatherG, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherG, 0, { + code += "textureGather(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", 1)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherG, 1, { + code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + ", 1)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherG, 2, { + code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + + "ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 1)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherG, 0, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherGreen(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1))." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1))." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0))." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0))." + 'g' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0))." + 'g' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherG, 1, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherGreen(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1))." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1))." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0))." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0))." + 'g' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0))." + 'g' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherG, 2, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherGreen(" + s + ".s, " + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + " - int2(0, 1), " + + id_to_name(args[3].base) + " - int2(1, 1), " + + id_to_name(args[4].base) + " - int2(1, 0), " + + id_to_name(args[5].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ")." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ")." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ")." + 'g' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ")." + 'g' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0))." + 'g' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0))." + 'g' + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherG, 0, { + const spv::Id comp = emit_constant(1u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsMaskNone) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherG, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(1u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherG, 2, { + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(1u); + const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants) + .add(args[2].base) + .add(args[3].base) + .add(args[4].base) + .add(args[5].base) + .result; + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsConstOffsetsMask) + .add(offsets) + .result; + }) +// ret tex2DgatherB(s, coords) +// ret tex2DgatherB(s, coords, offset) +// ret tex2DgatherB(s, coords, offset0, offset1, offset2, offset3) +DEFINE_INTRINSIC(tex2DgatherB, 0, float4, sampler2d_float4, float2) +DEFINE_INTRINSIC(tex2DgatherB, 1, float4, sampler2d_float4, float2, int2) +DEFINE_INTRINSIC(tex2DgatherB, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherB, 0, { + code += "textureGather(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", 2)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherB, 1, { + code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + ", 2)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherB, 2, { + code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + + "ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 2)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherB, 0, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherBlue(" + s + ".s, " + id_to_name(args[1].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1))." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1))." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0))." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0))." + 'b' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0))." + 'b' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherB, 1, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherBlue(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1))." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1))." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0))." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0))." + 'b' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0))." + 'b' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherB, 2, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherBlue(" + s + ".s, " + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + " - int2(0, 1), " + + id_to_name(args[3].base) + " - int2(1, 1), " + + id_to_name(args[4].base) + " - int2(1, 0), " + + id_to_name(args[5].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ")." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ")." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ")." + 'b' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ")." + 'b' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0))." + 'b' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0))." + 'b' + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherB, 0, { + const spv::Id comp = emit_constant(2u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsMaskNone) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherB, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(2u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherB, 2, { + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(2u); + const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants) + .add(args[2].base) + .add(args[3].base) + .add(args[4].base) + .add(args[5].base) + .result; + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsConstOffsetsMask) + .add(offsets) + .result; + }) +// ret tex2DgatherA(s, coords) +// ret tex2DgatherA(s, coords, offset) +// ret tex2DgatherA(s, coords, offset0, offset1, offset2, offset3) +DEFINE_INTRINSIC(tex2DgatherA, 0, float4, sampler2d_float4, float2) +DEFINE_INTRINSIC(tex2DgatherA, 1, float4, sampler2d_float4, float2, int2) +DEFINE_INTRINSIC(tex2DgatherA, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherA, 0, { + code += "textureGather(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", 3)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherA, 1, { + code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + ", 3)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2DgatherA, 2, { + code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + + "ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 3)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherA, 0, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherAlpha(" + s + ".s, " + id_to_name(args[1].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1))." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1))." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0))." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0))." + 'a' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0))." + 'a' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherA, 1, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherAlpha(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1))." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1))." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0))." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0))." + 'a' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0))." + 'a' + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2DgatherA, 2, { + const std::string s = id_to_name(args[0].base); + if (_shader_model >= 50) + code += s + ".t.GatherAlpha(" + s + ".s, " + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base) + " - int2(0, 1), " + + id_to_name(args[3].base) + " - int2(1, 1), " + + id_to_name(args[4].base) + " - int2(1, 0), " + + id_to_name(args[5].base) + ')'; + else if (_shader_model >= 40) + code += "float4(" + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ")." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ")." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ")." + 'a' + ", " + + s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ")." + 'a' + ')'; + else + code += "float4(" + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0))." + 'a' + ", " + "tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0))." + 'a' + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherA, 0, { + const spv::Id comp = emit_constant(3u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsMaskNone) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherA, 1, { + if (!args[2].is_constant) + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(3u); + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherA, 2, { + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id comp = emit_constant(3u); + const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants) + .add(args[2].base) + .add(args[3].base) + .add(args[4].base) + .add(args[5].base) + .result; + + return + add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(comp) + .add(spv::ImageOperandsConstOffsetsMask) + .add(offsets) + .result; + }) + +// tex1Dstore(s, coords, value) +DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_int, int, int) +DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_uint, int, uint) +DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_float, int, float) +DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_float4, int, float4) +IMPLEMENT_INTRINSIC_GLSL(tex1Dstore, 0, { + code += "imageStore(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base); + if (args[2].type.rows == 1) + code += ".xxxx"; // Expand last argument to a 4-component vector + code += ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dstore, 0, { + if (_shader_model >= 50) { + code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + "] = " + id_to_name(args[2].base); + } + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dstore, 0, { + spv::Id data = args[2].base; + if (args[2].type.rows == 1) + { + // Expand last argument to a 4-component vector + auto comp_type = args[2].type; + comp_type.rows = 4; + + data = add_instruction(spv::OpCompositeConstruct, convert_type(comp_type)) + .add(data) + .add(data) + .add(data) + .add(data) + .result; + } + + add_instruction_without_result(spv::OpImageWrite) + .add(args[0].base) + .add(args[1].base) + .add(data); + return 0; + }) + +// tex2Dstore(s, coords, value) +DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_int, int2, int) +DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_uint, int2, uint) +DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_float, int2, float) +DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_float4, int2, float4) +IMPLEMENT_INTRINSIC_GLSL(tex2Dstore, 0, { + code += "imageStore(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base); + if (args[2].type.rows == 1) + code += ".xxxx"; // Expand last argument to a 4-component vector + code += ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dstore, 0, { + if (_shader_model >= 50) + code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + "] = " + id_to_name(args[2].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dstore, 0, { + spv::Id data = args[2].base; + if (args[2].type.rows == 1) + { + // Expand last argument to a 4-component vector + auto comp_type = args[2].type; + comp_type.rows = 4; + + data = add_instruction(spv::OpCompositeConstruct, convert_type(comp_type)) + .add(data) + .add(data) + .add(data) + .add(data) + .result; + } + + add_instruction_without_result(spv::OpImageWrite) + .add(args[0].base) + .add(args[1].base) + .add(data); + return 0; + }) + +// tex3Dstore(s, coords, value) +DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_int, int3, int) +DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_uint, int3, uint) +DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_float, int3, float) +DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_float4, int3, float4) +IMPLEMENT_INTRINSIC_GLSL(tex3Dstore, 0, { + code += "imageStore(" + id_to_name(args[0].base) + ", " + + id_to_name(args[1].base) + ", " + + id_to_name(args[2].base); + if (args[2].type.rows == 1) + code += ".xxxx"; // Expand last argument to a 4-component vector + code += ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dstore, 0, { + code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + "] = " + id_to_name(args[2].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dstore, 0, { + spv::Id data = args[2].base; + if (args[2].type.rows == 1) + { + // Expand last argument to a 4-component vector + auto comp_type = args[2].type; + comp_type.rows = 4; + + data = add_instruction(spv::OpCompositeConstruct, convert_type(comp_type)) + .add(data) + .add(data) + .add(data) + .add(data) + .result; + } + + add_instruction_without_result(spv::OpImageWrite) + .add(args[0].base) + .add(args[1].base) + .add(data); + return 0; + }) + +// ret tex1Dsize(s) +// ret tex1Dsize(s, lod) +DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_int) +DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_uint) +DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_float) +DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_float4) +DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_int, int) +DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_uint, int) +DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_float, int) +DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_float4, int) +DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_int) +DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_uint) +DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_float) +DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_float4) +IMPLEMENT_INTRINSIC_GLSL(tex1Dsize, 0, { + code += "textureSize(" + id_to_name(args[0].base) + ", 0)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex1Dsize, 1, { + code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(tex1Dsize, 2, { + code += "imageSize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dsize, 0, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ')'; + else + code += "int(1.0 / " + id_to_name(args[0].base) + ".pixelsize)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dsize, 1, { + if (_shader_model >= 40) + code += "uint temp" + std::to_string(res) + "; " + // Don't need the number of levels out value, so route that to a dummy variable + id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ", temp" + std::to_string(res) + ')'; + else + code += "int(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex1Dsize, 2, { + if (_shader_model >= 50) + code += id_to_name(args[0].base) + ".GetDimensions(" + id_to_name(res) + ')'; + else + code += "0"; // Only supported on SM5+ + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dsize, 0, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + const spv::Id level = emit_constant(0u); + + return + add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(level) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dsize, 1, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + return + add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex1Dsize, 2, { + add_capability(spv::CapabilityImageQuery); + + return + add_instruction(spv::OpImageQuerySize, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret tex2Dsize(s) +// ret tex2Dsize(s, lod) +DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_int) +DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_uint) +DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_float) +DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_float4) +DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_int, int) +DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_uint, int) +DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_float, int) +DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_float4, int) +DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_int) +DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_uint) +DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_float) +DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_float4) +IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 0, { + code += "textureSize(" + id_to_name(args[0].base) + ", 0)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 1, { + code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 2, { + code += "imageSize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 0, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y)"; + else + code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 1, { + if (_shader_model >= 40) + code += "uint temp" + std::to_string(res) + "; " + // Don't need the number of levels out value, so route that to a dummy variable + id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ".x, " + id_to_name(res) + ".y, temp" + std::to_string(res) + ')'; + else + code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 2, { + if (_shader_model >= 50) + code += id_to_name(args[0].base) + ".GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y)"; + else + code += "int2(0, 0)"; // Only supported on SM5+ + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 0, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + const spv::Id level = emit_constant(0u); + + return + add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(level) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 1, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + return + add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 2, { + add_capability(spv::CapabilityImageQuery); + + return + add_instruction(spv::OpImageQuerySize, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret tex3Dsize(s) +// ret tex3Dsize(s, lod) +DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_int) +DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_uint) +DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_float) +DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_float4) +DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_int, int) +DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_uint, int) +DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_float, int) +DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_float4, int) +DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_int) +DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_uint) +DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_float) +DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_float4) +IMPLEMENT_INTRINSIC_GLSL(tex3Dsize, 0, { + code += "textureSize(" + id_to_name(args[0].base) + ", 0)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex3Dsize, 1, { + code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(tex3Dsize, 2, { + code += "imageSize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dsize, 0, { + if (_shader_model >= 40) + code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y, " + id_to_name(res) + ".z)"; + else + code += "int3(1.0 / " + id_to_name(args[0].base) + ".pixelsize)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dsize, 1, { + if (_shader_model >= 40) + code += "uint temp" + std::to_string(res) + "; " + // Don't need the number of levels out value, so route that to a dummy variable + id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ".x, " + id_to_name(res) + ".y, " + id_to_name(res) + ".z, temp" + std::to_string(res) + ')'; + else + code += "int3(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex3Dsize, 2, { + if (_shader_model >= 50) + code += id_to_name(args[0].base) + ".GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y, " + id_to_name(res) + ".z)"; + else + code += "int3(0, 0, 0)"; // Only supported on SM5+ + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dsize, 0, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + const spv::Id level = emit_constant(0u); + + return + add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(level) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dsize, 1, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type)) + .add(args[0].base) + .result; + + return + add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex3Dsize, 2, { + add_capability(spv::CapabilityImageQuery); + + return + add_instruction(spv::OpImageQuerySize, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// barrier() +DEFINE_INTRINSIC(barrier, 0, void) +IMPLEMENT_INTRINSIC_GLSL(barrier, 0, { + code += "barrier()"; + }) +IMPLEMENT_INTRINSIC_HLSL(barrier, 0, { + if (_shader_model >= 50) + code += "GroupMemoryBarrierWithGroupSync()"; + }) +IMPLEMENT_INTRINSIC_SPIRV(barrier, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeWorkgroup); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsAcquireReleaseMask); + + add_instruction_without_result(spv::OpControlBarrier) + .add(mem_scope) // Execution scope + .add(mem_scope) + .add(mem_semantics); + return 0; + }) + +// memoryBarrier() +DEFINE_INTRINSIC(memoryBarrier, 0, void) +IMPLEMENT_INTRINSIC_GLSL(memoryBarrier, 0, { + code += "memoryBarrier()"; + }) +IMPLEMENT_INTRINSIC_HLSL(memoryBarrier, 0, { + if (_shader_model >= 50) + code += "AllMemoryBarrier()"; + }) +IMPLEMENT_INTRINSIC_SPIRV(memoryBarrier, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsImageMemoryMask | spv::MemorySemanticsUniformMemoryMask | spv::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsAcquireReleaseMask); + + add_instruction_without_result(spv::OpMemoryBarrier) + .add(mem_scope) + .add(mem_semantics); + return 0; + }) +// groupMemoryBarrier() +DEFINE_INTRINSIC(groupMemoryBarrier, 0, void) +IMPLEMENT_INTRINSIC_GLSL(groupMemoryBarrier, 0, { + code += "groupMemoryBarrier()"; + }) +IMPLEMENT_INTRINSIC_HLSL(groupMemoryBarrier, 0, { + if (_shader_model >= 50) + code += "GroupMemoryBarrier()"; + }) +IMPLEMENT_INTRINSIC_SPIRV(groupMemoryBarrier, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeWorkgroup); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsAcquireReleaseMask); + + add_instruction_without_result(spv::OpMemoryBarrier) + .add(mem_scope) + .add(mem_semantics); + return 0; + }) + +// ret atomicAdd(inout mem, data) +DEFINE_INTRINSIC(atomicAdd, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicAdd, 0, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicAdd, 0, { + code += "atomicAdd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicAdd, 0, { + if (_shader_model >= 50) + code += "InterlockedAdd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " += " + id_to_name(args[1].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicAdd, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicIAdd, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicAdd(s, coords, data) +DEFINE_INTRINSIC(atomicAdd, 1, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicAdd, 1, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicAdd, 1, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicAdd, 1, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicAdd, 1, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicAdd, 1, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicAdd, 1, { + code += "imageAtomicAdd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicAdd, 1, { + if (_shader_model >= 50) + code += "InterlockedAdd(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicAdd, 1, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicIAdd, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicAnd(inout mem, data) +DEFINE_INTRINSIC(atomicAnd, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicAnd, 0, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicAnd, 0, { + code += "atomicAnd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicAnd, 0, { + if (_shader_model >= 50) + code += "InterlockedAnd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " &= " + id_to_name(args[1].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicAnd, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicAnd, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicAnd(s, coords, data) +DEFINE_INTRINSIC(atomicAnd, 1, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicAnd, 1, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicAnd, 1, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicAnd, 1, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicAnd, 1, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicAnd, 1, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicAnd, 1, { + code += "imageAtomicAnd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicAnd, 1, { + if (_shader_model >= 50) + code += "InterlockedAnd(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicAnd, 1, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicAnd, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicOr(inout mem, data) +DEFINE_INTRINSIC(atomicOr, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicOr, 0, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicOr, 0, { + code += "atomicOr(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicOr, 0, { + if (_shader_model >= 50) + code += "InterlockedOr(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " |= " + id_to_name(args[1].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicOr, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicOr, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicOr(s, coords, data) +DEFINE_INTRINSIC(atomicOr, 1, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicOr, 1, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicOr, 1, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicOr, 1, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicOr, 1, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicOr, 1, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicOr, 1, { + code += "imageAtomicOr(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicOr, 1, { + if (_shader_model >= 50) + code += "InterlockedOr(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicOr, 1, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicOr, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicXor(inout mem, data) +DEFINE_INTRINSIC(atomicXor, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicXor, 0, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicXor, 0, { + code += "atomicXor(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicXor, 0, { + if (_shader_model >= 50) + code += "InterlockedXor(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " ^= " + id_to_name(args[1].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicXor, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicXor, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicXor(s, coords, data) +DEFINE_INTRINSIC(atomicXor, 1, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicXor, 1, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicXor, 1, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicXor, 1, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicXor, 1, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicXor, 1, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicXor, 1, { + code += "imageAtomicXor(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicXor, 1, { + if (_shader_model >= 50) + code += "InterlockedXor(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicXor, 1, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicXor, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicMin(inout mem, data) +DEFINE_INTRINSIC(atomicMin, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicMin, 1, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicMin, 0, { + code += "atomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(atomicMin, 1, { + code += "atomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMin, 0, { + if (_shader_model >= 50) + code += "InterlockedMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMin, 1, { + if (_shader_model >= 50) + code += "InterlockedMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicSMin, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 1, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicUMin, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicMin(s, coords, data) +DEFINE_INTRINSIC(atomicMin, 2, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicMin, 2, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicMin, 2, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicMin, 3, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicMin, 3, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicMin, 3, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicMin, 2, { + code += "imageAtomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(atomicMin, 3, { + code += "imageAtomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMin, 2, { + if (_shader_model >= 50) + code += "InterlockedMin(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMin, 3, { + if (_shader_model >= 50) + code += "InterlockedMin(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 2, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicSMin, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 3, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicUMin, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicMax(inout mem, data) +DEFINE_INTRINSIC(atomicMax, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicMax, 1, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicMax, 0, { + code += "atomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(atomicMax, 1, { + code += "atomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMax, 0, { + if (_shader_model >= 50) + code += "InterlockedMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMax, 1, { + if (_shader_model >= 50) + code += "InterlockedMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicSMax, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 1, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicUMax, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicMax(s, coords, data) +DEFINE_INTRINSIC(atomicMax, 2, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicMax, 2, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicMax, 2, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicMax, 3, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicMax, 3, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicMax, 3, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicMax, 2, { + code += "imageAtomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(atomicMax, 3, { + code += "imageAtomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMax, 2, { + if (_shader_model >= 50) + code += "InterlockedMax(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicMax, 3, { + if (_shader_model >= 50) + code += "InterlockedMax(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 2, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicSMax, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 3, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicUMax, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicExchange(inout mem, data) +DEFINE_INTRINSIC(atomicExchange, 0, int, inout_int, int) +DEFINE_INTRINSIC(atomicExchange, 0, uint, inout_uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicExchange, 0, { + code += "atomicExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicExchange, 0, { + if (_shader_model >= 50) + code += "InterlockedExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = " + id_to_name(args[1].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicExchange, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicExchange, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(args[1].base) + .result; + }) +// ret atomicExchange(s, coords, data) +DEFINE_INTRINSIC(atomicExchange, 1, int, inout_storage1d_int, int, int) +DEFINE_INTRINSIC(atomicExchange, 1, int, inout_storage2d_int, int2, int) +DEFINE_INTRINSIC(atomicExchange, 1, int, inout_storage3d_int, int3, int) +DEFINE_INTRINSIC(atomicExchange, 1, uint, inout_storage1d_uint, int, uint) +DEFINE_INTRINSIC(atomicExchange, 1, uint, inout_storage2d_uint, int2, uint) +DEFINE_INTRINSIC(atomicExchange, 1, uint, inout_storage3d_uint, int3, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicExchange, 1, { + code += "imageAtomicExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicExchange, 1, { + if (_shader_model >= 50) + code += "InterlockedExchange(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicExchange, 1, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicExchange, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(args[2].base) + .result; + }) + +// ret atomicCompareExchange(inout mem, compare, data) +DEFINE_INTRINSIC(atomicCompareExchange, 0, int, inout_int, int, int) +DEFINE_INTRINSIC(atomicCompareExchange, 0, uint, inout_uint, uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicCompareExchange, 0, { + code += "atomicCompSwap(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicCompareExchange, 0, { + if (_shader_model >= 50) + code += "InterlockedCompareExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')'; + else + code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; if (" + id_to_name(args[0].base) + " == " + id_to_name(args[1].base) + ") " + id_to_name(args[0].base) + " = " + id_to_name(args[2].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicCompareExchange, 0, { + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicCompareExchange, convert_type(res_type)) + .add(args[0].base) + .add(mem_scope) + .add(mem_semantics) + .add(mem_semantics) + .add(args[2].base) + .add(args[1].base) + .result; + }) +// ret atomicCompareExchange(s, coords, compare, data) +DEFINE_INTRINSIC(atomicCompareExchange, 1, int, inout_storage1d_int, int, int, int) +DEFINE_INTRINSIC(atomicCompareExchange, 1, int, inout_storage2d_int, int2, int, int) +DEFINE_INTRINSIC(atomicCompareExchange, 1, int, inout_storage3d_int, int3, int, int) +DEFINE_INTRINSIC(atomicCompareExchange, 1, uint, inout_storage1d_uint, int, uint, uint) +DEFINE_INTRINSIC(atomicCompareExchange, 1, uint, inout_storage2d_uint, int2, uint, uint) +DEFINE_INTRINSIC(atomicCompareExchange, 1, uint, inout_storage3d_uint, int3, uint, uint) +IMPLEMENT_INTRINSIC_GLSL(atomicCompareExchange, 1, { + code += "imageAtomicCompSwap(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atomicCompareExchange, 1, { + if (_shader_model >= 50) + code += "InterlockedCompareExchange(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(res) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atomicCompareExchange, 1, { + const spv::Id ms_sample = emit_constant(0u); + + const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage)) + .add(args[0].base) + .add(args[1].base) + .add(ms_sample) + .result; + const spv::Id mem_scope = emit_constant(spv::ScopeDevice); + const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone); + + return + add_instruction(spv::OpAtomicCompareExchange, convert_type(res_type)) + .add(texel) + .add(mem_scope) + .add(mem_semantics) + .add(mem_semantics) + .add(args[3].base) + .add(args[2].base) + .result; + }) + +#undef DEFINE_INTRINSIC +#undef IMPLEMENT_INTRINSIC_GLSL +#undef IMPLEMENT_INTRINSIC_HLSL +#undef IMPLEMENT_INTRINSIC_SPIRV diff --git a/duckstation.sln b/duckstation.sln index fac7fbf03..e4c147bc6 100644 --- a/duckstation.sln +++ b/duckstation.sln @@ -119,6 +119,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zydis", "dep\zydis\zydis.vc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "d3d12ma", "dep\d3d12ma\d3d12ma.vcxproj", "{F351C4D8-594A-4850-B77B-3C1249812CCE}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshadefx", "dep\reshadefx\reshadefx.vcxproj", "{27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -893,6 +895,30 @@ Global {F351C4D8-594A-4850-B77B-3C1249812CCE}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 {F351C4D8-594A-4850-B77B-3C1249812CCE}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32 {F351C4D8-594A-4850-B77B-3C1249812CCE}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Debug|ARM64.Build.0 = Debug|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Debug|x64.ActiveCfg = Debug|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Debug|x64.Build.0 = Debug|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Debug|x86.ActiveCfg = Debug|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Debug|x86.Build.0 = Debug|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast|ARM64.ActiveCfg = DebugFast|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast|ARM64.Build.0 = DebugFast|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast|x64.ActiveCfg = DebugFast|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast|x64.Build.0 = DebugFast|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast|x86.ActiveCfg = DebugFast|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.DebugFast|x86.Build.0 = DebugFast|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|ARM64.ActiveCfg = Release|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|ARM64.Build.0 = Release|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|x64.ActiveCfg = Release|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|x64.Build.0 = Release|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|x86.ActiveCfg = Release|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.Release|x86.Build.0 = Release|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.ReleaseLTCG|ARM64.Build.0 = ReleaseLTCG|ARM64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32 + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -921,6 +947,7 @@ Global {EE55AA65-EA6B-4861-810B-78354B53A807} = {BA490C0E-497D-4634-A21E-E65012006385} {C51A346A-86B2-46DF-9BB3-D0AA7E5D8699} = {BA490C0E-497D-4634-A21E-E65012006385} {F351C4D8-594A-4850-B77B-3C1249812CCE} = {BA490C0E-497D-4634-A21E-E65012006385} + {27B8D4BB-4F01-4432-BC14-9BF6CA458EEE} = {BA490C0E-497D-4634-A21E-E65012006385} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {26E40B32-7C1D-48D0-95F4-1A500E054028}