Subdivision Surface: add dependency graph tracking when CPU mesh is needed #104461

Open
Alexander Gavrilov wants to merge 1 commits from angavrilov/blender:pr-depsgraph-subdiv-cpu into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
19 changed files with 97 additions and 17 deletions

View File

@ -19,6 +19,7 @@ extern "C" {
* programmatic way of detecting this. */
#define MAX_GPU_SUBDIV_SSBOS 12
struct Depsgraph;
struct Mesh;
struct Object;
struct Scene;
@ -68,12 +69,15 @@ bool BKE_subsurf_modifier_use_custom_loop_normals(const struct SubsurfModifierDa
* and supported by the GPU. It is mainly useful for showing UI messages.
*/
bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(
const struct SubsurfModifierData *smd, const struct Mesh *mesh);
const struct Depsgraph *depsgraph,
const struct Object *ob,
const struct Mesh *mesh,
const struct SubsurfModifierData *smd);
/**
* \param skip_check_is_last: When true, we assume that the modifier passed is the last enabled
* modifier in the stack.
*/
bool BKE_subsurf_modifier_can_do_gpu_subdiv(const struct Scene *scene,
bool BKE_subsurf_modifier_can_do_gpu_subdiv(const struct Depsgraph *depsgraph,
const struct Object *ob,
const struct Mesh *mesh,
const struct SubsurfModifierData *smd,

View File

@ -15,6 +15,8 @@
#include "BKE_modifier.h"
#include "BKE_subdiv.h"
#include "DEG_depsgraph_query.h"
#include "GPU_capabilities.h"
#include "GPU_context.h"
@ -112,8 +114,25 @@ static bool is_subdivision_evaluation_possible_on_gpu()
return true;
}
bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(const SubsurfModifierData *smd,
const Mesh *mesh)
static bool subsurf_modifier_has_cpu_dependents(const Depsgraph *depsgraph, const Object *ob)
{
/* The sculpt and paint mode UI requires the mesh - see sculpt_update_object. */
if ((ob->mode & OB_MODE_ALL_SCULPT) && DEG_is_active(depsgraph)) {

I can understand why paint mode needs the mesh, but from what I remember about sculpt mode, it doesn't seem to. What am I missing there?

I can understand why paint mode needs the mesh, but from what I remember about sculpt mode, it doesn't seem to. What am I missing there?

Anything that calls sculpt_update_object (e.g. via BKE_sculpt_update_object_after_eval) would access the mesh. It is possible that code retrieves the mesh even when it doesn't need it and can be improved, but that is probably out of the scope of this patch.

One thing though, this code here probably should check DEG_is_active too - the location calling to BKE_sculpt_update_object_after_eval does check it.

Anything that calls `sculpt_update_object` (e.g. via `BKE_sculpt_update_object_after_eval`) would access the mesh. It is possible that code retrieves the mesh even when it doesn't need it and can be improved, but that is probably out of the scope of this patch. One thing though, this code here probably should check `DEG_is_active` too - the location calling to `BKE_sculpt_update_object_after_eval` does check it.
return true;
}
/* Some dependencies request it through depsgraph. */
if (DEG_get_eval_flags_for_id(depsgraph, &ob->id) & DAG_EVAL_NEED_CPU_EVALUATED_MESH) {
return true;
}
return false;
}
bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(const Depsgraph *depsgraph,
const Object *ob,
const Mesh *mesh,
const SubsurfModifierData *smd)
{
if ((U.gpu_flag & USER_GPU_FLAG_SUBDIVISION_EVALUATION) == 0) {
/* GPU subdivision is explicitly disabled, so we don't force it. */
@ -125,10 +144,11 @@ bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(const SubsurfMod
return false;
}
return subsurf_modifier_use_autosmooth_or_split_normals(smd, mesh);
return subsurf_modifier_use_autosmooth_or_split_normals(smd, mesh) ||
subsurf_modifier_has_cpu_dependents(depsgraph, ob);
}
bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene,
bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Depsgraph *depsgraph,
const Object *ob,
const Mesh *mesh,
const SubsurfModifierData *smd,
@ -144,6 +164,12 @@ bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene,
return false;
}
/* Deactivate if some other dependencies need a final CPU mesh. */
if (subsurf_modifier_has_cpu_dependents(depsgraph, ob)) {
return false;
}
const Scene *scene = DEG_get_evaluated_scene(depsgraph);
ModifierData *md = modifier_get_last_enabled_for_mode(scene, ob, required_mode);
if (md != (const ModifierData *)smd) {
return false;

View File

@ -55,6 +55,8 @@ enum {
/* A shrinkwrap modifier or constraint targeting this mesh needs information
* about non-manifold boundary edges for the Target Normal Project mode. */
DAG_EVAL_NEED_SHRINKWRAP_BOUNDARY = (1 << 1),
/* A modifier or constraints needs fully subdivided mesh on the CPU. */
DAG_EVAL_NEED_CPU_EVALUATED_MESH = (1 << 2),
};
#ifdef __cplusplus

View File

@ -134,6 +134,9 @@ void DEG_add_collection_geometry_relation(struct DepsNodeHandle *node_handle,
void DEG_add_collection_geometry_customdata_mask(struct DepsNodeHandle *node_handle,
struct Collection *collection,
const struct CustomData_MeshMasks *masks);
void DEG_add_collection_geometry_special_eval_flag(struct DepsNodeHandle *node_handle,
struct Collection *collection,
uint32_t flag);
void DEG_add_simulation_relation(struct DepsNodeHandle *node_handle,
struct Simulation *simulation,
const char *description);

View File

@ -1312,6 +1312,7 @@ void DepsgraphRelationBuilder::build_constraints(ID *id,
add_relation(target_transform_key, constraint_op_key, cti->name);
add_relation(target_geometry_key, constraint_op_key, cti->name);
add_customdata_mask(ct->tar, DEGCustomDataMeshMasks::MaskVert(CD_MASK_MDEFORMVERT));
add_special_eval_flag(&ct->tar->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
else if (con->type == CONSTRAINT_TYPE_SHRINKWRAP) {
bShrinkwrapConstraint *scon = (bShrinkwrapConstraint *)con->data;
@ -1319,6 +1320,7 @@ void DepsgraphRelationBuilder::build_constraints(ID *id,
/* Constraints which requires the target object surface. */
ComponentKey target_key(&ct->tar->id, NodeType::GEOMETRY);
add_relation(target_key, constraint_op_key, cti->name);
add_special_eval_flag(&ct->tar->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
/* Add dependency on normal layers if necessary. */
if (ct->tar->type == OB_MESH && scon->shrinkType != MOD_SHRINKWRAP_NEAREST_VERTEX) {
@ -2019,10 +2021,13 @@ void DepsgraphRelationBuilder::build_rigidbody(Scene *scene)
add_relation(object_transform_simulation_init_key,
rb_simulate_key,
"Object Transform -> Rigidbody Sim Eval");
/* Geometry must be known to create the rigid body. RBO_MESH_BASE
* uses the non-evaluated mesh, so then the evaluation is
* unnecessary. */
if (rigidbody_object_depends_on_evaluated_geometry(object->rigidbody_object)) {
eRigidBody_MeshSource mesh_source = rigidbody_object_depends_on_evaluated_geometry(
object->rigidbody_object);
if (mesh_source != RBO_MESH_BASE) {
/* NOTE: We prefer this relation to be never killed, to avoid
* access partially evaluated mesh from solver. */
ComponentKey object_geometry_key(&object->id, NodeType::GEOMETRY);
@ -2030,6 +2035,10 @@ void DepsgraphRelationBuilder::build_rigidbody(Scene *scene)
rb_simulate_key,
"Object Geom Eval -> Rigidbody Sim Eval",
RELATION_FLAG_GODMODE);
/* Request the CPU mesh when necessary. */
if (mesh_source == RBO_MESH_FINAL) {
add_special_eval_flag(&object->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
}
/* Final transform is whatever the solver gave to us. */

View File

@ -105,17 +105,16 @@ Relation *DepsgraphRelationBuilder::add_node_handle_relation(const KeyType &key_
return nullptr;
}
static inline bool rigidbody_object_depends_on_evaluated_geometry(const RigidBodyOb *rbo)
static inline eRigidBody_MeshSource rigidbody_object_depends_on_evaluated_geometry(
const RigidBodyOb *rbo)
{
if (rbo == nullptr) {
return false;
return RBO_MESH_BASE;
}
if (ELEM(rbo->shape, RB_SHAPE_CONVEXH, RB_SHAPE_TRIMESH)) {
if (rbo->mesh_source != RBO_MESH_BASE) {
return true;
}
return static_cast<eRigidBody_MeshSource>(rbo->mesh_source);
}
return false;
return RBO_MESH_BASE;
}
template<typename KeyTo>

View File

@ -122,6 +122,19 @@ void DEG_add_collection_geometry_customdata_mask(DepsNodeHandle *node_handle,
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
void DEG_add_collection_geometry_special_eval_flag(struct DepsNodeHandle *node_handle,
struct Collection *collection,
uint32_t flag)
{
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, ob) {
DEG_add_special_eval_flag(node_handle, &ob->id, flag);
if (ob->type == OB_EMPTY && ob->instance_collection != nullptr) {
DEG_add_collection_geometry_special_eval_flag(node_handle, ob->instance_collection, flag);
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
void DEG_add_simulation_relation(DepsNodeHandle *node_handle,
Simulation *simulation,
const char *description)

View File

@ -140,6 +140,10 @@ void DEG_add_forcefield_relations(DepsNodeHandle *handle,
DEG_add_object_pointcache_relation(handle, relation->ob, DEG_OB_COMP_GEOMETRY, name);
}
if (ELEM(relation->pd->shape, PFIELD_SHAPE_POINTS)) {
DEG_add_special_eval_flag(handle, &relation->ob->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
/* Smoke flow relations. */
if (relation->pd->forcefield == PFIELD_FLUIDFLOW && relation->pd->f_source != nullptr) {
DEG_add_object_pointcache_relation(

View File

@ -209,6 +209,7 @@ static void updateDepsgraph(GpencilModifierData *md,
DEG_add_object_relation(ctx->node, mmd->target, DEG_OB_COMP_TRANSFORM, "Shrinkwrap Modifier");
DEG_add_object_relation(ctx->node, mmd->target, DEG_OB_COMP_GEOMETRY, "Shrinkwrap Modifier");
DEG_add_customdata_mask(ctx->node, mmd->target, &mask);
DEG_add_special_eval_flag(ctx->node, &mmd->target->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
if (mmd->shrink_type == MOD_SHRINKWRAP_TARGET_PROJECT) {
DEG_add_special_eval_flag(ctx->node, &mmd->target->id, DAG_EVAL_NEED_SHRINKWRAP_BOUNDARY);
}
@ -219,6 +220,7 @@ static void updateDepsgraph(GpencilModifierData *md,
DEG_add_object_relation(
ctx->node, mmd->aux_target, DEG_OB_COMP_GEOMETRY, "Shrinkwrap Modifier");
DEG_add_customdata_mask(ctx->node, mmd->aux_target, &mask);
DEG_add_special_eval_flag(ctx->node, &mmd->aux_target->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
if (mmd->shrink_type == MOD_SHRINKWRAP_TARGET_PROJECT) {
DEG_add_special_eval_flag(
ctx->node, &mmd->aux_target->id, DAG_EVAL_NEED_SHRINKWRAP_BOUNDARY);

View File

@ -82,10 +82,12 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
if (amd->start_cap != nullptr) {
DEG_add_object_relation(
ctx->node, amd->start_cap, DEG_OB_COMP_GEOMETRY, "Array Modifier Start Cap");
DEG_add_special_eval_flag(ctx->node, &amd->start_cap->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
if (amd->end_cap != nullptr) {
DEG_add_object_relation(
ctx->node, amd->end_cap, DEG_OB_COMP_GEOMETRY, "Array Modifier End Cap");
DEG_add_special_eval_flag(ctx->node, &amd->end_cap->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
if (amd->curve_ob) {
DEG_add_object_relation(

View File

@ -108,12 +108,15 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
if ((bmd->flag & eBooleanModifierFlag_Object) && bmd->object != nullptr) {
DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_TRANSFORM, "Boolean Modifier");
DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_GEOMETRY, "Boolean Modifier");
DEG_add_special_eval_flag(ctx->node, &bmd->object->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
Collection *col = bmd->collection;
if ((bmd->flag & eBooleanModifierFlag_Collection) && col != nullptr) {
DEG_add_collection_geometry_relation(ctx->node, col, "Boolean Modifier");
DEG_add_collection_geometry_special_eval_flag(
ctx->node, col, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
/* We need own transformation as well. */
DEG_add_depends_on_transform_relation(ctx->node, "Boolean Modifier");

View File

@ -124,6 +124,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
DEG_add_object_relation(
ctx->node, dtmd->ob_source, DEG_OB_COMP_GEOMETRY, "DataTransfer Modifier");
DEG_add_customdata_mask(ctx->node, dtmd->ob_source, &cddata_masks);
DEG_add_special_eval_flag(ctx->node, &dtmd->ob_source->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
if (dtmd->flags & MOD_DATATRANSFER_OBSRC_TRANSFORM) {
DEG_add_object_relation(

View File

@ -66,6 +66,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
ctx->node, mvmd->object, DEG_OB_COMP_GEOMETRY, "Mesh to Volume Modifier");
DEG_add_object_relation(
ctx->node, mvmd->object, DEG_OB_COMP_TRANSFORM, "Mesh to Volume Modifier");
DEG_add_special_eval_flag(ctx->node, &mvmd->object->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
}

View File

@ -156,6 +156,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
if (mmd->object != NULL) {
DEG_add_object_relation(ctx->node, mmd->object, DEG_OB_COMP_TRANSFORM, "Mesh Deform Modifier");
DEG_add_object_relation(ctx->node, mmd->object, DEG_OB_COMP_GEOMETRY, "Mesh Deform Modifier");
DEG_add_special_eval_flag(ctx->node, &mmd->object->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
/* We need own transformation as well. */
DEG_add_depends_on_transform_relation(ctx->node, "Mesh Deform Modifier");

View File

@ -261,6 +261,8 @@ static void add_collection_relation(const ModifierUpdateDepsgraphContext *ctx,
{
DEG_add_collection_geometry_relation(ctx->node, &collection, "Nodes Modifier");
DEG_add_collection_geometry_customdata_mask(ctx->node, &collection, &dependency_data_mask);
DEG_add_collection_geometry_special_eval_flag(
ctx->node, &collection, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Object &object)
@ -273,6 +275,7 @@ static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Objec
else if (DEG_object_has_geometry_component(&object)) {
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
DEG_add_special_eval_flag(ctx->node, &object.id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
}
}

View File

@ -163,6 +163,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
DEG_add_object_relation(ctx->node, smd->target, DEG_OB_COMP_TRANSFORM, "Shrinkwrap Modifier");
DEG_add_object_relation(ctx->node, smd->target, DEG_OB_COMP_GEOMETRY, "Shrinkwrap Modifier");
DEG_add_customdata_mask(ctx->node, smd->target, &mask);
DEG_add_special_eval_flag(ctx->node, &smd->target->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
if (smd->shrinkType == MOD_SHRINKWRAP_TARGET_PROJECT) {
DEG_add_special_eval_flag(ctx->node, &smd->target->id, DAG_EVAL_NEED_SHRINKWRAP_BOUNDARY);
}
@ -173,6 +174,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
DEG_add_object_relation(
ctx->node, smd->auxTarget, DEG_OB_COMP_GEOMETRY, "Shrinkwrap Modifier");
DEG_add_customdata_mask(ctx->node, smd->auxTarget, &mask);
DEG_add_special_eval_flag(ctx->node, &smd->auxTarget->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
if (smd->shrinkType == MOD_SHRINKWRAP_TARGET_PROJECT) {
DEG_add_special_eval_flag(ctx->node, &smd->auxTarget->id, DAG_EVAL_NEED_SHRINKWRAP_BOUNDARY);
}

View File

@ -242,14 +242,14 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
/* Delay evaluation to the draw code if possible, provided we do not have to apply the modifier.
*/
if ((ctx->flag & MOD_APPLY_TO_BASE_MESH) == 0) {
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
const bool is_render_mode = (ctx->flag & MOD_APPLY_RENDER) != 0;
/* Same check as in `DRW_mesh_batch_cache_create_requested` to keep both code coherent. The
* difference is that here we do not check for the final edit mesh pointer as it is not yet
* assigned at this stage of modifier stack evaluation. */
const bool is_editmode = (mesh->edit_mesh != nullptr);
const int required_mode = BKE_subsurf_modifier_eval_required_mode(is_render_mode, is_editmode);
if (BKE_subsurf_modifier_can_do_gpu_subdiv(scene, ctx->object, mesh, smd, required_mode)) {
if (BKE_subsurf_modifier_can_do_gpu_subdiv(
ctx->depsgraph, ctx->object, mesh, smd, required_mode)) {
subdiv_cache_mesh_wrapper_settings(ctx, mesh, smd, runtime_data);
return result;
}
@ -425,8 +425,10 @@ static void panel_draw(const bContext *C, Panel *panel)
SubsurfModifierData *smd = static_cast<SubsurfModifierData *>(ptr->data);
Object *ob = static_cast<Object *>(ob_ptr.data);
const Mesh *mesh = static_cast<const Mesh *>(ob->data);
if (BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(smd, mesh)) {
uiItemL(layout, "Autosmooth or custom normals detected, disabling GPU subdivision", ICON_INFO);
if (BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(depsgraph, ob, mesh, smd)) {
uiItemL(layout,
"Disabling GPU subdivision due to autosmooth, custom normals or dependencies",

I'm not sure an optimization like this deserves a UI label. If there is a way to show whether GPU subdivision is being used, it should probably be more subtle IMO.

I'm not sure an optimization like this deserves a UI label. If there is a way to show whether GPU subdivision is being used, it should probably be more subtle IMO.

Well, I wasn't the one who put this here, I just change the text to reflect my additions ;)

Well, I wasn't the one who put this here, I just change the text to reflect my additions ;)

Custom normals are incompatible with subdivision surfaces. The fact that GPU subdivision is disabled in the presence of such normals is to start a differentiation between workflows with custom shading and workflows with subdivision surfaces. See #68891 and #68893. This is what this UI label is for. Essentially to put in users mind that subdivision and custom normals should be different workflows. It only affects GPU subdivision as it is easier/more acceptable to prevent new features from introducing "bad" workflows than breaking compatibility.

Custom normals are incompatible with subdivision surfaces. The fact that GPU subdivision is disabled in the presence of such normals is to start a differentiation between workflows with custom shading and workflows with subdivision surfaces. See #68891 and #68893. This is what this UI label is for. Essentially to put in users mind that subdivision and custom normals should be different workflows. It only affects GPU subdivision as it is easier/more acceptable to prevent new features from introducing "bad" workflows than breaking compatibility.

Custom normals are incompatible with subdivision surfaces. ... Essentially to put in users mind that subdivision and custom normals should be different workflows.

If you really mean this, rather than just simplifying the phrase 'the GPU implementation of subdivision surfaces', I think the NPR people will very much beg to differ. In fact, the subdivision surface modifier specifically has an option to subdivide and interpolate custom normals.

In my view the purpose of the message is to let the user know when the GPU optimization won't be used, so they can be aware and plan accordingly. IIRC something similar happened in 2.7* versions. In #104441 I even added a message tracking the internal state when it is actually being used for feedback.

> Custom normals are incompatible with subdivision surfaces. ... Essentially to put in users mind that subdivision and custom normals should be different workflows. If you really mean this, rather than just simplifying the phrase 'the *GPU implementation* of subdivision surfaces', I think the NPR people will *very much* beg to differ. In fact, the subdivision surface modifier specifically has an option to subdivide and interpolate custom normals. In my view the purpose of the message is to let the user know when the GPU optimization won't be used, so they can be aware and plan accordingly. IIRC something similar happened in 2.7* versions. In #104441 I even added a message tracking the internal state when it is actually being used for feedback.
ICON_INFO);
}
else if (Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob)) {
if (ModifierData *md_eval = BKE_modifiers_findby_name(ob_eval, smd->modifier.name)) {

View File

@ -274,6 +274,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
if (smd->target != nullptr) {
DEG_add_object_relation(
ctx->node, smd->target, DEG_OB_COMP_GEOMETRY, "Surface Deform Modifier");
DEG_add_special_eval_flag(ctx->node, &smd->target->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
}

View File

@ -381,6 +381,8 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
wmd->proximity_mode == MOD_WVG_PROXIMITY_GEOMETRY) {
DEG_add_object_relation(
ctx->node, wmd->proximity_ob_target, DEG_OB_COMP_GEOMETRY, "WeightVGProximity Modifier");
DEG_add_special_eval_flag(
ctx->node, &wmd->proximity_ob_target->id, DAG_EVAL_NEED_CPU_EVALUATED_MESH);
}
need_transform_relation = true;
}