Blender V5.0
gpu_shader_dependency.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11
12#include <algorithm>
13#include <fmt/format.h>
14#include <iomanip>
15#include <iostream>
16#include <regex>
17#include <string>
18
19#include "BLI_map.hh"
20#include "BLI_string_ref.hh"
21
22#include "CLG_log.h"
23
28
29#ifdef WITH_OPENSUBDIV
32#endif
33
35
36extern "C" {
37#define SHADER_SOURCE(filename_underscore, filename, filepath) \
38 extern char datatoc_##filename_underscore[];
39#include "glsl_compositor_source_list.h"
40#include "glsl_draw_source_list.h"
41#include "glsl_gpu_source_list.h"
42#include "glsl_ocio_source_list.h"
43#ifdef WITH_OPENSUBDIV
44# include "glsl_osd_source_list.h"
45#endif
46#undef SHADER_SOURCE
47}
48
49static CLG_LogRef LOG = {"shader.dependencies"};
50
51namespace blender::gpu {
52
56
57struct GPUSource {
61 std::string patched_source;
64 bool dependencies_init = false;
66 /* True if this file content is supposed to be generated at runtime. */
67 bool generated = false;
68
69 /* NOTE: The next few functions are needed to keep isolation of the preprocessor.
70 * Eventually, this should be revisited and the preprocessor should output
71 * GPU structures. */
72
74 {
75 using namespace blender::gpu::shader;
76 using namespace blender::gpu::shader::metadata;
77 switch (builtin) {
107#ifndef NDEBUG
109#else
110 return BuiltinBits::NONE;
111#endif
112 case Builtin::assert:
113 case Builtin::printf:
114#if GPU_SHADER_PRINTF_ENABLE
116#else
117 return BuiltinBits::NONE;
118#endif
121 }
123 return BuiltinBits::NONE;
124 }
125
127 {
128 using namespace blender::gpu::shader;
129 switch (qualifier) {
131 return FUNCTION_QUAL_IN;
133 return FUNCTION_QUAL_OUT;
135 return FUNCTION_QUAL_INOUT;
136 }
138 return FUNCTION_QUAL_IN;
139 }
140
142 {
143 using namespace blender::gpu::shader;
144 switch (type) {
146 return GPU_FLOAT;
148 return GPU_VEC2;
150 return GPU_VEC3;
152 return GPU_VEC4;
154 return GPU_MAT3;
156 return GPU_MAT4;
158 return GPU_TEX1D_ARRAY;
160 return GPU_TEX2D_ARRAY;
162 return GPU_TEX2D;
164 return GPU_TEX3D;
166 return GPU_CLOSURE;
167 }
169 return GPU_NONE;
170 }
171
173 const char *path,
174 const char *file,
175 const char *datatoc,
178 std::function<void(GPUSource &, GPUFunctionDictionary *, GPUPrintFormatMap *)> metadata_fn)
179 : fullpath(path), filename(file), source(datatoc)
180 {
181 metadata_fn(*this, g_functions, g_formats);
182 };
183
185 {
186 builtins |= convert_builtin_bit(builtin);
187 }
188
190 {
191 dependencies_names.append(line);
192 }
193
194 void add_printf_format(uint32_t format_hash, std::string format, GPUPrintFormatMap *format_map)
195 {
196 /* TODO(fclem): Move this to gpu log. */
197 if (format_map->contains(format_hash)) {
198 if (format_map->lookup(format_hash).format_str != format) {
199 print_error(format, 0, "printf format hash collision.");
200 }
201 else {
202 /* The format map already have the same format. */
203 }
204 }
205 else {
207 /* Save for hash collision comparison. */
208 fmt.format_str = format;
209
210 /* Escape characters replacement. Do the most common ones. */
211 format = std::regex_replace(format, std::regex(R"(\\n)"), "\n");
212 format = std::regex_replace(format, std::regex(R"(\\v)"), "\v");
213 format = std::regex_replace(format, std::regex(R"(\\t)"), "\t");
214 format = std::regex_replace(format, std::regex(R"(\\')"), "\'");
215 format = std::regex_replace(format, std::regex(R"(\\")"), "\"");
216 format = std::regex_replace(format, std::regex(R"(\\\\)"), "\\");
217
220 int64_t start = 0, end = 0;
221 while ((end = format.find_first_of('%', start + 1)) != -1) {
222 /* Add the previous block without the newly found % character. */
223 fmt.format_blocks.append({type, format.substr(start, end - start)});
224 /* Format type of the next block. */
225 /* TODO(fclem): This doesn't support advance formats like `%3.2f`. */
226 switch (format[end + 1]) {
227 case 'x':
228 case 'u':
230 break;
231 case 'd':
233 break;
234 case 'f':
236 break;
237 default:
238 BLI_assert_msg(0, "Printing format unsupported");
239 break;
240 }
241 /* Start of the next block. */
242 start = end;
243 }
244 fmt.format_blocks.append({type, format.substr(start, format.size() - start)});
245
246 format_map->add(format_hash, fmt);
247 }
248 }
249
253 {
254 GPUFunction *func = MEM_new<GPUFunction>(__func__);
255 name.copy_utf8_truncated(func->name, sizeof(func->name));
256 func->source = reinterpret_cast<void *>(this);
257 func->totparam = 0;
258 for (auto arg : arguments) {
259 if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
260 print_error(source, source.find(name), "Too many parameters in function");
261 break;
262 }
263 func->paramqual[func->totparam] = convert_qualifier(arg.qualifier);
264 func->paramtype[func->totparam] = convert_type(arg.type);
265 func->totparam++;
266 }
267
268 bool insert = g_functions->add(func->name, func);
269 /* NOTE: We allow overloading non void function, but only if the function comes from the
270 * same file. Otherwise the dependency system breaks. */
271 if (!insert) {
272 GPUSource *other_source = reinterpret_cast<GPUSource *>(g_functions->lookup(name)->source);
273 if (other_source != this) {
274 const char *msg = "Function redefinition or overload in two different files ...";
275 print_error(source, source.find(name), msg);
276 print_error(other_source->source,
277 other_source->source.find(name),
278 "... previous definition was here");
279 }
280 else {
281 /* Non-void function overload. */
282 MEM_delete(func);
283 }
284 }
285 }
286
287 void print_error(const StringRef &input, int64_t offset, const StringRef message)
288 {
289 StringRef sub = input.substr(0, offset);
290 int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1;
291 int64_t line_end = input.find("\n", offset);
292 int64_t line_start = input.rfind("\n", offset) + 1;
293 int64_t char_number = offset - line_start + 1;
294
295 /* TODO Use clog. */
296
297 std::cerr << fullpath << ":" << line_number << ":" << char_number;
298
299 std::cerr << " error: " << message << "\n";
300 std::cerr << std::setw(5) << line_number << " | "
301 << input.substr(line_start, line_end - line_start) << "\n";
302 std::cerr << " | ";
303 for (int64_t i = 0; i < char_number - 1; i++) {
304 std::cerr << " ";
305 }
306 std::cerr << "^\n";
307 }
308
309 /* Return 1 one error. */
311 {
312 if (this->dependencies_init) {
313 return 0;
314 }
315 this->dependencies_init = true;
316
317 using namespace shader;
318 /* Auto dependency injection for debug capabilities. */
320 dependencies.append_non_duplicates(dict.lookup("gpu_shader_print_lib.glsl"));
321 }
323 dependencies.append_non_duplicates(dict.lookup("draw_debug_draw_lib.glsl"));
324 }
325
326 for (auto dependency_name : dependencies_names) {
327 GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
328 if (dependency_source == nullptr) {
329 std::string error = std::string("Dependency not found : ") + dependency_name;
330 print_error(source, 0, error.c_str());
331 return 1;
332 }
333
334 /* Recursive. */
335 int result = dependency_source->init_dependencies(dict);
336 if (result != 0) {
337 return 1;
338 }
339 dependencies.append_non_duplicates(dependency_source);
340 }
341 dependencies_names.clear();
342 return 0;
343 }
344
346 const shader::GeneratedSourceList &generated_sources,
347 const GPUSourceDictionary &dict,
348 const GPUSource &from) const
349 {
350#define CLOG_FILE_INCLUDE(_from, _include) \
351 if (CLOG_CHECK(&LOG, CLG_LEVEL_INFO) && (from).filename.c_str() != (_include).filename.c_str()) \
352 { \
353 const char *from_filename = (_from).filename.c_str(); \
354 const char *include_filename = (_include).filename.c_str(); \
355 const int from_size = int((_from).source.size()); \
356 const int include_size = int((_include).source.size()); \
357 std::string link = fmt::format( \
358 "{}_{} --> {}_{}\n", from_filename, from_size, include_filename, include_size); \
359 std::string style = fmt::format("style {}_{} fill:#{:x}{:x}0\n", \
360 include_filename, \
361 include_size, \
362 min_uu(15, include_size / 1000), \
363 15 - min_uu(15, include_size / 1000)); \
364 CLG_log_raw(LOG.type, link.c_str()); \
365 CLG_log_raw(LOG.type, style.c_str()); \
366 }
367
368 /* Check if this file was already included. */
369 for (const StringRefNull &source_content : result) {
370 /* Yes, compare pointer instead of string for speed.
371 * Each source is guaranteed to be unique and non-moving during the building process. */
372 if (source_content.c_str() == this->source.c_str()) {
373 /* Already included. */
374 CLOG_FILE_INCLUDE(from, *this);
375 return;
376 }
377 }
378
379 if (!bool(this->builtins & shader::BuiltinBits::RUNTIME_GENERATED)) {
380 for (const auto &dependency : this->dependencies) {
381 /* WATCH: Recursive. */
382 dependency->source_get(result, generated_sources, dict, *this);
383 }
384 CLOG_FILE_INCLUDE(from, *this);
385 result.append(this->source);
386 return;
387 }
388
389 /* Linear lookup since we won't have more than a few per shaders.
390 * Also avoid the complexity of a Map in info creation. */
391 for (const shader::GeneratedSource &generated_src : generated_sources) {
392 if (generated_src.filename == this->filename) {
393 /* Include dependencies before the generated file. */
394 for (const auto &dependency_name : generated_src.dependencies) {
395 BLI_assert_msg(dependency_name != this->filename, "Recursive include");
396
397 GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
398 if (dependency_source == nullptr) {
399 /* Will certainly fail compilation. But avoid crashing the application. */
400 std::cerr << "Generated dependency not found : " + dependency_name << std::endl;
401 return;
402 }
403 /* WATCH: Recursive. */
404 dependency_source->source_get(result, generated_sources, dict, *this);
405 }
406 CLOG_FILE_INCLUDE(from, *this);
407 result.append(generated_src.content);
408 return;
409 }
410 }
411
412 std::cerr << "warn: Generated source not provided. Using fallback for : " << this->filename
413 << std::endl;
414 /* Dependencies for generated sources are not folded on startup.
415 * This allows for different set of dependencies at runtime. */
416 for (const auto &dependency : this->dependencies) {
417 /* WATCH: Recursive. */
418 dependency->source_get(result, generated_sources, dict, *this);
419 }
420 CLOG_FILE_INCLUDE(from, *this);
421 result.append(this->source);
422 }
423
424 /* Returns the final string with all includes done. */
426 const shader::GeneratedSourceList &generated_sources,
427 const GPUSourceDictionary &dict) const
428 {
429 source_get(result, generated_sources, dict, *this);
430 }
431
433 {
434 shader::BuiltinBits out_builtins = builtins;
435 for (auto *dep : dependencies) {
436 out_builtins |= dep->builtins_get();
437 }
438 return out_builtins;
439 }
440
442 {
443 return (filename.startswith("gpu_shader_material_") ||
444 filename.startswith("gpu_shader_common_") ||
445 filename.startswith("gpu_shader_compositor_")) &&
446 filename.endswith(".glsl");
447 }
448};
449
450namespace shader {
451
452#include "glsl_compositor_metadata_list.hh"
453#include "glsl_draw_metadata_list.hh"
454#include "glsl_gpu_metadata_list.hh"
455#include "glsl_ocio_metadata_list.hh"
456#ifdef WITH_OPENSUBDIV
457# include "glsl_osd_metadata_list.hh"
458#endif
459
460} // namespace shader
461
462} // namespace blender::gpu
463
464using namespace blender::gpu;
465
466static GPUPrintFormatMap *g_formats = nullptr;
469static bool force_printf_injection = false;
470
472{
476
477#define SHADER_SOURCE(filename_underscore, filename, filepath) \
478 g_sources->add_new(filename, \
479 new GPUSource(filepath, \
480 filename, \
481 datatoc_##filename_underscore, \
482 g_functions, \
483 g_formats, \
484 blender::gpu::shader::metadata_##filename_underscore));
485
486#include "glsl_compositor_source_list.h"
487#include "glsl_draw_source_list.h"
488#include "glsl_gpu_source_list.h"
489#include "glsl_ocio_source_list.h"
490#ifdef WITH_OPENSUBDIV
491# include "glsl_osd_source_list.h"
492#endif
493#undef SHADER_SOURCE
494#ifdef WITH_OPENSUBDIV
496 g_sources->add_new(
497 "osd_patch_basis.glsl",
498 new GPUSource("osd_patch_basis.glsl",
499 "osd_patch_basis.glsl",
500 patch_basis_source.c_str(),
502 g_formats,
504#endif
505
506 int errors = 0;
507 for (auto *value : g_sources->values()) {
508 errors += value->init_dependencies(*g_sources);
509 }
510 BLI_assert_msg(errors == 0, "Dependency errors detected: Aborting");
511 UNUSED_VARS_NDEBUG(errors);
512
513#if GPU_SHADER_PRINTF_ENABLE
514 if (!g_formats->is_empty()) {
515 /* Detect if there is any printf in node lib files.
516 * See gpu_shader_dependency_force_gpu_print_injection(). */
517 for (auto *value : g_sources->values()) {
518 if (bool(value->builtins & shader::BuiltinBits::USE_PRINTF)) {
519 if (value->filename.startswith("gpu_shader_material_")) {
521 break;
522 }
523 }
524 }
525 }
526#endif
527}
528
530{
531 for (auto *value : g_sources->values()) {
532 delete value;
533 }
534 for (auto *value : g_functions->values()) {
535 MEM_delete(value);
536 }
537 delete g_formats;
538 delete g_sources;
539 delete g_functions;
540 g_formats = nullptr;
541 g_sources = nullptr;
542 g_functions = nullptr;
543}
544
546{
547 GPUFunction *function = g_functions->lookup_default(name, nullptr);
548 BLI_assert_msg(function != nullptr, "Requested function not in the function library");
549 return function;
550}
551
553 const char *name)
554{
555 GPUFunction *function = g_functions->lookup_default(name, nullptr);
556 BLI_assert_msg(function != nullptr, "Requested function not in the function library");
557 GPUSource *source = reinterpret_cast<GPUSource *>(function->source);
558 used_libraries.add(source->filename.c_str());
559}
560
561namespace blender::gpu::shader {
562
564{
565 /* WORKAROUND: We cannot know what shader will require printing if the printf is inside shader
566 * node code. In this case, we just force injection inside all shaders. */
568}
569
571{
572 return (g_formats != nullptr) && !g_formats->is_empty();
573}
574
576{
577 return g_formats->lookup(format_hash);
578}
579
581{
582 if (shader_source_name.is_empty()) {
584 }
585 if (g_sources->contains(shader_source_name) == false) {
586 std::cerr << "Error: Could not find \"" << shader_source_name
587 << "\" in the list of registered source.\n";
588 BLI_assert(0);
590 }
591 GPUSource *source = g_sources->lookup(shader_source_name);
592 return source->builtins_get();
593}
594
596 const StringRefNull shader_source_name,
597 const shader::GeneratedSourceList &generated_sources,
598 const StringRefNull shader_name)
599{
601 GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
602 if (src == nullptr) {
603 std::cerr << "Error source not found : " << shader_source_name << std::endl;
604 }
605 CLOG_INFO(&LOG, "Resolved Source Tree (Mermaid flowchart) %s", shader_name.c_str());
607 CLG_log_raw(LOG.type, "flowchart LR\n");
608 }
609 src->build(result, generated_sources, *g_sources);
611 CLG_log_raw(LOG.type, "\n");
612 }
613 return result;
614}
615
617{
618 GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
619 if (src == nullptr) {
620 std::cerr << "Error source not found : " << shader_source_name << std::endl;
621 }
622 return src->source;
623}
624
626{
627 for (auto &source : g_sources->values()) {
628 if (source->source == source_string) {
629 return source->filename;
630 }
631 }
632 return "";
633}
634
635} // namespace blender::gpu::shader
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
void BLI_kdtree_nd_ insert(KDTree *tree, int index, const float co[KD_DIMS]) ATTR_NONNULL(1
#define ARRAY_SIZE(arr)
#define UNUSED_VARS_NDEBUG(...)
void CLG_log_raw(const CLG_LogType *lg, const char *message)
Definition clog.cc:700
#define CLOG_CHECK(clg_ref, verbose_level,...)
Definition CLG_log.h:147
@ CLG_LEVEL_INFO
Definition CLG_log.h:60
#define CLOG_INFO(clg_ref,...)
Definition CLG_log.h:190
GPUType
@ GPU_VEC2
@ GPU_MAT4
@ GPU_TEX1D_ARRAY
@ GPU_TEX2D_ARRAY
@ GPU_TEX2D
@ GPU_VEC4
@ GPU_NONE
@ GPU_CLOSURE
@ GPU_VEC3
@ GPU_MAT3
@ GPU_TEX3D
@ GPU_FLOAT
long long int int64_t
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
bool contains(const Key &key) const
Definition BLI_map.hh:353
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr int64_t find(char c, int64_t pos=0) const
constexpr const char * begin() const
constexpr const char * end() const
constexpr bool is_empty() const
constexpr const char * c_str() const
const char * openSubdiv_getGLSLPatchBasisSource()
@ FUNCTION_QUAL_IN
@ FUNCTION_QUAL_OUT
@ FUNCTION_QUAL_INOUT
#define input
static bool force_printf_injection
void gpu_shader_dependency_init()
#define CLOG_FILE_INCLUDE(_from, _include)
void gpu_material_library_use_function(blender::Set< blender::StringRefNull > &used_libraries, const char *name)
void gpu_shader_dependency_exit()
static GPUSourceDictionary * g_sources
static GPUPrintFormatMap * g_formats
GPUFunction * gpu_material_library_get_function(const char *name)
static GPUFunctionDictionary * g_functions
format
#define LOG(level)
Definition log.h:97
static void error(const char *str)
Vector< shader::GeneratedSource, 0 > GeneratedSourceList
Vector< StringRefNull > gpu_shader_dependency_get_resolved_source(const StringRefNull shader_source_name, const shader::GeneratedSourceList &generated_sources, const StringRefNull shader_name)
const PrintfFormat & gpu_shader_dependency_get_printf_format(uint32_t format_hash)
BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull shader_source_name)
bool gpu_shader_dependency_force_gpu_print_injection()
StringRefNull gpu_shader_dependency_get_filename_from_source_string(const StringRef source_string)
Find the name of the file from which the given string was generated.
StringRefNull gpu_shader_dependency_get_source(const StringRefNull shader_source_name)
Map< StringRef, GPUSource * > GPUSourceDictionary
static CLG_LogRef LOG
Map< uint32_t, shader::PrintfFormat > GPUPrintFormatMap
Map< StringRef, GPUFunction * > GPUFunctionDictionary
const char * name
char name[MAX_FUNCTION_NAME]
GPUType paramtype[MAX_PARAMETER]
GPUFunctionQual paramqual[MAX_PARAMETER]
Vector< GPUSource * > dependencies
void add_function(StringRefNull name, Span< shader::metadata::ArgumentFormat > arguments, GPUFunctionDictionary *g_functions)
GPUType convert_type(shader::metadata::Type type)
void build(Vector< StringRefNull > &result, const shader::GeneratedSourceList &generated_sources, const GPUSourceDictionary &dict) const
shader::BuiltinBits builtins_get() const
void source_get(Vector< StringRefNull > &result, const shader::GeneratedSourceList &generated_sources, const GPUSourceDictionary &dict, const GPUSource &from) const
shader::BuiltinBits convert_builtin_bit(shader::metadata::Builtin builtin)
void add_dependency(StringRef line)
Vector< StringRef > dependencies_names
GPUFunctionQual convert_qualifier(shader::metadata::Qualifier qualifier)
int init_dependencies(const GPUSourceDictionary &dict)
void add_builtin(shader::metadata::Builtin builtin)
void add_printf_format(uint32_t format_hash, std::string format, GPUPrintFormatMap *format_map)
void print_error(const StringRef &input, int64_t offset, const StringRef message)
GPUSource(const char *path, const char *file, const char *datatoc, GPUFunctionDictionary *g_functions, GPUPrintFormatMap *g_formats, std::function< void(GPUSource &, GPUFunctionDictionary *, GPUPrintFormatMap *)> metadata_fn)
i
Definition text_draw.cc:230