Fix: GPU: Avoid GPUMaterial/Pass collisions between engines #115819

Merged
Miguel Pozo merged 3 commits from pragma37/blender:pull-draw-fix-shader-collision into main 2023-12-07 16:48:26 +01:00
9 changed files with 68 additions and 29 deletions

View File

@ -41,7 +41,8 @@ using namespace nodes::derived_node_tree_types;
ShaderOperation::ShaderOperation(Context &context, ShaderCompileUnit &compile_unit)
: Operation(context), compile_unit_(compile_unit)
{
material_ = GPU_material_from_callbacks(&construct_material, &generate_code, this);
material_ = GPU_material_from_callbacks(
GPU_MAT_COMPOSITOR, &construct_material, &generate_code, this);
GPU_material_status_set(material_, GPU_MAT_QUEUED);
GPU_material_compile(material_);
}

View File

@ -1384,11 +1384,13 @@ static GPUMaterial *eevee_material_get_ex(
if (ma) {
bNodeTree *ntree = !is_default ? ma->nodetree : EEVEE_shader_default_surface_nodetree(ma);
mat = DRW_shader_from_material(ma, ntree, options, is_volume, deferred, cbfn, nullptr);
mat = DRW_shader_from_material(
ma, ntree, GPU_MAT_EEVEE_LEGACY, options, is_volume, deferred, cbfn, nullptr);
}
else {
bNodeTree *ntree = !is_default ? wo->nodetree : EEVEE_shader_default_world_nodetree(wo);
mat = DRW_shader_from_world(wo, ntree, options, is_volume, deferred, cbfn, nullptr);
mat = DRW_shader_from_world(
wo, ntree, GPU_MAT_EEVEE_LEGACY, options, is_volume, deferred, cbfn, nullptr);
}
return mat;
}

View File

