PyAPI: optimize depsgraph use in PyDrivers

Avoid re-creating & freeing the depsgraph for every driver evaluation.

Now the depsgraph is kept in the name-space (matching self),
only re-created when the value changes.

In a contrived test-case with many drivers this gave ~15% overall
speedup for animation playback.
This commit is contained in:
Campbell Barton 2022-03-08 22:07:59 +11:00
parent f76f48be23
commit 73dc8c24e4
3 changed files with 41 additions and 38 deletions

View File

@ -162,9 +162,11 @@ static struct {
/* borrowed reference to the 'self' in 'bpy_pydriver_Dict'
* keep for as long as the same self is used. */
PyObject *self;
BPy_StructRNA *depsgraph;
} g_pydriver_state_prev = {
.evaltime = FLT_MAX,
.self = NULL,
.depsgraph = NULL,
};
static void bpy_pydriver_namespace_update_frame(const float evaltime)
@ -199,6 +201,38 @@ static void bpy_pydriver_namespace_clear_self(void)
}
}
static PyObject *bpy_pydriver_depsgraph_as_pyobject(struct Depsgraph *depsgraph)
{
struct PointerRNA depsgraph_ptr;
RNA_pointer_create(NULL, &RNA_Depsgraph, depsgraph, &depsgraph_ptr);
return pyrna_struct_CreatePyObject(&depsgraph_ptr);
}
/**
* Adds a variable 'depsgraph' to the name-space. This can then be used to obtain evaluated
* data-blocks, and the current view layer and scene. See T75553.
*/
static void bpy_pydriver_namespace_update_depsgraph(struct Depsgraph *depsgraph)
{
/* This should never happen, but it's probably better to have None in Python
* than a NULL-wrapping Depsgraph Python struct. */
BLI_assert(depsgraph != NULL);
if (UNLIKELY(depsgraph == NULL)) {
PyDict_SetItem(bpy_pydriver_Dict, bpy_intern_str_depsgraph, Py_None);
g_pydriver_state_prev.depsgraph = NULL;
return;
}
if ((g_pydriver_state_prev.depsgraph == NULL) ||
((depsgraph != g_pydriver_state_prev.depsgraph->ptr.data))) {
PyObject *item = bpy_pydriver_depsgraph_as_pyobject(depsgraph);
PyDict_SetItem(bpy_pydriver_Dict, bpy_intern_str_depsgraph, item);
Py_DECREF(item);
g_pydriver_state_prev.depsgraph = (BPy_StructRNA *)item;
}
}
void BPY_driver_reset(void)
{
PyGILState_STATE gilstate;
@ -226,6 +260,7 @@ void BPY_driver_reset(void)
/* freed when clearing driver dict */
g_pydriver_state_prev.self = NULL;
g_pydriver_state_prev.depsgraph = NULL;
if (use_gil) {
PyGILState_Release(gilstate);
@ -369,41 +404,6 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d
}
#endif /* USE_BYTECODE_WHITELIST */
static PyObject *bpy_pydriver_depsgraph_as_pyobject(struct Depsgraph *depsgraph)
{
/* This should never happen, but it's probably better to have None in Python
* than a NULL-wrapping Depsgraph py struct. */
BLI_assert(depsgraph != NULL);
if (depsgraph == NULL) {
Py_RETURN_NONE;
}
struct PointerRNA depsgraph_ptr;
RNA_pointer_create(NULL, &RNA_Depsgraph, depsgraph, &depsgraph_ptr);
return pyrna_struct_CreatePyObject(&depsgraph_ptr);
}
/**
* Adds a variable 'depsgraph' to the driver variables. This can then be used to obtain evaluated
* data-blocks, and the current view layer and scene. See T75553.
*/
static void bpy_pydriver_namespace_add_depsgraph(PyObject *driver_vars,
struct Depsgraph *depsgraph)
{
PyObject *py_depsgraph = bpy_pydriver_depsgraph_as_pyobject(depsgraph);
const char *depsgraph_variable_name = "depsgraph";
if (PyDict_SetItemString(driver_vars, depsgraph_variable_name, py_depsgraph) == -1) {
fprintf(stderr,
"\tBPY_driver_eval() - couldn't add variable '%s' to namespace\n",
depsgraph_variable_name);
PyErr_Print();
PyErr_Clear();
}
Py_DECREF(py_depsgraph);
}
float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
ChannelDriver *driver,
ChannelDriver *driver_orig,
@ -489,6 +489,8 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
bpy_pydriver_namespace_clear_self();
}
bpy_pydriver_namespace_update_depsgraph(anim_eval_context->depsgraph);
if (driver_orig->expr_comp == NULL) {
driver_orig->flag |= DRIVER_FLAG_RECOMPILE;
}
@ -613,8 +615,6 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
}
#endif /* USE_BYTECODE_WHITELIST */
bpy_pydriver_namespace_add_depsgraph(driver_vars, anim_eval_context->depsgraph);
#if 0 /* slow, with this can avoid all Py_CompileString above. */
/* execute expression to get a value */
retval = PyRun_String(expr, Py_eval_input, bpy_pydriver_Dict, driver_vars);

View File

@ -14,7 +14,7 @@
#include "BLI_utildefines.h"
static PyObject *bpy_intern_str_arr[16];
static PyObject *bpy_intern_str_arr[17];
PyObject *bpy_intern_str___annotations__;
PyObject *bpy_intern_str___doc__;
@ -31,6 +31,7 @@ PyObject *bpy_intern_str_frame;
PyObject *bpy_intern_str_properties;
PyObject *bpy_intern_str_register;
PyObject *bpy_intern_str_self;
PyObject *bpy_intern_str_depsgraph;
PyObject *bpy_intern_str_unregister;
void bpy_intern_string_init(void)
@ -58,6 +59,7 @@ void bpy_intern_string_init(void)
BPY_INTERN_STR(bpy_intern_str_properties, "properties");
BPY_INTERN_STR(bpy_intern_str_register, "register");
BPY_INTERN_STR(bpy_intern_str_self, "self");
BPY_INTERN_STR(bpy_intern_str_depsgraph, "depsgraph");
BPY_INTERN_STR(bpy_intern_str_unregister, "unregister");
#undef BPY_INTERN_STR

View File

@ -28,6 +28,7 @@ extern PyObject *bpy_intern_str_frame;
extern PyObject *bpy_intern_str_properties;
extern PyObject *bpy_intern_str_register;
extern PyObject *bpy_intern_str_self;
extern PyObject *bpy_intern_str_depsgraph;
extern PyObject *bpy_intern_str_unregister;
#ifdef __cplusplus