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

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