Blender  V2.93
gpu_py_framebuffer.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
27 #include <Python.h>
28 
29 #include "GPU_context.h"
30 #include "GPU_framebuffer.h"
31 #include "GPU_init_exit.h"
32 
33 #include "../generic/py_capi_utils.h"
34 #include "../generic/python_utildefines.h"
35 #include "../mathutils/mathutils.h"
36 
37 #include "gpu_py.h"
38 #include "gpu_py_texture.h"
39 
40 #include "gpu_py_framebuffer.h" /* own include */
41 
42 /* -------------------------------------------------------------------- */
47 {
48  if (UNLIKELY(bpygpu_fb->fb == NULL)) {
49  PyErr_SetString(PyExc_ReferenceError,
51  "GPU framebuffer was freed, no further access is valid"
52 #else
53  "GPU framebuffer: internal error"
54 #endif
55  );
56  return -1;
57  }
58  return 0;
59 }
60 
61 #define PYGPU_FRAMEBUFFER_CHECK_OBJ(bpygpu) \
62  { \
63  if (UNLIKELY(pygpu_framebuffer_valid_check(bpygpu) == -1)) { \
64  return NULL; \
65  } \
66  } \
67  ((void)0)
68 
70 {
71  if (!fb) {
72  return;
73  }
74 
75  if (GPU_is_init()) {
77  }
78  else {
79  printf("PyFramebuffer freed after the context has been destroyed.\n");
80  }
81 }
82 
83 /* Keep less than or equal to #FRAMEBUFFER_STACK_DEPTH */
84 #define GPU_PY_FRAMEBUFFER_STACK_LEN 16
85 
87 {
89  PyErr_SetString(
90  PyExc_RuntimeError,
91  "Maximum framebuffer stack depth " STRINGIFY(GPU_PY_FRAMEBUFFER_STACK_LEN) " reached");
92  return false;
93  }
96  return true;
97 }
98 
100 {
101  if (GPU_framebuffer_stack_level_get() == 0) {
102  PyErr_SetString(PyExc_RuntimeError, "Minimum framebuffer stack depth reached");
103  return false;
104  }
105 
106  if (fb && !GPU_framebuffer_bound(fb)) {
107  PyErr_SetString(PyExc_RuntimeError, "Framebuffer is not bound");
108  return false;
109  }
110 
111  GPUFrameBuffer *fb_prev = GPU_framebuffer_pop();
112  GPU_framebuffer_bind(fb_prev);
113  return true;
114 }
115 
118 /* -------------------------------------------------------------------- */
125 typedef struct {
126  PyObject_HEAD /* required python macro */
128  int level;
130 
132 {
133  Py_DECREF(self->py_fb);
134  PyObject_DEL(self);
135 }
136 
138 {
140 
141  /* sanity - should never happen */
142  if (self->level != -1) {
143  PyErr_SetString(PyExc_RuntimeError, "Already in use");
144  return NULL;
145  }
146 
148  return NULL;
149  }
150 
151  self->level = GPU_framebuffer_stack_level_get();
152  Py_RETURN_NONE;
153 }
154 
156  PyObject *UNUSED(args))
157 {
159 
160  /* sanity - should never happen */
161  if (self->level == -1) {
162  fprintf(stderr, "Not yet in use\n");
163  return NULL;
164  }
165 
166  const int level = GPU_framebuffer_stack_level_get();
167  if (level != self->level) {
168  fprintf(stderr, "Level of bind mismatch, expected %d, got %d\n", self->level, level);
169  }
170 
172  return NULL;
173  }
174  Py_RETURN_NONE;
175 }
176 
178  {"__enter__", (PyCFunction)pygpu_framebuffer_stack_context_enter, METH_NOARGS},
179  {"__exit__", (PyCFunction)pygpu_framebuffer_stack_context_exit, METH_VARARGS},
180  {NULL},
181 };
182 
183 static PyTypeObject FramebufferStackContext_Type = {
184  PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUFrameBufferStackContext",
185  .tp_basicsize = sizeof(PyFrameBufferStackContext),
186  .tp_dealloc = (destructor)pygpu_framebuffer_stack_context__tp_dealloc,
187  .tp_flags = Py_TPFLAGS_DEFAULT,
189 };
190 
191 PyDoc_STRVAR(pygpu_framebuffer_bind_doc,
192  ".. function:: bind()\n"
193  "\n"
194  " Context manager to ensure balanced bind calls, even in the case of an error.\n");
196 {
199  ret->py_fb = self;
200  ret->level = -1;
201  Py_INCREF(self);
202  return (PyObject *)ret;
203 }
204 
207 /* -------------------------------------------------------------------- */
211 /* Fill in the GPUAttachment according to the PyObject parameter.
212  * PyObject *o can be NULL, Py_None, BPyGPUTexture or a dictionary containing the keyword "texture"
213  * and the optional keywords "layer" and "mip".
214  * Returns false on error. In this case, a python message will be raised and GPUAttachment will not
215  * be touched. */
216 static bool pygpu_framebuffer_new_parse_arg(PyObject *o, GPUAttachment *r_attach)
217 {
218  GPUAttachment tmp_attach = GPU_ATTACHMENT_NONE;
219 
220  if (!o || o == Py_None) {
221  /* Pass. */;
222  }
223  else if (BPyGPUTexture_Check(o)) {
224  if (!bpygpu_ParseTexture(o, &tmp_attach.tex)) {
225  return false;
226  }
227  }
228  else {
229  const char *c_texture = "texture";
230  const char *c_layer = "layer";
231  const char *c_mip = "mip";
232  PyObject *key, *value;
233  Py_ssize_t pos = 0;
234  while (PyDict_Next(o, &pos, &key, &value)) {
235  if (!PyUnicode_Check(key)) {
236  PyErr_SetString(PyExc_TypeError, "keywords must be strings");
237  return false;
238  }
239 
240  if (c_texture && _PyUnicode_EqualToASCIIString(key, c_texture)) {
241  /* Compare only once. */
242  c_texture = NULL;
243  if (!bpygpu_ParseTexture(value, &tmp_attach.tex)) {
244  return false;
245  }
246  }
247  else if (c_layer && _PyUnicode_EqualToASCIIString(key, c_layer)) {
248  /* Compare only once. */
249  c_layer = NULL;
250  tmp_attach.layer = PyLong_AsLong(value);
251  if (tmp_attach.layer == -1 && PyErr_Occurred()) {
252  return false;
253  }
254  }
255  else if (c_mip && _PyUnicode_EqualToASCIIString(key, c_mip)) {
256  /* Compare only once. */
257  c_mip = NULL;
258  tmp_attach.mip = PyLong_AsLong(value);
259  if (tmp_attach.mip == -1 && PyErr_Occurred()) {
260  return false;
261  }
262  }
263  else {
264  PyErr_Format(
265  PyExc_TypeError, "'%U' is an invalid keyword argument for this attribute", key);
266  return false;
267  }
268  }
269  }
270 
271  *r_attach = tmp_attach;
272  return true;
273 }
274 
275 static PyObject *pygpu_framebuffer__tp_new(PyTypeObject *UNUSED(self),
276  PyObject *args,
277  PyObject *kwds)
278 {
280  if (!GPU_context_active_get()) {
281  PyErr_SetString(PyExc_RuntimeError, "No active GPU context found");
282  return NULL;
283  }
284 
285  PyObject *depth_attachment = NULL;
286  PyObject *color_attachements = NULL;
287  static const char *_keywords[] = {"depth_slot", "color_slots", NULL};
288  static _PyArg_Parser _parser = {"|$OO:GPUFrameBuffer.__new__", _keywords, 0};
289  if (!_PyArg_ParseTupleAndKeywordsFast(
290  args, kwds, &_parser, &depth_attachment, &color_attachements)) {
291  return NULL;
292  }
293 
294  /* Keep in sync with #GPU_FB_MAX_COLOR_ATTACHMENT.
295  * TODO: share the define. */
296 #define BPYGPU_FB_MAX_COLOR_ATTACHMENT 6
297 
299 
300  if (!pygpu_framebuffer_new_parse_arg(depth_attachment, &config[0])) {
301  return NULL;
302  }
303  if (config[0].tex && !GPU_texture_depth(config[0].tex)) {
304  PyErr_SetString(PyExc_ValueError, "Depth texture with incompatible format");
305  return NULL;
306  }
307 
308  int color_attachements_len = 0;
309  if (color_attachements && color_attachements != Py_None) {
310  if (PySequence_Check(color_attachements)) {
311  color_attachements_len = PySequence_Size(color_attachements);
312  if (color_attachements_len > BPYGPU_FB_MAX_COLOR_ATTACHMENT) {
313  PyErr_SetString(
314  PyExc_AttributeError,
315  "too many attachements, max is " STRINGIFY(BPYGPU_FB_MAX_COLOR_ATTACHMENT));
316  return NULL;
317  }
318 
319  for (int i = 0; i < color_attachements_len; i++) {
320  PyObject *o = PySequence_GetItem(color_attachements, i);
321  bool ok = pygpu_framebuffer_new_parse_arg(o, &config[i + 1]);
322  Py_DECREF(o);
323  if (!ok) {
324  return NULL;
325  }
326  }
327  }
328  else {
329  if (!pygpu_framebuffer_new_parse_arg(color_attachements, &config[1])) {
330  return NULL;
331  }
332  color_attachements_len = 1;
333  }
334  }
335 
336  GPUFrameBuffer *fb_python = GPU_framebuffer_create("fb_python");
337  GPU_framebuffer_config_array(fb_python, config, color_attachements_len + 1);
338 
339  return BPyGPUFrameBuffer_CreatePyObject(fb_python);
340 }
341 
342 PyDoc_STRVAR(pygpu_framebuffer_is_bound_doc,
343  "Checks if this is the active framebuffer in the context.");
345 {
347  return PyBool_FromLong(GPU_framebuffer_bound(self->fb));
348 }
349 
350 PyDoc_STRVAR(pygpu_framebuffer_clear_doc,
351  ".. method:: clear(color=None, depth=None, stencil=None)\n"
352  "\n"
353  " Fill color, depth and stencil textures with specific value.\n"
354  " Common values: color=(0.0, 0.0, 0.0, 1.0), depth=1.0, stencil=0.\n"
355  "\n"
356  " :arg color: float sequence each representing ``(r, g, b, a)``.\n"
357  " :type color: sequence of 3 or 4 floats\n"
358  " :arg depth: depth value.\n"
359  " :type depth: float\n"
360  " :arg stencil: stencil value.\n"
361  " :type stencil: int\n");
362 static PyObject *pygpu_framebuffer_clear(BPyGPUFrameBuffer *self, PyObject *args, PyObject *kwds)
363 {
365 
366  if (!GPU_framebuffer_bound(self->fb)) {
367  return NULL;
368  }
369 
370  PyObject *py_col = NULL;
371  PyObject *py_depth = NULL;
372  PyObject *py_stencil = NULL;
373 
374  static const char *_keywords[] = {"color", "depth", "stencil", NULL};
375  static _PyArg_Parser _parser = {"|$OOO:clear", _keywords, 0};
376  if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &py_col, &py_depth, &py_stencil)) {
377  return NULL;
378  }
379 
380  eGPUFrameBufferBits buffers = 0;
381  float col[4] = {0.0f, 0.0f, 0.0f, 1.0f};
382  float depth = 1.0f;
383  uint stencil = 0;
384 
385  if (py_col && py_col != Py_None) {
386  if (mathutils_array_parse(col, 3, 4, py_col, "GPUFrameBuffer.clear(), invalid 'color' arg") ==
387  -1) {
388  return NULL;
389  }
390  buffers |= GPU_COLOR_BIT;
391  }
392 
393  if (py_depth && py_depth != Py_None) {
394  depth = PyFloat_AsDouble(py_depth);
395  if (PyErr_Occurred()) {
396  return NULL;
397  }
398  buffers |= GPU_DEPTH_BIT;
399  }
400 
401  if (py_stencil && py_stencil != Py_None) {
402  if ((stencil = PyC_Long_AsU32(py_stencil)) == (uint)-1) {
403  return NULL;
404  }
405  buffers |= GPU_STENCIL_BIT;
406  }
407 
408  GPU_framebuffer_clear(self->fb, buffers, col, depth, stencil);
409  Py_RETURN_NONE;
410 }
411 
412 PyDoc_STRVAR(pygpu_framebuffer_viewport_set_doc,
413  ".. function:: viewport_set(x, y, xsize, ysize)\n"
414  "\n"
415  " Set the viewport for this framebuffer object.\n"
416  " Note: The viewport state is not saved upon framebuffer rebind.\n"
417  "\n"
418  " :param x, y: lower left corner of the viewport_set rectangle, in pixels.\n"
419  " :param xsize, ysize: width and height of the viewport_set.\n"
420  " :type x, y, xsize, ysize: int\n");
422  PyObject *args,
423  void *UNUSED(type))
424 {
425  int x, y, xsize, ysize;
426  if (!PyArg_ParseTuple(args, "iiii:viewport_set", &x, &y, &xsize, &ysize)) {
427  return NULL;
428  }
429 
430  GPU_framebuffer_viewport_set(self->fb, x, y, xsize, ysize);
431  Py_RETURN_NONE;
432 }
433 
434 PyDoc_STRVAR(pygpu_framebuffer_viewport_get_doc,
435  ".. function:: viewport_get()\n"
436  "\n"
437  " Returns position and dimension to current viewport.\n");
439 {
441  int viewport[4];
442  GPU_framebuffer_viewport_get(self->fb, viewport);
443 
444  PyObject *ret = PyTuple_New(4);
446  PyLong_FromLong(viewport[0]),
447  PyLong_FromLong(viewport[1]),
448  PyLong_FromLong(viewport[2]),
449  PyLong_FromLong(viewport[3]));
450  return ret;
451 }
452 
453 #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
454 PyDoc_STRVAR(pygpu_framebuffer_free_doc,
455  ".. method:: free()\n"
456  "\n"
457  " Free the framebuffer object.\n"
458  " The framebuffer will no longer be accessible.\n");
459 static PyObject *pygpu_framebuffer_free(BPyGPUFrameBuffer *self)
460 {
463  self->fb = NULL;
464  Py_RETURN_NONE;
465 }
466 #endif
467 
469 {
471  Py_TYPE(self)->tp_free((PyObject *)self);
472 }
473 
474 static PyGetSetDef pygpu_framebuffer__tp_getseters[] = {
475  {"is_bound",
477  (setter)NULL,
478  pygpu_framebuffer_is_bound_doc,
479  NULL},
480  {NULL, NULL, NULL, NULL, NULL} /* Sentinel */
481 };
482 
483 static struct PyMethodDef pygpu_framebuffer__tp_methods[] = {
484  {"bind", (PyCFunction)pygpu_framebuffer_bind, METH_NOARGS, pygpu_framebuffer_bind_doc},
485  {"clear",
486  (PyCFunction)pygpu_framebuffer_clear,
487  METH_VARARGS | METH_KEYWORDS,
488  pygpu_framebuffer_clear_doc},
489  {"viewport_set",
490  (PyCFunction)pygpu_framebuffer_viewport_set,
491  METH_NOARGS,
492  pygpu_framebuffer_viewport_set_doc},
493  {"viewport_get",
494  (PyCFunction)pygpu_framebuffer_viewport_get,
495  METH_NOARGS,
496  pygpu_framebuffer_viewport_get_doc},
497 #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
498  {"free", (PyCFunction)pygpu_framebuffer_free, METH_NOARGS, pygpu_framebuffer_free_doc},
499 #endif
500  {NULL, NULL, 0, NULL},
501 };
502 
503 PyDoc_STRVAR(pygpu_framebuffer__tp_doc,
504  ".. class:: GPUFrameBuffer(depth_slot=None, color_slots=None)\n"
505  "\n"
506  " This object gives access to framebuffer functionallities.\n"
507  " When a 'layer' is specified in a argument, a single layer of a 3D or array "
508  "texture is attached to the frame-buffer.\n"
509  " For cube map textures, layer is translated into a cube map face.\n"
510  "\n"
511  " :arg depth_slot: GPUTexture to attach or a `dict` containing keywords: "
512  "'texture', 'layer' and 'mip'.\n"
513  " :type depth_slot: :class:`gpu.types.GPUTexture`, dict or Nonetype\n"
514  " :arg color_slots: Tuple where each item can be a GPUTexture or a `dict` "
515  "containing keywords: 'texture', 'layer' and 'mip'.\n"
516  " :type color_slots: tuple or Nonetype\n");
517 PyTypeObject BPyGPUFrameBuffer_Type = {
518  PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUFrameBuffer",
519  .tp_basicsize = sizeof(BPyGPUFrameBuffer),
520  .tp_dealloc = (destructor)BPyGPUFrameBuffer__tp_dealloc,
521  .tp_flags = Py_TPFLAGS_DEFAULT,
522  .tp_doc = pygpu_framebuffer__tp_doc,
523  .tp_methods = pygpu_framebuffer__tp_methods,
524  .tp_getset = pygpu_framebuffer__tp_getseters,
525  .tp_new = pygpu_framebuffer__tp_new,
526 };
527 
530 /* -------------------------------------------------------------------- */
535 {
536  BPyGPUFrameBuffer *self;
537 
538  self = PyObject_New(BPyGPUFrameBuffer, &BPyGPUFrameBuffer_Type);
539  self->fb = fb;
540 
541  return (PyObject *)self;
542 }
543 
546 #undef PYGPU_FRAMEBUFFER_CHECK_OBJ
unsigned int uint
Definition: BLI_sys_types.h:83
#define STRINGIFY(x)
#define UNUSED(x)
#define UNLIKELY(x)
GPUContext * GPU_context_active_get(void)
Definition: gpu_context.cc:136
struct GPUFrameBuffer GPUFrameBuffer
eGPUFrameBufferBits
@ GPU_DEPTH_BIT
@ GPU_STENCIL_BIT
@ GPU_COLOR_BIT
GPUFrameBuffer * GPU_framebuffer_active_get(void)
void GPU_framebuffer_free(GPUFrameBuffer *fb)
void GPU_framebuffer_bind(GPUFrameBuffer *fb)
bool GPU_framebuffer_bound(GPUFrameBuffer *fb)
GPUFrameBuffer * GPU_framebuffer_create(const char *name)
bool GPU_is_init(void)
Definition: gpu_init_exit.c:76
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum type
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum const void *lists _GL_VOID_RET _GL_VOID const GLdouble *equation _GL_VOID_RET _GL_VOID GLdouble GLdouble blue _GL_VOID_RET _GL_VOID GLfloat GLfloat blue _GL_VOID_RET _GL_VOID GLint GLint blue _GL_VOID_RET _GL_VOID GLshort GLshort blue _GL_VOID_RET _GL_VOID GLubyte GLubyte blue _GL_VOID_RET _GL_VOID GLuint GLuint blue _GL_VOID_RET _GL_VOID GLushort GLushort blue _GL_VOID_RET _GL_VOID GLbyte GLbyte GLbyte alpha _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble alpha _GL_VOID_RET _GL_VOID GLfloat GLfloat GLfloat alpha _GL_VOID_RET _GL_VOID GLint GLint GLint alpha _GL_VOID_RET _GL_VOID GLshort GLshort GLshort alpha _GL_VOID_RET _GL_VOID GLubyte GLubyte GLubyte alpha _GL_VOID_RET _GL_VOID GLuint GLuint GLuint alpha _GL_VOID_RET _GL_VOID GLushort GLushort GLushort alpha _GL_VOID_RET _GL_VOID GLenum mode _GL_VOID_RET _GL_VOID GLint y
bool GPU_texture_depth(const GPUTexture *tex)
Definition: gpu_texture.cc:559
PyObject * self
Definition: bpy_driver.c:185
uint pos
uint col
void GPU_framebuffer_clear(GPUFrameBuffer *gpu_fb, eGPUFrameBufferBits buffers, const float clear_col[4], float clear_depth, uint clear_stencil)
uint GPU_framebuffer_stack_level_get(void)
void GPU_framebuffer_config_array(GPUFrameBuffer *gpu_fb, const GPUAttachment *config, int config_len)
GPUFrameBuffer * GPU_framebuffer_pop(void)
void GPU_framebuffer_viewport_get(GPUFrameBuffer *gpu_fb, int r_viewport[4])
void GPU_framebuffer_push(GPUFrameBuffer *fb)
void GPU_framebuffer_viewport_set(GPUFrameBuffer *gpu_fb, int x, int y, int width, int height)
#define BPYGPU_IS_INIT_OR_ERROR_OBJ
Definition: gpu_py.h:28
static bool pygpu_framebuffer_stack_pop_and_restore_or_error(GPUFrameBuffer *fb)
static PyObject * pygpu_framebuffer_clear(BPyGPUFrameBuffer *self, PyObject *args, PyObject *kwds)
static PyObject * pygpu_framebuffer_viewport_get(BPyGPUFrameBuffer *self, void *UNUSED(type))
static PyTypeObject FramebufferStackContext_Type
static PyObject * pygpu_framebuffer_is_bound(BPyGPUFrameBuffer *self, void *UNUSED(type))
#define PYGPU_FRAMEBUFFER_CHECK_OBJ(bpygpu)
static void pygpu_framebuffer_stack_context__tp_dealloc(PyFrameBufferStackContext *self)
static void pygpu_framebuffer_free_if_possible(GPUFrameBuffer *fb)
#define BPYGPU_FB_MAX_COLOR_ATTACHMENT
static PyObject * pygpu_framebuffer_viewport_set(BPyGPUFrameBuffer *self, PyObject *args, void *UNUSED(type))
static PyObject * pygpu_framebuffer_bind(BPyGPUFrameBuffer *self)
static bool pygpu_framebuffer_stack_push_and_bind_or_error(GPUFrameBuffer *fb)
static struct PyMethodDef pygpu_framebuffer__tp_methods[]
#define GPU_PY_FRAMEBUFFER_STACK_LEN
static PyObject * pygpu_framebuffer_stack_context_enter(PyFrameBufferStackContext *self)
PyObject * BPyGPUFrameBuffer_CreatePyObject(GPUFrameBuffer *fb)
static PyObject * pygpu_framebuffer_stack_context_exit(PyFrameBufferStackContext *self, PyObject *UNUSED(args))
static bool pygpu_framebuffer_new_parse_arg(PyObject *o, GPUAttachment *r_attach)
static PyGetSetDef pygpu_framebuffer__tp_getseters[]
PyDoc_STRVAR(pygpu_framebuffer_bind_doc, ".. function:: bind()\n" "\n" " Context manager to ensure balanced bind calls, even in the case of an error.\n")
static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb)
static void BPyGPUFrameBuffer__tp_dealloc(BPyGPUFrameBuffer *self)
static PyMethodDef pygpu_framebuffer_stack_context__tp_methods[]
static PyObject * pygpu_framebuffer__tp_new(PyTypeObject *UNUSED(self), PyObject *args, PyObject *kwds)
PyTypeObject BPyGPUFrameBuffer_Type
struct BPyGPUFrameBuffer BPyGPUFrameBuffer
#define BPYGPU_USE_GPUOBJ_FREE_METHOD
int bpygpu_ParseTexture(PyObject *o, void *p)
#define BPyGPUTexture_Check(v)
BLI_INLINE float fb(float length, float L)
int mathutils_array_parse(float *array, int array_min, int array_max, PyObject *value, const char *error_prefix)
Definition: mathutils.c:118
uint32_t PyC_Long_AsU32(PyObject *value)
#define PyTuple_SET_ITEMS(op_arg,...)
return ret
PyObject_HEAD struct GPUFrameBuffer * fb
struct GPUTexture * tex
PyObject_HEAD BPyGPUFrameBuffer * py_fb