[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