Files
test/source/blender/gpu/glsl_preprocess/glsl_preprocess.hh
Campbell Barton f3c2deac3e Cleanup: replace U+00A0 with space
Using ASCII space is sufficient in source.
2024-10-09 16:34:39 +11:00

220 lines
7.9 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup glsl_preprocess
*/
#pragma once
#include <algorithm>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
namespace blender::gpu::shader {
/**
* 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.
*/
template<typename T, bool no_linting = false> class Preprocessor {
T &report_error;
struct SharedVar {
std::string type;
std::string name;
std::string array;
};
std::vector<SharedVar> shared_vars_;
std::stringstream output_;
public:
Preprocessor(T &error_cb) : report_error(error_cb) {}
Preprocessor &operator<<(std::string str)
{
threadgroup_variable_parsing(str);
matrix_constructor_linting(str);
array_constructor_linting(str);
str = preprocessor_directive_mutation(str);
str = argument_decorator_macro_injection(str);
str = array_constructor_macro_injection(str);
output_ << str;
return *this;
}
Preprocessor &operator<<(char c)
{
output_ << c;
return *this;
}
std::string str()
{
return output_.str() + suffix();
}
private:
std::string preprocessor_directive_mutation(const std::string &str)
{
/* Example: `#include "deps.glsl"` > `//include "deps.glsl"` */
std::regex regex("#\\s*(include|pragma once)");
return std::regex_replace(str, regex, "//$1");
}
void threadgroup_variable_parsing(std::string str)
{
std::regex regex("shared\\s+(\\w+)\\s+(\\w+)([^;]*);");
for (std::smatch match; std::regex_search(str, match, regex); str = match.suffix()) {
shared_vars_.push_back({match[1].str(), match[2].str(), match[3].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("(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("=\\s*(\\w+)\\s*\\[[^\\]]*\\]\\s*\\(");
return std::regex_replace(str, regex, "= ARRAY_T($1) ARRAY_V(");
}
/* TODO(fclem): Too many false positive and false negative to be applied to python shaders. */
void matrix_constructor_linting(std::string str)
{
if constexpr (no_linting) {
return;
}
/* Example: `mat4(other_mat)`. */
std::regex regex("\\s+(mat(\\d|\\dx\\d)|float\\dx\\d)\\([^,\\s\\d]+\\)");
for (std::smatch match; std::regex_search(str, match, regex); str = match.suffix()) {
/* This only catches some invalid usage. For the rest, the CI will catch them. */
const char *msg =
"Matrix constructor is not cross API compatible. "
"Use to_floatNxM to reshape the matrix or use other constructors instead.";
report_error(str, match, msg);
}
}
void array_constructor_linting(std::string str)
{
if constexpr (no_linting) {
return;
}
std::regex regex("=\\s*(\\w+)\\s*\\[[^\\]]*\\]\\s*\\(");
for (std::smatch match; std::regex_search(str, match, regex); str = match.suffix()) {
/* This only catches some invalid usage. For the rest, the CI will catch them. */
const char *msg =
"Array constructor is not cross API compatible. Use type_array instead of type[].";
report_error(str, match, msg);
}
}
std::string 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.
*/
/* 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:
*
* `
* // 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
* `
*/
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";
return suffix.str();
}
};
template<typename T> class PreprocessorPython : public Preprocessor<T, true> {
public:
PreprocessorPython(T &error_cb) : Preprocessor<T, true>(error_cb){};
};
} // namespace blender::gpu::shader