/* * 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 * \ingroup gpu * * Convert material node-trees to GLSL. */ #include "MEM_guardedalloc.h" #include "DNA_customdata_types.h" #include "DNA_image_types.h" #include "BLI_blenlib.h" #include "BLI_dynstr.h" #include "BLI_ghash.h" #include "BLI_hash_mm2a.h" #include "BLI_link_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" #include "PIL_time.h" #include "BKE_material.h" #include "GPU_capabilities.h" #include "GPU_material.h" #include "GPU_shader.h" #include "GPU_uniform_buffer.h" #include "GPU_vertex_format.h" #include "BLI_sys_types.h" /* for intptr_t support */ #include "gpu_codegen.h" #include "gpu_material_library.h" #include "gpu_node_graph.h" #include #include extern char datatoc_gpu_shader_codegen_lib_glsl[]; extern char datatoc_gpu_shader_common_obinfos_lib_glsl[]; /* -------------------- 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 linked-list 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, ListBase *attributes) { BLI_HashMurmur2A hm2a; BLI_hash_mm2a_init(&hm2a, 0); BLI_hash_mm2a_add(&hm2a, (uchar *)frag_gen, strlen(frag_gen)); LISTBASE_FOREACH (GPUMaterialAttribute *, attr, attributes) { BLI_hash_mm2a_add(&hm2a, (uchar *)attr->name, strlen(attr->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) && (!STREQ(pass->defines, defs))) { /* Pass */ } else if ((geom != NULL) && (!STREQ(pass->geometrycode, geom))) { /* Pass */ } else if ((!STREQ(pass->fragmentcode, frag) == 0) && (STREQ(pass->vertexcode, vert))) { BLI_spin_unlock(&pass_cache_spin); return pass; } } BLI_spin_unlock(&pass_cache_spin); return NULL; } /* 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, "dot(%s.rgb, vec3(0.2126, 0.7152, 0.0722))", 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_data_type_to_string(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 const char *gpu_builtin_name(eGPUBuiltin builtin) { if (builtin == GPU_VIEW_MATRIX) { return "unfviewmat"; } if (builtin == GPU_OBJECT_MATRIX) { return "unfobmat"; } if (builtin == GPU_INVERSE_VIEW_MATRIX) { return "unfinvviewmat"; } if (builtin == GPU_INVERSE_OBJECT_MATRIX) { return "unfinvobmat"; } if (builtin == GPU_LOC_TO_VIEW_MATRIX) { return "unflocaltoviewmat"; } if (builtin == GPU_INVERSE_LOC_TO_VIEW_MATRIX) { return "unfinvlocaltoviewmat"; } if (builtin == GPU_VIEW_POSITION) { return "varposition"; } if (builtin == GPU_WORLD_NORMAL) { return "varwnormal"; } if (builtin == GPU_VIEW_NORMAL) { return "varnormal"; } if (builtin == GPU_OBJECT_COLOR) { return "unfobjectcolor"; } if (builtin == GPU_AUTO_BUMPSCALE) { return "unfobautobumpscale"; } if (builtin == GPU_CAMERA_TEXCO_FACTORS) { return "unfcameratexfactors"; } if (builtin == GPU_PARTICLE_SCALAR_PROPS) { return "unfparticlescalarprops"; } if (builtin == GPU_PARTICLE_LOCATION) { return "unfparticleco"; } if (builtin == GPU_PARTICLE_VELOCITY) { return "unfparticlevel"; } if (builtin == GPU_PARTICLE_ANG_VELOCITY) { return "unfparticleangvel"; } if (builtin == GPU_OBJECT_INFO) { return "unfobjectinfo"; } if (builtin == GPU_BARYCENTRIC_TEXCO) { return "unfbarycentrictex"; } if (builtin == GPU_BARYCENTRIC_DIST) { return "unfbarycentricdist"; } return ""; } static void codegen_set_unique_ids(GPUNodeGraph *graph) { int id = 1; LISTBASE_FOREACH (GPUNode *, node, &graph->nodes) { LISTBASE_FOREACH (GPUInput *, input, &node->inputs) { /* set id for unique names of uniform variables */ input->id = id++; } LISTBASE_FOREACH (GPUOutput *, output, &node->outputs) { /* set id for unique names of tmp variables storing output */ output->id = id++; } } } /** * It will create an UBO for GPUMaterial if there is any GPU_DYNAMIC_UBO. */ static int codegen_process_uniforms_functions(GPUMaterial *material, DynStr *ds, GPUNodeGraph *graph) { const char *name; int builtins = 0; ListBase ubo_inputs = {NULL, NULL}; /* Textures */ LISTBASE_FOREACH (GPUMaterialTexture *, tex, &graph->textures) { if (tex->colorband) { BLI_dynstr_appendf(ds, "uniform sampler1DArray %s;\n", tex->sampler_name); } else if (tex->tiled_mapping_name[0]) { BLI_dynstr_appendf(ds, "uniform sampler2DArray %s;\n", tex->sampler_name); BLI_dynstr_appendf(ds, "uniform sampler1DArray %s;\n", tex->tiled_mapping_name); } else { BLI_dynstr_appendf(ds, "uniform sampler2D %s;\n", tex->sampler_name); } } /* Volume Grids */ LISTBASE_FOREACH (GPUMaterialVolumeGrid *, grid, &graph->volume_grids) { BLI_dynstr_appendf(ds, "uniform sampler3D %s;\n", grid->sampler_name); BLI_dynstr_appendf(ds, "uniform mat4 %s = mat4(0.0);\n", grid->transform_name); } /* Print other uniforms */ LISTBASE_FOREACH (GPUNode *, node, &graph->nodes) { LISTBASE_FOREACH (GPUInput *, input, &node->inputs) { 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 (BLI_str_startswith(name, "unf")) { BLI_dynstr_appendf(ds, "uniform %s %s;\n", gpu_data_type_to_string(input->type), name); } else { BLI_dynstr_appendf(ds, "in %s %s;\n", gpu_data_type_to_string(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_data_type_to_string(input->type), input->id); codegen_print_datatype(ds, input->type, input->vec); BLI_dynstr_append(ds, ";\n"); } } } /* 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); LISTBASE_FOREACH (LinkData *, link, &ubo_inputs) { GPUInput *input = (GPUInput *)(link->data); BLI_dynstr_appendf(ds, " %s unf%d;\n", gpu_data_type_to_string(input->type), input->id); } BLI_dynstr_append(ds, "};\n"); BLI_freelistN(&ubo_inputs); } /* Generate the uniform attribute UBO if necessary. */ if (!BLI_listbase_is_empty(&graph->uniform_attrs.list)) { BLI_dynstr_append(ds, "\nstruct UniformAttributes {\n"); LISTBASE_FOREACH (GPUUniformAttr *, attr, &graph->uniform_attrs.list) { BLI_dynstr_appendf(ds, " vec4 attr%d;\n", attr->id); } BLI_dynstr_append(ds, "};\n"); BLI_dynstr_appendf(ds, "layout (std140) uniform %s {\n", GPU_ATTRIBUTE_UBO_BLOCK_NAME); BLI_dynstr_append(ds, " UniformAttributes uniform_attrs[DRW_RESOURCE_CHUNK_LEN];\n"); BLI_dynstr_append(ds, "};\n"); BLI_dynstr_append(ds, "#define GET_UNIFORM_ATTR(name) (uniform_attrs[resource_id].name)\n"); } BLI_dynstr_append(ds, "\n"); return builtins; } static void codegen_declare_tmps(DynStr *ds, GPUNodeGraph *graph) { LISTBASE_FOREACH (GPUNode *, node, &graph->nodes) { /* declare temporary variables for node output storage */ LISTBASE_FOREACH (GPUOutput *, output, &node->outputs) { if (output->type == GPU_CLOSURE) { BLI_dynstr_appendf(ds, " Closure tmp%d;\n", output->id); } else { BLI_dynstr_appendf(ds, " %s tmp%d;\n", gpu_data_type_to_string(output->type), output->id); } } } BLI_dynstr_append(ds, "\n"); } static void codegen_call_functions(DynStr *ds, GPUNodeGraph *graph) { LISTBASE_FOREACH (GPUNode *, node, &graph->nodes) { BLI_dynstr_appendf(ds, " %s(", node->name); LISTBASE_FOREACH (GPUInput *, input, &node->inputs) { if (input->source == GPU_SOURCE_TEX) { BLI_dynstr_append(ds, input->texture->sampler_name); } else if (input->source == GPU_SOURCE_TEX_TILED_MAPPING) { BLI_dynstr_append(ds, input->texture->tiled_mapping_name); } else if (input->source == GPU_SOURCE_VOLUME_GRID) { BLI_dynstr_append(ds, input->volume_grid->sampler_name); } else if (input->source == GPU_SOURCE_VOLUME_GRID_TRANSFORM) { BLI_dynstr_append(ds, input->volume_grid->transform_name); } 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_OBJECT_INFO) { BLI_dynstr_append(ds, "ObjectInfo"); } else if (input->builtin == GPU_OBJECT_COLOR) { BLI_dynstr_append(ds, "ObjectColor"); } 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 if (input->builtin == GPU_WORLD_NORMAL) { BLI_dynstr_append(ds, "facingwnormal"); } 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) { codegen_convert_datatype(ds, input->attr->gputype, input->type, "var", input->attr->id); } else if (input->source == GPU_SOURCE_UNIFORM_ATTR) { BLI_dynstr_appendf(ds, "GET_UNIFORM_ATTR(attr%d)", input->uniform_attr->id); } BLI_dynstr_append(ds, ", "); } LISTBASE_FOREACH (GPUOutput *, output, &node->outputs) { BLI_dynstr_appendf(ds, "tmp%d", output->id); if (output->next) { BLI_dynstr_append(ds, ", "); } } BLI_dynstr_append(ds, ");\n"); } } static void codegen_final_output(DynStr *ds, GPUOutput *finaloutput) { BLI_dynstr_appendf(ds, "return tmp%d;\n", finaloutput->id); } static char *code_generate_fragment(GPUMaterial *material, GPUNodeGraph *graph, const char *interface_str) { DynStr *ds = BLI_dynstr_new(); char *code; int builtins; codegen_set_unique_ids(graph); /* Attributes, Shader stage interface. */ if (interface_str) { BLI_dynstr_appendf(ds, "in codegenInterface {%s};\n\n", interface_str); } builtins = codegen_process_uniforms_functions(material, ds, graph); if (builtins & (GPU_OBJECT_INFO | GPU_OBJECT_COLOR)) { BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, datatoc_gpu_shader_codegen_lib_glsl); } BLI_dynstr_append(ds, "Closure nodetree_exec(void)\n{\n"); if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, " vec2 barytexco = barycentric_resolve(barycentricTexCo);\n"); } /* TODO(fclem): get rid of that. */ if (builtins & GPU_VIEW_MATRIX) { BLI_dynstr_append(ds, " #define viewmat ViewMatrix\n"); } if (builtins & GPU_CAMERA_TEXCO_FACTORS) { BLI_dynstr_append(ds, " #define camtexfac CameraTexCoFactors\n"); } if (builtins & GPU_OBJECT_MATRIX) { BLI_dynstr_append(ds, " #define objmat ModelMatrix\n"); } if (builtins & GPU_INVERSE_OBJECT_MATRIX) { BLI_dynstr_append(ds, " #define objinv ModelMatrixInverse\n"); } if (builtins & GPU_INVERSE_VIEW_MATRIX) { BLI_dynstr_append(ds, " #define viewinv ViewMatrixInverse\n"); } if (builtins & GPU_LOC_TO_VIEW_MATRIX) { BLI_dynstr_append(ds, " #define localtoviewmat (ViewMatrix * ModelMatrix)\n"); } if (builtins & GPU_INVERSE_LOC_TO_VIEW_MATRIX) { BLI_dynstr_append(ds, " #define invlocaltoviewmat (ModelMatrixInverse * ViewMatrixInverse)\n"); } if (builtins & GPU_VIEW_NORMAL) { BLI_dynstr_append(ds, "#ifdef HAIR_SHADER\n"); BLI_dynstr_append(ds, " vec3 n;\n"); BLI_dynstr_append(ds, " world_normals_get(n);\n"); BLI_dynstr_append(ds, " vec3 facingnormal = transform_direction(ViewMatrix, n);\n"); BLI_dynstr_append(ds, "#else\n"); BLI_dynstr_append(ds, " vec3 facingnormal = gl_FrontFacing ? viewNormal: -viewNormal;\n"); BLI_dynstr_append(ds, "#endif\n"); } if (builtins & GPU_WORLD_NORMAL) { BLI_dynstr_append(ds, " vec3 facingwnormal;\n"); if (builtins & GPU_VIEW_NORMAL) { BLI_dynstr_append(ds, "#ifdef HAIR_SHADER\n"); BLI_dynstr_append(ds, " facingwnormal = n;\n"); BLI_dynstr_append(ds, "#else\n"); BLI_dynstr_append(ds, " world_normals_get(facingwnormal);\n"); BLI_dynstr_append(ds, "#endif\n"); } else { BLI_dynstr_append(ds, " world_normals_get(facingwnormal);\n"); } } if (builtins & GPU_VIEW_POSITION) { BLI_dynstr_append(ds, " #define viewposition viewPosition\n"); } codegen_declare_tmps(ds, graph); codegen_call_functions(ds, graph); BLI_dynstr_append(ds, " #ifndef VOLUMETRICS\n"); BLI_dynstr_append(ds, " if (renderPassAOV) {\n"); BLI_dynstr_append(ds, " switch (render_pass_aov_hash()) {\n"); GSet *aovhashes_added = BLI_gset_int_new(__func__); LISTBASE_FOREACH (GPUNodeGraphOutputLink *, aovlink, &graph->outlink_aovs) { void *aov_key = POINTER_FROM_INT(aovlink->hash); if (BLI_gset_haskey(aovhashes_added, aov_key)) { continue; } BLI_dynstr_appendf(ds, " case %d: {\n ", aovlink->hash); codegen_final_output(ds, aovlink->outlink->output); BLI_dynstr_append(ds, " }\n"); BLI_gset_add(aovhashes_added, aov_key); } BLI_gset_free(aovhashes_added, NULL); BLI_dynstr_append(ds, " default: {\n"); BLI_dynstr_append(ds, " Closure no_aov = CLOSURE_DEFAULT;\n"); BLI_dynstr_append(ds, " no_aov.holdout = 1.0;\n"); BLI_dynstr_append(ds, " return no_aov;\n"); BLI_dynstr_append(ds, " }\n"); BLI_dynstr_append(ds, " }\n"); BLI_dynstr_append(ds, " } else {\n"); BLI_dynstr_append(ds, " #else /* VOLUMETRICS */\n"); BLI_dynstr_append(ds, " {\n"); BLI_dynstr_append(ds, " #endif /* VOLUMETRICS */\n "); codegen_final_output(ds, graph->outlink->output); BLI_dynstr_append(ds, " }\n"); BLI_dynstr_append(ds, "}\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_PROP_COLOR: return "c"; case CD_AUTO_FROM_NAME: return "a"; case CD_HAIRLENGTH: return "hl"; default: BLI_assert_msg(0, "GPUVertAttr Prefix type not found : This should not happen!"); return ""; } } /* We talk about shader stage interface, not to be mistaken with GPUShaderInterface. */ static char *code_generate_interface(GPUNodeGraph *graph, int builtins) { if (BLI_listbase_is_empty(&graph->attributes) && (builtins & (GPU_BARYCENTRIC_DIST | GPU_BARYCENTRIC_TEXCO)) == 0) { return NULL; } DynStr *ds = BLI_dynstr_new(); BLI_dynstr_append(ds, "\n"); LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph->attributes) { if (attr->type == CD_HAIRLENGTH) { BLI_dynstr_appendf(ds, "float var%d;\n", attr->id); } else { BLI_dynstr_appendf(ds, "%s var%d;\n", gpu_data_type_to_string(attr->gputype), attr->id); } } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, "vec2 barycentricTexCo;\n"); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_append(ds, "vec3 barycentricDist;\n"); } char *code = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); return code; } static char *code_generate_vertex(GPUNodeGraph *graph, const char *interface_str, const char *vert_code, int builtins) { DynStr *ds = BLI_dynstr_new(); BLI_dynstr_append(ds, datatoc_gpu_shader_codegen_lib_glsl); /* Inputs */ LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph->attributes) { const char *type_str = gpu_data_type_to_string(attr->gputype); const char *prefix = attr_prefix_get(attr->type); /* XXX FIXME: see notes in mesh_render_data_create() */ /* NOTE: Replicate changes to mesh_render_data_create() in draw_cache_impl_mesh.c */ if (attr->type == CD_ORCO) { /* OPTI: orco is computed from local positions, but only if no modifier is present. */ BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); BLI_dynstr_append(ds, "DEFINE_ATTR(vec4, orco);\n"); } else if (attr->type == CD_HAIRLENGTH) { BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); BLI_dynstr_append(ds, "DEFINE_ATTR(float, hairLen);\n"); } else if (attr->name[0] == '\0') { BLI_dynstr_appendf(ds, "DEFINE_ATTR(%s, %s);\n", type_str, prefix); BLI_dynstr_appendf(ds, "#define att%d %s\n", attr->id, prefix); } else { char attr_safe_name[GPU_MAX_SAFE_ATTR_NAME]; GPU_vertformat_safe_attr_name(attr->name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME); BLI_dynstr_appendf(ds, "DEFINE_ATTR(%s, %s%s);\n", type_str, prefix, attr_safe_name); BLI_dynstr_appendf(ds, "#define att%d %s%s\n", attr->id, prefix, attr_safe_name); } } /* Outputs interface */ if (interface_str) { BLI_dynstr_appendf(ds, "out codegenInterface {%s};\n\n", interface_str); } /* Prototype. Needed for hair functions. */ BLI_dynstr_append(ds, "void pass_attr(vec3 position, mat3 normalmat, mat4 modelmatinv);\n"); BLI_dynstr_append(ds, "#define USE_ATTR\n\n"); BLI_dynstr_append(ds, vert_code); BLI_dynstr_append(ds, "\n\n"); BLI_dynstr_append(ds, "void pass_attr(vec3 position, mat3 normalmat, mat4 modelmatinv) {\n"); /* GPU_BARYCENTRIC_TEXCO cannot be computed based on gl_VertexID * for MESH_SHADER because of indexed drawing. In this case a * geometry shader is needed. */ if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_appendf(ds, " barycentricTexCo = barycentric_get();\n"); } if (builtins & GPU_BARYCENTRIC_DIST) { BLI_dynstr_appendf(ds, " barycentricDist = vec3(0);\n"); } LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph->attributes) { if (attr->type == CD_TANGENT) { /* silly exception */ BLI_dynstr_appendf(ds, " var%d = tangent_get(att%d, normalmat);\n", attr->id, attr->id); } else if (attr->type == CD_ORCO) { BLI_dynstr_appendf( ds, " var%d = orco_get(position, modelmatinv, OrcoTexCoFactors, orco);\n", attr->id); } else if (attr->type == CD_HAIRLENGTH) { BLI_dynstr_appendf(ds, " var%d = hair_len_get(hair_get_strand_id(), hairLen);\n", attr->id); } else { const char *type_str = gpu_data_type_to_string(attr->gputype); BLI_dynstr_appendf(ds, " var%d = GET_ATTR(%s, att%d);\n", attr->id, type_str, attr->id); } } BLI_dynstr_append(ds, "}\n"); char *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(GPUNodeGraph *graph, const char *interface_str, const char *geom_code, int builtins) { if (!geom_code) { return NULL; } DynStr *ds = BLI_dynstr_new(); /* Attributes, Shader interface; */ if (interface_str) { BLI_dynstr_appendf(ds, "in codegenInterface {%s} dataAttrIn[];\n\n", interface_str); BLI_dynstr_appendf(ds, "out codegenInterface {%s} dataAttrOut;\n\n", interface_str); } BLI_dynstr_append(ds, datatoc_gpu_shader_codegen_lib_glsl); if (builtins & GPU_BARYCENTRIC_DIST) { /* geom_code should do something with this, but may not. */ BLI_dynstr_append(ds, "#define DO_BARYCENTRIC_DISTANCES\n"); } /* Generate varying assignments. */ BLI_dynstr_append(ds, "#define USE_ATTR\n"); /* This needs to be a define. Some drivers don't like variable vert index inside dataAttrIn. */ BLI_dynstr_append(ds, "#define pass_attr(vert) {\\\n"); if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, "dataAttrOut.barycentricTexCo = calc_barycentric_co(vert);\\\n"); } LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph->attributes) { /* TODO: let shader choose what to do depending on what the attribute is. */ BLI_dynstr_appendf(ds, "dataAttrOut.var%d = dataAttrIn[vert].var%d;\\\n", attr->id, attr->id); } BLI_dynstr_append(ds, "}\n\n"); BLI_dynstr_append(ds, geom_code); char *code = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); return code; } GPUShader *GPU_pass_shader_get(GPUPass *pass) { return pass->shader; } /* Pass create/free */ 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, GPUNodeGraph *graph, const char *vert_code, const char *geom_code, const char *frag_lib, const char *defines) { /* Prune the unused nodes and extract attributes before compiling so the * generated VBOs are ready to accept the future shader. */ gpu_node_graph_prune_unused(graph); gpu_node_graph_finalize_uniform_attrs(graph); int builtins = 0; LISTBASE_FOREACH (GPUNode *, node, &graph->nodes) { LISTBASE_FOREACH (GPUInput *, input, &node->inputs) { if (input->source == GPU_SOURCE_BUILTIN) { builtins |= input->builtin; } } } /* generate code */ char *interface_str = code_generate_interface(graph, builtins); char *fragmentgen = code_generate_fragment(material, graph, interface_str); /* Cache lookup: Reuse shaders already compiled */ uint32_t hash = gpu_pass_hash(fragmentgen, defines, &graph->attributes); GPUPass *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_SAFE_FREE(interface_str); 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. */ GSet *used_libraries = gpu_material_used_libraries(material); char *tmp = gpu_material_library_generate_code(used_libraries, frag_lib); char *geometrycode = code_generate_geometry(graph, interface_str, geom_code, builtins); char *vertexcode = code_generate_vertex(graph, interface_str, vert_code, builtins); char *fragmentcode = BLI_strdupcat(tmp, fragmentgen); MEM_SAFE_FREE(interface_str); MEM_freeN(fragmentgen); MEM_freeN(tmp); GPUPass *pass = NULL; if (pass_hash) { /* Cache lookup: Reuse shaders already compiled */ pass = gpu_pass_cache_resolve_collision( pass_hash, vertexcode, geometrycode, fragmentcode, defines, hash); } if (pass) { MEM_SAFE_FREE(vertexcode); MEM_SAFE_FREE(fragmentcode); MEM_SAFE_FREE(geometrycode); /* 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; } 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, const char *source) { const char *code = source; /* Remember this is per stage. */ GSet *sampler_ids = BLI_gset_int_new(__func__); int num_samplers = 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 (BLI_str_startswith(code, "sampler")) { /* Move past "uniform". */ code += 7; /* Skip sampler type suffix. */ while (!ELEM(*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(shader, sampler_name); if (id == -1) { continue; } /* Catch duplicates. */ if (BLI_gset_add(sampler_ids, POINTER_FROM_INT(id))) { num_samplers++; } } } } BLI_gset_free(sampler_ids, NULL); return num_samplers; } static bool gpu_pass_shader_validate(GPUPass *pass, GPUShader *shader) { if (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(shader, pass->vertexcode); int frag_samplers_len = count_active_texture_sampler(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()) || (vert_samplers_len > GPU_max_textures_vert())) { return false; } if (pass->geometrycode) { int geom_samplers_len = count_active_texture_sampler(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()); } bool GPU_pass_compile(GPUPass *pass, const char *shname) { bool success = true; if (!pass->compiled) { GPUShader *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, shader)) { success = false; if (shader != NULL) { fprintf(stderr, "GPUShader: error: too many samplers in shader.\n"); GPU_shader_free(shader); shader = NULL; } } pass->shader = shader; pass->compiled = true; } return success; } 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_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); } /* Module */ void gpu_codegen_init(void) { } void gpu_codegen_exit(void) { BKE_material_defaults_free_gpu(); GPU_shader_free_builtin_shaders(); }