dep: Add reshadefx

This commit is contained in:
Stenzek 2023-08-13 14:03:17 +10:00
parent c01f249e0f
commit 8c638b4c78
24 changed files with 19519 additions and 0 deletions

View file

@ -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"
)

9
dep/reshadefx/LICENSE.md Normal file
View file

@ -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.

3
dep/reshadefx/README.md Normal file
View file

@ -0,0 +1,3 @@
This directory contains a partial copy of the ReShade post-processing injector, from:
https://github.com/crosire/reshade

View file

@ -0,0 +1,377 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_module.hpp"
#include <memory> // std::unique_ptr
#include <algorithm> // std::find_if
namespace reshadefx
{
/// <summary>
/// A SSA code generation back-end interface for the parser to call into.
/// </summary>
class codegen
{
public:
/// <summary>
/// Virtual destructor to guarantee that memory of the implementations deriving from this interface is properly destroyed.
/// </summary>
virtual ~codegen() {}
/// <summary>
/// Writes result of the code generation to the specified <paramref name="module"/>.
/// </summary>
/// <param name="module">Target module to fill.</param>
virtual void write_result(module &module) = 0;
public:
/// <summary>
/// An opaque ID referring to a SSA value or basic block.
/// </summary>
using id = uint32_t;
/// <summary>
/// Defines a new struct type.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="info">Description of the type.</param>
/// <returns>New SSA ID of the type.</returns>
virtual id define_struct(const location &loc, struct_info &info) = 0;
/// <summary>
/// Defines a new texture binding.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="info">Description of the texture object.</param>
/// <returns>New SSA ID of the binding.</returns>
virtual id define_texture(const location &loc, texture_info &info) = 0;
/// <summary>
/// Defines a new sampler binding.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="tex_info">Description of the texture this sampler object references.</param>
/// <param name="info">Description of the sampler object.</param>
/// <returns>New SSA ID of the binding.</returns>
virtual id define_sampler(const location &loc, const texture_info &tex_info, sampler_info &info) = 0;
/// <summary>
/// Defines a new storage binding.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="tex_info">Description of the texture this storage object references.</param>
/// <param name="info">Description of the storage object.</param>
/// <returns>New SSA ID of the binding.</returns>
virtual id define_storage(const location &loc, const texture_info &tex_info, storage_info &info) = 0;
/// <summary>
/// Defines a new uniform variable.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="info">Description of the uniform variable.</param>
/// <returns>New SSA ID of the variable.</returns>
virtual id define_uniform(const location &loc, uniform_info &info) = 0;
/// <summary>
/// Defines a new variable.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="type">Data type of the variable.</param>
/// <param name="name">Name of the variable.</param>
/// <param name="global"><c>true</c> if this variable is in global scope, <c>false</c> otherwise.</param>
/// <param name="initializer_value">SSA ID of an optional initializer value.</param>
/// <returns>New SSA ID of the variable.</returns>
virtual id define_variable(const location &loc, const type &type, std::string name = std::string(), bool global = false, id initializer_value = 0) = 0;
/// <summary>
/// Defines a new function and its function parameters and make it current. Any code added after this call is added to this function.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="info">Description of the function.</param>
/// <returns>New SSA ID of the function.</returns>
virtual id define_function(const location &loc, function_info &info) = 0;
/// <summary>
/// Defines a new effect technique.
/// </summary>
/// <param name="loc">Source location matching this definition (for debugging).</param>
/// <param name="info">Description of the technique.</param>
void define_technique(technique_info &&info) { _module.techniques.push_back(std::move(info)); }
/// <summary>
/// Makes a function a shader entry point.
/// </summary>
/// <param name="function">Function to use as entry point. May be overwritten to point to a new unique function for this entry point.</param>
/// <param name="type">Shader type (vertex, pixel or compute shader).</param>
/// <param name="num_threads">Number of local threads it this is a compute entry point.</param>
virtual void define_entry_point(function_info &function, shader_type type, int num_threads[3] = nullptr) = 0;
/// <summary>
/// Resolves the access chain and add a load operation to the output.
/// </summary>
/// <param name="chain">Access chain pointing to the variable to load from.</param>
/// <param name="force_new_id">Set to <see langword="true"/> to force this to return a new SSA ID for l-value loads.</param>
/// <returns>New SSA ID with the loaded value.</returns>
virtual id emit_load(const expression &chain, bool force_new_id = false) = 0;
/// <summary>
/// Resolves the access chain and add a store operation to the output.
/// </summary>
/// <param name="chain">Access chain pointing to the variable to store to.</param>
/// <param name="value">SSA ID of the value to store.</param>
virtual void emit_store(const expression &chain, id value) = 0;
/// <summary>
/// Resolves the access chain, but do not add a load operation. This returns a pointer instead.
/// </summary>
/// <param name="chain">Access chain pointing to the variable to resolve.</param>
/// <param name="chain_index">Output value which is set to the index in the access chain up to which the access chain went.</param>
/// <returns>New SSA ID with a pointer to the value.</returns>
virtual id emit_access_chain(const expression &chain, size_t &chain_index) { chain_index = chain.chain.size(); return emit_load(chain); }
/// <summary>
/// Creates a SSA constant value.
/// </summary>
/// <param name="type">Data type of the constant.</param>
/// <param name="data">Actual constant data to convert into a SSA ID.</param>
/// <returns>New SSA ID with the constant value.</returns>
virtual id emit_constant(const type &type, const constant &data) = 0;
/// <summary>
/// Adds an unary operation to the output (built-in operation with one argument).
/// </summary>
/// <param name="loc">Source location matching this operation (for debugging).</param>
/// <param name="op">Unary operator to use.</param>
/// <param name="type">Data type of the input value.</param>
/// <param name="val">SSA ID of value to perform the operation on.</param>
/// <returns>New SSA ID with the result of the operation.</returns>
virtual id emit_unary_op(const location &loc, tokenid op, const type &type, id val) = 0;
/// <summary>
/// Adds a binary operation to the output (built-in operation with two arguments).
/// </summary>
/// <param name="loc">Source location matching this operation (for debugging).</param>
/// <param name="op">Binary operator to use.</param>
/// <param name="res_type">Data type of the result.</param>
/// <param name="type">Data type of the input values.</param>
/// <param name="lhs">SSA ID of the value on the left-hand side of the binary operation.</param>
/// <param name="rhs">SSA ID of the value on the right-hand side of the binary operation.</param>
/// <returns>New SSA ID with the result of the operation.</returns>
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); }
/// <summary>
/// Adds a ternary operation to the output (built-in operation with three arguments).
/// </summary>
/// <param name="loc">Source location matching this operation (for debugging).</param>
/// <param name="op">Ternary operator to use.</param>
/// <param name="type">Data type of the input values.</param>
/// <param name="condition">SSA ID of the condition value of the ternary operation.</param>
/// <param name="true_value">SSA ID of the first value of the ternary operation.</param>
/// <param name="false_value">SSA ID of the second value of the ternary operation.</param>
/// <returns>New SSA ID with the result of the operation.</returns>
virtual id emit_ternary_op(const location &loc, tokenid op, const type &type, id condition, id true_value, id false_value) = 0;
/// <summary>
/// Adds a function call to the output.
/// </summary>
/// <param name="loc">Source location matching this operation (for debugging).</param>
/// <param name="function">SSA ID of the function to call.</param>
/// <param name="res_type">Data type of the call result.</param>
/// <param name="args">List of SSA IDs representing the call arguments.</param>
/// <returns>New SSA ID with the result of the function call.</returns>
virtual id emit_call(const location &loc, id function, const type &res_type, const std::vector<expression> &args) = 0;
/// <summary>
/// Adds an intrinsic function call to the output.
/// </summary>
/// <param name="loc">Source location matching this operation (for debugging).</param>
/// <param name="function">Intrinsic to call.</param>
/// <param name="res_type">Data type of the call result.</param>
/// <param name="args">List of SSA IDs representing the call arguments.</param>
/// <returns>New SSA ID with the result of the function call.</returns>
virtual id emit_call_intrinsic(const location &loc, id function, const type &res_type, const std::vector<expression> &args) = 0;
/// <summary>
/// Adds a type constructor call to the output.
/// </summary>
/// <param name="type">Data type to construct.</param>
/// <param name="args">List of SSA IDs representing the scalar constructor arguments.</param>
/// <returns>New SSA ID with the constructed value.</returns>
virtual id emit_construct(const location &loc, const type &type, const std::vector<expression> &args) = 0;
/// <summary>
/// Adds a structured branch control flow to the output.
/// </summary>
/// <param name="loc">Source location matching this branch (for debugging).</param>
/// <param name="flags">0 - default, 1 - flatten, 2 - do not flatten</param>
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;
/// <summary>
/// Adds a branch control flow with a SSA phi operation to the output.
/// </summary>
/// <param name="loc">Source location matching this branch (for debugging).</param>
/// <returns>New SSA ID with the result of the phi operation.</returns>
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;
/// <summary>
/// Adds a structured loop control flow to the output.
/// </summary>
/// <param name="loc">Source location matching this loop (for debugging).</param>
/// <param name="flags">0 - default, 1 - unroll, 2 - do not unroll</param>
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;
/// <summary>
/// Adds a structured switch control flow to the output.
/// </summary>
/// <param name="loc">Source location matching this switch (for debugging).</param>
/// <param name="flags">0 - default, 1 - flatten, 2 - do not flatten</param>
virtual void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, id default_block, const std::vector<id> &case_literal_and_labels, const std::vector<id> &case_blocks, unsigned int flags) = 0;
/// <summary>
/// Returns <see langword="true"/> if code is currently added to a basic block.
/// </summary>
bool is_in_block() const { return _current_block != 0; }
/// <summary>
/// Returns <see langword="true"/> if code is currently added to a function.
/// </summary>
virtual bool is_in_function() const { return is_in_block(); }
/// <summary>
/// Creates a new basic block.
/// </summary>
/// <returns>New ID of the basic block.</returns>
virtual id create_block() { return make_id(); }
/// <summary>
/// Overwrites the current block ID.
/// </summary>
/// <param name="id">ID of the block to make current.</param>
/// <returns>ID of the previous basic block.</returns>
virtual id set_block(id id) = 0;
/// <summary>
/// Creates a new basic block and make it current.
/// </summary>
/// <param name="id">ID of the basic block to create and make current.</param>
virtual void enter_block(id id) = 0;
/// <summary>
/// Returns from the current basic block and kill the shader invocation.
/// </summary>
/// <returns>ID of the current basic block.</returns>
virtual id leave_block_and_kill() = 0;
/// <summary>
/// Returns from the current basic block and hand control flow over to the function call side.
/// </summary>
/// <param name="value">Optional SSA ID of a return value.</param>
/// <returns>ID of the current basic block.</returns>
virtual id leave_block_and_return(id value = 0) = 0;
/// <summary>
/// Diverges the current control flow and enter a switch.
/// </summary>
/// <param name="value">SSA ID of the selector value to decide the switch path.</param>
/// <returns>ID of the current basic block.</returns>
virtual id leave_block_and_switch(id value, id default_target) = 0;
/// <summary>
/// Diverges the current control flow and jump to the specified target block.
/// </summary>
/// <param name="target">ID of the basic block to jump to.</param>
/// <param name="is_continue">Set to <see langword="true"/> if this corresponds to a loop continue statement.</param>
/// <returns>ID of the current basic block.</returns>
virtual id leave_block_and_branch(id target, unsigned int loop_flow = 0) = 0;
/// <summary>
/// Diverges the current control flow and jump to one of the specified target blocks, depending on the condition.
/// </summary>
/// <param name="condition">SSA ID of a value used to choose which path to take.</param>
/// <param name="true_target">ID of the basic block to jump to when the condition is true.</param>
/// <param name="false_target">ID of the basic block to jump to when the condition is false.</param>
/// <returns>ID of the current basic block.</returns>
virtual id leave_block_and_branch_conditional(id condition, id true_target, id false_target) = 0;
/// <summary>
/// Leaves the current function. Any code added after this call is added in the global scope.
/// </summary>
virtual void leave_function() = 0;
/// <summary>
/// Looks up an existing struct type.
/// </summary>
/// <param name="id">SSA ID of the type to find.</param>
/// <returns>Reference to the struct description.</returns>
const struct_info &get_struct(id id) const
{
return *std::find_if(_structs.begin(), _structs.end(),
[id](const auto &it) { return it.definition == id; });
}
/// <summary>
/// Looks up an existing texture binding.
/// </summary>
/// <param name="id">SSA ID of the texture binding to find.</param>
/// <returns>Reference to the texture description.</returns>
texture_info &get_texture(id id)
{
return *std::find_if(_module.textures.begin(), _module.textures.end(),
[id](const auto &it) { return it.id == id; });
}
/// <summary>
/// Looks up an existing sampler binding.
/// </summary>
/// <param name="id">SSA ID of the sampler binding to find.</param>
/// <returns>Reference to the sampler description.</returns>
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; });
}
/// <summary>
/// Looks up an existing storage binding.
/// </summary>
/// <param name="id">SSA ID of the storage binding to find.</param>
/// <returns>Reference to the storage description.</returns>
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; });
}
/// <summary>
/// Looks up an existing function definition.
/// </summary>
/// <param name="id">SSA ID of the function variable to find.</param>
/// <returns>Reference to the function description.</returns>
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<struct_info> _structs;
std::vector<std::unique_ptr<function_info>> _functions;
id _next_id = 1;
id _last_block = 0;
id _current_block = 0;
};
/// <summary>
/// Creates a back-end implementation for GLSL code generation.
/// </summary>
/// <param name="vulkan_semantics">Generate GLSL for OpenGL or for Vulkan.</param>
/// <param name="debug_info">Whether to append debug information like line directives to the generated code.</param>
/// <param name="uniforms_to_spec_constants">Whether to convert uniform variables to specialization constants.</param>
/// <param name="enable_16bit_types">Use real 16-bit types for the minimum precision types "min16int", "min16uint" and "min16float".</param>
/// <param name="flip_vert_y">Insert code to flip the Y component of the output position in vertex shaders.</param>
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);
/// <summary>
/// Creates a back-end implementation for HLSL code generation.
/// </summary>
/// <param name="shader_model">The HLSL shader model version (e.g. 30, 41, 50, 60, ...)</param>
/// <param name="debug_info">Whether to append debug information like line directives to the generated code.</param>
/// <param name="uniforms_to_spec_constants">Whether to convert uniform variables to specialization constants.</param>
codegen *create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants);
/// <summary>
/// Creates a back-end implementation for SPIR-V code generation.
/// </summary>
/// <param name="vulkan_semantics">Generate SPIR-V for OpenGL or for Vulkan.</param>
/// <param name="debug_info">Whether to append debug information like line directives to the generated code.</param>
/// <param name="uniforms_to_spec_constants">Whether to convert uniform variables to specialization constants.</param>
/// <param name="enable_16bit_types">Use real 16-bit types for the minimum precision types "min16int", "min16uint" and "min16float".</param>
/// <param name="flip_vert_y">Insert code to flip the Y component of the output position in vertex shaders.</param>
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);
}

