564 lines
17 KiB
C++
564 lines
17 KiB
C++
/* SPDX-FileCopyrightText: 2021 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*
|
|
* Shader source dependency builder that make possible to support #include directive inside the
|
|
* shader files.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <regex>
|
|
#include <string>
|
|
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_map.hh"
|
|
#include "BLI_string_ref.hh"
|
|
|
|
#include "gpu_material_library.hh"
|
|
#include "gpu_shader_create_info.hh"
|
|
#include "gpu_shader_dependency_private.hh"
|
|
|
|
#include "../glsl_preprocess/glsl_preprocess.hh"
|
|
|
|
extern "C" {
|
|
#define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[];
|
|
#include "glsl_compositor_source_list.h"
|
|
#include "glsl_draw_source_list.h"
|
|
#include "glsl_gpu_source_list.h"
|
|
#ifdef WITH_OCIO
|
|
# include "glsl_ocio_source_list.h"
|
|
#endif
|
|
#undef SHADER_SOURCE
|
|
}
|
|
|
|
namespace blender::gpu {
|
|
|
|
using GPUPrintFormatMap = Map<uint32_t, shader::PrintfFormat>;
|
|
using GPUSourceDictionnary = Map<StringRef, struct GPUSource *>;
|
|
using GPUFunctionDictionnary = Map<StringRef, GPUFunction *>;
|
|
|
|
struct GPUSource {
|
|
StringRefNull fullpath;
|
|
StringRefNull filename;
|
|
StringRefNull source;
|
|
Vector<StringRef> dependencies_names;
|
|
Vector<GPUSource *> dependencies;
|
|
bool dependencies_init = false;
|
|
shader::BuiltinBits builtins = shader::BuiltinBits::NONE;
|
|
|
|
shader::BuiltinBits parse_builtin_bit(StringRef builtin)
|
|
{
|
|
using namespace blender::gpu::shader;
|
|
using namespace blender::gpu::shader::metadata;
|
|
switch (Builtin(std::stoull(builtin))) {
|
|
case Builtin::FragCoord:
|
|
return BuiltinBits::FRAG_COORD;
|
|
case Builtin::FrontFacing:
|
|
return BuiltinBits::FRONT_FACING;
|
|
case Builtin::GlobalInvocationID:
|
|
return BuiltinBits::GLOBAL_INVOCATION_ID;
|
|
case Builtin::InstanceID:
|
|
return BuiltinBits::INSTANCE_ID;
|
|
case Builtin::LocalInvocationID:
|
|
return BuiltinBits::LOCAL_INVOCATION_ID;
|
|
case Builtin::LocalInvocationIndex:
|
|
return BuiltinBits::LOCAL_INVOCATION_INDEX;
|
|
case Builtin::NumWorkGroup:
|
|
return BuiltinBits::NUM_WORK_GROUP;
|
|
case Builtin::PointCoord:
|
|
return BuiltinBits::POINT_COORD;
|
|
case Builtin::PointSize:
|
|
return BuiltinBits::POINT_SIZE;
|
|
case Builtin::PrimitiveID:
|
|
return BuiltinBits::PRIMITIVE_ID;
|
|
case Builtin::VertexID:
|
|
return BuiltinBits::VERTEX_ID;
|
|
case Builtin::WorkGroupID:
|
|
return BuiltinBits::WORK_GROUP_ID;
|
|
case Builtin::WorkGroupSize:
|
|
return BuiltinBits::WORK_GROUP_SIZE;
|
|
case Builtin::drw_debug:
|
|
#ifndef NDEBUG
|
|
return BuiltinBits::USE_DEBUG_DRAW;
|
|
#else
|
|
return BuiltinBits::NONE;
|
|
#endif
|
|
case Builtin::assert:
|
|
case Builtin::printf:
|
|
#if GPU_SHADER_PRINTF_ENABLE
|
|
return BuiltinBits::USE_PRINTF;
|
|
#else
|
|
return BuiltinBits::NONE;
|
|
#endif
|
|
}
|
|
BLI_assert_unreachable();
|
|
return BuiltinBits::NONE;
|
|
}
|
|
|
|
GPUFunctionQual parse_qualifier(StringRef qualifier)
|
|
{
|
|
using namespace blender::gpu::shader;
|
|
switch (metadata::Qualifier(std::stoull(qualifier))) {
|
|
case metadata::Qualifier::in:
|
|
return FUNCTION_QUAL_IN;
|
|
case metadata::Qualifier::out:
|
|
return FUNCTION_QUAL_OUT;
|
|
case metadata::Qualifier::inout:
|
|
return FUNCTION_QUAL_INOUT;
|
|
}
|
|
BLI_assert_unreachable();
|
|
return FUNCTION_QUAL_IN;
|
|
}
|
|
|
|
eGPUType parse_type(StringRef type)
|
|
{
|
|
using namespace blender::gpu::shader;
|
|
switch (metadata::Type(std::stoull(type))) {
|
|
case metadata::Type::vec1:
|
|
return GPU_FLOAT;
|
|
case metadata::Type::vec2:
|
|
return GPU_VEC2;
|
|
case metadata::Type::vec3:
|
|
return GPU_VEC3;
|
|
case metadata::Type::vec4:
|
|
return GPU_VEC4;
|
|
case metadata::Type::mat3:
|
|
return GPU_MAT3;
|
|
case metadata::Type::mat4:
|
|
return GPU_MAT4;
|
|
case metadata::Type::sampler1DArray:
|
|
return GPU_TEX1D_ARRAY;
|
|
case metadata::Type::sampler2DArray:
|
|
return GPU_TEX2D_ARRAY;
|
|
case metadata::Type::sampler2D:
|
|
return GPU_TEX2D;
|
|
case metadata::Type::sampler3D:
|
|
return GPU_TEX3D;
|
|
case metadata::Type::Closure:
|
|
return GPU_CLOSURE;
|
|
}
|
|
BLI_assert_unreachable();
|
|
return GPU_NONE;
|
|
}
|
|
|
|
StringRef split_on(StringRef &data, char token)
|
|
{
|
|
/* Assume lines are terminated by `\n`. */
|
|
int64_t pos = data.find(token);
|
|
if (pos == StringRef::not_found) {
|
|
StringRef line = data;
|
|
data = data.substr(0, 0);
|
|
return line;
|
|
}
|
|
StringRef line = data.substr(0, pos);
|
|
data = data.substr(pos + 1);
|
|
return line;
|
|
}
|
|
|
|
StringRef pop_line(StringRef &data)
|
|
{
|
|
/* Assume lines are terminated by `\n`. */
|
|
return split_on(data, '\n');
|
|
}
|
|
|
|
StringRef pop_token(StringRef &data)
|
|
{
|
|
/* Assumes tokens are split by spaces. */
|
|
return split_on(data, ' ');
|
|
}
|
|
|
|
GPUSource(const char *path,
|
|
const char *file,
|
|
const char *datatoc,
|
|
GPUFunctionDictionnary *g_functions,
|
|
GPUPrintFormatMap *g_formats)
|
|
: fullpath(path), filename(file), source(datatoc)
|
|
{
|
|
/* Extract metadata string. */
|
|
int64_t sta = source.rfind("//__blender_metadata_sta");
|
|
int64_t end = source.rfind("//__blender_metadata_end");
|
|
StringRef metadata = source.substr(sta, end - sta);
|
|
pop_line(metadata);
|
|
|
|
/* Non-library files contains functions with unsupported argument types.
|
|
* Also Non-library files are not supposed to be referenced for GPU node-tree. */
|
|
const bool do_parse_function = is_from_material_library();
|
|
|
|
StringRef line;
|
|
while ((line = pop_line(metadata)).is_empty() == false) {
|
|
using namespace blender::gpu::shader;
|
|
/* Skip comment start. */
|
|
pop_token(line);
|
|
|
|
StringRef identifier = pop_token(line);
|
|
switch (uint64_t(std::stoull(identifier))) {
|
|
case Preprocessor::hash("function"):
|
|
if (do_parse_function) {
|
|
parse_function(line, g_functions);
|
|
}
|
|
break;
|
|
case Preprocessor::hash("string"):
|
|
parse_string(line, g_formats);
|
|
break;
|
|
case Preprocessor::hash("builtin"):
|
|
parse_builtin(line);
|
|
break;
|
|
case Preprocessor::hash("dependency"):
|
|
parse_dependency(line);
|
|
break;
|
|
default:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
void parse_builtin(StringRef line)
|
|
{
|
|
builtins |= parse_builtin_bit(pop_token(line));
|
|
}
|
|
|
|
void parse_dependency(StringRef line)
|
|
{
|
|
dependencies_names.append(line);
|
|
}
|
|
|
|
void parse_string(StringRef line, GPUPrintFormatMap *format_map)
|
|
{
|
|
/* TODO(fclem): Move this to gpu log. */
|
|
auto add_format = [&](uint32_t format_hash, std::string format) {
|
|
if (format_map->contains(format_hash)) {
|
|
if (format_map->lookup(format_hash).format_str != format) {
|
|
print_error(format, 0, "printf format hash collision.");
|
|
}
|
|
else {
|
|
/* The format map already have the same format. */
|
|
}
|
|
}
|
|
else {
|
|
shader::PrintfFormat fmt;
|
|
/* Save for hash collision comparison. */
|
|
fmt.format_str = format;
|
|
|
|
/* Escape characters replacement. Do the most common ones. */
|
|
format = std::regex_replace(format, std::regex(R"(\\n)"), "\n");
|
|
format = std::regex_replace(format, std::regex(R"(\\v)"), "\v");
|
|
format = std::regex_replace(format, std::regex(R"(\\t)"), "\t");
|
|
format = std::regex_replace(format, std::regex(R"(\\')"), "\'");
|
|
format = std::regex_replace(format, std::regex(R"(\\")"), "\"");
|
|
format = std::regex_replace(format, std::regex(R"(\\\\)"), "\\");
|
|
|
|
shader::PrintfFormat::Block::ArgumentType type =
|
|
shader::PrintfFormat::Block::ArgumentType::NONE;
|
|
int64_t start = 0, end = 0;
|
|
while ((end = format.find_first_of('%', start + 1)) != -1) {
|
|
/* Add the previous block without the newly found % character. */
|
|
fmt.format_blocks.append({type, format.substr(start, end - start)});
|
|
/* Format type of the next block. */
|
|
/* TODO(fclem): This doesn't support advance formats like `%3.2f`. */
|
|
switch (format[end + 1]) {
|
|
case 'x':
|
|
case 'u':
|
|
type = shader::PrintfFormat::Block::ArgumentType::UINT;
|
|
break;
|
|
case 'd':
|
|
type = shader::PrintfFormat::Block::ArgumentType::INT;
|
|
break;
|
|
case 'f':
|
|
type = shader::PrintfFormat::Block::ArgumentType::FLOAT;
|
|
break;
|
|
default:
|
|
BLI_assert_msg(0, "Printing format unsupported");
|
|
break;
|
|
}
|
|
/* Start of the next block. */
|
|
start = end;
|
|
}
|
|
fmt.format_blocks.append({type, format.substr(start, format.size() - start)});
|
|
|
|
format_map->add(format_hash, fmt);
|
|
}
|
|
};
|
|
|
|
StringRef hash = pop_token(line);
|
|
StringRef string = line;
|
|
add_format(uint32_t(std::stoul(hash)), string);
|
|
}
|
|
|
|
void parse_function(StringRef line, GPUFunctionDictionnary *g_functions)
|
|
{
|
|
StringRef name = pop_token(line);
|
|
|
|
GPUFunction *func = MEM_new<GPUFunction>(__func__);
|
|
name.copy_utf8_truncated(func->name, sizeof(func->name));
|
|
func->source = reinterpret_cast<void *>(this);
|
|
func->totparam = 0;
|
|
while (true) {
|
|
StringRef arg_qual = pop_token(line);
|
|
StringRef arg_type = pop_token(line);
|
|
if (arg_qual.is_empty()) {
|
|
break;
|
|
}
|
|
if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
|
|
print_error(source, source.find(name), "Too many parameters in function");
|
|
break;
|
|
}
|
|
func->paramqual[func->totparam] = parse_qualifier(arg_qual);
|
|
func->paramtype[func->totparam] = parse_type(arg_type);
|
|
func->totparam++;
|
|
}
|
|
|
|
bool insert = g_functions->add(func->name, func);
|
|
/* NOTE: We allow overloading non void function, but only if the function comes from the
|
|
* same file. Otherwise the dependency system breaks. */
|
|
if (!insert) {
|
|
GPUSource *other_source = reinterpret_cast<GPUSource *>(g_functions->lookup(name)->source);
|
|
if (other_source != this) {
|
|
const char *msg = "Function redefinition or overload in two different files ...";
|
|
print_error(source, source.find(name), msg);
|
|
print_error(other_source->source,
|
|
other_source->source.find(name),
|
|
"... previous definition was here");
|
|
}
|
|
else {
|
|
/* Non-void function overload. */
|
|
MEM_delete(func);
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_error(const StringRef &input, int64_t offset, const StringRef message)
|
|
{
|
|
StringRef sub = input.substr(0, offset);
|
|
int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1;
|
|
int64_t line_end = input.find("\n", offset);
|
|
int64_t line_start = input.rfind("\n", offset) + 1;
|
|
int64_t char_number = offset - line_start + 1;
|
|
|
|
/* TODO Use clog. */
|
|
|
|
std::cerr << fullpath << ":" << line_number << ":" << char_number;
|
|
|
|
std::cerr << " error: " << message << "\n";
|
|
std::cerr << std::setw(5) << line_number << " | "
|
|
<< input.substr(line_start, line_end - line_start) << "\n";
|
|
std::cerr << " | ";
|
|
for (int64_t i = 0; i < char_number - 1; i++) {
|
|
std::cerr << " ";
|
|
}
|
|
std::cerr << "^\n";
|
|
}
|
|
|
|
/* Return 1 one error. */
|
|
int init_dependencies(const GPUSourceDictionnary &dict,
|
|
const GPUFunctionDictionnary &g_functions)
|
|
{
|
|
if (this->dependencies_init) {
|
|
return 0;
|
|
}
|
|
this->dependencies_init = true;
|
|
|
|
using namespace shader;
|
|
/* Auto dependency injection for debug capabilities. */
|
|
if ((builtins & BuiltinBits::USE_PRINTF) == BuiltinBits::USE_PRINTF) {
|
|
dependencies.append_non_duplicates(dict.lookup("gpu_shader_print_lib.glsl"));
|
|
}
|
|
if ((builtins & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) {
|
|
dependencies.append_non_duplicates(dict.lookup("common_debug_draw_lib.glsl"));
|
|
}
|
|
|
|
for (auto dependency_name : dependencies_names) {
|
|
GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
|
|
if (dependency_source == nullptr) {
|
|
std::string error = std::string("Dependency not found : ") + dependency_name;
|
|
print_error(source, 0, error.c_str());
|
|
return 1;
|
|
}
|
|
|
|
/* Recursive. */
|
|
int result = dependency_source->init_dependencies(dict, g_functions);
|
|
if (result != 0) {
|
|
return 1;
|
|
}
|
|
|
|
for (auto *dep : dependency_source->dependencies) {
|
|
dependencies.append_non_duplicates(dep);
|
|
}
|
|
dependencies.append_non_duplicates(dependency_source);
|
|
}
|
|
dependencies_names.clear();
|
|
return 0;
|
|
}
|
|
|
|
/* Returns the final string with all includes done. */
|
|
void build(Vector<StringRefNull> &result) const
|
|
{
|
|
for (auto *dep : dependencies) {
|
|
result.append(dep->source);
|
|
}
|
|
result.append(source);
|
|
}
|
|
|
|
shader::BuiltinBits builtins_get() const
|
|
{
|
|
shader::BuiltinBits out_builtins = builtins;
|
|
for (auto *dep : dependencies) {
|
|
out_builtins |= dep->builtins;
|
|
}
|
|
return out_builtins;
|
|
}
|
|
|
|
bool is_from_material_library() const
|
|
{
|
|
return (filename.startswith("gpu_shader_material_") ||
|
|
filename.startswith("gpu_shader_common_") ||
|
|
filename.startswith("gpu_shader_compositor_")) &&
|
|
filename.endswith(".glsl");
|
|
}
|
|
};
|
|
|
|
} // namespace blender::gpu
|
|
|
|
using namespace blender::gpu;
|
|
|
|
static GPUPrintFormatMap *g_formats = nullptr;
|
|
static GPUSourceDictionnary *g_sources = nullptr;
|
|
static GPUFunctionDictionnary *g_functions = nullptr;
|
|
static bool force_printf_injection = false;
|
|
|
|
void gpu_shader_dependency_init()
|
|
{
|
|
g_formats = new GPUPrintFormatMap();
|
|
g_sources = new GPUSourceDictionnary();
|
|
g_functions = new GPUFunctionDictionnary();
|
|
|
|
#define SHADER_SOURCE(datatoc, filename, filepath) \
|
|
g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc, g_functions, g_formats));
|
|
#include "glsl_compositor_source_list.h"
|
|
#include "glsl_draw_source_list.h"
|
|
#include "glsl_gpu_source_list.h"
|
|
#ifdef WITH_OCIO
|
|
# include "glsl_ocio_source_list.h"
|
|
#endif
|
|
#undef SHADER_SOURCE
|
|
|
|
int errors = 0;
|
|
for (auto *value : g_sources->values()) {
|
|
errors += value->init_dependencies(*g_sources, *g_functions);
|
|
}
|
|
BLI_assert_msg(errors == 0, "Dependency errors detected: Aborting");
|
|
UNUSED_VARS_NDEBUG(errors);
|
|
|
|
#if GPU_SHADER_PRINTF_ENABLE
|
|
if (!g_formats->is_empty()) {
|
|
/* Detect if there is any printf in node lib files.
|
|
* See gpu_shader_dependency_force_gpu_print_injection(). */
|
|
for (auto *value : g_sources->values()) {
|
|
if (bool(value->builtins & shader::BuiltinBits::USE_PRINTF)) {
|
|
if (value->filename.startswith("gpu_shader_material_")) {
|
|
force_printf_injection = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void gpu_shader_dependency_exit()
|
|
{
|
|
for (auto *value : g_sources->values()) {
|
|
delete value;
|
|
}
|
|
for (auto *value : g_functions->values()) {
|
|
MEM_delete(value);
|
|
}
|
|
delete g_formats;
|
|
delete g_sources;
|
|
delete g_functions;
|
|
g_formats = nullptr;
|
|
g_sources = nullptr;
|
|
g_functions = nullptr;
|
|
}
|
|
|
|
GPUFunction *gpu_material_library_use_function(GSet *used_libraries, const char *name)
|
|
{
|
|
GPUFunction *function = g_functions->lookup_default(name, nullptr);
|
|
BLI_assert_msg(function != nullptr, "Requested function not in the function library");
|
|
GPUSource *source = reinterpret_cast<GPUSource *>(function->source);
|
|
BLI_gset_add(used_libraries, const_cast<char *>(source->filename.c_str()));
|
|
return function;
|
|
}
|
|
|
|
namespace blender::gpu::shader {
|
|
|
|
bool gpu_shader_dependency_force_gpu_print_injection()
|
|
{
|
|
/* WORKAROUND: We cannot know what shader will require printing if the printf is inside shader
|
|
* node code. In this case, we just force injection inside all shaders. */
|
|
return force_printf_injection;
|
|
}
|
|
|
|
bool gpu_shader_dependency_has_printf()
|
|
{
|
|
return (g_formats != nullptr) && !g_formats->is_empty();
|
|
}
|
|
|
|
const PrintfFormat &gpu_shader_dependency_get_printf_format(uint32_t format_hash)
|
|
{
|
|
return g_formats->lookup(format_hash);
|
|
}
|
|
|
|
BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull shader_source_name)
|
|
{
|
|
if (shader_source_name.is_empty()) {
|
|
return shader::BuiltinBits::NONE;
|
|
}
|
|
if (g_sources->contains(shader_source_name) == false) {
|
|
std::cerr << "Error: Could not find \"" << shader_source_name
|
|
<< "\" in the list of registered source.\n";
|
|
BLI_assert(0);
|
|
return shader::BuiltinBits::NONE;
|
|
}
|
|
GPUSource *source = g_sources->lookup(shader_source_name);
|
|
return source->builtins_get();
|
|
}
|
|
|
|
Vector<StringRefNull> gpu_shader_dependency_get_resolved_source(
|
|
const StringRefNull shader_source_name)
|
|
{
|
|
Vector<StringRefNull> result;
|
|
GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
|
|
if (src == nullptr) {
|
|
std::cerr << "Error source not found : " << shader_source_name << std::endl;
|
|
}
|
|
src->build(result);
|
|
return result;
|
|
}
|
|
|
|
StringRefNull gpu_shader_dependency_get_source(const StringRefNull shader_source_name)
|
|
{
|
|
GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
|
|
if (src == nullptr) {
|
|
std::cerr << "Error source not found : " << shader_source_name << std::endl;
|
|
}
|
|
return src->source;
|
|
}
|
|
|
|
StringRefNull gpu_shader_dependency_get_filename_from_source_string(const StringRef source_string)
|
|
{
|
|
for (auto &source : g_sources->values()) {
|
|
if (source->source == source_string) {
|
|
return source->filename;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
} // namespace blender::gpu::shader
|