@ -666,8 +666,14 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat,
uint64_t shader_uuid = shader_uuid_from_material_type(
pipeline_type, geometry_type, displacement_type, blender_mat->blend_flag);
return DRW_shader_from_material(
blender_mat, nodetree, shader_uuid, is_volume, deferred_compilation, codegen_callback, this);
return DRW_shader_from_material(blender_mat,
nodetree,
GPU_MAT_EEVEE,
shader_uuid,
is_volume,
deferred_compilation,
codegen_callback,
this);
}
GPUMaterial *ShaderModule::world_shader_get(::World *blender_world,
@ -681,8 +687,14 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world,
uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type);
return DRW_shader_from_world(
blender_world, nodetree, shader_uuid, is_volume, defer_compilation, codegen_callback, this);
return DRW_shader_from_world(blender_world,
nodetree,
GPU_MAT_EEVEE,
shader_uuid,
is_volume,
defer_compilation,
codegen_callback,
this);
}
/* Variation to compile a material only with a nodetree. Caller needs to maintain the list of
@ -702,6 +714,7 @@ GPUMaterial *ShaderModule::material_shader_get(const char *name,
nodetree,
&materials,
name,
GPU_MAT_EEVEE,
shader_uuid,
is_volume,
false,

View File

@ -296,6 +296,7 @@ struct GPUShader *DRW_shader_create_fullscreen_with_shaderlib_ex(const char *fra
struct GPUMaterial *DRW_shader_from_world(struct World *wo,
struct bNodeTree *ntree,
eGPUMaterialEngine engine,
const uint64_t shader_id,
const bool is_volume_shader,
bool deferred,
@ -303,6 +304,7 @@ struct GPUMaterial *DRW_shader_from_world(struct World *wo,
void *thunk);
struct GPUMaterial *DRW_shader_from_material(struct Material *ma,
struct bNodeTree *ntree,
eGPUMaterialEngine engine,
const uint64_t shader_id,
const bool is_volume_shader,
bool deferred,

View File

@ -493,6 +493,7 @@ GPUShader *DRW_shader_create_fullscreen_with_shaderlib_ex(const char *frag,
GPUMaterial *DRW_shader_from_world(World *wo,
bNodeTree *ntree,
eGPUMaterialEngine engine,
const uint64_t shader_id,
const bool is_volume_shader,
bool deferred,
@ -505,6 +506,7 @@ GPUMaterial *DRW_shader_from_world(World *wo,
ntree,
&wo->gpumaterial,
wo->id.name,
engine,
shader_id,
is_volume_shader,
false,
@ -525,6 +527,7 @@ GPUMaterial *DRW_shader_from_world(World *wo,
GPUMaterial *DRW_shader_from_material(Material *ma,
bNodeTree *ntree,
eGPUMaterialEngine engine,
const uint64_t shader_id,
const bool is_volume_shader,
bool deferred,
@ -537,6 +540,7 @@ GPUMaterial *DRW_shader_from_material(Material *ma,
ntree,
&ma->gpumaterial,
ma->id.name,
engine,
shader_id,
is_volume_shader,
false,

View File

@ -233,19 +233,19 @@ struct GPUUniformBuf *GPU_material_sss_profile_get(GPUMaterial *material,
/**
* High level functions to create and use GPU materials.
*/
GPUMaterial *GPU_material_from_nodetree_find(struct ListBase *gpumaterials,
const void *engine_type,
int options);
/**
* \note Caller must use #GPU_material_from_nodetree_find to re-use existing materials,
* This is enforced since constructing other arguments to this function may be expensive
* so only do this when they are needed.
*/
typedef enum eGPUMaterialEngine {
GPU_MAT_EEVEE_LEGACY = 0,
GPU_MAT_EEVEE,
GPU_MAT_COMPOSITOR,
} eGPUMaterialEngine;
GPUMaterial *GPU_material_from_nodetree(struct Scene *scene,
struct Material *ma,
struct bNodeTree *ntree,
struct ListBase *gpumaterials,
const char *name,
eGPUMaterialEngine engine,
uint64_t shader_uuid,
bool is_volume_shader,
bool is_lookdev,
@ -421,7 +421,8 @@ typedef void (*ConstructGPUMaterialFn)(void *thunk, GPUMaterial *material);
/* Construct a GPU material from a set of callbacks. See the callback types for more information.
* The given thunk will be passed as the first parameter of each callback. */
GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_function_cb,
GPUMaterial *GPU_material_from_callbacks(eGPUMaterialEngine engine,
ConstructGPUMaterialFn construct_function_cb,
GPUCodegenCallbackFn generate_code_function_cb,
void *thunk);

View File

@ -97,6 +97,8 @@ struct GPUPass {
uint refcount;
/** The last time the refcount was greater than 0. */
int gc_timestamp;
/** The engine type this pass is compiled for. */
eGPUMaterialEngine engine;
/** Identity hash generated from all GLSL code. */
uint32_t hash;
/** Did we already tried to compile the attached GPUShader. */
@ -122,12 +124,12 @@ static SpinLock pass_cache_spin;
/* 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)
static GPUPass *gpu_pass_cache_lookup(eGPUMaterialEngine engine, 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) {
if (pass->hash == hash && pass->engine == engine) {
BLI_spin_unlock(&pass_cache_spin);
return pass;
}
@ -157,10 +159,12 @@ static GPUPass *gpu_pass_cache_resolve_collision(GPUPass *pass,
GPUShaderCreateInfo *info,
uint32_t hash)
{
eGPUMaterialEngine engine = pass->engine;
BLI_spin_lock(&pass_cache_spin);
for (; pass && (pass->hash == hash); pass = pass->next) {
if (*reinterpret_cast<ShaderCreateInfo *>(info) ==
*reinterpret_cast<ShaderCreateInfo *>(pass->create_info))
*reinterpret_cast<ShaderCreateInfo *>(pass->create_info) &&
pass->engine == engine)
{
BLI_spin_unlock(&pass_cache_spin);
return pass;
@ -732,6 +736,7 @@ void GPUCodegen::generate_graphs()
GPUPass *GPU_generate_pass(GPUMaterial *material,
GPUNodeGraph *graph,
eGPUMaterialEngine engine,
GPUCodegenCallbackFn finalize_source_cb,
void *thunk,
bool optimize_graph)
@ -763,7 +768,7 @@ GPUPass *GPU_generate_pass(GPUMaterial *material,
* NOTE: We only perform cache look-up for non-optimized shader
* graphs, as baked constant data among other optimizations will generate too many
* shader source permutations, with minimal re-usability. */
pass_hash = gpu_pass_cache_lookup(codegen.hash_get());
pass_hash = gpu_pass_cache_lookup(engine, codegen.hash_get());
/* FIXME(fclem): This is broken. Since we only check for the hash and not the full source
* there is no way to have a collision currently. Some advocated to only use a bigger hash. */
@ -813,6 +818,7 @@ GPUPass *GPU_generate_pass(GPUMaterial *material,
pass->shader = nullptr;
pass->refcount = 1;
pass->create_info = codegen.create_info;
pass->engine = engine;
pass->hash = codegen.hash_get();
pass->compiled = false;
pass->cached = false;

View File

@ -25,6 +25,7 @@ typedef struct GPUPass GPUPass;
GPUPass *GPU_generate_pass(GPUMaterial *material,
struct GPUNodeGraph *graph,
eGPUMaterialEngine engine,
GPUCodegenCallbackFn finalize_source_cb,
void *thunk,
bool optimize_graph);

View File

@ -99,8 +99,9 @@ struct GPUMaterial {
eGPUMaterialStatus status;
/** Some flags about the nodetree & the needed resources. */
eGPUMaterialFlag flag;
/* Identify shader variations (shadow, probe, world background...).
* Should be unique even across render engines. */
/** The engine type this material is compiled for. */
eGPUMaterialEngine engine;
/* Identify shader variations (shadow, probe, world background...) */
uint64_t uuid;
/* Number of generated function. */
int generated_function_len;
@ -821,6 +822,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
bNodeTree *ntree,
ListBase *gpumaterials,
const char *name,
eGPUMaterialEngine engine,
uint64_t shader_uuid,
bool is_volume_shader,
bool is_lookdev,
@ -830,7 +832,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
/* Search if this material is not already compiled. */
LISTBASE_FOREACH (LinkData *, link, gpumaterials) {
GPUMaterial *mat = (GPUMaterial *)link->data;
if (mat->uuid == shader_uuid) {
if (mat->uuid == shader_uuid && mat->engine == engine) {
return mat;
}
}
@ -838,6 +840,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
GPUMaterial *mat = static_cast<GPUMaterial *>(MEM_callocN(sizeof(GPUMaterial), "GPUMaterial"));
mat->ma = ma;
mat->scene = scene;
mat->engine = engine;
mat->uuid = shader_uuid;
mat->flag = GPU_MATFLAG_UPDATED;
mat->status = GPU_MAT_CREATED;
@ -860,7 +863,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
{
/* Create source code and search pass cache for an already compiled version. */
mat->pass = GPU_generate_pass(mat, &mat->graph, callback, thunk, false);
mat->pass = GPU_generate_pass(mat, &mat->graph, engine, callback, thunk, false);
if (mat->pass == nullptr) {
/* We had a cache hit and the shader has already failed to compile. */
@ -891,7 +894,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
mat->optimize_pass_info.callback = callback;
mat->optimize_pass_info.thunk = thunk;
#else
mat->optimized_pass = GPU_generate_pass(mat, &mat->graph, callback, thunk, true);
mat->optimized_pass = GPU_generate_pass(mat, &mat->graph, engine, callback, thunk, true);
if (mat->optimized_pass == nullptr) {
/* Failed to create optimized pass. */
gpu_node_graph_free_nodes(&mat->graph);
@ -1024,8 +1027,12 @@ void GPU_material_optimize(GPUMaterial *mat)
* optimal, as these do not benefit from caching, due to baked constants. However, this could
* possibly be cause for concern for certain cases. */
if (!mat->optimized_pass) {
mat->optimized_pass = GPU_generate_pass(
mat, &mat->graph, mat->optimize_pass_info.callback, mat->optimize_pass_info.thunk, true);
mat->optimized_pass = GPU_generate_pass(mat,
&mat->graph,
mat->engine,
mat->optimize_pass_info.callback,
mat->optimize_pass_info.thunk,
true);
BLI_assert(mat->optimized_pass);
}
#else
@ -1097,7 +1104,8 @@ void GPU_materials_free(Main *bmain)
BKE_material_defaults_free_gpu();
}
GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_function_cb,
GPUMaterial *GPU_material_from_callbacks(eGPUMaterialEngine engine,
ConstructGPUMaterialFn construct_function_cb,
GPUCodegenCallbackFn generate_code_function_cb,
void *thunk)
{
@ -1110,6 +1118,7 @@ GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_functi
material->optimization_status = GPU_MAT_OPTIMIZATION_SKIP;
material->optimized_pass = nullptr;
material->default_mat = nullptr;
material->engine = engine;
/* Construct the material graph by adding and linking the necessary GPU material nodes. */
construct_function_cb(thunk, material);
@ -1119,7 +1128,7 @@ GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_functi
/* Lookup an existing pass in the cache or generate a new one. */
material->pass = GPU_generate_pass(
material, &material->graph, generate_code_function_cb, thunk, false);
material, &material->graph, material->engine, generate_code_function_cb, thunk, false);
material->optimized_pass = nullptr;
/* The pass already exists in the pass cache but its shader already failed to compile. */