/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2005 Blender Foundation. * All rights reserved. */ /** \file blender/gpu/intern/gpu_codegen.c * \ingroup gpu * * Convert material node-trees to GLSL. */ #include "MEM_guardedalloc.h" #include "DNA_customdata_types.h" #include "DNA_image_types.h" #include "DNA_material_types.h" #include "DNA_node_types.h" #include "BLI_blenlib.h" #include "BLI_hash_mm2a.h" #include "BLI_link_utils.h" #include "BLI_utildefines.h" #include "BLI_dynstr.h" #include "BLI_ghash.h" #include "BLI_threads.h" #include "PIL_time.h" #include "GPU_extensions.h" #include "GPU_glew.h" #include "GPU_material.h" #include "GPU_shader.h" #include "GPU_texture.h" #include "GPU_uniformbuffer.h" #include "BLI_sys_types.h" /* for intptr_t support */ #include "gpu_codegen.h" #include #include extern char datatoc_gpu_shader_material_glsl[]; extern char datatoc_gpu_shader_geometry_glsl[]; static char *glsl_material_library = NULL; /* -------------------- GPUPass Cache ------------------ */ /** * Internal shader cache: This prevent the shader recompilation / stall when * using undo/redo AND also allows for GPUPass reuse if the Shader code is the * same for 2 different Materials. Unused GPUPasses are free by Garbage collection. **/ /* Only use one linklist that contains the GPUPasses grouped by hash. */ static GPUPass *pass_cache = NULL; static SpinLock pass_cache_spin; static uint32_t gpu_pass_hash(const char *frag_gen, const char *defs, GPUVertAttrLayers *attrs) { BLI_HashMurmur2A hm2a; BLI_hash_mm2a_init(&hm2a, 0); BLI_hash_mm2a_add(&hm2a, (uchar *)frag_gen, strlen(frag_gen)); if (attrs) { for (int att_idx = 0; att_idx < attrs->totlayer; att_idx++) { char *name = attrs->layer[att_idx].name; BLI_hash_mm2a_add(&hm2a, (uchar *)name, strlen(name)); } } if (defs) BLI_hash_mm2a_add(&hm2a, (uchar *)defs, strlen(defs)); return BLI_hash_mm2a_end(&hm2a); } /* Search by hash only. Return first pass with the same hash. * There is hash collision if (pass->next && pass->next->hash == hash) */ static GPUPass *gpu_pass_cache_lookup(uint32_t hash) { BLI_spin_lock(&pass_cache_spin); /* Could be optimized with a Lookup table. */ for (GPUPass *pass = pass_cache; pass; pass = pass->next) { if (pass->hash == hash) { BLI_spin_unlock(&pass_cache_spin); return pass; } } BLI_spin_unlock(&pass_cache_spin); return NULL; } /* Check all possible passes with the same hash. */ static GPUPass *gpu_pass_cache_resolve_collision( GPUPass *pass, const char *vert, const char *geom, const char *frag, const char *defs, uint32_t hash) { BLI_spin_lock(&pass_cache_spin); /* Collision, need to strcmp the whole shader. */ for (; pass && (pass->hash == hash); pass = pass->next) { if ((defs != NULL) && (strcmp(pass->defines, defs) != 0)) { /* Pass */ } else if ((geom != NULL) && (strcmp(pass->geometrycode, geom) != 0)) { /* Pass */ } else if ((strcmp(pass->fragmentcode, frag) == 0) && (strcmp(pass->vertexcode, vert) == 0)) { BLI_spin_unlock(&pass_cache_spin); return pass; } } BLI_spin_unlock(&pass_cache_spin); return NULL; } /* -------------------- GPU Codegen ------------------ */ /* type definitions and constants */ #define MAX_FUNCTION_NAME 64 #define MAX_PARAMETER 32 typedef enum { FUNCTION_QUAL_IN, FUNCTION_QUAL_OUT, FUNCTION_QUAL_INOUT } GPUFunctionQual; typedef struct GPUFunction { char name[MAX_FUNCTION_NAME]; eGPUType paramtype[MAX_PARAMETER]; GPUFunctionQual paramqual[MAX_PARAMETER]; int totparam; } GPUFunction; /* Indices match the eGPUType enum */ static const char *GPU_DATATYPE_STR[17] = { "", "float", "vec2", "vec3", "vec4", NULL, NULL, NULL, NULL, "mat3", NULL, NULL, NULL, NULL, NULL, NULL, "mat4", }; /* GLSL code parsing for finding function definitions. * These are stored in a hash for lookup when creating a material. */ static GHash *FUNCTION_HASH = NULL; #if 0 static char *FUNCTION_PROTOTYPES = NULL; static GPUShader *FUNCTION_LIB = NULL; #endif static int gpu_str_prefix(const char *str, const char *prefix) { while (*str && *prefix) { if (*str != *prefix) return 0; str++; prefix++; } return (*prefix == '\0'); } static char *gpu_str_skip_token(char *str, char *token, int max) { int len = 0; /* skip a variable/function name */ while (*str) { if (ELEM(*str, ' ', '(', ')', ',', ';', '\t', '\n', '\r')) break; else { if (token && len < max - 1) { *token = *str; token++; len++; } str++; } } if (token) *token = '\0'; /* skip the next special characters: * note the missing ')' */ while (*str) { if (ELEM(*str, ' ', '(', ',', ';', '\t', '\n', '\r')) str++; else break; } return str; } static void gpu_parse_functions_string(GHash *hash, char *code) { GPUFunction *function; eGPUType type; GPUFunctionQual qual; int i; while ((code = strstr(code, "void "))) { function = MEM_callocN(sizeof(GPUFunction), "GPUFunction"); code = gpu_str_skip_token(code, NULL, 0); code = gpu_str_skip_token(code, function->name, MAX_FUNCTION_NAME); /* get parameters */ while (*code && *code != ')') { /* test if it's an input or output */ qual = FUNCTION_QUAL_IN; if (gpu_str_prefix(code, "out ")) qual = FUNCTION_QUAL_OUT; if (gpu_str_prefix(code, "inout ")) qual = FUNCTION_QUAL_INOUT; if ((qual != FUNCTION_QUAL_IN) || gpu_str_prefix(code, "in ")) code = gpu_str_skip_token(code, NULL, 0); /* test for type */ type = GPU_NONE; for (i = 1; i < ARRAY_SIZE(GPU_DATATYPE_STR); i++) { if (GPU_DATATYPE_STR[i] && gpu_str_prefix(code, GPU_DATATYPE_STR[i])) { type = i; break; } } if (!type && gpu_str_prefix(code, "samplerCube")) { type = GPU_TEXCUBE; } if (!type && gpu_str_prefix(code, "sampler2DShadow")) { type = GPU_SHADOW2D; } if (!type && gpu_str_prefix(code, "sampler1DArray")) { type = GPU_TEX1D_ARRAY; } if (!type && gpu_str_prefix(code, "sampler2D")) { type = GPU_TEX2D; } if (!type && gpu_str_prefix(code, "sampler3D")) { type = GPU_TEX3D; } if (!type && gpu_str_prefix(code, "Closure")) { type = GPU_CLOSURE; } if (type) { /* add parameter */ code = gpu_str_skip_token(code, NULL, 0); code = gpu_str_skip_token(code, NULL, 0); function->paramqual[function->totparam] = qual; function->paramtype[function->totparam] = type; function->totparam++; } else { fprintf(stderr, "GPU invalid function parameter in %s.\n", function->name); break; } } if (function->name[0] == '\0' || function->totparam == 0) { fprintf(stderr, "GPU functions parse error.\n"); MEM_freeN(function); break; } BLI_ghash_insert(hash, function->name, function); } } #if 0 static char *gpu_generate_function_prototyps(GHash *hash) { DynStr *ds = BLI_dynstr_new(); GHashIterator *ghi; GPUFunction *function; char *name, *prototypes; int a; /* automatically generate function prototypes to add to the top of the * generated code, to avoid have to add the actual code & recompile all */ ghi = BLI_ghashIterator_new(hash); for (; !BLI_ghashIterator_done(ghi); BLI_ghashIterator_step(ghi)) { name = BLI_ghashIterator_getValue(ghi); function = BLI_ghashIterator_getValue(ghi); BLI_dynstr_appendf(ds, "void %s(", name); for (a = 0; a < function->totparam; a++) { if (function->paramqual[a] == FUNCTION_QUAL_OUT) BLI_dynstr_append(ds, "out "); else if (function->paramqual[a] == FUNCTION_QUAL_INOUT) BLI_dynstr_append(ds, "inout "); if (function->paramtype[a] == GPU_TEX2D) BLI_dynstr_append(ds, "sampler2D"); else if (function->paramtype[a] == GPU_SHADOW2D) BLI_dynstr_append(ds, "sampler2DShadow"); else BLI_dynstr_append(ds, GPU_DATATYPE_STR[function->paramtype[a]]); # if 0 BLI_dynstr_appendf(ds, " param%d", a); # endif if (a != function->totparam - 1) BLI_dynstr_append(ds, ", "); } BLI_dynstr_append(ds, ");\n"); } BLI_dynstr_append(ds, "\n"); prototypes = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); return prototypes; } #endif static GPUFunction *gpu_lookup_function(const char *name) { if (!FUNCTION_HASH) { FUNCTION_HASH = BLI_ghash_str_new("GPU_lookup_function gh"); gpu_parse_functions_string(FUNCTION_HASH, glsl_material_library); } return BLI_ghash_lookup(FUNCTION_HASH, (const void *)name); } void gpu_codegen_init(void) { GPU_code_generate_glsl_lib(); } void gpu_codegen_exit(void) { extern Material defmaterial; /* render module abuse... */ if (defmaterial.gpumaterial.first) GPU_material_free(&defmaterial.gpumaterial); if (FUNCTION_HASH) { BLI_ghash_free(FUNCTION_HASH, NULL, MEM_freeN); FUNCTION_HASH = NULL; } GPU_shader_free_builtin_shaders(); if (glsl_material_library) { MEM_freeN(glsl_material_library); glsl_material_library = NULL; } #if 0 if (FUNCTION_PROTOTYPES) { MEM_freeN(FUNCTION_PROTOTYPES); FUNCTION_PROTOTYPES = NULL; } if (FUNCTION_LIB) { GPU_shader_free(FUNCTION_LIB); FUNCTION_LIB = NULL; } #endif } /* GLSL code generation */ static void codegen_convert_datatype(DynStr *ds, int from, int to, const char *tmp, int id) { char name[1024]; BLI_snprintf(name, sizeof(name), "%s%d", tmp, id); if (from == to) { BLI_dynstr_append(ds, name); } else if (to == GPU_FLOAT) { if (from == GPU_VEC4) BLI_dynstr_appendf(ds, "convert_rgba_to_float(%s)", name); else if (from == GPU_VEC3) BLI_dynstr_appendf(ds, "(%s.r + %s.g + %s.b) / 3.0", name, name, name); else if (from == GPU_VEC2) BLI_dynstr_appendf(ds, "%s.r", name); } else if (to == GPU_VEC2) { if (from == GPU_VEC4) BLI_dynstr_appendf(ds, "vec2((%s.r + %s.g + %s.b) / 3.0, %s.a)", name, name, name, name); else if (from == GPU_VEC3) BLI_dynstr_appendf(ds, "vec2((%s.r + %s.g + %s.b) / 3.0, 1.0)", name, name, name); else if (from == GPU_FLOAT) BLI_dynstr_appendf(ds, "vec2(%s, 1.0)", name); } else if (to == GPU_VEC3) { if (from == GPU_VEC4) BLI_dynstr_appendf(ds, "%s.rgb", name); else if (from == GPU_VEC2) BLI_dynstr_appendf(ds, "vec3(%s.r, %s.r, %s.r)", name, name, name); else if (from == GPU_FLOAT) BLI_dynstr_appendf(ds, "vec3(%s, %s, %s)", name, name, name); } else if (to == GPU_VEC4) { if (from == GPU_VEC3) BLI_dynstr_appendf(ds, "vec4(%s, 1.0)", name); else if (from == GPU_VEC2) BLI_dynstr_appendf(ds, "vec4(%s.r, %s.r, %s.r, %s.g)", name, name, name, name); else if (from == GPU_FLOAT) BLI_dynstr_appendf(ds, "vec4(%s, %s, %s, 1.0)", name, name, name); } else if (to == GPU_CLOSURE) { if (from == GPU_VEC4) BLI_dynstr_appendf(ds, "closure_emission(%s.rgb)", name); else if (from == GPU_VEC3) BLI_dynstr_appendf(ds, "closure_emission(%s.rgb)", name); else if (from == GPU_VEC2) BLI_dynstr_appendf(ds, "closure_emission(%s.rrr)", name); else if (from == GPU_FLOAT) BLI_dynstr_appendf(ds, "closure_emission(vec3(%s, %s, %s))", name, name, name); } else { BLI_dynstr_append(ds, name); } } static void codegen_print_datatype(DynStr *ds, const eGPUType type, float *data) { int i; BLI_dynstr_appendf(ds, "%s(", GPU_DATATYPE_STR[type]); for (i = 0; i < type; i++) { BLI_dynstr_appendf(ds, "%.12f", data[i]); if (i == type - 1) BLI_dynstr_append(ds, ")"); else BLI_dynstr_append(ds, ", "); } } static int codegen_input_has_texture(GPUInput *input) { if (input->link) return 0; else return (input->source == GPU_SOURCE_TEX); } const char *GPU_builtin_name(eGPUBuiltin builtin) { if (builtin == GPU_VIEW_MATRIX) return "unfviewmat"; else if (builtin == GPU_OBJECT_MATRIX) return "unfobmat"; else if (builtin == GPU_INVERSE_VIEW_MATRIX) return "unfinvviewmat"; else if (builtin == GPU_INVERSE_OBJECT_MATRIX) return "unfinvobmat"; else if (builtin == GPU_LOC_TO_VIEW_MATRIX) return "unflocaltoviewmat"; else if (builtin == GPU_INVERSE_LOC_TO_VIEW_MATRIX) return "unfinvlocaltoviewmat"; else if (builtin == GPU_VIEW_POSITION) return "varposition"; else if (builtin == GPU_VIEW_NORMAL) return "varnormal"; else if (builtin == GPU_OBCOLOR) return "unfobcolor"; else if (builtin == GPU_AUTO_BUMPSCALE) return "unfobautobumpscale"; else if (builtin == GPU_CAMERA_TEXCO_FACTORS) return "unfcameratexfactors"; else if (builtin == GPU_PARTICLE_SCALAR_PROPS) return "unfparticlescalarprops"; else if (builtin == GPU_PARTICLE_LOCATION) return "unfparticleco"; else if (builtin == GPU_PARTICLE_VELOCITY) return "unfparticlevel"; else if (builtin == GPU_PARTICLE_ANG_VELOCITY) return "unfparticleangvel"; else if (builtin == GPU_OBJECT_INFO) return "unfobjectinfo"; else if (builtin == GPU_VOLUME_DENSITY) return "sampdensity"; else if (builtin == GPU_VOLUME_FLAME) return "sampflame"; else if (builtin == GPU_VOLUME_TEMPERATURE) return "unftemperature"; else if (builtin == GPU_BARYCENTRIC_TEXCO) return "unfbarycentrictex"; else if (builtin == GPU_BARYCENTRIC_DIST) return "unfbarycentricdist"; else return ""; } /* assign only one texid per buffer to avoid sampling the same texture twice */ static void codegen_set_texid(GHash *bindhash, GPUInput *input, int *texid, void *key) { if (BLI_ghash_haskey(bindhash, key)) { /* Reuse existing texid */ input->texid = POINTER_AS_INT(BLI_ghash_lookup(bindhash, key)); } else { /* Allocate new texid */ input->texid = *texid; (*texid)++; input->bindtex = true; BLI_ghash_insert(bindhash, key, POINTER_FROM_INT(input->texid)); } } static void codegen_set_unique_ids(ListBase *nodes) { GHash *bindhash; GPUNode *node; GPUInput *input; GPUOutput *output; int id = 1, texid = 0; bindhash = BLI_ghash_ptr_new("codegen_set_unique_ids1 gh"); for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { /* set id for unique names of uniform variables */ input->id = id++; /* set texid used for settings texture slot */ if (codegen_input_has_texture(input)) { input->bindtex = false; if (input->ima) { /* input is texture from image */ codegen_set_texid(bindhash, input, &texid, input->ima); } else if (input->coba) { /* input is color band texture, check coba pointer */ codegen_set_texid(bindhash, input, &texid, input->coba); } else { /* Either input->ima or input->coba should be non-NULL. */ BLI_assert(0); } } } for (output = node->outputs.first; output; output = output->next) { /* set id for unique names of tmp variables storing output */ output->id = id++; } } BLI_ghash_free(bindhash, NULL, NULL); } /** * It will create an UBO for GPUMaterial if there is any GPU_DYNAMIC_UBO. */ static int codegen_process_uniforms_functions(GPUMaterial *material, DynStr *ds, ListBase *nodes) { GPUNode *node; GPUInput *input; const char *name; int builtins = 0; ListBase ubo_inputs = {NULL, NULL}; /* print uniforms */ for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_TEX) { /* create exactly one sampler for each texture */ if (codegen_input_has_texture(input) && input->bindtex) { BLI_dynstr_appendf( ds, "uniform %s samp%d;\n", (input->coba) ? "sampler1DArray" : "sampler2D", input->texid); } } else if (input->source == GPU_SOURCE_BUILTIN) { /* only define each builtin uniform/varying once */ if (!(builtins & input->builtin)) { builtins |= input->builtin; name = GPU_builtin_name(input->builtin); if (gpu_str_prefix(name, "samp")) { if ((input->builtin == GPU_VOLUME_DENSITY) || (input->builtin == GPU_VOLUME_FLAME)) { BLI_dynstr_appendf(ds, "uniform sampler3D %s;\n", name); } } else if (gpu_str_prefix(name, "unf")) { BLI_dynstr_appendf( ds, "uniform %s %s;\n", GPU_DATATYPE_STR[input->type], name); } else { BLI_dynstr_appendf( ds, "in %s %s;\n", GPU_DATATYPE_STR[input->type], name); } } } else if (input->source == GPU_SOURCE_STRUCT) { /* Add other struct here if needed. */ BLI_dynstr_appendf(ds, "Closure strct%d = CLOSURE_DEFAULT;\n", input->id); } else if (input->source == GPU_SOURCE_UNIFORM) { if (!input->link) { /* We handle the UBOuniforms separately. */ BLI_addtail(&ubo_inputs, BLI_genericNodeN(input)); } } else if (input->source == GPU_SOURCE_CONSTANT) { BLI_dynstr_appendf( ds, "const %s cons%d = ", GPU_DATATYPE_STR[input->type], input->id); codegen_print_datatype(ds, input->type, input->vec); BLI_dynstr_append(ds, ";\n"); } else if (input->source == GPU_SOURCE_ATTR && input->attr_first) { BLI_dynstr_appendf( ds, "in %s var%d;\n", GPU_DATATYPE_STR[input->type], input->attr_id); } } } /* Handle the UBO block separately. */ if ((material != NULL) && !BLI_listbase_is_empty(&ubo_inputs)) { GPU_material_uniform_buffer_create(material, &ubo_inputs); /* Inputs are sorted */ BLI_dynstr_appendf(ds, "\nlayout (std140) uniform %s {\n", GPU_UBO_BLOCK_NAME); for (LinkData *link = ubo_inputs.first; link; link = link->next) { input = link->data; BLI_dynstr_appendf( ds, "\t%s unf%d;\n", GPU_DATATYPE_STR[input->type], input->id); } BLI_dynstr_append(ds, "};\n"); BLI_freelistN(&ubo_inputs); } BLI_dynstr_append(ds, "\n"); return builtins; } static void codegen_declare_tmps(DynStr *ds, ListBase *nodes) { GPUNode *node; GPUOutput *output; for (node = nodes->first; node; node = node->next) { /* declare temporary variables for node output storage */ for (output = node->outputs.first; output; output = output->next) { if (output->type == GPU_CLOSURE) { BLI_dynstr_appendf( ds, "\tClosure tmp%d;\n", output->id); } else { BLI_dynstr_appendf( ds, "\t%s tmp%d;\n", GPU_DATATYPE_STR[output->type], output->id); } } } BLI_dynstr_append(ds, "\n"); } static void codegen_call_functions(DynStr *ds, ListBase *nodes, GPUOutput *finaloutput) { GPUNode *node; GPUInput *input; GPUOutput *output; for (node = nodes->first; node; node = node->next) { BLI_dynstr_appendf(ds, "\t%s(", node->name); for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_TEX) { BLI_dynstr_appendf(ds, "samp%d", input->texid); } else if (input->source == GPU_SOURCE_OUTPUT) { codegen_convert_datatype( ds, input->link->output->type, input->type, "tmp", input->link->output->id); } else if (input->source == GPU_SOURCE_BUILTIN) { /* TODO(fclem) get rid of that. */ if (input->builtin == GPU_INVERSE_VIEW_MATRIX) BLI_dynstr_append(ds, "viewinv"); else if (input->builtin == GPU_VIEW_MATRIX) BLI_dynstr_append(ds, "viewmat"); else if (input->builtin == GPU_CAMERA_TEXCO_FACTORS) BLI_dynstr_append(ds, "camtexfac"); else if (input->builtin == GPU_LOC_TO_VIEW_MATRIX) BLI_dynstr_append(ds, "localtoviewmat"); else if (input->builtin == GPU_INVERSE_LOC_TO_VIEW_MATRIX) BLI_dynstr_append(ds, "invlocaltoviewmat"); else if (input->builtin == GPU_BARYCENTRIC_DIST) BLI_dynstr_append(ds, "barycentricDist"); else if (input->builtin == GPU_BARYCENTRIC_TEXCO) BLI_dynstr_append(ds, "barytexco"); else if (input->builtin == GPU_OBJECT_MATRIX) BLI_dynstr_append(ds, "objmat"); else if (input->builtin == GPU_INVERSE_OBJECT_MATRIX) BLI_dynstr_append(ds, "objinv"); else if (input->builtin == GPU_VIEW_POSITION) BLI_dynstr_append(ds, "viewposition"); else if (input->builtin == GPU_VIEW_NORMAL) BLI_dynstr_append(ds, "facingnormal"); else BLI_dynstr_append(ds, GPU_builtin_name(input->builtin)); } else if (input->source == GPU_SOURCE_STRUCT) { BLI_dynstr_appendf(ds, "strct%d", input->id); } else if (input->source == GPU_SOURCE_UNIFORM) { BLI_dynstr_appendf(ds, "unf%d", input->id); } else if (input->source == GPU_SOURCE_CONSTANT) { BLI_dynstr_appendf(ds, "cons%d", input->id); } else if (input->source == GPU_SOURCE_ATTR) { BLI_dynstr_appendf(ds, "var%d", input->attr_id); } BLI_dynstr_append(ds, ", "); } for (output = node->outputs.first; output; output = output->next) { BLI_dynstr_appendf(ds, "tmp%d", output->id); if (output->next) BLI_dynstr_append(ds, ", "); } BLI_dynstr_append(ds, ");\n"); } BLI_dynstr_appendf(ds, "\n\treturn tmp%d", finaloutput->id); BLI_dynstr_append(ds, ";\n"); } static char *code_generate_fragment(GPUMaterial *material, ListBase *nodes, GPUOutput *output, int *rbuiltins) { DynStr *ds = BLI_dynstr_new(); char *code; int builtins; #if 0 BLI_dynstr_append(ds, FUNCTION_PROTOTYPES); #endif codegen_set_unique_ids(nodes); *rbuiltins = builtins = codegen_process_uniforms_functions(material, ds, nodes); if (builtins & GPU_BARYCENTRIC_TEXCO) BLI_dynstr_append(ds, "in vec2 barycentricTexCo;\n"); if (builtins & GPU_BARYCENTRIC_DIST) BLI_dynstr_append(ds, "flat in vec3 barycentricDist;\n"); BLI_dynstr_append(ds, "Closure nodetree_exec(void)\n{\n"); if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, "#ifdef HAIR_SHADER\n"); BLI_dynstr_append(ds, "\tvec2 barytexco = vec2((fract(barycentricTexCo.y) != 0.0)\n" "\t ? barycentricTexCo.x\n" "\t : 1.0 - barycentricTexCo.x,\n" "\t 0.0);\n"); BLI_dynstr_append(ds, "#else\n"); BLI_dynstr_append(ds, "\tvec2 barytexco = barycentricTexCo;\n"); BLI_dynstr_append(ds, "#endif\n"); } /* TODO(fclem) get rid of that. */ if (builtins & GPU_VIEW_MATRIX) BLI_dynstr_append(ds, "\t#define viewmat ViewMatrix\n"); if (builtins & GPU_CAMERA_TEXCO_FACTORS) BLI_dynstr_append(ds, "\t#define camtexfac CameraTexCoFactors\n"); if (builtins & GPU_OBJECT_MATRIX) BLI_dynstr_append(ds, "\t#define objmat ModelMatrix\n"); if (builtins & GPU_INVERSE_OBJECT_MATRIX) BLI_dynstr_append(ds, "\t#define objinv ModelMatrixInverse\n"); if (builtins & GPU_INVERSE_VIEW_MATRIX) BLI_dynstr_append(ds, "\t#define viewinv ViewMatrixInverse\n"); if (builtins & GPU_LOC_TO_VIEW_MATRIX) BLI_dynstr_append(ds, "\t#define localtoviewmat ModelViewMatrix\n"); if (builtins & GPU_INVERSE_LOC_TO_VIEW_MATRIX) BLI_dynstr_append(ds, "\t#define invlocaltoviewmat ModelViewMatrixInverse\n"); if (builtins & GPU_VIEW_NORMAL) BLI_dynstr_append(ds, "\tvec3 facingnormal = gl_FrontFacing? viewNormal: -viewNormal;\n"); if (builtins & GPU_VIEW_POSITION) BLI_dynstr_append(ds, "\t#define viewposition viewPosition\n"); codegen_declare_tmps(ds, nodes); codegen_call_functions(ds, nodes, output); BLI_dynstr_append(ds, "}\n"); /* XXX This cannot go into gpu_shader_material.glsl because main() would be parsed and generate error */ /* Old glsl mode compat. */ BLI_dynstr_append(ds, "#ifndef NODETREE_EXEC\n"); BLI_dynstr_append(ds, "out vec4 fragColor;\n"); BLI_dynstr_append(ds, "void main()\n"); BLI_dynstr_append(ds, "{\n"); BLI_dynstr_append(ds, "\tClosure cl = nodetree_exec();\n"); BLI_dynstr_append(ds, "\tfragColor = vec4(cl.radiance, cl.opacity);\n"); BLI_dynstr_append(ds, "}\n"); BLI_dynstr_append(ds, "#endif\n\n"); /* create shader */ code = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); #if 0 if (G.debug & G_DEBUG) printf("%s\n", code); #endif return code; } static const char *attr_prefix_get(CustomDataType type) { switch (type) { case CD_ORCO: return "orco"; case CD_MTFACE: return "u"; case CD_TANGENT: return "t"; case CD_MCOL: return "c"; case CD_AUTO_FROM_NAME: return "a"; default: BLI_assert(false && "GPUVertAttr Prefix type not found : This should not happen!"); return ""; } } static char *code_generate_vertex(ListBase *nodes, const char *vert_code, bool use_geom) { DynStr *ds = BLI_dynstr_new(); GPUNode *node; GPUInput *input; char *code; int builtins = 0; /* Hairs uv and col attributes are passed by bufferTextures. */ BLI_dynstr_append( ds, "#ifdef HAIR_SHADER\n" "#define DEFINE_ATTR(type, attr) uniform samplerBuffer attr\n" "#else\n" "#define DEFINE_ATTR(type, attr) in type attr\n" "#endif\n" ); for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_BUILTIN) { builtins |= input->builtin; } if (input->source == GPU_SOURCE_ATTR && input->attr_first) { /* XXX FIXME : see notes in mesh_render_data_create() */ /* NOTE : Replicate changes to mesh_render_data_create() in draw_cache_impl_mesh.c */ if (input->attr_type == CD_ORCO) { /* orco is computed from local positions, see below */ BLI_dynstr_appendf(ds, "uniform vec3 OrcoTexCoFactors[2];\n"); } else if (input->attr_name[0] == '\0') { BLI_dynstr_appendf(ds, "DEFINE_ATTR(%s, %s);\n", GPU_DATATYPE_STR[input->type], attr_prefix_get(input->attr_type)); BLI_dynstr_appendf(ds, "#define att%d %s\n", input->attr_id, attr_prefix_get(input->attr_type)); } else { uint hash = BLI_ghashutil_strhash_p(input->attr_name); BLI_dynstr_appendf( ds, "DEFINE_ATTR(%s, %s%u);\n", GPU_DATATYPE_STR[input->type], attr_prefix_get(input->attr_type), hash); BLI_dynstr_appendf( ds, "#define att%d %s%u\n", input->attr_id, attr_prefix_get(input->attr_type), hash); /* Auto attribute can be vertex color byte buffer. * We need to know and convert them to linear space in VS. */ if (!use_geom && input->attr_type == CD_AUTO_FROM_NAME) { BLI_dynstr_appendf(ds, "uniform bool ba%u;\n", hash); BLI_dynstr_appendf(ds, "#define att%d_is_srgb ba%u\n", input->attr_id, hash); } } BLI_dynstr_appendf( ds, "out %s var%d%s;\n", GPU_DATATYPE_STR[input->type], input->attr_id, use_geom ? "g" : ""); } } } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_appendf( ds, "out vec2 barycentricTexCo%s;\n", use_geom ? "g" : ""); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, "out vec3 barycentricPosg;\n"); } BLI_dynstr_append(ds, "\n"); BLI_dynstr_append( ds, "#define USE_ATTR\n" "uniform mat3 NormalMatrix;\n" "uniform mat4 ModelMatrixInverse;\n" "uniform mat4 ModelMatrix;\n" "vec3 srgb_to_linear_attr(vec3 c) {\n" "\tc = max(c, vec3(0.0));\n" "\tvec3 c1 = c * (1.0 / 12.92);\n" "\tvec3 c2 = pow((c + 0.055) * (1.0 / 1.055), vec3(2.4));\n" "\treturn mix(c1, c2, step(vec3(0.04045), c));\n" "}\n\n" ); /* Prototype because defined later. */ BLI_dynstr_append( ds, "vec2 hair_get_customdata_vec2(const samplerBuffer);\n" "vec3 hair_get_customdata_vec3(const samplerBuffer);\n" "vec4 hair_get_customdata_vec4(const samplerBuffer);\n" "vec3 hair_get_strand_pos(void);\n" "int hair_get_base_id(void);\n" "\n" ); BLI_dynstr_append(ds, "void pass_attr(in vec3 position) {\n"); BLI_dynstr_append(ds, "#ifdef HAIR_SHADER\n"); if (builtins & GPU_BARYCENTRIC_TEXCO) { /* To match cycles without breaking into individual segment we encode if we need to invert * the first component into the second component. We invert if the barycentricTexCo.y * is NOT 0.0 or 1.0. */ BLI_dynstr_appendf( ds, "\tint _base_id = hair_get_base_id();\n"); BLI_dynstr_appendf( ds, "\tbarycentricTexCo%s.x = float((_base_id %% 2) == 1);\n", use_geom ? "g" : ""); BLI_dynstr_appendf( ds, "\tbarycentricTexCo%s.y = float(((_base_id %% 4) %% 3) > 0);\n", use_geom ? "g" : ""); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, "\tbarycentricPosg = position;\n"); } for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_ATTR && input->attr_first) { if (input->attr_type == CD_TANGENT) { /* Not supported by hairs */ BLI_dynstr_appendf( ds, "\tvar%d%s = vec4(0.0);\n", input->attr_id, use_geom ? "g" : ""); } else if (input->attr_type == CD_ORCO) { BLI_dynstr_appendf( ds, "\tvar%d%s = OrcoTexCoFactors[0] + (ModelMatrixInverse * vec4(hair_get_strand_pos(), 1.0)).xyz * OrcoTexCoFactors[1];\n", input->attr_id, use_geom ? "g" : ""); } else { BLI_dynstr_appendf( ds, "\tvar%d%s = hair_get_customdata_%s(att%d);\n", input->attr_id, use_geom ? "g" : "", GPU_DATATYPE_STR[input->type], input->attr_id); } } } } BLI_dynstr_append(ds, "#else /* MESH_SHADER */\n"); if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_appendf( ds, "\tbarycentricTexCo%s.x = float((gl_VertexID %% 3) == 0);\n", use_geom ? "g" : ""); BLI_dynstr_appendf( ds, "\tbarycentricTexCo%s.y = float((gl_VertexID %% 3) == 1);\n", use_geom ? "g" : ""); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, "\tbarycentricPosg = (ModelMatrix * vec4(position, 1.0)).xyz;\n"); } for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_ATTR && input->attr_first) { if (input->attr_type == CD_TANGENT) { /* silly exception */ BLI_dynstr_appendf( ds, "\tvar%d%s.xyz = normalize(NormalMatrix * att%d.xyz);\n", input->attr_id, use_geom ? "g" : "", input->attr_id); BLI_dynstr_appendf( ds, "\tvar%d%s.w = att%d.w;\n", input->attr_id, use_geom ? "g" : "", input->attr_id); } else if (input->attr_type == CD_ORCO) { BLI_dynstr_appendf( ds, "\tvar%d%s = OrcoTexCoFactors[0] + position * OrcoTexCoFactors[1];\n", input->attr_id, use_geom ? "g" : ""); } else if (input->attr_type == CD_MCOL) { BLI_dynstr_appendf( ds, "\tvar%d%s = srgb_to_linear_attr(att%d);\n", input->attr_id, use_geom ? "g" : "", input->attr_id); } else if (input->attr_type == CD_AUTO_FROM_NAME) { BLI_dynstr_appendf( ds, "\tvar%d%s = (att%d_is_srgb) ? srgb_to_linear_attr(att%d) : att%d;\n", input->attr_id, use_geom ? "g" : "", input->attr_id, input->attr_id, input->attr_id); } else { BLI_dynstr_appendf( ds, "\tvar%d%s = att%d;\n", input->attr_id, use_geom ? "g" : "", input->attr_id); } } } } BLI_dynstr_append(ds, "#endif /* HAIR_SHADER */\n"); BLI_dynstr_append(ds, "}\n"); if (use_geom) { /* XXX HACK: Eevee specific. */ char *vert_new, *vert_new2; vert_new = BLI_str_replaceN(vert_code, "worldPosition", "worldPositiong"); vert_new2 = vert_new; vert_new = BLI_str_replaceN(vert_new2, "viewPosition", "viewPositiong"); MEM_freeN(vert_new2); vert_new2 = vert_new; vert_new = BLI_str_replaceN(vert_new2, "worldNormal", "worldNormalg"); MEM_freeN(vert_new2); vert_new2 = vert_new; vert_new = BLI_str_replaceN(vert_new2, "viewNormal", "viewNormalg"); MEM_freeN(vert_new2); BLI_dynstr_append(ds, vert_new); MEM_freeN(vert_new); } else { BLI_dynstr_append(ds, vert_code); } code = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); #if 0 if (G.debug & G_DEBUG) printf("%s\n", code); #endif return code; } static char *code_generate_geometry(ListBase *nodes, const char *geom_code) { DynStr *ds = BLI_dynstr_new(); GPUNode *node; GPUInput *input; char *code; int builtins = 0; /* Create prototype because attributes cannot be declared before layout. */ BLI_dynstr_appendf(ds, "void pass_attr(in int vert);\n"); BLI_dynstr_appendf(ds, "void calc_barycentric_distances(vec3 pos0, vec3 pos1, vec3 pos2);\n"); BLI_dynstr_append(ds, "#define USE_ATTR\n"); /* Generate varying declarations. */ for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_BUILTIN) { builtins |= input->builtin; } if (input->source == GPU_SOURCE_ATTR && input->attr_first) { BLI_dynstr_appendf( ds, "in %s var%dg[];\n", GPU_DATATYPE_STR[input->type], input->attr_id); BLI_dynstr_appendf( ds, "out %s var%d;\n", GPU_DATATYPE_STR[input->type], input->attr_id); } } } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_appendf(ds, "in vec2 barycentricTexCog[];\n"); BLI_dynstr_appendf(ds, "out vec2 barycentricTexCo;\n"); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, "in vec3 barycentricPosg[];\n"); BLI_dynstr_appendf(ds, "flat out vec3 barycentricDist;\n"); } if (geom_code == NULL) { if ((builtins & GPU_BARYCENTRIC_DIST) == 0) { /* Early out */ BLI_dynstr_free(ds); return NULL; } else { /* Force geom shader usage */ /* TODO put in external file. */ BLI_dynstr_appendf(ds, "layout(triangles) in;\n"); BLI_dynstr_appendf(ds, "layout(triangle_strip, max_vertices=3) out;\n"); BLI_dynstr_appendf(ds, "in vec3 worldPositiong[];\n"); BLI_dynstr_appendf(ds, "in vec3 viewPositiong[];\n"); BLI_dynstr_appendf(ds, "in vec3 worldNormalg[];\n"); BLI_dynstr_appendf(ds, "in vec3 viewNormalg[];\n"); BLI_dynstr_appendf(ds, "out vec3 worldPosition;\n"); BLI_dynstr_appendf(ds, "out vec3 viewPosition;\n"); BLI_dynstr_appendf(ds, "out vec3 worldNormal;\n"); BLI_dynstr_appendf(ds, "out vec3 viewNormal;\n"); BLI_dynstr_appendf(ds, "void main(){\n"); if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, "\tcalc_barycentric_distances(barycentricPosg[0], barycentricPosg[1], barycentricPosg[2]);\n"); } BLI_dynstr_appendf(ds, "\tgl_Position = gl_in[0].gl_Position;\n"); BLI_dynstr_appendf(ds, "\tpass_attr(0);\n"); BLI_dynstr_appendf(ds, "\tEmitVertex();\n"); BLI_dynstr_appendf(ds, "\tgl_Position = gl_in[1].gl_Position;\n"); BLI_dynstr_appendf(ds, "\tpass_attr(1);\n"); BLI_dynstr_appendf(ds, "\tEmitVertex();\n"); BLI_dynstr_appendf(ds, "\tgl_Position = gl_in[2].gl_Position;\n"); BLI_dynstr_appendf(ds, "\tpass_attr(2);\n"); BLI_dynstr_appendf(ds, "\tEmitVertex();\n"); BLI_dynstr_appendf(ds, "};\n"); } } else { BLI_dynstr_append(ds, geom_code); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, "void calc_barycentric_distances(vec3 pos0, vec3 pos1, vec3 pos2) {\n"); BLI_dynstr_appendf(ds, "\tvec3 edge21 = pos2 - pos1;\n"); BLI_dynstr_appendf(ds, "\tvec3 edge10 = pos1 - pos0;\n"); BLI_dynstr_appendf(ds, "\tvec3 edge02 = pos0 - pos2;\n"); BLI_dynstr_appendf(ds, "\tvec3 d21 = normalize(edge21);\n"); BLI_dynstr_appendf(ds, "\tvec3 d10 = normalize(edge10);\n"); BLI_dynstr_appendf(ds, "\tvec3 d02 = normalize(edge02);\n"); BLI_dynstr_appendf(ds, "\tfloat d = dot(d21, edge02);\n"); BLI_dynstr_appendf(ds, "\tbarycentricDist.x = sqrt(dot(edge02, edge02) - d * d);\n"); BLI_dynstr_appendf(ds, "\td = dot(d02, edge10);\n"); BLI_dynstr_appendf(ds, "\tbarycentricDist.y = sqrt(dot(edge10, edge10) - d * d);\n"); BLI_dynstr_appendf(ds, "\td = dot(d10, edge21);\n"); BLI_dynstr_appendf(ds, "\tbarycentricDist.z = sqrt(dot(edge21, edge21) - d * d);\n"); BLI_dynstr_append(ds, "}\n"); } /* Generate varying assignments. */ BLI_dynstr_appendf(ds, "void pass_attr(in int vert) {\n"); /* XXX HACK: Eevee specific. */ if (geom_code == NULL) { BLI_dynstr_appendf(ds, "\tworldPosition = worldPositiong[vert];\n"); BLI_dynstr_appendf(ds, "\tviewPosition = viewPositiong[vert];\n"); BLI_dynstr_appendf(ds, "\tworldNormal = worldNormalg[vert];\n"); BLI_dynstr_appendf(ds, "\tviewNormal = viewNormalg[vert];\n"); } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_appendf(ds, "\tbarycentricTexCo = barycentricTexCog[vert];\n"); } for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_ATTR && input->attr_first) { /* TODO let shader choose what to do depending on what the attribute is. */ BLI_dynstr_appendf(ds, "\tvar%d = var%dg[vert];\n", input->attr_id, input->attr_id); } } } BLI_dynstr_append(ds, "}\n"); code = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); return code; } void GPU_code_generate_glsl_lib(void) { DynStr *ds; /* only initialize the library once */ if (glsl_material_library) return; ds = BLI_dynstr_new(); BLI_dynstr_append(ds, datatoc_gpu_shader_material_glsl); glsl_material_library = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); } /* GPU pass binding/unbinding */ GPUShader *GPU_pass_shader_get(GPUPass *pass) { return pass->shader; } void GPU_nodes_extract_dynamic_inputs(GPUShader *shader, ListBase *inputs, ListBase *nodes) { GPUNode *node; GPUInput *next, *input; BLI_listbase_clear(inputs); if (!shader) return; GPU_shader_bind(shader); for (node = nodes->first; node; node = node->next) { int z = 0; for (input = node->inputs.first; input; input = next, z++) { next = input->next; /* attributes don't need to be bound, they already have * an id that the drawing functions will use. Builtins have * constant names. */ if (ELEM(input->source, GPU_SOURCE_ATTR, GPU_SOURCE_BUILTIN)) { continue; } if (input->source == GPU_SOURCE_TEX) BLI_snprintf(input->shadername, sizeof(input->shadername), "samp%d", input->texid); else { BLI_snprintf(input->shadername, sizeof(input->shadername), "unf%d", input->id); } if (input->source == GPU_SOURCE_TEX) { if (input->bindtex) { input->shaderloc = GPU_shader_get_uniform_ensure(shader, input->shadername); /* extract nodes */ BLI_remlink(&node->inputs, input); BLI_addtail(inputs, input); } } } } GPU_shader_unbind(); } /* Node Link Functions */ static GPUNodeLink *GPU_node_link_create(void) { GPUNodeLink *link = MEM_callocN(sizeof(GPUNodeLink), "GPUNodeLink"); link->users++; return link; } static void gpu_node_link_free(GPUNodeLink *link) { link->users--; if (link->users < 0) fprintf(stderr, "GPU_node_link_free: negative refcount\n"); if (link->users == 0) { if (link->output) link->output->link = NULL; MEM_freeN(link); } } /* Node Functions */ static GPUNode *GPU_node_begin(const char *name) { GPUNode *node = MEM_callocN(sizeof(GPUNode), "GPUNode"); node->name = name; return node; } static void gpu_node_input_link(GPUNode *node, GPUNodeLink *link, const eGPUType type) { GPUInput *input; GPUNode *outnode; const char *name; if (link->link_type == GPU_NODE_LINK_OUTPUT) { outnode = link->output->node; name = outnode->name; input = outnode->inputs.first; if ((STREQ(name, "set_value") || STREQ(name, "set_rgb") || STREQ(name, "set_rgba")) && (input->type == type)) { input = MEM_dupallocN(outnode->inputs.first); if (input->link) input->link->users++; BLI_addtail(&node->inputs, input); return; } } input = MEM_callocN(sizeof(GPUInput), "GPUInput"); input->node = node; input->type = type; switch (link->link_type) { case GPU_NODE_LINK_BUILTIN: input->source = GPU_SOURCE_BUILTIN; input->builtin = link->builtin; break; case GPU_NODE_LINK_OUTPUT: input->source = GPU_SOURCE_OUTPUT; input->link = link; link->users++; break; case GPU_NODE_LINK_COLORBAND: input->source = GPU_SOURCE_TEX; input->coba = link->coba; break; case GPU_NODE_LINK_IMAGE_BLENDER: input->source = GPU_SOURCE_TEX; input->ima = link->ima; input->iuser = link->iuser; input->image_isdata = link->image_isdata; break; case GPU_NODE_LINK_ATTR: input->source = GPU_SOURCE_ATTR; input->attr_type = link->attr_type; BLI_strncpy(input->attr_name, link->attr_name, sizeof(input->attr_name)); break; case GPU_NODE_LINK_CONSTANT: input->source = (type == GPU_CLOSURE) ? GPU_SOURCE_STRUCT : GPU_SOURCE_CONSTANT; break; case GPU_NODE_LINK_UNIFORM: input->source = GPU_SOURCE_UNIFORM; break; default: break; } if (ELEM(input->source, GPU_SOURCE_CONSTANT, GPU_SOURCE_UNIFORM)) { memcpy(input->vec, link->data, type * sizeof(float)); } if (link->link_type != GPU_NODE_LINK_OUTPUT) { MEM_freeN(link); } BLI_addtail(&node->inputs, input); } static const char *gpu_uniform_set_function_from_type(eNodeSocketDatatype type) { switch (type) { case SOCK_FLOAT: return "set_value"; case SOCK_VECTOR: return "set_rgb"; case SOCK_RGBA: return "set_rgba"; default: BLI_assert(!"No gpu function for non-supported eNodeSocketDatatype"); return NULL; } } /** * Link stack uniform buffer. * This is called for the input/output sockets that are note connected. */ static GPUNodeLink *gpu_uniformbuffer_link( GPUMaterial *mat, bNode *node, GPUNodeStack *stack, const int index, const eNodeSocketInOut in_out) { bNodeSocket *socket; if (in_out == SOCK_IN) { socket = BLI_findlink(&node->inputs, index); } else { socket = BLI_findlink(&node->outputs, index); } BLI_assert(socket != NULL); BLI_assert(socket->in_out == in_out); if ((socket->flag & SOCK_HIDE_VALUE) == 0) { GPUNodeLink *link; switch (socket->type) { case SOCK_FLOAT: { bNodeSocketValueFloat *socket_data = socket->default_value; link = GPU_uniform(&socket_data->value); break; } case SOCK_VECTOR: { bNodeSocketValueVector *socket_data = socket->default_value; link = GPU_uniform(socket_data->value); break; } case SOCK_RGBA: { bNodeSocketValueRGBA *socket_data = socket->default_value; link = GPU_uniform(socket_data->value); break; } default: return NULL; break; } if (in_out == SOCK_IN) { GPU_link(mat, gpu_uniform_set_function_from_type(socket->type), link, &stack->link); } return link; } return NULL; } static void gpu_node_input_socket(GPUMaterial *material, bNode *bnode, GPUNode *node, GPUNodeStack *sock, const int index) { if (sock->link) { gpu_node_input_link(node, sock->link, sock->type); } else if ((material != NULL) && (gpu_uniformbuffer_link(material, bnode, sock, index, SOCK_IN) != NULL)) { gpu_node_input_link(node, sock->link, sock->type); } else { gpu_node_input_link(node, GPU_constant(sock->vec), sock->type); } } static void gpu_node_output(GPUNode *node, const eGPUType type, GPUNodeLink **link) { GPUOutput *output = MEM_callocN(sizeof(GPUOutput), "GPUOutput"); output->type = type; output->node = node; if (link) { *link = output->link = GPU_node_link_create(); output->link->link_type = GPU_NODE_LINK_OUTPUT; output->link->output = output; /* note: the caller owns the reference to the link, GPUOutput * merely points to it, and if the node is destroyed it will * set that pointer to NULL */ } BLI_addtail(&node->outputs, output); } void GPU_inputs_free(ListBase *inputs) { GPUInput *input; for (input = inputs->first; input; input = input->next) { if (input->link) gpu_node_link_free(input->link); } BLI_freelistN(inputs); } static void gpu_node_free(GPUNode *node) { GPUOutput *output; GPU_inputs_free(&node->inputs); for (output = node->outputs.first; output; output = output->next) if (output->link) { output->link->output = NULL; gpu_node_link_free(output->link); } BLI_freelistN(&node->outputs); MEM_freeN(node); } static void gpu_nodes_free(ListBase *nodes) { GPUNode *node; while ((node = BLI_pophead(nodes))) { gpu_node_free(node); } } /* vertex attributes */ void GPU_nodes_get_vertex_attrs(ListBase *nodes, GPUVertAttrLayers *attrs) { GPUNode *node; GPUInput *input; int a; /* convert attributes requested by node inputs to an array of layers, * checking for duplicates and assigning id's starting from zero. */ memset(attrs, 0, sizeof(*attrs)); for (node = nodes->first; node; node = node->next) { for (input = node->inputs.first; input; input = input->next) { if (input->source == GPU_SOURCE_ATTR) { for (a = 0; a < attrs->totlayer; a++) { if (attrs->layer[a].type == input->attr_type && STREQ(attrs->layer[a].name, input->attr_name)) { break; } } if (a < GPU_MAX_ATTR) { if (a == attrs->totlayer) { input->attr_id = attrs->totlayer++; input->attr_first = true; attrs->layer[a].type = input->attr_type; attrs->layer[a].attr_id = input->attr_id; BLI_strncpy( attrs->layer[a].name, input->attr_name, sizeof(attrs->layer[a].name)); } else { input->attr_id = attrs->layer[a].attr_id; } } } } } } /* varargs linking */ GPUNodeLink *GPU_attribute(const CustomDataType type, const char *name) { GPUNodeLink *link = GPU_node_link_create(); link->link_type = GPU_NODE_LINK_ATTR; link->attr_name = name; /* Fall back to the UV layer, which matches old behavior. */ if (type == CD_AUTO_FROM_NAME && name[0] == '\0') { link->attr_type = CD_MTFACE; } else { link->attr_type = type; } return link; } GPUNodeLink *GPU_constant(float *num) { GPUNodeLink *link = GPU_node_link_create(); link->link_type = GPU_NODE_LINK_CONSTANT; link->data = num; return link; } GPUNodeLink *GPU_uniform(float *num) { GPUNodeLink *link = GPU_node_link_create(); link->link_type = GPU_NODE_LINK_UNIFORM; link->data = num; return link; } GPUNodeLink *GPU_image(Image *ima, ImageUser *iuser, bool is_data) { GPUNodeLink *link = GPU_node_link_create(); link->link_type = GPU_NODE_LINK_IMAGE_BLENDER; link->ima = ima; link->iuser = iuser; link->image_isdata = is_data; return link; } GPUNodeLink *GPU_color_band(GPUMaterial *mat, int size, float *pixels, float *row) { GPUNodeLink *link = GPU_node_link_create(); link->link_type = GPU_NODE_LINK_COLORBAND; link->coba = gpu_material_ramp_texture_row_set(mat, size, pixels, row); MEM_freeN(pixels); return link; } GPUNodeLink *GPU_builtin(eGPUBuiltin builtin) { GPUNodeLink *link = GPU_node_link_create(); link->link_type = GPU_NODE_LINK_BUILTIN; link->builtin = builtin; return link; } bool GPU_link(GPUMaterial *mat, const char *name, ...) { GPUNode *node; GPUFunction *function; GPUNodeLink *link, **linkptr; va_list params; int i; function = gpu_lookup_function(name); if (!function) { fprintf(stderr, "GPU failed to find function %s\n", name); return false; } node = GPU_node_begin(name); va_start(params, name); for (i = 0; i < function->totparam; i++) { if (function->paramqual[i] != FUNCTION_QUAL_IN) { linkptr = va_arg(params, GPUNodeLink **); gpu_node_output(node, function->paramtype[i], linkptr); } else { link = va_arg(params, GPUNodeLink *); gpu_node_input_link(node, link, function->paramtype[i]); } } va_end(params); gpu_material_add_node(mat, node); return true; } bool GPU_stack_link(GPUMaterial *material, bNode *bnode, const char *name, GPUNodeStack *in, GPUNodeStack *out, ...) { GPUNode *node; GPUFunction *function; GPUNodeLink *link, **linkptr; va_list params; int i, totin, totout; function = gpu_lookup_function(name); if (!function) { fprintf(stderr, "GPU failed to find function %s\n", name); return false; } node = GPU_node_begin(name); totin = 0; totout = 0; if (in) { for (i = 0; !in[i].end; i++) { if (in[i].type != GPU_NONE) { gpu_node_input_socket(material, bnode, node, &in[i], i); totin++; } } } if (out) { for (i = 0; !out[i].end; i++) { if (out[i].type != GPU_NONE) { gpu_node_output(node, out[i].type, &out[i].link); totout++; } } } va_start(params, out); for (i = 0; i < function->totparam; i++) { if (function->paramqual[i] != FUNCTION_QUAL_IN) { if (totout == 0) { linkptr = va_arg(params, GPUNodeLink **); gpu_node_output(node, function->paramtype[i], linkptr); } else totout--; } else { if (totin == 0) { link = va_arg(params, GPUNodeLink *); if (link->socket) gpu_node_input_socket(NULL, NULL, node, link->socket, -1); else gpu_node_input_link(node, link, function->paramtype[i]); } else totin--; } } va_end(params); gpu_material_add_node(material, node); return true; } GPUNodeLink *GPU_uniformbuffer_link_out(GPUMaterial *mat, bNode *node, GPUNodeStack *stack, const int index) { return gpu_uniformbuffer_link(mat, node, stack, index, SOCK_OUT); } /* Pass create/free */ static void gpu_nodes_tag(GPUNodeLink *link) { GPUNode *node; GPUInput *input; if (!link->output) return; node = link->output->node; if (node->tag) return; node->tag = true; for (input = node->inputs.first; input; input = input->next) if (input->link) gpu_nodes_tag(input->link); } void GPU_nodes_prune(ListBase *nodes, GPUNodeLink *outlink) { GPUNode *node, *next; for (node = nodes->first; node; node = node->next) node->tag = false; gpu_nodes_tag(outlink); for (node = nodes->first; node; node = next) { next = node->next; if (!node->tag) { BLI_remlink(nodes, node); gpu_node_free(node); } } } static bool gpu_pass_is_valid(GPUPass *pass) { /* Shader is not null if compilation is successful. */ return (pass->compiled == false || pass->shader != NULL); } GPUPass *GPU_generate_pass( GPUMaterial *material, GPUNodeLink *frag_outlink, struct GPUVertAttrLayers *attrs, ListBase *nodes, int *builtins, const char *vert_code, const char *geom_code, const char *frag_lib, const char *defines) { char *vertexcode, *geometrycode, *fragmentcode; GPUPass *pass = NULL, *pass_hash = NULL; /* prune unused nodes */ GPU_nodes_prune(nodes, frag_outlink); GPU_nodes_get_vertex_attrs(nodes, attrs); /* generate code */ char *fragmentgen = code_generate_fragment(material, nodes, frag_outlink->output, builtins); /* Cache lookup: Reuse shaders already compiled */ uint32_t hash = gpu_pass_hash(fragmentgen, defines, attrs); pass_hash = gpu_pass_cache_lookup(hash); if (pass_hash && (pass_hash->next == NULL || pass_hash->next->hash != hash)) { /* No collision, just return the pass. */ MEM_freeN(fragmentgen); if (!gpu_pass_is_valid(pass_hash)) { /* Shader has already been created but failed to compile. */ return NULL; } pass_hash->refcount += 1; return pass_hash; } /* Either the shader is not compiled or there is a hash collision... * continue generating the shader strings. */ char *tmp = BLI_strdupcat(frag_lib, glsl_material_library); geometrycode = code_generate_geometry(nodes, geom_code); vertexcode = code_generate_vertex(nodes, vert_code, (geometrycode != NULL)); fragmentcode = BLI_strdupcat(tmp, fragmentgen); MEM_freeN(fragmentgen); MEM_freeN(tmp); if (pass_hash) { /* Cache lookup: Reuse shaders already compiled */ pass = gpu_pass_cache_resolve_collision(pass_hash, vertexcode, geometrycode, fragmentcode, defines, hash); } if (pass) { /* Cache hit. Reuse the same GPUPass and GPUShader. */ if (!gpu_pass_is_valid(pass)) { /* Shader has already been created but failed to compile. */ return NULL; } MEM_SAFE_FREE(vertexcode); MEM_SAFE_FREE(fragmentcode); MEM_SAFE_FREE(geometrycode); pass->refcount += 1; } else { /* We still create a pass even if shader compilation * fails to avoid trying to compile again and again. */ pass = MEM_callocN(sizeof(GPUPass), "GPUPass"); pass->shader = NULL; pass->refcount = 1; pass->hash = hash; pass->vertexcode = vertexcode; pass->fragmentcode = fragmentcode; pass->geometrycode = geometrycode; pass->defines = (defines) ? BLI_strdup(defines) : NULL; pass->compiled = false; BLI_spin_lock(&pass_cache_spin); if (pass_hash != NULL) { /* Add after the first pass having the same hash. */ pass->next = pass_hash->next; pass_hash->next = pass; } else { /* No other pass have same hash, just prepend to the list. */ BLI_LINKS_PREPEND(pass_cache, pass); } BLI_spin_unlock(&pass_cache_spin); } return pass; } static int count_active_texture_sampler(GPUShader *shader, char *source) { char *code = source; int samplers_id[64]; /* Remember this is per stage. */ int sampler_len = 0; while ((code = strstr(code, "uniform "))) { /* Move past "uniform". */ code += 7; /* Skip following spaces. */ while (*code == ' ') { code++; } /* Skip "i" from potential isamplers. */ if (*code == 'i') { code++; } /* Skip following spaces. */ if (gpu_str_prefix(code, "sampler")) { /* Move past "uniform". */ code += 7; /* Skip sampler type suffix. */ while (*code != ' ' && *code != '\0') { code++; } /* Skip following spaces. */ while (*code == ' ') { code++; } if (*code != '\0') { char sampler_name[64]; code = gpu_str_skip_token(code, sampler_name, sizeof(sampler_name)); int id = GPU_shader_get_uniform_ensure(shader, sampler_name); if (id == -1) { continue; } /* Catch duplicates. */ bool is_duplicate = false; for (int i = 0; i < sampler_len; ++i) { if (samplers_id[i] == id) { is_duplicate = true; } } if (!is_duplicate) { samplers_id[sampler_len] = id; sampler_len++; } } } } return sampler_len; } static bool gpu_pass_shader_validate(GPUPass *pass) { if (pass->shader == NULL) { return false; } /* NOTE: The only drawback of this method is that it will count a sampler * used in the fragment shader and only declared (but not used) in the vertex * shader as used by both. But this corner case is not happening for now. */ int vert_samplers_len = count_active_texture_sampler(pass->shader, pass->vertexcode); int frag_samplers_len = count_active_texture_sampler(pass->shader, pass->fragmentcode); int total_samplers_len = vert_samplers_len + frag_samplers_len; /* Validate against opengl limit. */ if ((frag_samplers_len > GPU_max_textures_frag()) || (frag_samplers_len > GPU_max_textures_vert())) { return false; } if (pass->geometrycode) { int geom_samplers_len = count_active_texture_sampler(pass->shader, pass->geometrycode); total_samplers_len += geom_samplers_len; if (geom_samplers_len > GPU_max_textures_geom()) { return false; } } return (total_samplers_len <= GPU_max_textures()); } void GPU_pass_compile(GPUPass *pass, const char *shname) { if (!pass->compiled) { pass->shader = GPU_shader_create( pass->vertexcode, pass->fragmentcode, pass->geometrycode, NULL, pass->defines, shname); /* NOTE: Some drivers / gpu allows more active samplers than the opengl limit. * We need to make sure to count active samplers to avoid undefined behavior. */ if (!gpu_pass_shader_validate(pass)) { if (pass->shader != NULL) { fprintf(stderr, "GPUShader: error: too many samplers in shader.\n"); GPU_shader_free(pass->shader); } pass->shader = NULL; } pass->compiled = true; } } void GPU_pass_release(GPUPass *pass) { BLI_assert(pass->refcount > 0); pass->refcount--; } static void gpu_pass_free(GPUPass *pass) { BLI_assert(pass->refcount == 0); if (pass->shader) { GPU_shader_free(pass->shader); } MEM_SAFE_FREE(pass->fragmentcode); MEM_SAFE_FREE(pass->geometrycode); MEM_SAFE_FREE(pass->vertexcode); MEM_SAFE_FREE(pass->defines); MEM_freeN(pass); } void GPU_pass_free_nodes(ListBase *nodes) { gpu_nodes_free(nodes); } void GPU_pass_cache_garbage_collect(void) { static int lasttime = 0; const int shadercollectrate = 60; /* hardcoded for now. */ int ctime = (int)PIL_check_seconds_timer(); if (ctime < shadercollectrate + lasttime) return; lasttime = ctime; BLI_spin_lock(&pass_cache_spin); GPUPass *next, **prev_pass = &pass_cache; for (GPUPass *pass = pass_cache; pass; pass = next) { next = pass->next; if (pass->refcount == 0) { /* Remove from list */ *prev_pass = next; gpu_pass_free(pass); } else { prev_pass = &pass->next; } } BLI_spin_unlock(&pass_cache_spin); } void GPU_pass_cache_init(void) { BLI_spin_init(&pass_cache_spin); } void GPU_pass_cache_free(void) { BLI_spin_lock(&pass_cache_spin); while (pass_cache) { GPUPass *next = pass_cache->next; gpu_pass_free(pass_cache); pass_cache = next; } BLI_spin_unlock(&pass_cache_spin); BLI_spin_end(&pass_cache_spin); }