[Bf-blender-cvs] [005040e01db] pygpu_extensions: GPU Python: Implement new object type 'gpu.types.GPUFrameBuffer'
Germano Cavalcante
noreply at git.blender.org
Thu Feb 11 19:03:05 CET 2021
Commit: 005040e01db1a86964e9b34ce94302c17ebdc2af
Author: Germano Cavalcante
Date: Thu Feb 11 14:46:36 2021 -0300
Branches: pygpu_extensions
https://developer.blender.org/rB005040e01db1a86964e9b34ce94302c17ebdc2af
GPU Python: Implement new object type 'gpu.types.GPUFrameBuffer'
For framebuffer operations on the GPU.
The API of this object includes:
```
>>> gpu.types.GPUFrameBuffer.
__enter__(
__exit__(
bind(
clear(
free(
is_bound
viewport
```
===================================================================
M source/blender/gpu/GPU_framebuffer.h
M source/blender/gpu/intern/gpu_framebuffer.cc
M source/blender/python/gpu/CMakeLists.txt
A source/blender/python/gpu/gpu_py_framebuffer.c
A source/blender/python/gpu/gpu_py_framebuffer.h
M source/blender/python/gpu/gpu_py_types.c
M source/blender/python/gpu/gpu_py_types.h
===================================================================
diff --git a/source/blender/gpu/GPU_framebuffer.h b/source/blender/gpu/GPU_framebuffer.h
index 8c6592adf6d..af94c1fb0e6 100644
--- a/source/blender/gpu/GPU_framebuffer.h
+++ b/source/blender/gpu/GPU_framebuffer.h
@@ -209,6 +209,10 @@ void GPU_framebuffer_recursive_downsample(GPUFrameBuffer *fb,
void (*callback)(void *userData, int level),
void *userData);
+void GPU_framebuffer_push(GPUFrameBuffer *fb);
+GPUFrameBuffer *GPU_framebuffer_pop(void);
+uint GPU_framebuffer_stack_level_get(void);
+
/* GPU OffScreen
* - wrapper around frame-buffer and texture for simple off-screen drawing
*/
diff --git a/source/blender/gpu/intern/gpu_framebuffer.cc b/source/blender/gpu/intern/gpu_framebuffer.cc
index 2e183f52eea..2813132c799 100644
--- a/source/blender/gpu/intern/gpu_framebuffer.cc
+++ b/source/blender/gpu/intern/gpu_framebuffer.cc
@@ -476,10 +476,9 @@ void GPU_framebuffer_recursive_downsample(GPUFrameBuffer *gpu_fb,
/** \} */
/* -------------------------------------------------------------------- */
-/** \name GPUOffScreen
+/** \name Framebuffer Stack
*
- * Container that holds a frame-buffer and its textures.
- * Might be bound to multiple contexts.
+ * Keeps track of framebuffer binding operation to restore previously bound frambuffers.
* \{ */
#define FRAMEBUFFER_STACK_DEPTH 16
@@ -489,22 +488,36 @@ static struct {
uint top;
} FrameBufferStack = {{nullptr}};
-static void gpuPushFrameBuffer(GPUFrameBuffer *fb)
+void GPU_framebuffer_push(GPUFrameBuffer *fb)
{
BLI_assert(FrameBufferStack.top < FRAMEBUFFER_STACK_DEPTH);
FrameBufferStack.framebuffers[FrameBufferStack.top] = fb;
FrameBufferStack.top++;
}
-static GPUFrameBuffer *gpuPopFrameBuffer()
+GPUFrameBuffer *GPU_framebuffer_pop(void)
{
BLI_assert(FrameBufferStack.top > 0);
FrameBufferStack.top--;
return FrameBufferStack.framebuffers[FrameBufferStack.top];
}
+uint GPU_framebuffer_stack_level_get(void)
+{
+ return FrameBufferStack.top;
+}
+
#undef FRAMEBUFFER_STACK_DEPTH
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name GPUOffScreen
+ *
+ * Container that holds a frame-buffer and its textures.
+ * Might be bound to multiple contexts.
+ * \{ */
+
#define MAX_CTX_FB_LEN 3
struct GPUOffScreen {
@@ -614,7 +627,7 @@ void GPU_offscreen_bind(GPUOffScreen *ofs, bool save)
{
if (save) {
GPUFrameBuffer *fb = GPU_framebuffer_active_get();
- gpuPushFrameBuffer(fb);
+ GPU_framebuffer_push(fb);
}
unwrap(gpu_offscreen_fb_get(ofs))->bind(false);
}
@@ -623,7 +636,7 @@ void GPU_offscreen_unbind(GPUOffScreen *UNUSED(ofs), bool restore)
{
GPUFrameBuffer *fb = nullptr;
if (restore) {
- fb = gpuPopFrameBuffer();
+ fb = GPU_framebuffer_pop();
}
if (fb) {
diff --git a/source/blender/python/gpu/CMakeLists.txt b/source/blender/python/gpu/CMakeLists.txt
index 010c0c8441e..ba3561c0293 100644
--- a/source/blender/python/gpu/CMakeLists.txt
+++ b/source/blender/python/gpu/CMakeLists.txt
@@ -38,6 +38,7 @@ set(SRC
gpu_py_batch.c
gpu_py_buffer.c
gpu_py_element.c
+ gpu_py_framebuffer.c
gpu_py_matrix.c
gpu_py_offscreen.c
gpu_py_select.c
@@ -53,6 +54,7 @@ set(SRC
gpu_py_batch.h
gpu_py_buffer.h
gpu_py_element.h
+ gpu_py_framebuffer.h
gpu_py_matrix.h
gpu_py_offscreen.h
gpu_py_select.h
diff --git a/source/blender/python/gpu/gpu_py_framebuffer.c b/source/blender/python/gpu/gpu_py_framebuffer.c
new file mode 100644
index 00000000000..62b2a011c92
--- /dev/null
+++ b/source/blender/python/gpu/gpu_py_framebuffer.c
@@ -0,0 +1,500 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup bpygpu
+ *
+ * This file defines the framebuffer functionalities of the 'gpu' module
+ * used for off-screen OpenGL rendering.
+ *
+ * - Use ``bpygpu_`` for local API.
+ * - Use ``BPyGPU`` for public API.
+ */
+
+#include <Python.h>
+
+#include "GPU_context.h"
+#include "GPU_framebuffer.h"
+#include "GPU_init_exit.h"
+
+#include "../generic/py_capi_utils.h"
+#include "../generic/python_utildefines.h"
+#include "../mathutils/mathutils.h"
+
+#include "gpu_py_api.h"
+#include "gpu_py_texture.h"
+
+#include "gpu_py_framebuffer.h" /* own include */
+
+/* -------------------------------------------------------------------- */
+/** \name GPUFrameBuffer Common Utilities
+ * \{ */
+
+static int py_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb)
+{
+ if (UNLIKELY(bpygpu_fb->fb == NULL)) {
+ PyErr_SetString(PyExc_ReferenceError, "GPU framebuffer was freed, no further access is valid");
+ return -1;
+ }
+ return 0;
+}
+
+#define PY_FRAMEBUFFER_CHECK_OBJ(bpygpu) \
+ { \
+ if (UNLIKELY(py_framebuffer_valid_check(bpygpu) == -1)) { \
+ return NULL; \
+ } \
+ } \
+ ((void)0)
+
+static void py_framebuffer_free_if_possible(GPUFrameBuffer *fb)
+{
+ if (!fb) {
+ return;
+ }
+
+ if (GPU_is_init()) {
+ GPU_framebuffer_free(fb);
+ }
+ else {
+ printf("PyFramebuffer freed after the context has been destroyed.\n");
+ }
+}
+
+/* Keep less than or equal to #FRAMEBUFFER_STACK_DEPTH */
+#define GPU_PY_FRAMEBUFFER_STACK_LEN 16
+
+static bool py_framebuffer_stack_push_and_bind_or_error(GPUFrameBuffer *fb)
+{
+ if (GPU_framebuffer_stack_level_get() >= GPU_PY_FRAMEBUFFER_STACK_LEN) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Maximum framebuffer stack depth " STRINGIFY(GPU_PY_FRAMEBUFFER_STACK_LEN) " reached");
+ return false;
+ }
+ GPU_framebuffer_push(GPU_framebuffer_active_get());
+ GPU_framebuffer_bind(fb);
+ return true;
+}
+
+static bool py_framebuffer_stack_pop_and_restore_or_error(GPUFrameBuffer *fb)
+{
+ if (GPU_framebuffer_stack_level_get() == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Minimum framebuffer stack depth reached");
+ return false;
+ }
+
+ if (fb && !GPU_framebuffer_bound(fb)) {
+ PyErr_SetString(PyExc_RuntimeError, "Framebuffer is not bound");
+ return false;
+ }
+
+ GPUFrameBuffer *fb_prev = GPU_framebuffer_pop();
+ GPU_framebuffer_bind(fb_prev);
+ return true;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Stack (Context Manager)
+ *
+ * Safer alternative to ensure balanced push/pop calls.
+ *
+ * \{ */
+
+typedef struct {
+ PyObject_HEAD /* required python macro */
+ BPyGPUFrameBuffer *py_fb;
+ int level;
+} BPyGPU_FrameBufferStackContext;
+
+static void BPyGPUFrameBufferStackContext__tp_dealloc(BPyGPU_FrameBufferStackContext *self)
+{
+ Py_DECREF(self->py_fb);
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *py_framebuffer_stack_context_enter(BPyGPU_FrameBufferStackContext *self)
+{
+ PY_FRAMEBUFFER_CHECK_OBJ(self->py_fb);
+
+ /* sanity - should never happen */
+ if (self->level != -1) {
+ PyErr_SetString(PyExc_RuntimeError, "Already in use");
+ return NULL;
+ }
+
+ if (!py_framebuffer_stack_push_and_bind_or_error(self->py_fb->fb)) {
+ return NULL;
+ }
+
+ self->level = GPU_framebuffer_stack_level_get();
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_framebuffer_stack_context_exit(BPyGPU_FrameBufferStackContext *self,
+ PyObject *UNUSED(args))
+{
+ PY_FRAMEBUFFER_CHECK_OBJ(self->py_fb);
+
+ /* sanity - should never happen */
+ if (self->level == -1) {
+ fprintf(stderr, "Not yet in use\n");
+ return NULL;
+ }
+
+ const int level = GPU_framebuffer_stack_level_get();
+ if (level != self->level) {
+ fprintf(stderr, "Level of bind mismatch, expected %d, got %d\n", self->level, level);
+ }
+
+ if (!py_framebuffer_stack_pop_and_restore_or_error(self->py_fb->fb)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef py_framebuffer_stack_context_methods[] = {
+ {"__enter__", (PyCFunction)py_framebuffer_stack_context_enter, METH_NOARGS},
+ {"__exit__", (PyCFunction)py_framebuffer_stack_context_exit, METH_VARARGS},
+ {NULL},
+};
+
+static PyTypeObject BPyGPU_framebuffer_stack_context_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUFrameBufferStackContext",
+ .tp_basicsize = sizeof(BPyGPU_FrameBufferStackContext),
+ .tp_dealloc = (destructor)BPyGPUFrameBufferStackContext__tp_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_methods = py_framebuffer_stack_context_methods,
+};
+
+PyDoc_STRVAR(py_framebuffer_bind_doc,
+ ".. function:: bind()\n"
+ "\n"
+ " Context manager to ensure balanced bind calls, even in the case of an error.\n");
+static PyObject *py_framebuffer_bind(BPyGPUFrameBuffer *self)
+{
+ BPyGPU_FrameBufferStackContext *ret = PyObject_New(BPyGPU_FrameBufferStackContext,
+ &BPyGPU_framebuffer_stack_context_Type);
+ ret->py_fb = self;
+ ret->level = -1;
+ Py_INCREF(self);
+ return (PyObject *)ret;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name GPUFramebuffer Type
+ * \{ */
+
+static PyObject *py_framebuffer_new(PyTypeObject *UNUSED(self), PyObject *args, PyObject *kwds)
+{
+ BPYGPU_IS_INIT_OR_ERROR_OBJ;
+ if (!GPU_context_active_get()) {
+ PyErr_SetString(PyExc_RuntimeError, "No active GPU context found");
+ return NULL;
+ }
+
+ struct {
+ GPUTexture *tex;
+ int layer;
+ int mip;
+ } slot[7] = {{.layer = -1},
+ {.layer = -1},
+ {.layer = -1},
+ {.layer = -1},
+ {.layer = -1},
+
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-blender-cvs
mailing list