PyAPI: gpu.matrix.push_pop context manager

Avoid un-balanced push/pop usage (which can interfere with Blender's
internal state) using a context manager.
This commit is contained in:
2017-08-20 19:31:59 +10:00
parent 8037f3602f
commit cb03cc6600

View File

@@ -43,7 +43,52 @@
#undef USE_GPU_PY_MATRIX_API
/* -------------------------------------------------------------------- */
/** \name Helper Functions
* \{ */
static bool pygpu_stack_is_push_model_view_ok_or_error(void)
{
if (GPU_matrix_stack_level_get_model_view() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(PyExc_RuntimeError,
"Maximum model-view stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
return false;
}
return true;
}
static bool pygpu_stack_is_push_projection_ok_or_error(void)
{
if (GPU_matrix_stack_level_get_projection() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(PyExc_RuntimeError,
"Maximum projection stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
return false;
}
return true;
}
static bool pygpu_stack_is_pop_model_view_ok_or_error(void)
{
if (GPU_matrix_stack_level_get_model_view() == 0) {
PyErr_SetString(PyExc_RuntimeError,
"Minimum model-view stack depth reached");
return false;
}
return true;
}
static bool pygpu_stack_is_pop_projection_ok_or_error(void)
{
if (GPU_matrix_stack_level_get_projection() == 0) {
PyErr_SetString(PyExc_RuntimeError,
"Minimum projection stack depth reached");
return false;
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manage Stack
* \{ */
@@ -54,9 +99,7 @@ PyDoc_STRVAR(pygpu_matrix_push_doc,
);
static PyObject *pygpu_matrix_push(PyObject *UNUSED(self))
{
if (GPU_matrix_stack_level_get_model_view() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(PyExc_RuntimeError,
"Maximum model-view stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
if (!pygpu_stack_is_push_model_view_ok_or_error()) {
return NULL;
}
gpuPushMatrix();
@@ -70,9 +113,7 @@ PyDoc_STRVAR(pygpu_matrix_pop_doc,
);
static PyObject *pygpu_matrix_pop(PyObject *UNUSED(self))
{
if (GPU_matrix_stack_level_get_model_view() == 0) {
PyErr_SetString(PyExc_RuntimeError,
"Minimum model-view stack depth reached");
if (!pygpu_stack_is_pop_model_view_ok_or_error()) {
return NULL;
}
gpuPopMatrix();
@@ -86,12 +127,9 @@ PyDoc_STRVAR(pygpu_matrix_push_projection_doc,
);
static PyObject *pygpu_matrix_push_projection(PyObject *UNUSED(self))
{
if (GPU_matrix_stack_level_get_projection() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(PyExc_RuntimeError,
"Maximum projection stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
if (!pygpu_stack_is_push_projection_ok_or_error()) {
return NULL;
}
gpuPushProjectionMatrix();
Py_RETURN_NONE;
}
@@ -103,9 +141,7 @@ PyDoc_STRVAR(pygpu_matrix_pop_projection_doc,
);
static PyObject *pygpu_matrix_pop_projection(PyObject *UNUSED(self))
{
if (GPU_matrix_stack_level_get_projection() == 0) {
PyErr_SetString(PyExc_RuntimeError,
"Minimum projection stack depth reached");
if (!pygpu_stack_is_pop_projection_ok_or_error()) {
return NULL;
}
gpuPopProjectionMatrix();
@@ -115,7 +151,132 @@ static PyObject *pygpu_matrix_pop_projection(PyObject *UNUSED(self))
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
typedef struct {
PyObject_HEAD /* required python macro */
int type;
int level;
} BPy_GPU_MatrixStackContext;
enum {
PYGPU_MATRIX_TYPE_MODEL_VIEW = 1,
PYGPU_MATRIX_TYPE_PROJECTION = 2,
};
static PyObject *pygpu_matrix_stack_context_enter(BPy_GPU_MatrixStackContext *self);
static PyObject *pygpu_matrix_stack_context_exit(BPy_GPU_MatrixStackContext *self, PyObject *args);
static PyMethodDef pygpu_matrix_stack_context_methods[] = {
{"__enter__", (PyCFunction)pygpu_matrix_stack_context_enter, METH_NOARGS},
{"__exit__", (PyCFunction)pygpu_matrix_stack_context_exit, METH_VARARGS},
{NULL}
};
static PyTypeObject pygpu_matrix_stack_context_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "GPUMatrixStackContext",
.tp_basicsize = sizeof(BPy_GPU_MatrixStackContext),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = pygpu_matrix_stack_context_methods,
};
static PyObject *pygpu_matrix_stack_context_enter(BPy_GPU_MatrixStackContext *self)
{
/* sanity - should never happen */
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return NULL;
}
if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
if (!pygpu_stack_is_push_model_view_ok_or_error()) {
return NULL;
}
gpuPushMatrix();
self->level = GPU_matrix_stack_level_get_model_view();
}
else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
if (!pygpu_stack_is_push_projection_ok_or_error()) {
return NULL;
}
gpuPushProjectionMatrix();
self->level = GPU_matrix_stack_level_get_projection();
}
else {
BLI_assert(0);
}
Py_RETURN_NONE;
}
static PyObject *pygpu_matrix_stack_context_exit(BPy_GPU_MatrixStackContext *self, PyObject *UNUSED(args))
{
/* sanity - should never happen */
if (self->level == -1) {
fprintf(stderr, "Not yet in use\n");
goto finally;
}
if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
const int level = GPU_matrix_stack_level_get_model_view();
if (level != self->level) {
fprintf(stderr, "Level push/pop mismatch, expected %d, got %d\n", self->level, level);
}
if (level != 0) {
gpuPopMatrix();
}
}
else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
const int level = GPU_matrix_stack_level_get_projection();
if (level != self->level) {
fprintf(stderr, "Level push/pop mismatch, expected %d, got %d", self->level, level);
}
if (level != 0) {
gpuPopProjectionMatrix();
}
}
else {
BLI_assert(0);
}
finally:
Py_RETURN_NONE;
}
static PyObject *pygpu_matrix_push_pop_impl(int type)
{
BPy_GPU_MatrixStackContext *ret = PyObject_New(BPy_GPU_MatrixStackContext, &pygpu_matrix_stack_context_Type);
ret->type = type;
ret->level = -1;
return (PyObject *)ret;
}
PyDoc_STRVAR(pygpu_matrix_push_pop_doc,
"push_pop()\n"
"\n"
" Context manager to ensure balanced push/pop calls, even in the case of an error.\n"
);
static PyObject *pygpu_matrix_push_pop(PyObject *UNUSED(self))
{
return pygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_MODEL_VIEW);
}
PyDoc_STRVAR(pygpu_matrix_push_pop_projection_doc,
"push_pop_projection()\n"
"\n"
" Context manager to ensure balanced push/pop calls, even in the case of an error.\n"
);
static PyObject *pygpu_matrix_push_pop_projection(PyObject *UNUSED(self))
{
return pygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_PROJECTION);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manipulate State
* \{ */
@@ -207,7 +368,6 @@ static PyObject *pygpu_matrix_translate(PyObject *UNUSED(self), PyObject *value)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Write State
* \{ */
@@ -254,7 +414,6 @@ static PyObject *pygpu_matrix_load_matrix(PyObject *UNUSED(self), PyObject *valu
/** \} */
/* -------------------------------------------------------------------- */
/** \name Read State
* \{ */
@@ -306,13 +465,10 @@ static PyObject *pygpu_matrix_get_normal_matrix(PyObject *UNUSED(self))
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
static struct PyMethodDef BPy_GPU_matrix_methods[] = {
/* Manage Stack */
{"push", (PyCFunction)pygpu_matrix_push,
@@ -325,6 +481,12 @@ static struct PyMethodDef BPy_GPU_matrix_methods[] = {
{"pop_projection", (PyCFunction)pygpu_matrix_pop_projection,
METH_NOARGS, pygpu_matrix_pop_projection_doc},
/* Stack (Context Manager) */
{"push_pop", (PyCFunction)pygpu_matrix_push_pop,
METH_NOARGS, pygpu_matrix_push_pop_doc},
{"push_pop_projection", (PyCFunction)pygpu_matrix_push_pop_projection,
METH_NOARGS, pygpu_matrix_push_pop_projection_doc},
/* Manipulate State */
{"multiply_matrix", (PyCFunction)pygpu_matrix_multiply_matrix,
METH_O, pygpu_matrix_multiply_matrix_doc},
@@ -380,6 +542,10 @@ PyObject *BPyInit_gpu_matrix(void)
submodule = PyModule_Create(&BPy_GPU_matrix_module_def);
if (PyType_Ready(&pygpu_matrix_stack_context_Type) < 0) {
return NULL;
}
return submodule;
}