View file

@ -0,0 +1,249 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_token.hpp"
namespace reshadefx
{
/// <summary>
/// Structure which encapsulates a parsed value type
/// </summary>
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,
};
/// <summary>
/// Gets the result type of an operation involving the two input types.
/// </summary>
static type merge(const type &lhs, const type &rhs);
/// <summary>
/// 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.
/// </summary>
static unsigned int rank(const type &src, const type &dst);
/// <summary>
/// Returns a human-readable description of this type definition.
/// </summary>
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;
};
/// <summary>
/// Structure which encapsulates a parsed constant value
/// </summary>
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<constant> array_data;
};
/// <summary>
/// Structures which keeps track of the access chain of an expression
/// </summary>
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<operation> chain;
/// <summary>
/// Initializes the expression to a l-value.
/// </summary>
/// <param name="loc">Code location of the expression.</param>
/// <param name="base">SSA ID of the l-value.</param>
/// <param name="type">Value type of the expression result.</param>
void reset_to_lvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type);
/// <summary>
/// Initializes the expression to a r-value.
/// </summary>
/// <param name="loc">Code location of the expression.</param>
/// <param name="base">SSA ID of the r-value.</param>
/// <param name="type">Value type of the expression result.</param>
void reset_to_rvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type);
/// <summary>
/// Initializes the expression to a constant value.
/// </summary>
/// <param name="loc">Code location of the constant expression.</param>
/// <param name="data">Constant value to initialize to.</param>
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);
/// <summary>
/// Adds a cast operation to the current access chain.
/// </summary>
/// <param name="type">Type to cast the expression to.</param>
void add_cast_operation(const reshadefx::type &type);
/// <summary>
/// Adds a structure member lookup to the current access chain.
/// </summary>
/// <param name="index">Index of the member to dereference.</param>
/// <param name="type">Value type of the member.</param>
void add_member_access(unsigned int index, const reshadefx::type &type);
/// <summary>
/// Adds an index operation to the current access chain.
/// </summary>
/// <param name="index_expression">SSA ID of the indexing value.</param>
void add_dynamic_index_access(uint32_t index_expression);
/// <summary>
/// Adds an constant index operation to the current access chain.
/// </summary>
/// <param name="index">Constant indexing value.</param>
void add_constant_index_access(unsigned int index);
/// <summary>
/// Adds a swizzle operation to the current access chain.
/// </summary>
/// <param name="swizzle">Swizzle for each component. -1 = unused, 0 = x, 1 = y, 2 = z, 3 = w.</param>
/// <param name="length">Number of components in the swizzle. The maximum is 4.</param>
void add_swizzle_access(const signed char swizzle[4], unsigned int length);
/// <summary>
/// Applies an unary operation to this constant expression.
/// </summary>
/// <param name="op">Unary operator to apply.</param>
bool evaluate_constant_expression(reshadefx::tokenid op);
/// <summary>
/// Applies a binary operation to this constant expression.
/// </summary>
/// <param name="op">Binary operator to apply.</param>
/// <param name="rhs">Constant value to use as right-hand side of the binary operation.</param>
bool evaluate_constant_expression(reshadefx::tokenid op, const reshadefx::constant &rhs);
};
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_token.hpp"
namespace reshadefx
{
/// <summary>
/// A lexical analyzer for C-like languages.
/// </summary>
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;
}
/// <summary>
/// Gets the current position in the input string.
/// </summary>
size_t input_offset() const { return _cur - _input.data(); }
/// <summary>
/// Gets the input string this lexical analyzer works on.
/// </summary>
/// <returns>Constant reference to the input string.</returns>
const std::string &input_string() const { return _input; }
/// <summary>
/// Performs lexical analysis on the input string and return the next token in sequence.
/// </summary>
/// <returns>Next token from the input string.</returns>
token lex();
/// <summary>
/// Advances to the next token that is not whitespace.
/// </summary>
void skip_space();
/// <summary>
/// Advances to the next new line, ignoring all tokens.
/// </summary>
void skip_to_next_line();
/// <summary>
/// Resets position to the specified <paramref name="offset"/>.
/// </summary>
/// <param name="offset">Offset in characters from the start of the input string.</param>
void reset_to_offset(size_t offset);
private:
/// <summary>
/// Skips an arbitrary amount of characters in the input string.
/// </summary>
/// <param name="length">Number of input characters to skip.</param>
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;
};
}

