This is the first step of moving the create infos back inside shader sources. All info files are now treated as source files. However, they are not considered in the include tree yet. This will come in another following PR. Each shader source file now generate a `.info` file containing only the create info declarations. This renames all info files so that they do not conflict with their previous versions that were copied (non-generated). Pull Request: https://projects.blender.org/blender/blender/pulls/146676
2580 lines
95 KiB
C++
2580 lines
95 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup glsl_preprocess
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cctype>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <regex>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "shader_parser.hh"
|
|
|
|
namespace blender::gpu::shader {
|
|
|
|
#define ERROR_TOK(token) (token).line_number(), (token).char_number(), (token).line_str()
|
|
|
|
/* Metadata extracted from shader source file.
|
|
* These are then converted to their GPU module equivalent. */
|
|
/* TODO(fclem): Make GPU enums standalone and directly use them instead of using separate enums
|
|
* and types. */
|
|
namespace metadata {
|
|
|
|
/* Compile-time hashing function which converts string to a 64bit hash. */
|
|
constexpr static uint64_t hash(const char *name)
|
|
{
|
|
uint64_t hash = 2166136261u;
|
|
while (*name) {
|
|
hash = hash * 16777619u;
|
|
hash = hash ^ *name;
|
|
++name;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static uint64_t hash(const std::string &name)
|
|
{
|
|
return hash(name.c_str());
|
|
}
|
|
|
|
enum Builtin : uint64_t {
|
|
FragCoord = hash("gl_FragCoord"),
|
|
FrontFacing = hash("gl_FrontFacing"),
|
|
GlobalInvocationID = hash("gl_GlobalInvocationID"),
|
|
InstanceID = hash("gl_InstanceID"),
|
|
LocalInvocationID = hash("gl_LocalInvocationID"),
|
|
LocalInvocationIndex = hash("gl_LocalInvocationIndex"),
|
|
NumWorkGroup = hash("gl_NumWorkGroup"),
|
|
PointCoord = hash("gl_PointCoord"),
|
|
PointSize = hash("gl_PointSize"),
|
|
PrimitiveID = hash("gl_PrimitiveID"),
|
|
VertexID = hash("gl_VertexID"),
|
|
WorkGroupID = hash("gl_WorkGroupID"),
|
|
WorkGroupSize = hash("gl_WorkGroupSize"),
|
|
drw_debug = hash("drw_debug_"),
|
|
printf = hash("printf"),
|
|
assert = hash("assert"),
|
|
runtime_generated = hash("runtime_generated"),
|
|
};
|
|
|
|
enum Qualifier : uint64_t {
|
|
in = hash("in"),
|
|
out = hash("out"),
|
|
inout = hash("inout"),
|
|
};
|
|
|
|
enum Type : uint64_t {
|
|
float1 = hash("float"),
|
|
float2 = hash("float2"),
|
|
float3 = hash("float3"),
|
|
float4 = hash("float4"),
|
|
float3x3 = hash("float3x3"),
|
|
float4x4 = hash("float4x4"),
|
|
sampler1DArray = hash("sampler1DArray"),
|
|
sampler2DArray = hash("sampler2DArray"),
|
|
sampler2D = hash("sampler2D"),
|
|
sampler3D = hash("sampler3D"),
|
|
Closure = hash("Closure"),
|
|
};
|
|
|
|
struct ArgumentFormat {
|
|
Qualifier qualifier;
|
|
Type type;
|
|
};
|
|
|
|
struct FunctionFormat {
|
|
std::string name;
|
|
std::vector<ArgumentFormat> arguments;
|
|
};
|
|
|
|
struct PrintfFormat {
|
|
uint32_t hash;
|
|
std::string format;
|
|
};
|
|
|
|
struct Source {
|
|
std::vector<Builtin> builtins;
|
|
/* Note: Could be a set, but for now the order matters. */
|
|
std::vector<std::string> dependencies;
|
|
std::vector<PrintfFormat> printf_formats;
|
|
std::vector<FunctionFormat> functions;
|
|
|
|
std::string serialize(const std::string &function_name) const
|
|
{
|
|
std::stringstream ss;
|
|
ss << "static void " << function_name
|
|
<< "(GPUSource &source, GPUFunctionDictionary *g_functions, GPUPrintFormatMap *g_formats) "
|
|
"{\n";
|
|
for (auto function : functions) {
|
|
ss << " {\n";
|
|
ss << " Vector<metadata::ArgumentFormat> args = {\n";
|
|
for (auto arg : function.arguments) {
|
|
ss << " "
|
|
<< "metadata::ArgumentFormat{"
|
|
<< "metadata::Qualifier(" << std::to_string(uint64_t(arg.qualifier)) << "LLU), "
|
|
<< "metadata::Type(" << std::to_string(uint64_t(arg.type)) << "LLU)"
|
|
<< "},\n";
|
|
}
|
|
ss << " };\n";
|
|
ss << " source.add_function(\"" << function.name << "\", args, g_functions);\n";
|
|
ss << " }\n";
|
|
}
|
|
for (auto builtin : builtins) {
|
|
ss << " source.add_builtin(metadata::Builtin(" << std::to_string(builtin) << "LLU));\n";
|
|
}
|
|
for (auto dependency : dependencies) {
|
|
ss << " source.add_dependency(\"" << dependency << "\");\n";
|
|
}
|
|
for (auto format : printf_formats) {
|
|
ss << " source.add_printf_format(uint32_t(" << std::to_string(format.hash) << "), "
|
|
<< format.format << ", g_formats);\n";
|
|
}
|
|
/* Avoid warnings. */
|
|
ss << " UNUSED_VARS(source, g_functions, g_formats);\n";
|
|
ss << "}\n";
|
|
return ss.str();
|
|
}
|
|
};
|
|
|
|
} // namespace metadata
|
|
|
|
/**
|
|
* Shader source preprocessor that allow to mutate GLSL into cross API source that can be
|
|
* interpreted by the different GPU backends. Some syntax are mutated or reported as incompatible.
|
|
*
|
|
* Implementation speed is not a huge concern as we only apply this at compile time or on python
|
|
* shaders source.
|
|
*/
|
|
class Preprocessor {
|
|
using uint64_t = std::uint64_t;
|
|
using report_callback = std::function<void(
|
|
int error_line, int error_char, std::string error_line_string, const char *error_str)>;
|
|
struct SharedVar {
|
|
std::string type;
|
|
std::string name;
|
|
std::string array;
|
|
};
|
|
|
|
std::vector<SharedVar> shared_vars_;
|
|
|
|
metadata::Source metadata;
|
|
|
|
using Parser = shader::parser::Parser;
|
|
|
|
public:
|
|
enum SourceLanguage {
|
|
UNKNOWN = 0,
|
|
CPP,
|
|
MSL,
|
|
GLSL,
|
|
/* Same as GLSL but enable partial C++ feature support like template, references,
|
|
* include system, etc ... */
|
|
BLENDER_GLSL,
|
|
};
|
|
|
|
static SourceLanguage language_from_filename(const std::string &filename)
|
|
{
|
|
if (filename.find(".msl") != std::string::npos) {
|
|
return MSL;
|
|
}
|
|
if (filename.find(".glsl") != std::string::npos) {
|
|
return GLSL;
|
|
}
|
|
if (filename.find(".hh") != std::string::npos) {
|
|
return CPP;
|
|
}
|
|
return UNKNOWN;
|
|
}
|
|
|
|
/* Takes a whole source file and output processed source. */
|
|
std::string process(SourceLanguage language,
|
|
std::string str,
|
|
const std::string &filename,
|
|
bool do_parse_function,
|
|
bool do_small_type_linting,
|
|
report_callback report_error,
|
|
metadata::Source &r_metadata)
|
|
{
|
|
if (language == UNKNOWN) {
|
|
report_error(0, 0, "", "Unknown file type");
|
|
return "";
|
|
}
|
|
str = remove_comments(str, report_error);
|
|
threadgroup_variables_parsing(str);
|
|
parse_builtins(str, filename);
|
|
if (language == BLENDER_GLSL || language == CPP) {
|
|
if (do_parse_function) {
|
|
parse_library_functions(str);
|
|
}
|
|
if (language == BLENDER_GLSL) {
|
|
pragma_runtime_generated_parsing(str);
|
|
pragma_once_linting(str, filename, report_error);
|
|
}
|
|
str = disabled_code_mutation(str, report_error);
|
|
str = include_parse_and_remove(str, report_error);
|
|
str = pragmas_mutation(str, report_error);
|
|
str = swizzle_function_mutation(str, report_error);
|
|
str = enum_macro_injection(str, language == CPP, report_error);
|
|
if (language == BLENDER_GLSL) {
|
|
Parser parser(str, report_error);
|
|
using_mutation(parser, report_error);
|
|
|
|
namespace_mutation(parser, report_error);
|
|
template_struct_mutation(parser, report_error);
|
|
struct_method_mutation(parser, report_error);
|
|
empty_struct_mutation(parser, report_error);
|
|
method_call_mutation(parser, report_error);
|
|
stage_function_mutation(parser, report_error);
|
|
resource_guard_mutation(parser, report_error);
|
|
loop_unroll(parser, report_error);
|
|
assert_processing(parser, filename, report_error);
|
|
static_strings_merging(parser, report_error);
|
|
static_strings_parsing_and_mutation(parser, report_error);
|
|
str = parser.result_get();
|
|
str = printf_processing(str, report_error);
|
|
quote_linting(str, report_error);
|
|
}
|
|
{
|
|
Parser parser(str, report_error);
|
|
global_scope_constant_linting(parser, report_error);
|
|
if (do_small_type_linting) {
|
|
small_type_linting(parser, report_error);
|
|
}
|
|
remove_quotes(parser, report_error);
|
|
argument_reference_mutation(parser, report_error);
|
|
default_argument_mutation(parser, report_error);
|
|
str = parser.result_get();
|
|
}
|
|
str = variable_reference_mutation(str, report_error);
|
|
str = template_definition_mutation(str, report_error);
|
|
if (language == BLENDER_GLSL) {
|
|
str = namespace_separator_mutation(str);
|
|
}
|
|
str = template_call_mutation(str, report_error);
|
|
}
|
|
else if (language == MSL) {
|
|
pragma_runtime_generated_parsing(str);
|
|
str = include_parse_and_remove(str, report_error);
|
|
str = pragmas_mutation(str, report_error);
|
|
}
|
|
#ifdef __APPLE__ /* Limiting to Apple hardware since GLSL compilers might have issues. */
|
|
if (language == GLSL) {
|
|
str = matrix_constructor_mutation(str);
|
|
}
|
|
#endif
|
|
str = argument_decorator_macro_injection(str);
|
|
str = array_constructor_macro_injection(str);
|
|
r_metadata = metadata;
|
|
return line_directive_prefix(filename) + str + threadgroup_variables_suffix();
|
|
}
|
|
|
|
/* Variant use for python shaders. */
|
|
std::string process(const std::string &str)
|
|
{
|
|
auto no_err_report = [](int, int, std::string, const char *) {};
|
|
metadata::Source unused;
|
|
return process(GLSL, str, "", false, false, no_err_report, unused);
|
|
}
|
|
|
|
private:
|
|
using regex_callback = std::function<void(const std::smatch &)>;
|
|
using regex_callback_with_line_count = std::function<void(const std::smatch &, int64_t)>;
|
|
|
|
/* Helper to make the code more readable in parsing functions. */
|
|
void regex_global_search(const std::string &str,
|
|
const std::regex ®ex,
|
|
regex_callback callback)
|
|
{
|
|
using namespace std;
|
|
string::const_iterator it = str.begin();
|
|
for (smatch match; regex_search(it, str.end(), match, regex); it = match.suffix().first) {
|
|
callback(match);
|
|
}
|
|
}
|
|
|
|
void regex_global_search(const std::string &str,
|
|
const std::regex ®ex,
|
|
regex_callback_with_line_count callback)
|
|
{
|
|
using namespace std;
|
|
int64_t line = 1;
|
|
regex_global_search(str, regex, [&line, &callback](const std::smatch &match) {
|
|
line += line_count(match.prefix().str());
|
|
callback(match, line);
|
|
line += line_count(match[0].str());
|
|
});
|
|
}
|
|
|
|
template<typename ReportErrorF>
|
|
std::string remove_comments(const std::string &str, const ReportErrorF &report_error)
|
|
{
|
|
std::string out_str = str;
|
|
{
|
|
/* Multi-line comments. */
|
|
size_t start, end = 0;
|
|
while ((start = out_str.find("/*", end)) != std::string::npos) {
|
|
end = out_str.find("*/", start + 2);
|
|
if (end == std::string::npos) {
|
|
break;
|
|
}
|
|
for (size_t i = start; i < end + 2; ++i) {
|
|
if (out_str[i] != '\n') {
|
|
out_str[i] = ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (end == std::string::npos) {
|
|
report_error(line_number(out_str, start),
|
|
char_number(out_str, start),
|
|
line_str(out_str, start),
|
|
"Malformed multi-line comment.");
|
|
return out_str;
|
|
}
|
|
}
|
|
{
|
|
/* Single-line comments. */
|
|
size_t start, end = 0;
|
|
while ((start = out_str.find("//", end)) != std::string::npos) {
|
|
end = out_str.find('\n', start + 2);
|
|
if (end == std::string::npos) {
|
|
break;
|
|
}
|
|
for (size_t i = start; i < end; ++i) {
|
|
out_str[i] = ' ';
|
|
}
|
|
}
|
|
|
|
if (end == std::string::npos) {
|
|
report_error(line_number(out_str, start),
|
|
char_number(out_str, start),
|
|
line_str(out_str, start),
|
|
"Malformed single line comment, missing newline.");
|
|
return out_str;
|
|
}
|
|
}
|
|
/* Remove trailing white space as they make the subsequent regex much slower. */
|
|
std::regex regex(R"((\ )*?\n)");
|
|
return std::regex_replace(out_str, regex, "\n");
|
|
}
|
|
|
|
static std::string template_arguments_mangle(const shader::parser::Scope template_args)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
string args_concat;
|
|
template_args.foreach_scope(ScopeType::TemplateArg, [&](const Scope &scope) {
|
|
args_concat += 'T' + scope.start().str();
|
|
});
|
|
return args_concat;
|
|
}
|
|
|
|
void template_struct_mutation(Parser &parser, report_callback &report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
{
|
|
parser.foreach_match("w<..>(..)", [&](const vector<Token> &tokens) {
|
|
const Scope template_args = tokens[1].scope();
|
|
template_args.foreach_match("w<..>", [&parser](const vector<Token> &tokens) {
|
|
parser.replace(tokens[1].scope(), template_arguments_mangle(tokens[1].scope()), true);
|
|
});
|
|
});
|
|
parser.apply_mutations();
|
|
|
|
/* Replace full specialization by simple struct. */
|
|
parser.foreach_match("t<>sw<..>", [&](const std::vector<Token> &tokens) {
|
|
parser.erase(tokens[0], tokens[2]);
|
|
parser.replace(tokens[5].scope(), template_arguments_mangle(tokens[5].scope()), true);
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
{
|
|
parser.foreach_scope(ScopeType::Template, [&](Scope temp) {
|
|
/* Parse template declaration. */
|
|
Token struct_start = temp.end().next();
|
|
if (struct_start != Struct) {
|
|
return;
|
|
}
|
|
Token struct_name = struct_start.next();
|
|
Scope struct_body = struct_name.next().scope();
|
|
|
|
bool error = false;
|
|
temp.foreach_match("=", [&](const std::vector<Token> &tokens) {
|
|
report_error(ERROR_TOK(tokens[0]),
|
|
"Default arguments are not supported inside template declaration");
|
|
error = true;
|
|
});
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
string arg_pattern;
|
|
vector<string> arg_list;
|
|
temp.foreach_scope(ScopeType::TemplateArg, [&](Scope arg) {
|
|
const Token type = arg.start();
|
|
const Token name = type.next();
|
|
const string name_str = name.str();
|
|
const string type_str = type.str();
|
|
|
|
arg_list.emplace_back(name_str);
|
|
|
|
if (type_str == "typename") {
|
|
arg_pattern += ",w";
|
|
}
|
|
else if (type_str == "enum" || type_str == "bool") {
|
|
arg_pattern += ",w";
|
|
}
|
|
else if (type_str == "int" || type_str == "uint") {
|
|
arg_pattern += ",0";
|
|
}
|
|
else {
|
|
report_error(ERROR_TOK(type), "Invalid template argument type");
|
|
}
|
|
});
|
|
|
|
Token struct_end = struct_body.end();
|
|
const string fn_decl = parser.substr_range_inclusive(struct_start.str_index_start(),
|
|
struct_end.str_index_last());
|
|
|
|
/* Remove declaration. */
|
|
Token template_keyword = temp.start().prev();
|
|
parser.erase(template_keyword.str_index_start(), struct_end.line_end());
|
|
|
|
/* Replace instantiations. */
|
|
Scope parent_scope = temp.scope();
|
|
string specialization_pattern = "tsw<" + arg_pattern.substr(1) + ">";
|
|
parent_scope.foreach_match(specialization_pattern, [&](const std::vector<Token> &tokens) {
|
|
if (struct_name.str() != tokens[2].str()) {
|
|
return;
|
|
}
|
|
/* Parse template values. */
|
|
vector<pair<string, string>> arg_name_value_pairs;
|
|
for (int i = 0; i < arg_list.size(); i++) {
|
|
arg_name_value_pairs.emplace_back(arg_list[i], tokens[4 + 2 * i].str());
|
|
}
|
|
/* Specialize template content. */
|
|
Parser instance_parser(fn_decl, report_error, true);
|
|
instance_parser.foreach_match("w", [&](const std::vector<Token> &tokens) {
|
|
string token_str = tokens[0].str();
|
|
for (const auto &arg_name_value : arg_name_value_pairs) {
|
|
if (token_str == arg_name_value.first) {
|
|
instance_parser.replace(tokens[0], arg_name_value.second);
|
|
}
|
|
}
|
|
});
|
|
|
|
const string template_args = parser.substr_range_inclusive(
|
|
tokens[3], tokens[3 + arg_pattern.size()]);
|
|
size_t pos = fn_decl.find(" " + struct_name.str());
|
|
instance_parser.insert_after(pos + struct_name.str().size(), template_args);
|
|
/* Paste template content in place of instantiation. */
|
|
Token end_of_instantiation = tokens.back();
|
|
string instance = instance_parser.result_get();
|
|
parser.insert_line_number(tokens.front().str_index_start() - 1,
|
|
struct_start.line_number());
|
|
parser.replace(tokens.front().str_index_start(),
|
|
end_of_instantiation.str_index_last_no_whitespace(),
|
|
instance);
|
|
parser.insert_line_number(end_of_instantiation.line_end() + 1,
|
|
end_of_instantiation.line_number() + 1);
|
|
});
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
{
|
|
/* This rely on our codestyle that do not put spaces between template name and the opening
|
|
* angle bracket. */
|
|
parser.foreach_match("sw<..>", [&](const std::vector<Token> &tokens) {
|
|
parser.replace(tokens[2].scope(), template_arguments_mangle(tokens[2].scope()), true);
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
}
|
|
|
|
std::string template_definition_mutation(const std::string &str, report_callback &report_error)
|
|
{
|
|
if (str.find("template") == std::string::npos) {
|
|
return str;
|
|
}
|
|
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
std::string out_str = str;
|
|
|
|
Parser parser(out_str, report_error);
|
|
|
|
auto process_specialization = [&](const Token specialization_start,
|
|
const Scope template_args) {
|
|
parser.erase(specialization_start, specialization_start.next().next());
|
|
parser.replace(template_args, template_arguments_mangle(template_args), true);
|
|
};
|
|
|
|
parser.foreach_scope(ScopeType::Global, [&](Scope scope) {
|
|
/* Replace full specialization by simple functions. */
|
|
scope.foreach_match("t<>ww<", [&](const std::vector<Token> &tokens) {
|
|
process_specialization(tokens[0], tokens[5].scope());
|
|
});
|
|
scope.foreach_match("t<>ww::w<", [&](const std::vector<Token> &tokens) {
|
|
process_specialization(tokens[0], tokens[8].scope());
|
|
});
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
|
|
auto process_template = [&](const Token fn_start,
|
|
const string &fn_name,
|
|
const Scope fn_args,
|
|
const Scope temp,
|
|
const Token fn_end) {
|
|
bool error = false;
|
|
temp.foreach_match("=", [&](const std::vector<Token> &tokens) {
|
|
report_error(tokens[0].line_number(),
|
|
tokens[0].char_number(),
|
|
tokens[0].line_str(),
|
|
"Default arguments are not supported inside template declaration");
|
|
error = true;
|
|
});
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
string arg_pattern;
|
|
vector<string> arg_list;
|
|
bool all_template_args_in_function_signature = true;
|
|
temp.foreach_scope(ScopeType::TemplateArg, [&](Scope arg) {
|
|
const Token type = arg.start();
|
|
const Token name = type.next();
|
|
const string name_str = name.str();
|
|
const string type_str = type.str();
|
|
|
|
arg_list.emplace_back(name_str);
|
|
|
|
if (type_str == "typename") {
|
|
arg_pattern += ",w";
|
|
bool found = false;
|
|
/* Search argument list for typenames. If typename matches, the template argument is
|
|
* present inside the function signature. */
|
|
fn_args.foreach_match("ww", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[0].str() == name_str) {
|
|
found = true;
|
|
}
|
|
});
|
|
all_template_args_in_function_signature &= found;
|
|
}
|
|
else if (type_str == "enum" || type_str == "bool") {
|
|
arg_pattern += ",w";
|
|
/* Values cannot be resolved using type deduction. */
|
|
all_template_args_in_function_signature = false;
|
|
}
|
|
else if (type_str == "int" || type_str == "uint") {
|
|
arg_pattern += ",0";
|
|
/* Values cannot be resolved using type deduction. */
|
|
all_template_args_in_function_signature = false;
|
|
}
|
|
else {
|
|
report_error(ERROR_TOK(type), "Invalid template argument type");
|
|
}
|
|
});
|
|
|
|
const string fn_decl = parser.substr_range_inclusive(fn_start.str_index_start(),
|
|
fn_end.line_end());
|
|
|
|
/* Remove declaration. */
|
|
Token template_keyword = temp.start().prev();
|
|
parser.erase(template_keyword.str_index_start(), fn_end.line_end());
|
|
|
|
auto process_instantiation = [&](const string &inst_name,
|
|
const Token inst_start,
|
|
const Token inst_end,
|
|
const Scope &inst_args) {
|
|
if (fn_name != inst_name) {
|
|
return;
|
|
}
|
|
/* Parse template values. */
|
|
vector<pair<string, string>> arg_name_value_pairs;
|
|
for (int i = 0; i < arg_list.size(); i++) {
|
|
arg_name_value_pairs.emplace_back(arg_list[i], inst_args[1 + 2 * i].str());
|
|
}
|
|
/* Specialize template content. */
|
|
Parser instance_parser(fn_decl, report_error, true);
|
|
instance_parser.foreach_token(Word, [&](const Token &word) {
|
|
string token_str = word.str();
|
|
for (const auto &arg_name_value : arg_name_value_pairs) {
|
|
if (token_str == arg_name_value.first) {
|
|
instance_parser.replace(word, arg_name_value.second);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!all_template_args_in_function_signature) {
|
|
/* Append template args after function name.
|
|
* `void func() {}` > `void func<a, 1>() {}`. */
|
|
size_t pos = fn_decl.find(" " + fn_name);
|
|
instance_parser.insert_after(pos + fn_name.size(), inst_args.str());
|
|
}
|
|
/* Paste template content in place of instantiation. */
|
|
string instance = instance_parser.result_get();
|
|
parser.insert_line_number(inst_start.str_index_start() - 1, fn_start.line_number());
|
|
parser.replace(
|
|
inst_start.str_index_start(), inst_end.str_index_last_no_whitespace(), instance);
|
|
parser.insert_line_number(inst_end.line_end() + 1, inst_end.line_number() + 1);
|
|
};
|
|
|
|
/* Replace instantiations. */
|
|
Scope parent_scope = temp.scope();
|
|
{
|
|
string specialization_pattern = "tww<" + arg_pattern.substr(1) + ">(..);";
|
|
parent_scope.foreach_match(specialization_pattern, [&](const vector<Token> &tokens) {
|
|
process_instantiation(tokens[2].str(), tokens.front(), tokens.back(), tokens[3].scope());
|
|
});
|
|
}
|
|
{
|
|
string specialization_pattern = "tww::w<" + arg_pattern.substr(1) + ">(..);";
|
|
parent_scope.foreach_match(specialization_pattern, [&](const vector<Token> &tokens) {
|
|
const string inst_name = parser.substr_range_inclusive(tokens[2], tokens[5]);
|
|
process_instantiation(inst_name, tokens.front(), tokens.back(), tokens[6].scope());
|
|
});
|
|
}
|
|
};
|
|
|
|
parser.foreach_match("t<..>ww(..)c?{..}", [&](const vector<Token> &tokens) {
|
|
process_template(
|
|
tokens[5], tokens[6].str(), tokens[7].scope(), tokens[1].scope(), tokens[16]);
|
|
});
|
|
|
|
parser.foreach_match("t<..>ww::w(..)c?{..}", [&](const vector<Token> &tokens) {
|
|
const string fn_name = parser.substr_range_inclusive(tokens[6], tokens[9]);
|
|
process_template(tokens[5], fn_name, tokens[10].scope(), tokens[1].scope(), tokens[19]);
|
|
});
|
|
|
|
out_str = parser.result_get();
|
|
|
|
{
|
|
/* Check if there is no remaining declaration and instantiation that were not processed. */
|
|
size_t error_pos;
|
|
if ((error_pos = out_str.find("template<")) != std::string::npos) {
|
|
report_error(line_number(out_str, error_pos),
|
|
char_number(out_str, error_pos),
|
|
line_str(out_str, error_pos),
|
|
"Template declaration unsupported syntax");
|
|
}
|
|
if ((error_pos = out_str.find("template ")) != std::string::npos) {
|
|
report_error(line_number(out_str, error_pos),
|
|
char_number(out_str, error_pos),
|
|
line_str(out_str, error_pos),
|
|
"Template instantiation unsupported syntax");
|
|
}
|
|
}
|
|
return out_str;
|
|
}
|
|
|
|
std::string template_call_mutation(const std::string &str, report_callback &report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
parser.foreach_match("w<..>", [&](const std::vector<Token> &tokens) {
|
|
parser.replace(tokens[1].scope(), template_arguments_mangle(tokens[1].scope()), true);
|
|
});
|
|
return parser.result_get();
|
|
}
|
|
|
|
/* Remove remaining quotes that can be found in some unsupported C++ macros. */
|
|
void remove_quotes(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_token(TokenType::String, [&](const Token token) { parser.erase(token); });
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
std::string include_parse_and_remove(const std::string &str, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
|
|
parser.foreach_match("#w_", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[1].str() != "include") {
|
|
return;
|
|
}
|
|
string dependency_name = tokens[2].str_exclusive();
|
|
/* Assert that includes are at the top of the file. */
|
|
if (dependency_name == "gpu_shader_compat.hh") {
|
|
/* Skip GLSL-C++ stubs. They are only for IDE linting. */
|
|
parser.erase(tokens.front(), tokens.back());
|
|
return;
|
|
}
|
|
if (dependency_name.find("infos.hh") != std::string::npos) {
|
|
/* Skip info files. They are only for IDE linting. */
|
|
parser.erase(tokens.front(), tokens.back());
|
|
return;
|
|
}
|
|
if (dependency_name.find("gpu_shader_create_info.hh") != std::string::npos) {
|
|
/* Skip info files. They are only for IDE linting. */
|
|
parser.erase(tokens.front(), tokens.back());
|
|
return;
|
|
}
|
|
metadata.dependencies.emplace_back(dependency_name);
|
|
parser.erase(tokens.front(), tokens.back());
|
|
});
|
|
|
|
return parser.result_get();
|
|
}
|
|
|
|
void pragma_runtime_generated_parsing(const std::string &str)
|
|
{
|
|
if (str.find("\n#pragma runtime_generated") != std::string::npos) {
|
|
metadata.builtins.emplace_back(metadata::Builtin::runtime_generated);
|
|
}
|
|
}
|
|
|
|
void pragma_once_linting(const std::string &str,
|
|
const std::string &filename,
|
|
report_callback report_error)
|
|
{
|
|
if (filename.find("_lib.") == std::string::npos && filename.find(".hh") == std::string::npos) {
|
|
return;
|
|
}
|
|
if (str.find("\n#pragma once") == std::string::npos) {
|
|
report_error(0, 0, "", "Header files must contain #pragma once directive.");
|
|
}
|
|
}
|
|
|
|
void loop_unroll(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
auto parse_for_args =
|
|
[&](const Scope loop_args, Scope &r_init, Scope &r_condition, Scope &r_iter) {
|
|
r_init = r_condition = r_iter = Scope::invalid();
|
|
loop_args.foreach_scope(ScopeType::LoopArg, [&](const Scope arg) {
|
|
if (arg.start().prev() == '(' && arg.end().next() == ';') {
|
|
r_init = arg;
|
|
}
|
|
else if (arg.start().prev() == ';' && arg.end().next() == ';') {
|
|
r_condition = arg;
|
|
}
|
|
else if (arg.start().prev() == ';' && arg.end().next() == ')') {
|
|
r_iter = arg;
|
|
}
|
|
else {
|
|
report_error(ERROR_TOK(arg.start()), "Invalid loop declaration.");
|
|
}
|
|
});
|
|
};
|
|
|
|
auto process_loop = [&](const Token loop_start,
|
|
const int iter_count,
|
|
const int iter_init,
|
|
const int iter_incr,
|
|
const bool condition_is_trivial,
|
|
const bool iteration_is_trivial,
|
|
const Scope init,
|
|
const Scope cond,
|
|
const Scope iter,
|
|
const Scope body,
|
|
const string body_prefix = "",
|
|
const string body_suffix = "") {
|
|
/* Check that there is no unsupported keywords in the loop body. */
|
|
bool error = false;
|
|
/* Checks if `continue` exists, even in switch statement inside the unrolled loop. */
|
|
body.foreach_token(Continue, [&](const Token token) {
|
|
if (token.first_containing_scope_of_type(ScopeType::LoopBody) == body) {
|
|
report_error(ERROR_TOK(token), "Unrolled loop cannot contain \"continue\" statement.");
|
|
error = true;
|
|
}
|
|
});
|
|
/* Checks if `break` exists directly the unrolled loop scope. Switch statements are ok. */
|
|
body.foreach_token(Break, [&](const Token token) {
|
|
if (token.first_containing_scope_of_type(ScopeType::LoopBody) == body) {
|
|
const Scope switch_scope = token.first_containing_scope_of_type(ScopeType::SwitchBody);
|
|
if (switch_scope.is_invalid() || !body.contains(switch_scope)) {
|
|
report_error(ERROR_TOK(token), "Unrolled loop cannot contain \"break\" statement.");
|
|
error = true;
|
|
}
|
|
}
|
|
});
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
if (!parser.replace_try(loop_start, body.end(), "", true)) {
|
|
/* This is the case of nested loops. This loop will be processed in another parser pass. */
|
|
return;
|
|
}
|
|
|
|
string indent_init, indent_cond, indent_iter;
|
|
if (init.is_valid()) {
|
|
indent_init = string(init.start().char_number() - 1, ' ');
|
|
}
|
|
if (cond.is_valid()) {
|
|
indent_cond = string(cond.start().char_number() - 3, ' ');
|
|
}
|
|
if (iter.is_valid()) {
|
|
indent_iter = string(iter.start().char_number(), ' ');
|
|
}
|
|
string indent_body = string(body.start().char_number(), ' ');
|
|
string indent_end = string(body.end().char_number(), ' ');
|
|
|
|
/* If possible, replaces the index of the loop iteration inside the given string. */
|
|
auto replace_index = [&](const string &str, int loop_index) {
|
|
if (iter.is_invalid() || !iteration_is_trivial || str.empty()) {
|
|
return str;
|
|
}
|
|
Parser str_parser(str, report_error);
|
|
str_parser.foreach_token(Word, [&](const Token tok) {
|
|
if (tok.str() == iter[0].str()) {
|
|
str_parser.replace(tok, std::to_string(loop_index), true);
|
|
}
|
|
});
|
|
return str_parser.result_get();
|
|
};
|
|
|
|
parser.insert_after(body.end(), "\n");
|
|
if (init.is_valid() && !iteration_is_trivial) {
|
|
parser.insert_line_number(body.end(), init.start().line_number());
|
|
parser.insert_after(body.end(), indent_init + "{" + init.str() + ";\n");
|
|
}
|
|
else {
|
|
parser.insert_after(body.end(), "{\n");
|
|
}
|
|
for (int64_t i = 0, value = iter_init; i < iter_count; i++, value += iter_incr) {
|
|
if (cond.is_valid() && !condition_is_trivial) {
|
|
parser.insert_line_number(body.end(), cond.start().line_number());
|
|
parser.insert_after(body.end(), indent_cond + "if(" + cond.str() + ")\n");
|
|
}
|
|
parser.insert_after(body.end(), replace_index(body_prefix, value));
|
|
parser.insert_line_number(body.end(), body.start().line_number());
|
|
parser.insert_after(body.end(), indent_body + replace_index(body.str(), value) + "\n");
|
|
parser.insert_after(body.end(), body_suffix);
|
|
if (iter.is_valid() && !iteration_is_trivial) {
|
|
parser.insert_line_number(body.end(), iter.start().line_number());
|
|
parser.insert_after(body.end(), indent_iter + iter.str() + ";\n");
|
|
}
|
|
}
|
|
parser.insert_line_number(body.end(), body.end().line_number());
|
|
parser.insert_after(body.end(), indent_end + body.end().str_with_whitespace());
|
|
};
|
|
|
|
do {
|
|
/* [[gpu::unroll]]. */
|
|
parser.foreach_match("[[w::w]]f(..){..}", [&](const std::vector<Token> tokens) {
|
|
if (tokens[1].scope().str() != "[gpu::unroll]") {
|
|
return;
|
|
}
|
|
const Token for_tok = tokens[8];
|
|
const Scope loop_args = tokens[9].scope();
|
|
const Scope loop_body = tokens[13].scope();
|
|
|
|
Scope init, cond, iter;
|
|
parse_for_args(loop_args, init, cond, iter);
|
|
|
|
/* Init statement. */
|
|
const Token var_type = init[0];
|
|
const Token var_name = init[1];
|
|
const Token var_init = init[2];
|
|
if (var_type.str() != "int" && var_type.str() != "uint") {
|
|
report_error(ERROR_TOK(var_init), "Can only unroll integer based loop.");
|
|
return;
|
|
}
|
|
if (var_init != '=') {
|
|
report_error(ERROR_TOK(var_init), "Expecting assignment here.");
|
|
return;
|
|
}
|
|
if (init[3] != '0' && init[3] != '-') {
|
|
report_error(ERROR_TOK(init[3]), "Expecting integer literal here.");
|
|
return;
|
|
}
|
|
|
|
/* Conditional statement. */
|
|
const Token cond_var = cond[0];
|
|
const Token cond_type = cond[1];
|
|
const Token cond_sign = (cond[2] == '+' || cond[2] == '-') ? cond[2] : Token::invalid();
|
|
const Token cond_end = cond_sign.is_valid() ? cond[3] : cond[2];
|
|
if (cond_var.str() != var_name.str()) {
|
|
report_error(ERROR_TOK(cond_var), "Non matching loop counter variable.");
|
|
return;
|
|
}
|
|
if (cond_end != '0') {
|
|
report_error(ERROR_TOK(cond_end), "Expecting integer literal here.");
|
|
return;
|
|
}
|
|
|
|
/* Iteration statement. */
|
|
const Token iter_var = iter[0];
|
|
const Token iter_type = iter[1];
|
|
const Token iter_end = iter[1];
|
|
int iter_incr = 0;
|
|
if (iter_var.str() != var_name.str()) {
|
|
report_error(ERROR_TOK(iter_var), "Non matching loop counter variable.");
|
|
return;
|
|
}
|
|
if (iter_type == Increment) {
|
|
iter_incr = +1;
|
|
if (cond_type == '>') {
|
|
report_error(ERROR_TOK(for_tok), "Unsupported condition in unrolled loop.");
|
|
return;
|
|
}
|
|
}
|
|
else if (iter_type == Decrement) {
|
|
iter_incr = -1;
|
|
if (cond_type == '<') {
|
|
report_error(ERROR_TOK(for_tok), "Unsupported condition in unrolled loop.");
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
report_error(ERROR_TOK(iter_type), "Unsupported loop expression. Expecting ++ or --.");
|
|
return;
|
|
}
|
|
|
|
int64_t init_value = std::stol(
|
|
parser.substr_range_inclusive(var_init.next(), var_init.scope().end()));
|
|
int64_t end_value = std::stol(
|
|
parser.substr_range_inclusive(cond_sign.is_valid() ? cond_sign : cond_end, cond_end));
|
|
/* TODO(fclem): Support arbitrary strides (aka, arbitrary iter statement). */
|
|
int iter_count = std::abs(end_value - init_value);
|
|
if (cond_type == GEqual || cond_type == LEqual) {
|
|
iter_count += 1;
|
|
}
|
|
|
|
bool condition_is_trivial = (cond_end == cond.end());
|
|
bool iteration_is_trivial = (iter_end == iter.end());
|
|
|
|
process_loop(tokens[0],
|
|
iter_count,
|
|
init_value,
|
|
iter_incr,
|
|
condition_is_trivial,
|
|
iteration_is_trivial,
|
|
init,
|
|
cond,
|
|
iter,
|
|
loop_body);
|
|
});
|
|
|
|
/* [[gpu::unroll(n)]]. */
|
|
parser.foreach_match("[[w::w(0)]]f(..){..}", [&](const std::vector<Token> tokens) {
|
|
if (tokens[5].str() != "unroll") {
|
|
return;
|
|
}
|
|
const Scope loop_args = tokens[12].scope();
|
|
const Scope loop_body = tokens[16].scope();
|
|
|
|
Scope init, cond, iter;
|
|
parse_for_args(loop_args, init, cond, iter);
|
|
|
|
int iter_count = std::stol(tokens[7].str());
|
|
|
|
process_loop(tokens[0], iter_count, 0, 0, false, false, init, cond, iter, loop_body);
|
|
});
|
|
|
|
/* [[gpu::unroll_define(max_n)]]. */
|
|
parser.foreach_match("[[w::w(0)]]f(..){..}", [&](const std::vector<Token> tokens) {
|
|
if (tokens[5].str() != "unroll_define") {
|
|
return;
|
|
}
|
|
const Scope loop_args = tokens[12].scope();
|
|
const Scope loop_body = tokens[16].scope();
|
|
|
|
/* Validate format. */
|
|
Token define_name = Token::invalid();
|
|
Token iter_var = Token::invalid();
|
|
loop_args.foreach_match("ww=0;w<w;wP", [&](const std::vector<Token> tokens) {
|
|
if (tokens[1].str() != tokens[5].str() || tokens[5].str() != tokens[9].str()) {
|
|
return;
|
|
}
|
|
iter_var = tokens[1];
|
|
define_name = tokens[7];
|
|
});
|
|
|
|
if (define_name.is_invalid()) {
|
|
report_error(ERROR_TOK(loop_args.start()),
|
|
"Incompatible loop format for [[gpu::unroll_define(max_n)]], expected "
|
|
"'(int i = 0; i < DEFINE; i++)'");
|
|
return;
|
|
}
|
|
|
|
Scope init, cond, iter;
|
|
parse_for_args(loop_args, init, cond, iter);
|
|
|
|
int iter_count = std::stol(tokens[7].str());
|
|
|
|
string body_prefix = "#if " + define_name.str() + " > " + iter_var.str() + "\n";
|
|
|
|
process_loop(tokens[0],
|
|
iter_count,
|
|
0,
|
|
1,
|
|
true,
|
|
true,
|
|
init,
|
|
cond,
|
|
iter,
|
|
loop_body,
|
|
body_prefix,
|
|
"#endif\n");
|
|
});
|
|
} while (parser.apply_mutations());
|
|
|
|
/* Check for remaining keywords. */
|
|
parser.foreach_match("[[w::w", [&](const std::vector<Token> tokens) {
|
|
if (tokens[2].str() == "gpu" && tokens[5].str() == "unroll") {
|
|
report_error(ERROR_TOK(tokens[0]), "Incompatible loop format for [[gpu::unroll]].");
|
|
}
|
|
});
|
|
}
|
|
|
|
void namespace_mutation(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
/* Parse each namespace declaration. */
|
|
parser.foreach_scope(ScopeType::Namespace, [&](const Scope &scope) {
|
|
/* TODO(fclem): This could be supported using multiple passes. */
|
|
scope.foreach_match("n", [&](const std::vector<Token> &tokens) {
|
|
report_error(ERROR_TOK(tokens[0]), "Nested namespaces are unsupported.");
|
|
});
|
|
|
|
string namespace_prefix = namespace_separator_mutation(
|
|
scope.start().prev().full_symbol_name() + "::");
|
|
auto process_symbol = [&](const Token &symbol) {
|
|
if (symbol.next() == '<') {
|
|
/* Template instantiation or specialization. */
|
|
return;
|
|
}
|
|
/* Replace all occurrences of the non-namespace specified symbol. */
|
|
scope.foreach_token(Word, [&](const Token &token) {
|
|
if (token.str() != symbol.str()) {
|
|
return;
|
|
}
|
|
/* Reject symbols that already have namespace specified. */
|
|
if (token.namespace_start() != token) {
|
|
return;
|
|
}
|
|
/* Reject method calls. */
|
|
if (token.prev() == '.') {
|
|
return;
|
|
}
|
|
parser.replace(token, namespace_prefix + token.str(), true);
|
|
});
|
|
};
|
|
|
|
unordered_set<string> processed_functions;
|
|
|
|
scope.foreach_function([&](bool, Token, Token fn_name, Scope, bool, Scope) {
|
|
/* Note: Struct scopes are currently parsed as Local. */
|
|
if (fn_name.scope().type() == ScopeType::Local) {
|
|
/* Don't process functions inside a struct scope as the namespace must not be apply
|
|
* to them, but to the type. Otherwise, method calls will not work. */
|
|
return;
|
|
}
|
|
if (processed_functions.count(fn_name.str())) {
|
|
/* Don't process function names twice. Can happen with overloads. */
|
|
return;
|
|
}
|
|
processed_functions.emplace(fn_name.str());
|
|
process_symbol(fn_name);
|
|
});
|
|
scope.foreach_struct([&](Token, Token struct_name, Scope) { process_symbol(struct_name); });
|
|
|
|
Token namespace_tok = scope.start().prev().namespace_start().prev();
|
|
if (namespace_tok == Namespace) {
|
|
parser.erase(namespace_tok, scope.start());
|
|
parser.erase(scope.end());
|
|
}
|
|
else {
|
|
report_error(ERROR_TOK(namespace_tok), "Expected namespace token.");
|
|
}
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* Needs to run before namespace mutation so that `using` have more precedence. */
|
|
void using_mutation(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_match("un", [&](const std::vector<Token> &tokens) {
|
|
report_error(ERROR_TOK(tokens[0]),
|
|
"Unsupported `using namespace`. "
|
|
"Add individual `using` directives for each needed symbol.");
|
|
});
|
|
|
|
auto process_using = [&](const Token &using_tok,
|
|
const Token &from,
|
|
const Token &to_start,
|
|
const Token &to_end,
|
|
const Token &end_tok) {
|
|
string to = parser.substr_range_inclusive(to_start, to_end);
|
|
string namespace_prefix = parser.substr_range_inclusive(to_start,
|
|
to_end.prev().prev().prev());
|
|
Scope scope = from.scope();
|
|
|
|
/* Using the keyword in global or at namespace scope. */
|
|
if (scope.type() == ScopeType::Global) {
|
|
report_error(ERROR_TOK(using_tok), "The `using` keyword is not allowed in global scope.");
|
|
return;
|
|
}
|
|
if (scope.type() == ScopeType::Namespace) {
|
|
/* Ensure we are bringing symbols from the same namespace.
|
|
* Otherwise we can have different shadowing outcome between shader and C++. */
|
|
string namespace_name = scope.start().prev().full_symbol_name();
|
|
if (namespace_name != namespace_prefix) {
|
|
report_error(
|
|
ERROR_TOK(using_tok),
|
|
"The `using` keyword is only allowed in namespace scope to make visible symbols "
|
|
"from the same namespace declared in another scope, potentially from another "
|
|
"file.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
to = namespace_separator_mutation(to);
|
|
|
|
/* Assignments do not allow to alias functions symbols. */
|
|
const bool use_alias = from.str() != to_end.str();
|
|
const bool replace_fn = !use_alias;
|
|
/** IMPORTANT: If replace_fn is true, this can replace any symbol type if there are functions
|
|
* and types with the same name. We could support being more explicit about the type of
|
|
* symbol to replace using an optional attribute [[gpu::using_function]]. */
|
|
|
|
/* Replace all occurrences of the non-namespace specified symbol. */
|
|
scope.foreach_token(Word, [&](const Token &token) {
|
|
/* Do not replace symbols before the using statement. */
|
|
if (token.index <= to_end.index) {
|
|
return;
|
|
}
|
|
/* Reject symbols that contain the target symbol name. */
|
|
if (token.prev() == ':') {
|
|
return;
|
|
}
|
|
if (!replace_fn && token.next() == '(') {
|
|
return;
|
|
}
|
|
if (token.str() != from.str()) {
|
|
return;
|
|
}
|
|
parser.replace(token, to, true);
|
|
});
|
|
|
|
parser.erase(using_tok, end_tok);
|
|
};
|
|
|
|
parser.foreach_match("uw::w", [&](const std::vector<Token> &tokens) {
|
|
Token end = tokens.back().find_next(SemiColon);
|
|
process_using(tokens[0], end.prev(), tokens[1], end.prev(), end);
|
|
});
|
|
|
|
parser.foreach_match("uw=w::w", [&](const std::vector<Token> &tokens) {
|
|
Token end = tokens.back().find_next(SemiColon);
|
|
process_using(tokens[0], tokens[1], tokens[3], end.prev(), end);
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
|
|
/* Verify all using were processed. */
|
|
parser.foreach_token(Using, [&](const Token &token) {
|
|
report_error(ERROR_TOK(token), "Unsupported `using` keyword usage.");
|
|
});
|
|
}
|
|
|
|
std::string namespace_separator_mutation(const std::string &str)
|
|
{
|
|
std::string out = str;
|
|
|
|
/* Global namespace reference. */
|
|
replace_all(out, " ::", " ");
|
|
/* Specific namespace reference.
|
|
* Cannot use `__` because of some compilers complaining about reserved symbols. */
|
|
replace_all(out, "::", "_");
|
|
return out;
|
|
}
|
|
|
|
std::string disabled_code_mutation(const std::string &str, report_callback &report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
|
|
auto process_disabled_scope = [&](Token start_tok) {
|
|
/* Search for endif with the same indentation. Assume formatted input. */
|
|
string end_str = start_tok.str_with_whitespace() + "endif";
|
|
size_t scope_end = parser.data_get().str.find(end_str, start_tok.str_index_start());
|
|
if (scope_end == string::npos) {
|
|
report_error(ERROR_TOK(start_tok), "Couldn't find end of disabled scope.");
|
|
return;
|
|
}
|
|
/* Search for else/elif with the same indentation. Assume formatted input. */
|
|
string else_str = start_tok.str_with_whitespace() + "el";
|
|
size_t scope_else = parser.data_get().str.find(else_str, start_tok.str_index_start());
|
|
if (scope_else != string::npos && scope_else < scope_end) {
|
|
/* Only erase the content and keep the preprocessor directives. */
|
|
parser.erase(start_tok.line_end() + 1, scope_else - 1);
|
|
}
|
|
else {
|
|
/* Erase the content and the preprocessor directives. */
|
|
parser.erase(start_tok.str_index_start(), scope_end + end_str.size());
|
|
}
|
|
};
|
|
|
|
parser.foreach_match("#ww", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[1].str() == "ifndef" && tokens[2].str() == "GPU_SHADER") {
|
|
process_disabled_scope(tokens[0]);
|
|
}
|
|
});
|
|
parser.foreach_match("#i!w(w)", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[1].str() == "if" && tokens[3].str() == "defined" &&
|
|
tokens[5].str() == "GPU_SHADER")
|
|
{
|
|
process_disabled_scope(tokens[0]);
|
|
}
|
|
});
|
|
parser.foreach_match("#i0", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[1].str() == "if" && tokens[2].str() == "0") {
|
|
process_disabled_scope(tokens[0]);
|
|
}
|
|
});
|
|
return parser.result_get();
|
|
}
|
|
|
|
std::string pragmas_mutation(const std::string &str, report_callback &report_error)
|
|
{
|
|
/* Remove unsupported directives. */
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
parser.foreach_match("#ww", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[1].str() == "pragma") {
|
|
if (tokens[2].str() == "once") {
|
|
parser.erase(tokens.front(), tokens.back());
|
|
}
|
|
else if (tokens[2].str() == "runtime_generated") {
|
|
parser.erase(tokens.front(), tokens.back());
|
|
}
|
|
}
|
|
});
|
|
return parser.result_get();
|
|
}
|
|
|
|
std::string swizzle_function_mutation(const std::string &str, report_callback &report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
|
|
parser.foreach_scope(ScopeType::Global, [&](Scope scope) {
|
|
/* Change C++ swizzle functions into plain swizzle. */
|
|
/** IMPORTANT: This prevent the usage of any method with a swizzle name. */
|
|
scope.foreach_match(".w()", [&](const std::vector<Token> &tokens) {
|
|
string method_name = tokens[1].str();
|
|
if (method_name.length() > 1 && method_name.length() <= 4 &&
|
|
(method_name.find_first_not_of("xyzw") == string::npos ||
|
|
method_name.find_first_not_of("rgba") == string::npos))
|
|
{
|
|
/* `.xyz()` -> `.xyz` */
|
|
/* Keep character count the same. Replace parenthesis by spaces. */
|
|
parser.replace(tokens[2], tokens[3], " ");
|
|
}
|
|
});
|
|
});
|
|
return parser.result_get();
|
|
}
|
|
|
|
void threadgroup_variables_parsing(const std::string &str)
|
|
{
|
|
std::regex regex(R"(shared\s+(\w+)\s+(\w+)([^;]*);)");
|
|
regex_global_search(str, regex, [&](const std::smatch &match) {
|
|
shared_vars_.push_back({match[1].str(), match[2].str(), match[3].str()});
|
|
});
|
|
}
|
|
|
|
void parse_library_functions(const std::string &str)
|
|
{
|
|
using namespace metadata;
|
|
std::regex regex_func(R"(void\s+(\w+)\s*\(([^)]+\))\s*\{)");
|
|
regex_global_search(str, regex_func, [&](const std::smatch &match) {
|
|
std::string name = match[1].str();
|
|
std::string args = match[2].str();
|
|
|
|
FunctionFormat fn;
|
|
fn.name = name;
|
|
|
|
std::regex regex_arg(R"((?:(const|in|out|inout)\s)?(\w+)\s([\w\[\]]+)(?:,|\)))");
|
|
regex_global_search(args, regex_arg, [&](const std::smatch &arg) {
|
|
std::string qualifier = arg[1].str();
|
|
std::string type = arg[2].str();
|
|
if (qualifier.empty() || qualifier == "const") {
|
|
qualifier = "in";
|
|
}
|
|
fn.arguments.emplace_back(
|
|
ArgumentFormat{metadata::Qualifier(hash(qualifier)), metadata::Type(hash(type))});
|
|
});
|
|
metadata.functions.emplace_back(fn);
|
|
});
|
|
}
|
|
|
|
void parse_builtins(const std::string &str, const std::string &filename)
|
|
{
|
|
const bool skip_drw_debug = filename.find("draw_debug_draw_lib.glsl") != std::string::npos ||
|
|
filename.find("draw_debug_draw_display_vert.glsl") !=
|
|
std::string::npos;
|
|
using namespace metadata;
|
|
/* TODO: This can trigger false positive caused by disabled #if blocks. */
|
|
std::string tokens[] = {"gl_FragCoord",
|
|
"gl_FrontFacing",
|
|
"gl_GlobalInvocationID",
|
|
"gl_InstanceID",
|
|
"gl_LocalInvocationID",
|
|
"gl_LocalInvocationIndex",
|
|
"gl_NumWorkGroup",
|
|
"gl_PointCoord",
|
|
"gl_PointSize",
|
|
"gl_PrimitiveID",
|
|
"gl_VertexID",
|
|
"gl_WorkGroupID",
|
|
"gl_WorkGroupSize",
|
|
"drw_debug_",
|
|
#ifdef WITH_GPU_SHADER_ASSERT
|
|
"assert",
|
|
#endif
|
|
"printf"};
|
|
for (auto &token : tokens) {
|
|
if (skip_drw_debug && token == "drw_debug_") {
|
|
continue;
|
|
}
|
|
if (str.find(token) != std::string::npos) {
|
|
metadata.builtins.emplace_back(Builtin(hash(token)));
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename ReportErrorF>
|
|
std::string printf_processing(const std::string &str, const ReportErrorF &report_error)
|
|
{
|
|
std::string out_str = str;
|
|
{
|
|
/* Example: `printf(2, b, f(c, d));` > `printf(2@ b@ f(c@ d))$` */
|
|
size_t start, end = 0;
|
|
while ((start = out_str.find("printf(", end)) != std::string::npos) {
|
|
end = out_str.find(';', start);
|
|
if (end == std::string::npos) {
|
|
break;
|
|
}
|
|
out_str[end] = '$';
|
|
int bracket_depth = 0;
|
|
int arg_len = 0;
|
|
for (size_t i = start; i < end; ++i) {
|
|
if (out_str[i] == '(') {
|
|
bracket_depth++;
|
|
}
|
|
else if (out_str[i] == ')') {
|
|
bracket_depth--;
|
|
}
|
|
else if (bracket_depth == 1 && out_str[i] == ',') {
|
|
out_str[i] = '@';
|
|
arg_len++;
|
|
}
|
|
}
|
|
if (arg_len > 99) {
|
|
report_error(line_number(out_str, start),
|
|
char_number(out_str, start),
|
|
line_str(out_str, start),
|
|
"Too many parameters in printf. Max is 99.");
|
|
break;
|
|
}
|
|
/* Encode number of arg in the `ntf` of `printf`. */
|
|
out_str[start + sizeof("printf") - 4] = '$';
|
|
out_str[start + sizeof("printf") - 3] = ((arg_len / 10) > 0) ? ('0' + arg_len / 10) : '$';
|
|
out_str[start + sizeof("printf") - 2] = '0' + arg_len % 10;
|
|
}
|
|
if (end == 0) {
|
|
/* No printf in source. */
|
|
return str;
|
|
}
|
|
}
|
|
/* Example: `pri$$1(2@ b)$` > `{int c_ = print_header(1, 2); c_ = print_data(c_, b); }` */
|
|
{
|
|
std::regex regex(R"(pri\$\$?(\d{1,2})\()");
|
|
out_str = std::regex_replace(out_str, regex, "{uint c_ = print_header($1u, ");
|
|
}
|
|
{
|
|
std::regex regex(R"(\@)");
|
|
out_str = std::regex_replace(out_str, regex, "); c_ = print_data(c_,");
|
|
}
|
|
{
|
|
std::regex regex(R"(\$)");
|
|
out_str = std::regex_replace(out_str, regex, "; }");
|
|
}
|
|
return out_str;
|
|
}
|
|
|
|
void assert_processing(Parser &parser, const std::string &filepath, report_callback report_error)
|
|
{
|
|
std::string filename = std::regex_replace(filepath, std::regex(R"((?:.*)\/(.*))"), "$1");
|
|
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
/* Example: `assert(i < 0)` > `if (!(i < 0)) { printf(...); }` */
|
|
parser.foreach_match("w(..)", [&](const vector<Token> &tokens) {
|
|
if (tokens[0].str() != "assert") {
|
|
return;
|
|
}
|
|
string replacement;
|
|
#ifdef WITH_GPU_SHADER_ASSERT
|
|
string condition = tokens[1].scope().str();
|
|
replacement += "if (!" + condition + ") ";
|
|
replacement += "{";
|
|
replacement += " printf(\"";
|
|
replacement += "Assertion failed: " + condition + ", ";
|
|
replacement += "file " + filename + ", ";
|
|
replacement += "line %d, ";
|
|
replacement += "thread (%u,%u,%u).\\n";
|
|
replacement += "\"";
|
|
replacement += ", __LINE__, GPU_THREAD.x, GPU_THREAD.y, GPU_THREAD.z); ";
|
|
replacement += "}";
|
|
#endif
|
|
parser.replace(tokens[0], tokens[4], replacement);
|
|
});
|
|
#ifndef WITH_GPU_SHADER_ASSERT
|
|
(void)filename;
|
|
(void)report_error;
|
|
#endif
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* String hash are outputted inside GLSL and needs to fit 32 bits. */
|
|
static uint32_t hash_string(const std::string &str)
|
|
{
|
|
uint64_t hash_64 = metadata::hash(str);
|
|
uint32_t hash_32 = uint32_t(hash_64 ^ (hash_64 >> 32));
|
|
return hash_32;
|
|
}
|
|
|
|
void static_strings_merging(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
do {
|
|
parser.foreach_match("__", [&](const std::vector<Token> &tokens) {
|
|
string first = tokens[0].str();
|
|
string second = tokens[1].str();
|
|
string between = parser.substr_range_inclusive(
|
|
tokens[0].str_index_last_no_whitespace() + 1, tokens[1].str_index_start() - 1);
|
|
string trailing = parser.substr_range_inclusive(
|
|
tokens[1].str_index_last_no_whitespace() + 1, tokens[1].str_index_last());
|
|
string merged = first.substr(0, first.length() - 1) + second.substr(1) + between +
|
|
trailing;
|
|
parser.replace_try(tokens[0], tokens[1], merged);
|
|
});
|
|
} while (parser.apply_mutations());
|
|
}
|
|
|
|
void static_strings_parsing_and_mutation(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_token(String, [&](const Token &token) {
|
|
uint32_t hash = hash_string(token.str());
|
|
metadata::PrintfFormat format = {hash, token.str()};
|
|
metadata.printf_formats.emplace_back(format);
|
|
parser.replace(token, std::to_string(hash) + 'u', true);
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* Move all method definition outside of struct definition blocks. */
|
|
void struct_method_mutation(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_scope(ScopeType::Global, [&](Scope scope) {
|
|
/* `class` -> `struct` */
|
|
scope.foreach_match("S", [&](const std::vector<Token> &tokens) {
|
|
parser.replace(tokens[0], tokens[0], "struct ");
|
|
});
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
|
|
parser.foreach_scope(ScopeType::Global, [&](Scope scope) {
|
|
scope.foreach_match("sw", [&](const std::vector<Token> &tokens) {
|
|
const Token struct_name = tokens[1];
|
|
|
|
if (struct_name.next() == ':') {
|
|
report_error(struct_name.next().line_number(),
|
|
struct_name.next().char_number(),
|
|
struct_name.next().line_str(),
|
|
"class inheritance is not supported");
|
|
return;
|
|
}
|
|
if (struct_name.next() != '{') {
|
|
report_error(struct_name.line_number(),
|
|
struct_name.char_number(),
|
|
struct_name.line_str(),
|
|
"Expected `{`");
|
|
return;
|
|
}
|
|
|
|
const Scope struct_scope = struct_name.next().scope();
|
|
const Token struct_end = struct_scope.end().next();
|
|
|
|
/* Erase `public:` and `private:` keywords. */
|
|
struct_scope.foreach_match("v:", [&](const std::vector<Token> &tokens) {
|
|
parser.erase(tokens[0].line_start(), tokens[1].line_end());
|
|
});
|
|
struct_scope.foreach_match("V:", [&](const std::vector<Token> &tokens) {
|
|
parser.erase(tokens[0].line_start(), tokens[1].line_end());
|
|
});
|
|
|
|
struct_scope.foreach_match("ww(", [&](const std::vector<Token> &tokens) {
|
|
if (tokens[0].prev() == Const) {
|
|
report_error(tokens[0].prev().line_number(),
|
|
tokens[0].prev().char_number(),
|
|
tokens[0].prev().line_str(),
|
|
"function return type is marked `const` but it makes no sense for values "
|
|
"and returning reference is not supported");
|
|
return;
|
|
}
|
|
|
|
const bool is_static = tokens[0].prev() == Static;
|
|
const Token fn_start = is_static ? tokens[0].prev() : tokens[0];
|
|
const Scope fn_args = tokens[2].scope();
|
|
const Token after_args = fn_args.end().next();
|
|
const bool is_const = after_args == Const;
|
|
const Scope fn_body = (is_const ? after_args.next() : after_args).scope();
|
|
|
|
string fn_content = parser.substr_range_inclusive(fn_start.line_start(),
|
|
fn_body.end().line_end() + 1);
|
|
|
|
Parser fn_parser(fn_content, report_error);
|
|
fn_parser.foreach_scope(ScopeType::Global, [&](Scope scope) {
|
|
if (is_static) {
|
|
scope.foreach_match("mww(", [&](const std::vector<Token> &tokens) {
|
|
const Token fn_name = tokens[2];
|
|
fn_parser.replace(fn_name, fn_name, struct_name.str() + "::" + fn_name.str());
|
|
/* WORKAROUND: Erase the static keyword as it conflict with the wrapper class
|
|
* member accesses MSL. */
|
|
fn_parser.erase(tokens[0]);
|
|
});
|
|
}
|
|
else {
|
|
scope.foreach_match("ww(", [&](const std::vector<Token> &tokens) {
|
|
const Scope args = tokens[2].scope();
|
|
const bool has_no_args = args.token_count() == 2;
|
|
const char *suffix = (has_no_args ? "" : ", ");
|
|
|
|
if (is_const) {
|
|
fn_parser.erase(args.end().next());
|
|
fn_parser.insert_after(args.start(),
|
|
"const " + struct_name.str() + " this_" + suffix);
|
|
}
|
|
else {
|
|
fn_parser.insert_after(args.start(), struct_name.str() + " &this_" + suffix);
|
|
}
|
|
});
|
|
}
|
|
|
|
/* `*this` -> `this_` */
|
|
scope.foreach_match("*T", [&](const std::vector<Token> &tokens) {
|
|
fn_parser.replace(tokens[0], tokens[1], "this_");
|
|
});
|
|
/* `this->` -> `this_.` */
|
|
scope.foreach_match("TD", [&](const std::vector<Token> &tokens) {
|
|
fn_parser.replace(tokens[0], tokens[1], "this_.");
|
|
});
|
|
});
|
|
|
|
string line_directive = "#line " + std::to_string(fn_start.line_number()) + '\n';
|
|
parser.erase(fn_start.line_start(), fn_body.end().line_end());
|
|
parser.insert_after(struct_end.line_end() + 1, line_directive + fn_parser.result_get());
|
|
});
|
|
|
|
string line_directive = "#line " + std::to_string(struct_end.line_number() + 1) + '\n';
|
|
parser.insert_after(struct_end.line_end() + 1, line_directive);
|
|
});
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* Add padding member to empty structs.
|
|
* Empty structs are useful for templating. */
|
|
void empty_struct_mutation(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_scope(ScopeType::Global, [&](Scope scope) {
|
|
scope.foreach_match("sw{};", [&](const std::vector<Token> &tokens) {
|
|
parser.insert_after(tokens[2], "int _pad;");
|
|
});
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* Transform `a.fn(b)` into `fn(a, b)`. */
|
|
void method_call_mutation(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
do {
|
|
parser.foreach_scope(ScopeType::Function, [&](Scope scope) {
|
|
scope.foreach_match(".w(", [&](const std::vector<Token> &tokens) {
|
|
const Token dot = tokens[0];
|
|
const Token func = tokens[1];
|
|
const Token par_open = tokens[2];
|
|
const Token end_of_this = dot.prev();
|
|
Token start_of_this = end_of_this;
|
|
while (true) {
|
|
if (start_of_this == ')') {
|
|
/* Function call. Take argument scope and function name. No recursion. */
|
|
start_of_this = start_of_this.scope().start().prev();
|
|
break;
|
|
}
|
|
if (start_of_this == ']') {
|
|
/* Array subscript. Take scope and continue. */
|
|
start_of_this = start_of_this.scope().start().prev();
|
|
continue;
|
|
}
|
|
if (start_of_this == Word) {
|
|
/* Member. */
|
|
if (start_of_this.prev() == '.') {
|
|
start_of_this = start_of_this.prev().prev();
|
|
/* Continue until we find root member. */
|
|
continue;
|
|
}
|
|
/* End of chain. */
|
|
break;
|
|
}
|
|
report_error(start_of_this.line_number(),
|
|
start_of_this.char_number(),
|
|
start_of_this.line_str(),
|
|
"method_call_mutation parsing error");
|
|
break;
|
|
}
|
|
string this_str = parser.substr_range_inclusive(start_of_this, end_of_this);
|
|
string func_str = func.str();
|
|
const bool has_no_arg = par_open.next() == ')';
|
|
/* `a.fn(b)` -> `fn(a, b)` */
|
|
parser.replace_try(
|
|
start_of_this, par_open, func_str + "(" + this_str + (has_no_arg ? "" : ", "));
|
|
});
|
|
});
|
|
} while (parser.apply_mutations());
|
|
}
|
|
|
|
void stage_function_mutation(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_function([&](bool is_static, Token fn_type, Token, Scope, bool, Scope fn_body) {
|
|
Token attr_tok = (is_static) ? fn_type.prev().prev() : fn_type.prev();
|
|
if (attr_tok.is_invalid() || attr_tok != ']' || attr_tok.prev() != ']') {
|
|
return;
|
|
}
|
|
Scope attribute = attr_tok.prev().scope();
|
|
if (attribute.type() != ScopeType::Subscript) {
|
|
return;
|
|
}
|
|
|
|
const string attr = attribute.str_exclusive();
|
|
parser.erase(attribute.scope());
|
|
|
|
string condition = "defined(";
|
|
if (attr == "gpu::vertex_function") {
|
|
condition += "GPU_VERTEX_SHADER";
|
|
}
|
|
else if (attr == "gpu::fragment_function") {
|
|
condition += "GPU_FRAGMENT_SHADER";
|
|
}
|
|
else if (attr == "gpu::compute_function") {
|
|
condition += "GPU_COMPUTE_SHADER";
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
condition += ")";
|
|
|
|
guarded_scope_mutation(parser, fn_body, condition);
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
void resource_guard_mutation(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_function([&](bool, Token fn_type, Token, Scope, bool, Scope fn_body) {
|
|
fn_body.foreach_match("w(w,", [&](const std::vector<Token> &tokens) {
|
|
string func_name = tokens[0].str();
|
|
if (func_name != "specialization_constant_get" && func_name != "shared_variable_get" &&
|
|
func_name != "push_constant_get" && func_name != "interface_get" &&
|
|
func_name != "attribute_get" && func_name != "buffer_get" &&
|
|
func_name != "sampler_get" && func_name != "image_get")
|
|
{
|
|
return;
|
|
}
|
|
string info_name = tokens[2].str();
|
|
Scope scope = tokens[0].scope();
|
|
/* We can be in expression scope. Take parent scope until we find a local scope. */
|
|
while (scope.type() != ScopeType::Function && scope.type() != ScopeType::Local) {
|
|
scope = scope.scope();
|
|
}
|
|
|
|
string condition = "defined(CREATE_INFO_" + info_name + ")";
|
|
|
|
if (scope.type() == ScopeType::Function) {
|
|
guarded_scope_mutation(parser, scope, condition, fn_type);
|
|
}
|
|
else {
|
|
guarded_scope_mutation(parser, scope, condition);
|
|
}
|
|
});
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
void guarded_scope_mutation(parser::Parser &parser,
|
|
parser::Scope scope,
|
|
const std::string &condition,
|
|
parser::Token fn_type = parser::Token::invalid())
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
string line_start = "#line " + std::to_string(scope.start().next().line_number()) + "\n";
|
|
string line_end = "#line " + std::to_string(scope.end().line_number()) + "\n";
|
|
|
|
string guard_start = "#if " + condition + "\n";
|
|
string guard_else;
|
|
if (fn_type.is_valid() && fn_type.str() != "void") {
|
|
string type = fn_type.str();
|
|
bool is_trivial = false;
|
|
if (type == "float" || type == "float2" || type == "float3" || type == "float4" ||
|
|
/**/
|
|
type == "int" || type == "int2" || type == "int3" || type == "int4" ||
|
|
/**/
|
|
type == "uint" || type == "uint2" || type == "uint3" || type == "uint4" ||
|
|
/**/
|
|
type == "float2x2" || type == "float2x3" || type == "float2x4" ||
|
|
/**/
|
|
type == "float3x2" || type == "float3x3" || type == "float3x4" ||
|
|
/**/
|
|
type == "float4x2" || type == "float4x3" || type == "float4x4")
|
|
{
|
|
is_trivial = true;
|
|
}
|
|
guard_else += "#else\n";
|
|
guard_else += line_start;
|
|
guard_else += " return " + type + (is_trivial ? "(0)" : "::zero()") + ";\n";
|
|
}
|
|
string guard_end = "#endif\n";
|
|
|
|
parser.insert_after(scope.start().line_end() + 1, guard_start + line_start);
|
|
parser.insert_before(scope.end().line_start(), guard_else + guard_end + line_end);
|
|
};
|
|
|
|
std::string guarded_scope_mutation(std::string content, int64_t line_start, std::string check)
|
|
{
|
|
int64_t line_end = line_start + line_count(content);
|
|
std::string guarded_cope;
|
|
guarded_cope += "#if " + check + "\n";
|
|
guarded_cope += "#line " + std::to_string(line_start) + "\n";
|
|
guarded_cope += content;
|
|
guarded_cope += "#endif\n";
|
|
guarded_cope += "#line " + std::to_string(line_end) + "\n";
|
|
return guarded_cope;
|
|
}
|
|
|
|
std::string enum_macro_injection(const std::string &str,
|
|
bool is_shared_file,
|
|
report_callback &report_error)
|
|
{
|
|
/**
|
|
* Transform C,C++ enum declaration into GLSL compatible defines and constants:
|
|
*
|
|
* \code{.cpp}
|
|
* enum MyEnum : uint {
|
|
* ENUM_1 = 0u,
|
|
* ENUM_2 = 1u,
|
|
* ENUM_3 = 2u,
|
|
* };
|
|
* \endcode
|
|
*
|
|
* becomes
|
|
*
|
|
* \code{.glsl}
|
|
* #define MyEnum uint
|
|
* constant static constexpr uint ENUM_1 = 0u;
|
|
* constant static constexpr uint ENUM_2 = 1u;
|
|
* constant static constexpr uint ENUM_3 = 2u;
|
|
*
|
|
* \endcode
|
|
*
|
|
* It is made like so to avoid messing with error lines, allowing to point at the exact
|
|
* location inside the source file.
|
|
*
|
|
* IMPORTANT: This has some requirements:
|
|
* - Enums needs to have underlying types set to uint32_t to make them usable in UBO and SSBO.
|
|
* - All values needs to be specified using constant literals to avoid compiler differences.
|
|
* - All values needs to have the 'u' suffix to avoid GLSL compiler errors.
|
|
*/
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
|
|
auto missing_underlying_type = [&](vector<Token> tokens) {
|
|
report_error(tokens[0].line_number(),
|
|
tokens[0].char_number(),
|
|
tokens[0].line_str(),
|
|
"enum declaration must explicitly use an underlying type");
|
|
};
|
|
|
|
parser.foreach_match("Mw{", missing_underlying_type);
|
|
parser.foreach_match("MSw{", missing_underlying_type);
|
|
|
|
auto process_enum =
|
|
[&](Token enum_tok, Token class_tok, Token enum_name, Token enum_type, Scope enum_scope) {
|
|
string type_str = enum_type.str();
|
|
|
|
if (is_shared_file) {
|
|
if (type_str != "uint32_t" && type_str != "int32_t") {
|
|
report_error(
|
|
enum_type.line_number(),
|
|
enum_type.char_number(),
|
|
enum_type.line_str(),
|
|
"enum declaration must use uint32_t or int32_t underlying type for interface "
|
|
"compatibility");
|
|
return;
|
|
}
|
|
}
|
|
|
|
size_t insert_at = enum_scope.end().line_end();
|
|
parser.erase(enum_tok.str_index_start(), insert_at);
|
|
parser.insert_line_number(insert_at + 1, enum_tok.line_number());
|
|
parser.insert_after(insert_at + 1,
|
|
"#define " + enum_name.str() + " " + enum_type.str() + "\n");
|
|
|
|
enum_scope.foreach_scope(ScopeType::Assignment, [&](Scope scope) {
|
|
string name = scope.start().prev().str();
|
|
string value = scope.str();
|
|
if (class_tok.is_valid()) {
|
|
name = enum_name.str() + "::" + name;
|
|
}
|
|
string decl = "constant static constexpr " + type_str + " " + name + " " + value +
|
|
";\n";
|
|
parser.insert_line_number(insert_at + 1, scope.start().line_number());
|
|
parser.insert_after(insert_at + 1, decl);
|
|
});
|
|
parser.insert_line_number(insert_at + 1, enum_scope.end().line_number() + 1);
|
|
};
|
|
|
|
parser.foreach_match("MSw:w{", [&](vector<Token> tokens) {
|
|
process_enum(tokens[0], tokens[1], tokens[2], tokens[4], tokens[5].scope());
|
|
});
|
|
parser.foreach_match("Mw:w{", [&](vector<Token> tokens) {
|
|
process_enum(tokens[0], Token::invalid(), tokens[1], tokens[3], tokens[4].scope());
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
|
|
parser.foreach_match("M", [&](vector<Token> tokens) {
|
|
report_error(tokens[0].line_number(),
|
|
tokens[0].char_number(),
|
|
tokens[0].line_str(),
|
|
"invalid enum declaration");
|
|
});
|
|
return parser.result_get();
|
|
}
|
|
|
|
std::string strip_whitespace(const std::string &str) const
|
|
{
|
|
return str.substr(0, str.find_last_not_of(" \n") + 1);
|
|
}
|
|
|
|
/**
|
|
* Expand functions with default arguments to function overloads.
|
|
* Expects formatted input and that function bodies are followed by newline.
|
|
*/
|
|
void default_argument_mutation(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_function(
|
|
[&](bool, Token fn_type, Token fn_name, Scope fn_args, bool, Scope fn_body) {
|
|
if (!fn_args.contains_token('=')) {
|
|
return;
|
|
}
|
|
|
|
const bool has_non_void_return_type = fn_type.str() != "void";
|
|
|
|
string args_decl;
|
|
string args_names;
|
|
|
|
vector<string> fn_overloads;
|
|
|
|
fn_args.foreach_scope(ScopeType::FunctionArg, [&](Scope arg) {
|
|
Token equal = arg.find_token('=');
|
|
const char *comma = (args_decl.empty() ? "" : ", ");
|
|
if (equal.is_invalid()) {
|
|
args_decl += comma + arg.str();
|
|
args_names += comma + arg.end().str();
|
|
}
|
|
else {
|
|
string arg_name = equal.prev().str();
|
|
string value = parser.substr_range_inclusive(equal.next(), arg.end());
|
|
string decl = parser.substr_range_inclusive(arg.start(), equal.prev());
|
|
|
|
string fn_call = fn_name.str() + '(' + args_names + comma + value + ");";
|
|
if (has_non_void_return_type) {
|
|
fn_call = "return " + fn_call;
|
|
}
|
|
string overload;
|
|
overload += fn_type.str() + " ";
|
|
overload += fn_name.str() + '(' + args_decl + ")\n";
|
|
overload += "{\n";
|
|
overload += "#line " + std::to_string(fn_type.line_number()) + "\n";
|
|
overload += " " + fn_call + "\n}\n";
|
|
fn_overloads.emplace_back(overload);
|
|
|
|
args_decl += comma + strip_whitespace(decl);
|
|
args_names += comma + arg_name;
|
|
/* Erase the value assignment and keep the declaration. */
|
|
parser.erase(equal.scope());
|
|
}
|
|
});
|
|
size_t end_of_fn_char = fn_body.end().line_end() + 1;
|
|
/* Have to reverse the declaration order. */
|
|
for (auto it = fn_overloads.rbegin(); it != fn_overloads.rend(); ++it) {
|
|
parser.insert_line_number(end_of_fn_char, fn_type.line_number());
|
|
parser.insert_after(end_of_fn_char, *it);
|
|
}
|
|
parser.insert_line_number(end_of_fn_char, fn_body.end().line_number() + 1);
|
|
});
|
|
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* Used to make GLSL matrix constructor compatible with MSL in pyGPU shaders.
|
|
* This syntax is not supported in blender's own shaders. */
|
|
std::string matrix_constructor_mutation(const std::string &str)
|
|
{
|
|
if (str.find("mat") == std::string::npos) {
|
|
return str;
|
|
}
|
|
/* Example: `mat2(x)` > `mat2x2(x)` */
|
|
std::regex regex_parenthesis(R"(\bmat([234])\()");
|
|
std::string out = std::regex_replace(str, regex_parenthesis, "mat$1x$1(");
|
|
/* Only process square matrices since this is the only types we overload the constructors. */
|
|
/* Example: `mat2x2(x)` > `__mat2x2(x)` */
|
|
std::regex regex(R"(\bmat(2x2|3x3|4x4)\()");
|
|
return std::regex_replace(out, regex, "__mat$1(");
|
|
}
|
|
|
|
/* To be run before `argument_decorator_macro_injection()`. */
|
|
void argument_reference_mutation(Parser &parser, report_callback /*report_error*/)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
auto add_mutation = [&](Token type, Token arg_name, Token last_tok) {
|
|
if (type.prev() == Const) {
|
|
parser.replace(type.prev(), last_tok, type.str() + " " + arg_name.str());
|
|
}
|
|
else {
|
|
parser.replace(type, last_tok, "inout " + type.str() + " " + arg_name.str());
|
|
}
|
|
};
|
|
|
|
parser.foreach_scope(ScopeType::FunctionArgs, [&](const Scope scope) {
|
|
scope.foreach_match(
|
|
"w(&w)", [&](const vector<Token> toks) { add_mutation(toks[0], toks[3], toks[4]); });
|
|
scope.foreach_match(
|
|
"w&w", [&](const vector<Token> toks) { add_mutation(toks[0], toks[2], toks[2]); });
|
|
scope.foreach_match(
|
|
"w&T", [&](const vector<Token> toks) { add_mutation(toks[0], toks[2], toks[2]); });
|
|
});
|
|
parser.apply_mutations();
|
|
}
|
|
|
|
/* To be run after `argument_reference_mutation()`. */
|
|
std::string variable_reference_mutation(const std::string &str, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
/* Processing regex and logic is expensive. Check if they are needed at all. */
|
|
bool valid_match = false;
|
|
string next_str = str;
|
|
reference_search(next_str, [&](int parenthesis_depth, int /*bracket_depth*/, char &c) {
|
|
/* Check if inside a function body. */
|
|
if (parenthesis_depth == 0) {
|
|
valid_match = true;
|
|
/* Modify the & into @ to make sure we only match these references in the regex
|
|
* below. @ being forbidden in the shader language, it is safe to use a temp
|
|
* character. */
|
|
c = '@';
|
|
}
|
|
});
|
|
if (!valid_match) {
|
|
return str;
|
|
}
|
|
string out_str;
|
|
/* Example: `const float &var = value;` */
|
|
regex regex_ref(R"(\ ?(?:const)?\s*\w+\s+\@(\w+) =\s*([^;]+);)");
|
|
|
|
smatch match;
|
|
while (regex_search(next_str, match, regex_ref)) {
|
|
const string definition = match[0].str();
|
|
const string name = match[1].str();
|
|
const string value = match[2].str();
|
|
const string prefix = match.prefix().str();
|
|
const string suffix = match.suffix().str();
|
|
|
|
out_str += prefix;
|
|
|
|
/* Assert definition doesn't contain any side effect. */
|
|
if (value.find("++") != string::npos || value.find("--") != string::npos) {
|
|
report_error(line_number(match),
|
|
char_number(match),
|
|
line_str(match),
|
|
"Reference definitions cannot have side effects.");
|
|
return str;
|
|
}
|
|
if (value.find("(") != string::npos) {
|
|
if (value.find("specialization_constant_get(") == string::npos &&
|
|
value.find("push_constant_get(") == string::npos &&
|
|
value.find("interface_get(") == string::npos &&
|
|
value.find("attribute_get(") == string::npos &&
|
|
value.find("buffer_get(") == string::npos &&
|
|
value.find("sampler_get(") == string::npos && value.find("image_get(") == string::npos)
|
|
{
|
|
report_error(line_number(match),
|
|
char_number(match),
|
|
line_str(match),
|
|
"Reference definitions cannot contain function calls.");
|
|
return str;
|
|
}
|
|
}
|
|
if (value.find("[") != string::npos) {
|
|
const string index_var = get_content_between_balanced_pair(value, '[', ']');
|
|
|
|
if (index_var.find(' ') != string::npos) {
|
|
report_error(line_number(match),
|
|
char_number(match),
|
|
line_str(match),
|
|
"Array subscript inside reference declaration must be a single variable or "
|
|
"a constant, not an expression.");
|
|
return str;
|
|
}
|
|
|
|
/* Add a space to avoid empty scope breaking the loop. */
|
|
string scope_depth = " }";
|
|
bool found_var = false;
|
|
while (!found_var) {
|
|
string scope = get_content_between_balanced_pair(out_str + scope_depth, '{', '}', true);
|
|
scope_depth += '}';
|
|
|
|
if (scope.empty()) {
|
|
break;
|
|
}
|
|
/* Remove nested scopes. Avoid variable shadowing to mess with the detection. */
|
|
scope = regex_replace(scope, regex(R"(\{[^\}]*\})"), "{}");
|
|
/* Search if index variable definition qualifies it as `const`. */
|
|
regex regex_definition(R"((const)? \w+ )" + index_var + " =");
|
|
smatch match_definition;
|
|
if (regex_search(scope, match_definition, regex_definition)) {
|
|
found_var = true;
|
|
if (match_definition[1].matched == false) {
|
|
report_error(line_number(match),
|
|
char_number(match),
|
|
line_str(match),
|
|
"Array subscript variable must be declared as const qualified.");
|
|
return str;
|
|
}
|
|
}
|
|
}
|
|
if (!found_var) {
|
|
report_error(line_number(match),
|
|
char_number(match),
|
|
line_str(match),
|
|
"Cannot locate array subscript variable declaration. "
|
|
"If it is a global variable, assign it to a temporary const variable for "
|
|
"indexing inside the reference.");
|
|
return str;
|
|
}
|
|
}
|
|
|
|
/* Find scope this definition is active in. */
|
|
const string scope = get_content_between_balanced_pair('{' + suffix, '{', '}');
|
|
if (scope.empty()) {
|
|
report_error(line_number(match),
|
|
char_number(match),
|
|
line_str(match),
|
|
"Reference is defined inside a global or unterminated scope.");
|
|
return str;
|
|
}
|
|
string original = definition + scope;
|
|
string modified = original;
|
|
|
|
/* Replace definition by nothing. Keep number of lines. */
|
|
string newlines(line_count(definition), '\n');
|
|
replace_all(modified, definition, newlines);
|
|
/* Replace every occurrence of the reference. Avoid matching other symbols like class members
|
|
* and functions with the same name. */
|
|
modified = regex_replace(
|
|
modified, regex(R"(([^\.])\b)" + name + R"(\b([^(]))"), "$1" + value + "$2");
|
|
|
|
/** IMPORTANT: `match` is invalid after the assignment. */
|
|
next_str = definition + suffix;
|
|
|
|
/* Replace whole modified scope in output string. */
|
|
replace_all(next_str, original, modified);
|
|
}
|
|
out_str += next_str;
|
|
return out_str;
|
|
}
|
|
|
|
std::string argument_decorator_macro_injection(const std::string &str)
|
|
{
|
|
/* Example: `out float var[2]` > `out float _out_sta var _out_end[2]` */
|
|
std::regex regex(R"((out|inout|in|shared)\s+(\w+)\s+(\w+))");
|
|
return std::regex_replace(str, regex, "$1 $2 _$1_sta $3 _$1_end");
|
|
}
|
|
|
|
std::string array_constructor_macro_injection(const std::string &str)
|
|
{
|
|
/* Example: `= float[2](0.0, 0.0)` > `= ARRAY_T(float) ARRAY_V(0.0, 0.0)` */
|
|
std::regex regex(R"(=\s*(\w+)\s*\[[^\]]*\]\s*\()");
|
|
return std::regex_replace(str, regex, "= ARRAY_T($1) ARRAY_V(");
|
|
}
|
|
|
|
/* Assume formatted source with our code style. Cannot be applied to python shaders. */
|
|
void global_scope_constant_linting(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
/* Example: `const uint global_var = 1u;`. */
|
|
parser.foreach_match("cww=", [&](const vector<Token> &tokens) {
|
|
if (tokens[0].scope().type() == ScopeType::Global) {
|
|
report_error(
|
|
ERROR_TOK(tokens[2]),
|
|
"Global scope constant expression found. These get allocated per-thread in MSL. "
|
|
"Use Macro's or uniforms instead.");
|
|
}
|
|
});
|
|
}
|
|
|
|
void quote_linting(const std::string &str, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
Parser parser(str, report_error);
|
|
/* This only catches some invalid usage. For the rest, the CI will catch them. */
|
|
parser.foreach_token(TokenType::String, [&](const Token token) {
|
|
report_error(ERROR_TOK(token),
|
|
"Unprocessed string literal. "
|
|
"Strings are forbidden in GLSL.");
|
|
});
|
|
}
|
|
|
|
void small_type_linting(Parser &parser, report_callback report_error)
|
|
{
|
|
using namespace std;
|
|
using namespace shader::parser;
|
|
|
|
parser.foreach_scope(ScopeType::Struct, [&](const Scope scope) {
|
|
scope.foreach_match("ww;", [&](const vector<Token> tokens) {
|
|
string type = tokens[0].str();
|
|
if (type.find("char") != string::npos || type.find("short") != string::npos ||
|
|
type.find("half") != string::npos)
|
|
{
|
|
report_error(ERROR_TOK(tokens[0]), "Small types are forbidden in shader interfaces.");
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
std::string threadgroup_variables_suffix()
|
|
{
|
|
if (shared_vars_.empty()) {
|
|
return "";
|
|
}
|
|
|
|
std::stringstream suffix;
|
|
/**
|
|
* For Metal shaders to compile, shared (threadgroup) variable cannot be declared globally.
|
|
* They must reside within a function scope. Hence, we need to extract these declarations and
|
|
* generate shared memory blocks within the entry point function. These shared memory blocks
|
|
* can then be passed as references to the remaining shader via the class function scope.
|
|
*
|
|
* The shared variable definitions from the source file are replaced with references to
|
|
* threadgroup memory blocks (using _shared_sta and _shared_end macros), but kept in-line in
|
|
* case external macros are used to declare the dimensions.
|
|
*
|
|
* Each part of the codegen is stored inside macros so that we don't have to do string
|
|
* replacement at runtime.
|
|
*/
|
|
suffix << "\n";
|
|
/* Arguments of the wrapper class constructor. */
|
|
suffix << "#undef MSL_SHARED_VARS_ARGS\n";
|
|
/* References assignment inside wrapper class constructor. */
|
|
suffix << "#undef MSL_SHARED_VARS_ASSIGN\n";
|
|
/* Declaration of threadgroup variables in entry point function. */
|
|
suffix << "#undef MSL_SHARED_VARS_DECLARE\n";
|
|
/* Arguments for wrapper class constructor call. */
|
|
suffix << "#undef MSL_SHARED_VARS_PASS\n";
|
|
|
|
/**
|
|
* Example replacement:
|
|
*
|
|
* \code{.cc}
|
|
* // Source
|
|
* shared float bar[10]; // Source declaration.
|
|
* shared float foo; // Source declaration.
|
|
* // Rest of the source ...
|
|
* // End of Source
|
|
*
|
|
* // Backend Output
|
|
* class Wrapper { // Added at runtime by backend.
|
|
*
|
|
* threadgroup float (&foo); // Replaced by regex and macros.
|
|
* threadgroup float (&bar)[10]; // Replaced by regex and macros.
|
|
* // Rest of the source ...
|
|
*
|
|
* Wrapper ( // Added at runtime by backend.
|
|
* threadgroup float (&_foo), threadgroup float (&_bar)[10] // MSL_SHARED_VARS_ARGS
|
|
* ) // Added at runtime by backend.
|
|
* : foo(_foo), bar(_bar) // MSL_SHARED_VARS_ASSIGN
|
|
* {} // Added at runtime by backend.
|
|
*
|
|
* }; // End of Wrapper // Added at runtime by backend.
|
|
*
|
|
* kernel entry_point() { // Added at runtime by backend.
|
|
*
|
|
* threadgroup float foo; // MSL_SHARED_VARS_DECLARE
|
|
* threadgroup float bar[10] // MSL_SHARED_VARS_DECLARE
|
|
*
|
|
* Wrapper wrapper // Added at runtime by backend.
|
|
* (foo, bar) // MSL_SHARED_VARS_PASS
|
|
* ; // Added at runtime by backend.
|
|
*
|
|
* } // Added at runtime by backend.
|
|
* // End of Backend Output
|
|
* \endcode
|
|
*/
|
|
std::stringstream args, assign, declare, pass;
|
|
|
|
bool first = true;
|
|
for (SharedVar &var : shared_vars_) {
|
|
char sep = first ? ' ' : ',';
|
|
|
|
args << sep << "threadgroup " << var.type << "(&_" << var.name << ")" << var.array;
|
|
assign << (first ? ':' : ',') << var.name << "(_" << var.name << ")";
|
|
declare << "threadgroup " << var.type << ' ' << var.name << var.array << ";";
|
|
pass << sep << var.name;
|
|
first = false;
|
|
}
|
|
|
|
suffix << "#define MSL_SHARED_VARS_ARGS " << args.str() << "\n";
|
|
suffix << "#define MSL_SHARED_VARS_ASSIGN " << assign.str() << "\n";
|
|
suffix << "#define MSL_SHARED_VARS_DECLARE " << declare.str() << "\n";
|
|
suffix << "#define MSL_SHARED_VARS_PASS (" << pass.str() << ")\n";
|
|
suffix << "\n";
|
|
|
|
return suffix.str();
|
|
}
|
|
|
|
std::string line_directive_prefix(const std::string &filepath)
|
|
{
|
|
std::string filename = std::regex_replace(filepath, std::regex(R"((?:.*)\/(.*))"), "$1");
|
|
|
|
std::stringstream suffix;
|
|
/* NOTE: This is not supported by GLSL. All line directives are muted at runtime and the
|
|
* sources are scanned after error reporting for the locating the muted line. */
|
|
suffix << "#line 1 \"" << filename << "\"\n";
|
|
return suffix.str();
|
|
}
|
|
|
|
/* Made public for unit testing purpose. */
|
|
public:
|
|
static std::string get_content_between_balanced_pair(const std::string &input,
|
|
char start_delimiter,
|
|
char end_delimiter,
|
|
const bool backwards = false)
|
|
{
|
|
int balance = 0;
|
|
size_t start = std::string::npos;
|
|
size_t end = std::string::npos;
|
|
|
|
if (backwards) {
|
|
std::swap(start_delimiter, end_delimiter);
|
|
}
|
|
|
|
for (size_t i = 0; i < input.length(); ++i) {
|
|
size_t idx = backwards ? (input.length() - 1) - i : i;
|
|
if (input[idx] == start_delimiter) {
|
|
if (balance == 0) {
|
|
start = idx;
|
|
}
|
|
balance++;
|
|
}
|
|
else if (input[idx] == end_delimiter) {
|
|
balance--;
|
|
if (balance == 0 && start != std::string::npos) {
|
|
end = idx;
|
|
if (backwards) {
|
|
std::swap(start, end);
|
|
}
|
|
return input.substr(start + 1, end - start - 1);
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/* Replaces all occurrences of `from` by `to` between `start_delimiter`
|
|
* and `end_delimiter` even inside nested delimiters pair. */
|
|
static std::string replace_char_between_balanced_pair(const std::string &input,
|
|
const char start_delimiter,
|
|
const char end_delimiter,
|
|
const char from,
|
|
const char to)
|
|
{
|
|
int depth = 0;
|
|
|
|
std::string str = input;
|
|
for (char &string_char : str) {
|
|
if (string_char == start_delimiter) {
|
|
depth++;
|
|
}
|
|
else if (string_char == end_delimiter) {
|
|
depth--;
|
|
}
|
|
else if (depth > 0 && string_char == from) {
|
|
string_char = to;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/* Function to split a string by a delimiter and return a vector of substrings. */
|
|
static std::vector<std::string> split_string(const std::string &str, const char delimiter)
|
|
{
|
|
std::vector<std::string> substrings;
|
|
std::stringstream ss(str);
|
|
std::string item;
|
|
|
|
while (std::getline(ss, item, delimiter)) {
|
|
substrings.push_back(item);
|
|
}
|
|
return substrings;
|
|
}
|
|
|
|
/* Similar to split_string but only split if the delimiter is not between any pair_start and
|
|
* pair_end. */
|
|
static std::vector<std::string> split_string_not_between_balanced_pair(const std::string &str,
|
|
const char delimiter,
|
|
const char pair_start,
|
|
const char pair_end)
|
|
{
|
|
const char safe_char = '@';
|
|
const std::string safe_str = replace_char_between_balanced_pair(
|
|
str, pair_start, pair_end, delimiter, safe_char);
|
|
std::vector<std::string> split = split_string(safe_str, delimiter);
|
|
for (std::string &str : split) {
|
|
replace_all(str, safe_char, delimiter);
|
|
}
|
|
return split;
|
|
}
|
|
|
|
static void replace_all(std::string &str, const std::string &from, const std::string &to)
|
|
{
|
|
if (from.empty()) {
|
|
return;
|
|
}
|
|
size_t start_pos = 0;
|
|
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
|
str.replace(start_pos, from.length(), to);
|
|
start_pos += to.length();
|
|
}
|
|
}
|
|
|
|
static void replace_all(std::string &str, const char from, const char to)
|
|
{
|
|
for (char &string_char : str) {
|
|
if (string_char == from) {
|
|
string_char = to;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int64_t char_count(const std::string &str, char c)
|
|
{
|
|
return std::count(str.begin(), str.end(), c);
|
|
}
|
|
|
|
static int64_t line_count(const std::string &str)
|
|
{
|
|
return char_count(str, '\n');
|
|
}
|
|
|
|
/* Match any reference definition (e.g. `int &a = b`).
|
|
* Call the callback function for each `&` character that matches a reference definition.
|
|
* Expects the input `str` to be formatted with balanced parenthesis and curly brackets. */
|
|
static void reference_search(std::string &str, std::function<void(int, int, char &)> callback)
|
|
{
|
|
scopes_scan_for_char(
|
|
str, '&', [&](size_t pos, int parenthesis_depth, int bracket_depth, char &c) {
|
|
if (pos > 0 && pos <= str.length() - 2) {
|
|
/* This is made safe by the previous check. */
|
|
char prev_char = str[pos - 1];
|
|
char next_char = str[pos + 1];
|
|
/* Validate it is not an operator (`&`, `&&`, `&=`). */
|
|
if (prev_char == ' ' || prev_char == '(') {
|
|
if (next_char != ' ' && next_char != '\n' && next_char != '&' && next_char != '=') {
|
|
callback(parenthesis_depth, bracket_depth, c);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Match any default argument definition (e.g. `void func(int a = 0)`).
|
|
* Call the callback function for each `=` character inside a function argument list.
|
|
* Expects the input `str` to be formatted with balanced parenthesis and curly brackets. */
|
|
static void default_argument_search(std::string &str,
|
|
std::function<void(int, int, char &)> callback)
|
|
{
|
|
scopes_scan_for_char(
|
|
str, '=', [&](size_t pos, int parenthesis_depth, int bracket_depth, char &c) {
|
|
if (pos > 0 && pos <= str.length() - 2) {
|
|
/* This is made safe by the previous check. */
|
|
char prev_char = str[pos - 1];
|
|
char next_char = str[pos + 1];
|
|
/* Validate it is not an operator (`==`, `<=`, `>=`). Expects formatted input. */
|
|
if (prev_char == ' ' && next_char == ' ') {
|
|
if (parenthesis_depth == 1 && bracket_depth == 0) {
|
|
callback(parenthesis_depth, bracket_depth, c);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Scan through a string matching for every occurrence of a character.
|
|
* Calls the callback with the context in which the match occurs. */
|
|
static void scopes_scan_for_char(std::string &str,
|
|
char search_char,
|
|
std::function<void(size_t, int, int, char &)> callback)
|
|
{
|
|
size_t pos = 0;
|
|
int parenthesis_depth = 0;
|
|
int bracket_depth = 0;
|
|
for (char &c : str) {
|
|
if (c == search_char) {
|
|
callback(pos, parenthesis_depth, bracket_depth, c);
|
|
}
|
|
else if (c == '(') {
|
|
parenthesis_depth++;
|
|
}
|
|
else if (c == ')') {
|
|
parenthesis_depth--;
|
|
}
|
|
else if (c == '{') {
|
|
bracket_depth++;
|
|
}
|
|
else if (c == '}') {
|
|
bracket_depth--;
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
/* Return the line number this token is found at. Take into account the #line directives. */
|
|
static size_t line_number(const std::string &file_str, size_t pos)
|
|
{
|
|
std::string sub_str = file_str.substr(0, pos);
|
|
std::string directive = "#line ";
|
|
size_t nearest_line_directive = sub_str.rfind(directive);
|
|
size_t line_count = 1;
|
|
if (nearest_line_directive != std::string::npos) {
|
|
sub_str = sub_str.substr(nearest_line_directive + directive.size());
|
|
line_count = std::stoll(sub_str) - 1;
|
|
}
|
|
return line_count + std::count(sub_str.begin(), sub_str.end(), '\n');
|
|
}
|
|
static size_t line_number(const std::smatch &smatch)
|
|
{
|
|
std::string whole_file = smatch.prefix().str() + smatch[0].str() + smatch.suffix().str();
|
|
return line_number(whole_file, smatch.prefix().str().size());
|
|
}
|
|
|
|
/* Return the offset to the start of the line. */
|
|
static size_t char_number(const std::string &file_str, size_t pos)
|
|
{
|
|
std::string sub_str = file_str.substr(0, pos);
|
|
size_t nearest_line_directive = sub_str.find_last_of("\n");
|
|
return (nearest_line_directive == std::string::npos) ?
|
|
(sub_str.size() - 1) :
|
|
(sub_str.size() - nearest_line_directive);
|
|
}
|
|
static size_t char_number(const std::smatch &smatch)
|
|
{
|
|
std::string whole_file = smatch.prefix().str() + smatch[0].str() + smatch.suffix().str();
|
|
return char_number(whole_file, smatch.prefix().str().size());
|
|
}
|
|
|
|
/* Return the line the token is at. */
|
|
static std::string line_str(const std::string &file_str, size_t pos)
|
|
{
|
|
size_t start = file_str.rfind('\n', pos);
|
|
size_t end = file_str.find('\n', pos);
|
|
if (start == std::string::npos) {
|
|
start = 0;
|
|
}
|
|
return file_str.substr(start, end - start);
|
|
}
|
|
static std::string line_str(const std::smatch &smatch)
|
|
{
|
|
std::string whole_file = smatch.prefix().str() + smatch[0].str() + smatch.suffix().str();
|
|
return line_str(whole_file, smatch.prefix().str().size());
|
|
}
|
|
};
|
|
|
|
} // namespace blender::gpu::shader
|