Blender V4.3
gpu_py_batch.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2015 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
15#include <Python.h>
16
17#include "BLI_utildefines.h"
18
19#include "GPU_batch.hh"
20
23
24#include "gpu_py.hh"
25#include "gpu_py_element.hh"
26#include "gpu_py_shader.hh"
28
29#include "gpu_py_batch.hh" /* own include */
30
31/* -------------------------------------------------------------------- */
36{
37 if (!self->batch->shader) {
38 PyErr_SetString(PyExc_RuntimeError, "batch does not have any program assigned to it");
39 return false;
40 }
41 return true;
42}
43
46/* -------------------------------------------------------------------- */
50static PyObject *pygpu_batch__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
51{
53
54 const char *exc_str_missing_arg = "GPUBatch.__new__() missing required argument '%s' (pos %d)";
55
57 BPyGPUVertBuf *py_vertbuf = nullptr;
58 BPyGPUIndexBuf *py_indexbuf = nullptr;
59
60 static const char *_keywords[] = {"type", "buf", "elem", nullptr};
61 static _PyArg_Parser _parser = {
63 "|$" /* Optional keyword only arguments. */
64 "O&" /* `type` */
65 "O!" /* `buf` */
66 "O!" /* `elem` */
67 ":GPUBatch.__new__",
68 _keywords,
69 nullptr,
70 };
71 if (!_PyArg_ParseTupleAndKeywordsFast(args,
72 kwds,
73 &_parser,
75 &prim_type,
77 &py_vertbuf,
79 &py_indexbuf))
80 {
81 return nullptr;
82 }
83
85 if (prim_type.value_found == GPU_PRIM_LINE_LOOP) {
86 PyErr_WarnEx(PyExc_DeprecationWarning,
87 "'LINE_LOOP' is deprecated. Please use 'LINE_STRIP' and close the segment.",
88 1);
89 }
90 else if (prim_type.value_found == GPU_PRIM_TRI_FAN) {
91 PyErr_WarnEx(
92 PyExc_DeprecationWarning,
93 "'TRI_FAN' is deprecated. Please use 'TRI_STRIP' or 'TRIS' and try modifying your "
94 "vertices or indices to match the topology.",
95 1);
96 }
97
98 if (py_vertbuf == nullptr) {
99 PyErr_Format(PyExc_TypeError, exc_str_missing_arg, _keywords[1], 2);
100 return nullptr;
101 }
102
103 blender::gpu::Batch *batch = GPU_batch_create(GPUPrimType(prim_type.value_found),
104 py_vertbuf->buf,
105 py_indexbuf ? py_indexbuf->elem : nullptr);
106
108
109#ifdef USE_GPU_PY_REFERENCES
110 ret->references = PyList_New(py_indexbuf ? 2 : 1);
111 PyList_SET_ITEM(ret->references, 0, (PyObject *)py_vertbuf);
112 Py_INCREF(py_vertbuf);
113
114 if (py_indexbuf != nullptr) {
115 PyList_SET_ITEM(ret->references, 1, (PyObject *)py_indexbuf);
116 Py_INCREF(py_indexbuf);
117 }
118
119 BLI_assert(!PyObject_GC_IsTracked((PyObject *)ret));
120 PyObject_GC_Track(ret);
121#endif
122
123 return (PyObject *)ret;
124}
125
127 /* Wrap. */
128 pygpu_batch_vertbuf_add_doc, ".. method:: vertbuf_add(buf)\n"
129"\n"
130" Add another vertex buffer to the Batch.\n"
131" It is not possible to add more vertices to the batch using this method.\n"
132" Instead it can be used to add more attributes to the existing vertices.\n"
133" A good use case would be when you have a separate\n"
134" vertex buffer for vertex positions and vertex normals.\n"
135" Current a batch can have at most " STRINGIFY(GPU_BATCH_VBO_MAX_LEN) " vertex buffers.\n"
136"\n"
137" :arg buf: The vertex buffer that will be added to the batch.\n"
138" :type buf: :class:`gpu.types.GPUVertBuf`\n"
139);
141{
142 if (!BPyGPUVertBuf_Check(py_buf)) {
143 PyErr_Format(PyExc_TypeError, "Expected a GPUVertBuf, got %s", Py_TYPE(py_buf)->tp_name);
144 return nullptr;
145 }
146
147 if (GPU_vertbuf_get_vertex_len(self->batch->verts[0]) != GPU_vertbuf_get_vertex_len(py_buf->buf))
148 {
149 PyErr_Format(PyExc_TypeError,
150 "Expected %d length, got %d",
151 GPU_vertbuf_get_vertex_len(self->batch->verts[0]),
153 return nullptr;
154 }
155
156 if (self->batch->verts[GPU_BATCH_VBO_MAX_LEN - 1] != nullptr) {
157 PyErr_SetString(
158 PyExc_RuntimeError,
159 "Maximum number of vertex buffers exceeded: " STRINGIFY(GPU_BATCH_VBO_MAX_LEN));
160 return nullptr;
161 }
162
163#ifdef USE_GPU_PY_REFERENCES
164 /* Hold user */
165 PyList_Append(self->references, (PyObject *)py_buf);
166#endif
167
168 GPU_batch_vertbuf_add(self->batch, py_buf->buf, false);
169 Py_RETURN_NONE;
170}
171
173 /* Wrap. */
174 pygpu_batch_program_set_doc,
175 ".. method:: program_set(program)\n"
176 "\n"
177 " Assign a shader to this batch that will be used for drawing when not overwritten later.\n"
178 " Note: This method has to be called in the draw context that the batch will be drawn in.\n"
179 " This function does not need to be called when you always\n"
180 " set the shader when calling :meth:`gpu.types.GPUBatch.draw`.\n"
181 "\n"
182 " :arg program: The program/shader the batch will use in future draw calls.\n"
183 " :type program: :class:`gpu.types.GPUShader`\n");
185{
186 static bool deprecation_warning_issued = false;
187
188 /* Deprecation warning raised when calling `gpu.types.GPUBatch.program_set`. */
189 if (!deprecation_warning_issued) {
190 PyErr_WarnEx(PyExc_DeprecationWarning,
191 "Calls to GPUBatch.program_set are deprecated."
192 "Please set the shader via the 'program' parameter when calling "
193 "GPUBatch.draw/draw_instanced/draw_range.",
194 1);
195 deprecation_warning_issued = true;
196 }
197
198 if (!BPyGPUShader_Check(py_shader)) {
199 PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name);
200 return nullptr;
201 }
202
203 GPUShader *shader = py_shader->shader;
204 GPU_batch_set_shader(self->batch, shader);
205
206#ifdef USE_GPU_PY_REFERENCES
207 /* Remove existing user (if any), hold new user. */
208 int i = PyList_GET_SIZE(self->references);
209 while (--i != -1) {
210 PyObject *py_shader_test = PyList_GET_ITEM(self->references, i);
211 if (BPyGPUShader_Check(py_shader_test)) {
212 PyList_SET_ITEM(self->references, i, (PyObject *)py_shader);
213 Py_INCREF(py_shader);
214 Py_DECREF(py_shader_test);
215 /* Only ever reference one shader. */
216 break;
217 }
218 }
219 if (i != -1) {
220 PyList_Append(self->references, (PyObject *)py_shader);
221 }
222#endif
223
224 Py_RETURN_NONE;
225}
226
228 /* Wrap. */
229 pygpu_batch_draw_doc,
230 ".. method:: draw(program=None)\n"
231 "\n"
232 " Run the drawing program with the parameters assigned to the batch.\n"
233 "\n"
234 " :arg program: Program that performs the drawing operations.\n"
235 " If ``None`` is passed, the last program set to this batch will run.\n"
236 " :type program: :class:`gpu.types.GPUShader`\n");
237static PyObject *pygpu_batch_draw(BPyGPUBatch *self, PyObject *args)
238{
239 static bool deprecation_warning_issued = false;
240
241 BPyGPUShader *py_program = nullptr;
242
243 if (!PyArg_ParseTuple(args, "|O!:GPUBatch.draw", &BPyGPUShader_Type, &py_program)) {
244 return nullptr;
245 }
246 if (py_program == nullptr) {
247
248 if (!deprecation_warning_issued) {
249 /* Deprecation warning raised when calling gpu.types.GPUBatch.draw without a valid GPUShader.
250 */
251 PyErr_WarnEx(PyExc_DeprecationWarning,
252 "Calling GPUBatch.draw without specifying a program is deprecated. "
253 "Please provide a valid GPUShader as the 'program' parameter.",
254 1);
255 deprecation_warning_issued = true;
256 }
257
259 return nullptr;
260 }
261 }
262 else if (self->batch->shader != py_program->shader) {
263 GPU_batch_set_shader(self->batch, py_program->shader);
264 }
265
266 GPU_batch_draw(self->batch);
267 Py_RETURN_NONE;
268}
269
271 /* Wrap. */
272 pygpu_batch_draw_instanced_doc,
273 ".. method:: draw_instanced(program, *, instance_start=0, instance_count=0)\n"
274 "\n"
275 " Draw multiple instances of the drawing program with the parameters assigned\n"
276 " to the batch. In the vertex shader, `gl_InstanceID` will contain the instance\n"
277 " number being drawn.\n"
278 "\n"
279 " :arg program: Program that performs the drawing operations.\n"
280 " :type program: :class:`gpu.types.GPUShader`\n"
281 " :arg instance_start: Number of the first instance to draw.\n"
282 " :type instance_start: int\n"
283 " :arg instance_count: Number of instances to draw. When not provided or set to 0\n"
284 " the number of instances will be determined by the number of rows in the first\n"
285 " vertex buffer.\n"
286 " :type instance_count: int\n");
287static PyObject *pygpu_batch_draw_instanced(BPyGPUBatch *self, PyObject *args, PyObject *kw)
288{
289 BPyGPUShader *py_program = nullptr;
290 int instance_start = 0;
291 int instance_count = 0;
292
293 static const char *_keywords[] = {"program", "instance_start", "instance_count", nullptr};
294 static _PyArg_Parser _parser = {
296 "O!" /* `program` */
297 "|$" /* Optional keyword only arguments. */
298 "i" /* `instance_start` */
299 "i" /* `instance_count' */
300 ":GPUBatch.draw_instanced",
301 _keywords,
302 nullptr,
303 };
304 if (!_PyArg_ParseTupleAndKeywordsFast(
305 args, kw, &_parser, &BPyGPUShader_Type, &py_program, &instance_start, &instance_count))
306 {
307 return nullptr;
308 }
309
310 GPU_batch_set_shader(self->batch, py_program->shader);
311 GPU_batch_draw_instance_range(self->batch, instance_start, instance_count);
312 Py_RETURN_NONE;
313}
314
316 /* Wrap. */
317 pygpu_batch_draw_range_doc,
318 ".. method:: draw_range(program, *, elem_start=0, elem_count=0)\n"
319 "\n"
320 " Run the drawing program with the parameters assigned to the batch. "
321 "Only draw the ``elem_count`` elements of the index buffer starting at ``elem_start``.\n"
322 "\n"
323 " :arg program: Program that performs the drawing operations.\n"
324 " :type program: :class:`gpu.types.GPUShader`\n"
325 " :arg elem_start: First index to draw. When not provided or set to 0 drawing\n"
326 " will start from the first element of the index buffer.\n"
327 " :type elem_start: int\n"
328 " :arg elem_count: Number of elements of the index buffer to draw. When not\n"
329 " provided or set to 0 all elements from ``elem_start`` to the end of the\n"
330 " index buffer will be drawn.\n"
331 " :type elem_count: int\n");
332static PyObject *pygpu_batch_draw_range(BPyGPUBatch *self, PyObject *args, PyObject *kw)
333{
334 BPyGPUShader *py_program = nullptr;
335 int elem_start = 0;
336 int elem_count = 0;
337
338 static const char *_keywords[] = {"program", "elem_start", "elem_count", nullptr};
339 static _PyArg_Parser _parser = {
341 "O!" /* `program` */
342 "|$" /* Optional keyword only arguments. */
343 "i" /* `elem_start' */
344 "i" /* `elem_count' */
345 ":GPUBatch.draw_range",
346 _keywords,
347 nullptr,
348 };
349 if (!_PyArg_ParseTupleAndKeywordsFast(
350 args, kw, &_parser, &BPyGPUShader_Type, &py_program, &elem_start, &elem_count))
351 {
352 return nullptr;
353 }
354
355 GPU_batch_set_shader(self->batch, py_program->shader);
356 GPU_batch_draw_range(self->batch, elem_start, elem_count);
357 Py_RETURN_NONE;
358}
359
361{
363 return nullptr;
364 }
365 GPU_shader_bind(self->batch->shader);
366 Py_RETURN_NONE;
367}
368
370{
372 return nullptr;
373 }
375 Py_RETURN_NONE;
376}
377
378#if (defined(__GNUC__) && !defined(__clang__))
379# pragma GCC diagnostic push
380# pragma GCC diagnostic ignored "-Wcast-function-type"
381#endif
382
383static PyMethodDef pygpu_batch__tp_methods[] = {
384 {"vertbuf_add", (PyCFunction)pygpu_batch_vertbuf_add, METH_O, pygpu_batch_vertbuf_add_doc},
385 {"program_set", (PyCFunction)pygpu_batch_program_set, METH_O, pygpu_batch_program_set_doc},
386 {"draw", (PyCFunction)pygpu_batch_draw, METH_VARARGS, pygpu_batch_draw_doc},
387 {"draw_instanced",
388 (PyCFunction)pygpu_batch_draw_instanced,
389 METH_VARARGS | METH_KEYWORDS,
390 pygpu_batch_draw_instanced_doc},
391 {"draw_range",
392 (PyCFunction)pygpu_batch_draw_range,
393 METH_VARARGS | METH_KEYWORDS,
394 pygpu_batch_draw_range_doc},
395 {"_program_use_begin", (PyCFunction)pygpu_batch_program_use_begin, METH_NOARGS, ""},
396 {"_program_use_end", (PyCFunction)pygpu_batch_program_use_end, METH_NOARGS, ""},
397 {nullptr, nullptr, 0, nullptr},
398};
399
400#if (defined(__GNUC__) && !defined(__clang__))
401# pragma GCC diagnostic pop
402#endif
403
404#ifdef USE_GPU_PY_REFERENCES
405
406static int pygpu_batch__tp_traverse(BPyGPUBatch *self, visitproc visit, void *arg)
407{
408 Py_VISIT(self->references);
409 return 0;
410}
411
413{
414 Py_CLEAR(self->references);
415 return 0;
416}
417
419{
420 return self->references != nullptr;
421}
422
423#endif
424
426{
427 GPU_batch_discard(self->batch);
428
429#ifdef USE_GPU_PY_REFERENCES
430 PyObject_GC_UnTrack(self);
431 if (self->references) {
433 Py_XDECREF(self->references);
434 }
435#endif
436
437 Py_TYPE(self)->tp_free(self);
438}
439
441 /* Wrap. */
442 pygpu_batch__tp_doc,
443 ".. class:: GPUBatch(type, buf, elem=None)\n"
444 "\n"
445 " Reusable container for drawable geometry.\n"
446 "\n"
447 " :arg type: The primitive type of geometry to be drawn.\n"
448 " Possible values are `POINTS`, `LINES`, `TRIS`, `LINE_STRIP`, `LINE_LOOP`, `TRI_STRIP`, "
449 "`TRI_FAN`, `LINES_ADJ`, `TRIS_ADJ` and `LINE_STRIP_ADJ`.\n"
450 " :type type: str\n"
451 " :arg buf: Vertex buffer containing all or some of the attributes required for drawing.\n"
452 " :type buf: :class:`gpu.types.GPUVertBuf`\n"
453 " :arg elem: An optional index buffer.\n"
454 " :type elem: :class:`gpu.types.GPUIndexBuf`\n");
455PyTypeObject BPyGPUBatch_Type = {
456 /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
457 /*tp_name*/ "GPUBatch",
458 /*tp_basicsize*/ sizeof(BPyGPUBatch),
459 /*tp_itemsize*/ 0,
460 /*tp_dealloc*/ (destructor)pygpu_batch__tp_dealloc,
461 /*tp_vectorcall_offset*/ 0,
462 /*tp_getattr*/ nullptr,
463 /*tp_setattr*/ nullptr,
464 /*tp_as_async*/ nullptr,
465 /*tp_repr*/ nullptr,
466 /*tp_as_number*/ nullptr,
467 /*tp_as_sequence*/ nullptr,
468 /*tp_as_mapping*/ nullptr,
469 /*tp_hash*/ nullptr,
470 /*tp_call*/ nullptr,
471 /*tp_str*/ nullptr,
472 /*tp_getattro*/ nullptr,
473 /*tp_setattro*/ nullptr,
474 /*tp_as_buffer*/ nullptr,
475#ifdef USE_GPU_PY_REFERENCES
476 /*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
477#else
478 /*tp_flags*/ Py_TPFLAGS_DEFAULT,
479#endif
480 /*tp_doc*/ pygpu_batch__tp_doc,
481#ifdef USE_GPU_PY_REFERENCES
482 /*tp_traverse*/ (traverseproc)pygpu_batch__tp_traverse,
483#else
484 /*tp_traverse*/ nullptr,
485#endif
487 /*tp_clear*/ (inquiry)pygpu_batch__tp_clear,
488#else
489 /*tp_clear*/ nullptr,
490#endif
491 /*tp_richcompare*/ nullptr,
492 /*tp_weaklistoffset*/ 0,
493 /*tp_iter*/ nullptr,
494 /*tp_iternext*/ nullptr,
495 /*tp_methods*/ pygpu_batch__tp_methods,
496 /*tp_members*/ nullptr,
497 /*tp_getset*/ nullptr,
498 /*tp_base*/ nullptr,
499 /*tp_dict*/ nullptr,
500 /*tp_descr_get*/ nullptr,
501 /*tp_descr_set*/ nullptr,
502 /*tp_dictoffset*/ 0,
503 /*tp_init*/ nullptr,
504 /*tp_alloc*/ nullptr,
505 /*tp_new*/ pygpu_batch__tp_new,
506 /*tp_free*/ nullptr,
507#ifdef USE_GPU_PY_REFERENCES
508 /*tp_is_gc*/ (inquiry)pygpu_batch__tp_is_gc,
509#else
510 /*tp_is_gc*/ nullptr,
511#endif
512 /*tp_bases*/ nullptr,
513 /*tp_mro*/ nullptr,
514 /*tp_cache*/ nullptr,
515 /*tp_subclasses*/ nullptr,
516 /*tp_weaklist*/ nullptr,
517 /*tp_del*/ nullptr,
518 /*tp_version_tag*/ 0,
519 /*tp_finalize*/ nullptr,
520 /*tp_vectorcall*/ nullptr,
521};
522
525/* -------------------------------------------------------------------- */
529PyObject *BPyGPUBatch_CreatePyObject(blender::gpu::Batch *batch)
530{
532
533#ifdef USE_GPU_PY_REFERENCES
534 self = (BPyGPUBatch *)_PyObject_GC_New(&BPyGPUBatch_Type);
535 self->references = nullptr;
536#else
537 self = PyObject_New(BPyGPUBatch, &BPyGPUBatch_Type);
538#endif
539
540 self->batch = batch;
541
542 return (PyObject *)self;
543}
544
547#undef BPY_GPU_BATCH_CHECK_OBJ
#define BLI_assert(a)
Definition BLI_assert.h:50
#define STRINGIFY(x)
void GPU_batch_discard(blender::gpu::Batch *batch)
#define GPU_batch_create(primitive_type, vertex_buf, index_buf)
Definition GPU_batch.hh:149
void GPU_batch_draw_instance_range(blender::gpu::Batch *batch, int instance_first, int instance_count)
int GPU_batch_vertbuf_add(blender::gpu::Batch *batch, blender::gpu::VertBuf *vertex_buf, bool own_vbo)
#define GPU_BATCH_VBO_MAX_LEN
Definition GPU_batch.hh:32
void GPU_batch_set_shader(blender::gpu::Batch *batch, GPUShader *shader)
void GPU_batch_draw(blender::gpu::Batch *batch)
void GPU_batch_draw_range(blender::gpu::Batch *batch, int vertex_first, int vertex_count)
GPUPrimType
@ GPU_PRIM_TRI_FAN
@ GPU_PRIM_LINE_LOOP
@ GPU_PRIM_NONE
void GPU_shader_bind(GPUShader *shader)
void GPU_shader_unbind()
uint GPU_vertbuf_get_vertex_len(const blender::gpu::VertBuf *verts)
struct GPUShader GPUShader
PyObject * self
struct @620::@622 batch
PyC_StringEnumItems bpygpu_primtype_items[]
Definition gpu_py.cc:26
#define BPYGPU_IS_INIT_OR_ERROR_OBJ
Definition gpu_py.hh:18
static PyObject * pygpu_batch_draw(BPyGPUBatch *self, PyObject *args)
static int pygpu_batch__tp_clear(BPyGPUBatch *self)
PyObject * BPyGPUBatch_CreatePyObject(blender::gpu::Batch *batch)
static PyObject * pygpu_batch_program_use_begin(BPyGPUBatch *self)
static int pygpu_batch__tp_traverse(BPyGPUBatch *self, visitproc visit, void *arg)
static PyObject * pygpu_batch_program_set(BPyGPUBatch *self, BPyGPUShader *py_shader)
static PyObject * pygpu_batch__tp_new(PyTypeObject *, PyObject *args, PyObject *kwds)
static PyObject * pygpu_batch_program_use_end(BPyGPUBatch *self)
static void pygpu_batch__tp_dealloc(BPyGPUBatch *self)
static bool pygpu_batch_is_program_or_error(BPyGPUBatch *self)
static PyObject * pygpu_batch_draw_instanced(BPyGPUBatch *self, PyObject *args, PyObject *kw)
static PyMethodDef pygpu_batch__tp_methods[]
static PyObject * pygpu_batch_draw_range(BPyGPUBatch *self, PyObject *args, PyObject *kw)
static int pygpu_batch__tp_is_gc(BPyGPUBatch *self)
PyTypeObject BPyGPUBatch_Type
PyDoc_STRVAR(pygpu_batch_vertbuf_add_doc, ".. method:: vertbuf_add(buf)\n" "\n" " Add another vertex buffer to the Batch.\n" " It is not possible to add more vertices to the batch using this method.\n" " Instead it can be used to add more attributes to the existing vertices.\n" " A good use case would be when you have a separate\n" " vertex buffer for vertex positions and vertex normals.\n" " Current a batch can have at most " STRINGIFY(GPU_BATCH_VBO_MAX_LEN) " vertex buffers.\n" "\n" " :arg buf: The vertex buffer that will be added to the batch.\n" " :type buf: :class:`gpu.types.GPUVertBuf`\n")
static PyObject * pygpu_batch_vertbuf_add(BPyGPUBatch *self, BPyGPUVertBuf *py_buf)
#define USE_GPU_PY_REFERENCES
PyTypeObject BPyGPUIndexBuf_Type
PyTypeObject BPyGPUShader_Type
#define BPyGPUShader_Check(v)
PyTypeObject BPyGPUVertBuf_Type
#define BPyGPUVertBuf_Check(v)
int PyC_ParseStringEnum(PyObject *o, void *p)
header-only compatibility defines.
#define PY_ARG_PARSER_HEAD_COMPAT()
return ret
PyObject * references
PyObject_VAR_HEAD blender::gpu::IndexBuf * elem
PyObject_VAR_HEAD struct GPUShader * shader
PyObject_VAR_HEAD blender::gpu::VertBuf * buf