View file

@ -0,0 +1,350 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_expression.hpp"
#include <unordered_set>
namespace reshadefx
{
/// <summary>
/// A list of supported texture types.
/// </summary>
enum class texture_type
{
texture_1d = 1,
texture_2d = 2,
texture_3d = 3
};
/// <summary>
/// A list of supported texture formats.
/// </summary>
enum class texture_format
{
unknown,
r8,
r16,
r16f,
r32i,
r32u,
r32f,
rg8,
rg16,
rg16f,
rg32f,
rgba8,
rgba16,
rgba16f,
rgba32f,
rgb10a2
};
/// <summary>
/// A filtering type used for texture lookups.
/// </summary>
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
};
/// <summary>
/// Specifies behavior of sampling with texture coordinates outside an image.
/// </summary>
enum class texture_address_mode
{
wrap = 1,
mirror = 2,
clamp = 3,
border = 4
};
/// <summary>
/// Specifies RGB or alpha blending operations.
/// </summary>
enum class pass_blend_op : uint8_t
{
add = 1,
subtract,
reverse_subtract,
min,
max
};
/// <summary>
/// Specifies blend factors, which modulate values between the pixel shader output and render target.
/// </summary>
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
};
/// <summary>
/// Specifies the stencil operations that can be performed during depth-stencil testing.
/// </summary>
enum class pass_stencil_op : uint8_t
{
zero = 0,
keep,
replace,
increment_saturate,
decrement_saturate,
invert,
increment,
decrement
};
/// <summary>
/// Specifies comparison options for depth-stencil testing.
/// </summary>
enum class pass_stencil_func : uint8_t
{
never,
less,
equal,
less_equal,
greater,
not_equal,
greater_equal,
always
};
/// <summary>
/// Specifies the possible primitives.
/// </summary>
enum class primitive_topology : uint8_t
{
point_list = 1,
line_list,
line_strip,
triangle_list,
triangle_strip
};
/// <summary>
/// A struct type defined in the effect code.
/// </summary>
struct struct_info
{
std::string name;
std::string unique_name;
std::vector<struct struct_member_info> member_list;
uint32_t definition = 0;
};
/// <summary>
/// A struct field defined in the effect code.
/// </summary>
struct struct_member_info
{
reshadefx::type type;
std::string name;
std::string semantic;
reshadefx::location location;
uint32_t definition = 0;
};
/// <summary>
/// An annotation attached to a variable.
/// </summary>
struct annotation
{
reshadefx::type type;
std::string name;
reshadefx::constant value;
};
/// <summary>
/// A texture defined in the effect code.
/// </summary>
struct texture_info
{
uint32_t id = 0;
uint32_t binding = 0;
std::string name;
std::string semantic;
std::string unique_name;
std::vector<annotation> 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;
};
/// <summary>
/// A texture sampler defined in the effect code.
/// </summary>
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<annotation> 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;
};
/// <summary>
/// A texture storage object defined in the effect code.
/// </summary>
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;
};
/// <summary>
/// An uniform variable defined in the effect code.
/// </summary>
struct uniform_info
{
std::string name;
reshadefx::type type;
uint32_t size = 0;
uint32_t offset = 0;
std::vector<annotation> annotations;
bool has_initializer_value = false;
reshadefx::constant initializer_value;
};
/// <summary>
/// Type of a shader entry point.
/// </summary>
enum class shader_type
{
vs,
ps,
cs,
};
/// <summary>
/// A shader entry point function.
/// </summary>
struct entry_point
{
std::string name;
shader_type type;
};
/// <summary>
/// A function defined in the effect code.
/// </summary>
struct function_info
{
uint32_t definition;
std::string name;
std::string unique_name;
reshadefx::type return_type;
std::string return_semantic;
std::vector<struct_member_info> parameter_list;
std::unordered_set<uint32_t> referenced_samplers;
std::unordered_set<uint32_t> referenced_storages;
};
/// <summary>
/// A render pass with all its state info.
/// </summary>
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<sampler_info> samplers;
std::vector<storage_info> storages;
};
/// <summary>
/// A collection of passes that make up an effect.
/// </summary>
struct technique_info
{
std::string name;
std::vector<pass_info> passes;
std::vector<annotation> annotations;
};
/// <summary>
/// In-memory representation of an effect file.
/// </summary>
struct module
{
std::vector<char> code;
std::vector<entry_point> entry_points;
std::vector<texture_info> textures;
std::vector<sampler_info> samplers;
std::vector<storage_info> storages;
std::vector<uniform_info> uniforms, spec_constants;
std::vector<technique_info> 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;
};
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_symbol_table.hpp"
#include <memory> // std::unique_ptr
namespace reshadefx
{
/// <summary>
/// A parser for the ReShade FX shader language.
/// </summary>
class parser : symbol_table
{
public:
// Define constructor explicitly because lexer class is not included here
parser();
~parser();
/// <summary>
/// Parses the provided input string.
/// </summary>
/// <param name="source">String to analyze.</param>
/// <param name="backend">Code generation implementation to use.</param>
/// <returns><see langword="true"/> if parsing was successfull, <see langword="false"/> otherwise.</returns>
bool parse(std::string source, class codegen *backend);
/// <summary>
/// Gets the list of error messages.
/// </summary>
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<tokenid>(tok); }
bool peek(tokenid tokid) const { return _token_next.id == tokid; }
void consume();
void consume_until(char tok) { return consume_until(static_cast<tokenid>(tok)); }
void consume_until(tokenid tokid);
bool accept(char tok) { return accept(static_cast<tokenid>(tok)); }
bool accept(tokenid tokid);
bool expect(char tok) { return expect(static_cast<tokenid>(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<annotation> &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<class lexer> _lexer;
size_t _lexer_backup_offset = 0;
std::vector<uint32_t> _loop_break_target_stack;
std::vector<uint32_t> _loop_continue_target_stack;
reshadefx::function_info *_current_function = nullptr;
};
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_token.hpp"
#include <memory> // std::unique_ptr
#include <filesystem>
#include <unordered_map>
#include <unordered_set>
namespace reshadefx
{
/// <summary>
/// A C-style preprocessor implementation.
/// </summary>
class preprocessor
{
public:
struct macro
{
std::string replacement_list;
std::vector<std::string> 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();
/// <summary>
/// Adds an include directory to the list of search paths used when resolving #include directives.
/// </summary>
/// <param name="path">Path to the directory to add.</param>
void add_include_path(const std::filesystem::path &path);
/// <summary>
/// Adds a new macro definition. This is equal to appending '#define name macro' to this preprocessor instance.
/// </summary>
/// <param name="name">Name of the macro to define.</param>
/// <param name="macro">Definition of the macro function or value.</param>
/// <returns></returns>
bool add_macro_definition(const std::string &name, const macro &macro);
/// <summary>
/// Adds a new macro value definition. This is equal to appending '#define name macro' to this preprocessor instance.
/// </summary>
/// <param name="name">Name of the macro to define.</param>
/// <param name="value">Value to define that macro to.</param>
/// <returns></returns>
bool add_macro_definition(const std::string &name, std::string value = "1")
{
return add_macro_definition(name, macro { std::move(value), {}, true });
}
/// <summary>
/// Opens the specified file, parses its contents and appends them to the output.
/// </summary>
/// <param name="path">Path to the file to parse.</param>
/// <returns><see langword="true"/> if parsing was successful, <see langword="false"/> otherwise.</returns>
bool append_file(const std::filesystem::path &path);
/// <summary>
/// Parses the specified string and appends it to the output.
/// </summary>
/// <param name="source_code">String to parse.</param>
/// <param name="path">Optional file path to identify this string with.</param>
/// <returns><see langword="true"/> if parsing was successful, <see langword="false"/> otherwise.</returns>
bool append_string(std::string source_code, const std::filesystem::path &path = std::filesystem::path());
/// <summary>
/// Gets the list of error messages.
/// </summary>
const std::string &errors() const { return _errors; }
/// <summary>
/// Gets the current pre-processed output string.
/// </summary>
const std::string &output() const { return _output; }
/// <summary>
/// Gets a list of all included files.
/// </summary>
std::vector<std::filesystem::path> included_files() const;
/// <summary>
/// Gets a list of all defines that were used in #ifdef and #ifndef lines.
/// </summary>
std::vector<std::pair<std::string, std::string>> used_macro_definitions() const;
/// <summary>
/// Gets a list of pragma directives that occured.
/// </summary>
std::vector<std::pair<std::string, std::string>> 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<class lexer> lexer;
token next_token;
std::unordered_set<std::string> 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 &macro, const std::vector<std::string> &arguments);
void create_macro_replacement_list(macro &macro);
bool _success = true;
std::string _output, _errors;
std::string _current_token_raw_data;
reshadefx::token _token;
location _output_location;
std::vector<input_level> _input_stack;
size_t _next_input_index = 0;
size_t _current_input_index = 0;
std::vector<if_level> _if_stack;
unsigned short _recursion_count = 0;
std::unordered_set<std::string> _used_macros;
std::unordered_map<std::string, macro> _macros;
std::vector<std::filesystem::path> _include_paths;
std::unordered_map<std::string, std::string> _file_cache;
std::vector<std::pair<std::string, std::string>> _used_pragmas;
};
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "effect_module.hpp"
#include <unordered_map> // Used for symbol lookup table
namespace reshadefx
{
/// <summary>
/// A scope encapsulating symbols.
/// </summary>
struct scope
{
std::string name;
uint32_t level, namespace_level;
};
/// <summary>
/// Enumeration of all possible symbol types.
/// </summary>
enum class symbol_type
{
invalid,
variable,
constant,
function,
intrinsic,
structure,
};
/// <summary>
/// A single symbol in the symbol table.
/// </summary>
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
};
/// <summary>
/// A symbol table managing a list of scopes and symbols.
/// </summary>
class symbol_table
{
public:
symbol_table();
/// <summary>
/// Enters a new scope as child of the current one.
/// </summary>
void enter_scope();
/// <summary>
/// Enters a new namespace as child of the current one.
/// </summary>
void enter_namespace(const std::string &name);
/// <summary>
/// Leaves the current scope and enter the parent one.
/// </summary>
void leave_scope();
/// <summary>
/// Leaves the current namespace and enter the parent one.
/// </summary>
void leave_namespace();
/// <summary>
/// Gets the current scope the symbol table operates in.
/// </summary>
const scope &current_scope() const { return _current_scope; }
/// <summary>
/// Inserts an new symbol in the symbol table.
/// Returns <see langword="false"/> if a symbol by that name and type already exists.
/// </summary>
bool insert_symbol(const std::string &name, const symbol &symbol, bool global = false);
/// <summary>
/// Looks for an existing symbol with the specified <paramref name="name"/>.
/// </summary>
scoped_symbol find_symbol(const std::string &name) const;
scoped_symbol find_symbol(const std::string &name, const scope &scope, bool exclusive) const;
/// <summary>
/// Searches for the best function or intrinsic overload matching the argument list.
/// </summary>
bool resolve_function_call(const std::string &name, const std::vector<expression> &args, const scope &scope, symbol &data, bool &ambiguous) const;
private:
scope _current_scope;
// Lookup table from name to matching symbols
std::unordered_map<std::string, std::vector<scoped_symbol>> _symbol_stack;
};
}

View file

@ -0,0 +1,252 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <string>
#include <vector>
namespace reshadefx
{
/// <summary>
/// Structure which keeps track of a code location.
/// </summary>
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;
};
/// <summary>
/// A collection of identifiers for various possible tokens.
/// </summary>
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,
};
/// <summary>
/// A structure describing a single token in the input string.
/// </summary>
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);
};
}

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\msvc\vsprops\Configurations.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>{27B8D4BB-4F01-4432-BC14-9BF6CA458EEE}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<ClInclude Include="include\effect_codegen.hpp" />
<ClInclude Include="include\effect_expression.hpp" />
<ClInclude Include="include\effect_lexer.hpp" />
<ClInclude Include="include\effect_module.hpp" />
<ClInclude Include="include\effect_parser.hpp" />
<ClInclude Include="include\effect_preprocessor.hpp" />
<ClInclude Include="include\effect_symbol_table.hpp" />
<ClInclude Include="include\effect_token.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\effect_codegen_glsl.cpp" />
<ClCompile Include="src\effect_codegen_hlsl.cpp" />
<ClCompile Include="src\effect_codegen_spirv.cpp" />
<ClCompile Include="src\effect_expression.cpp" />
<ClCompile Include="src\effect_lexer.cpp" />
<ClCompile Include="src\effect_parser_exp.cpp" />
<ClCompile Include="src\effect_parser_stmt.cpp" />
<ClCompile Include="src\effect_preprocessor.cpp" />
<ClCompile Include="src\effect_symbol_table.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="src\effect_symbol_table_intrinsics.inl" />
</ItemGroup>
<Import Project="..\msvc\vsprops\StaticLibrary.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<AdditionalIncludeDirectories>$(ProjectDir)src;$(ProjectDir)include;$(ProjectDir)..\spirv-cross\include\spirv-cross;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="..\msvc\vsprops\Targets.props" />
</Project>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClInclude Include="include\effect_module.hpp" />
<ClInclude Include="include\effect_parser.hpp" />
<ClInclude Include="include\effect_preprocessor.hpp" />
<ClInclude Include="include\effect_symbol_table.hpp" />
<ClInclude Include="include\effect_token.hpp" />
<ClInclude Include="include\effect_codegen.hpp" />
<ClInclude Include="include\effect_expression.hpp" />
<ClInclude Include="include\effect_lexer.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\effect_codegen_hlsl.cpp" />
<ClCompile Include="src\effect_codegen_spirv.cpp" />
<ClCompile Include="src\effect_expression.cpp" />
<ClCompile Include="src\effect_lexer.cpp" />
<ClCompile Include="src\effect_parser_exp.cpp" />
<ClCompile Include="src\effect_parser_stmt.cpp" />
<ClCompile Include="src\effect_preprocessor.cpp" />
<ClCompile Include="src\effect_symbol_table.cpp" />
<ClCompile Include="src\effect_codegen_glsl.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="src\effect_symbol_table_intrinsics.inl" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,623 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "effect_lexer.hpp"
#include "effect_codegen.hpp"
#include <cmath> // fmod
#include <cassert>
#include <cstring> // memcpy, memset
#include <algorithm> // 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<int" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler2d_int:
result = "sampler2D<int" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler3d_int:
result = "sampler3D<int" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler1d_uint:
result = "sampler1D<uint" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler2d_uint:
result = "sampler2D<uint" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler3d_uint:
result = "sampler3D<uint" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler1d_float:
result = "sampler1D<float" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler2d_float:
result = "sampler2D<float" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_sampler3d_float:
result = "sampler3D<float" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage1d_int:
result = "storage1D<int" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage2d_int:
result = "storage2D<int" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage3d_int:
result = "storage3D<int" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage1d_uint:
result = "storage1D<uint" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage2d_uint:
result = "storage2D<uint" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage3d_uint:
result = "storage3D<uint" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage1d_float:
result = "storage1D<float" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage2d_float:
result = "storage2D<float" + std::to_string(rows) + '>';
break;
case reshadefx::type::t_storage3d_float:
result = "storage3D<float" + std::to_string(rows) + '>';
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<int>(constant.as_float[i]);
else
for (unsigned int i = 0; i < to.components(); ++i)
constant.as_float[i] = static_cast<float>(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<unsigned int>(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<uint32_t>(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<float>::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;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,477 @@
/*
* Copyright (C) 2014 Patrick Mours
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "effect_symbol_table.hpp"
#include <cassert>
#include <malloc.h> // alloca
#include <algorithm> // std::upper_bound, std::sort
#include <functional> // 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<reshadefx::type> 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<scoped_symbol> &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<reshadefx::expression> &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<unsigned int *>(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<unsigned int *>(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<unsigned int>());
std::sort(function2_ranks, function2_ranks + num_arguments, std::greater<unsigned int>());
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<expression> &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<uint32_t>(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;
}

File diff suppressed because it is too large Load diff

View file

@ -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}