/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2015 Blender Foundation. */ /** \file * \ingroup bpygpu * * This file defines the off-screen functionalities of the 'gpu' module * used for off-screen OpenGL rendering. * * - Use `bpygpu_` for local API. * - Use `BPyGPU` for public API. */ #include #include "BLI_utildefines.h" #include "GPU_batch.h" #include "../generic/py_capi_utils.h" #include "gpu_py.h" #include "gpu_py_element.h" #include "gpu_py_shader.h" #include "gpu_py_vertex_buffer.h" #include "gpu_py_batch.h" /* own include */ /* -------------------------------------------------------------------- */ /** \name Utility Functions * \{ */ static bool pygpu_batch_is_program_or_error(BPyGPUBatch *self) { if (!self->batch->shader) { PyErr_SetString(PyExc_RuntimeError, "batch does not have any program assigned to it"); return false; } return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \name GPUBatch Type * \{ */ static PyObject *pygpu_batch__tp_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *kwds) { const char *exc_str_missing_arg = "GPUBatch.__new__() missing required argument '%s' (pos %d)"; struct PyC_StringEnum prim_type = {bpygpu_primtype_items, GPU_PRIM_NONE}; BPyGPUVertBuf *py_vertbuf = NULL; BPyGPUIndexBuf *py_indexbuf = NULL; static const char *_keywords[] = {"type", "buf", "elem", NULL}; static _PyArg_Parser _parser = { "|$" /* Optional keyword only arguments. */ "O&" /* `type` */ "O!" /* `buf` */ "O!" /* `elem` */ ":GPUBatch.__new__", _keywords, 0, }; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, PyC_ParseStringEnum, &prim_type, &BPyGPUVertBuf_Type, &py_vertbuf, &BPyGPUIndexBuf_Type, &py_indexbuf)) { return NULL; } BLI_assert(prim_type.value_found != GPU_PRIM_NONE); if (prim_type.value_found == GPU_PRIM_LINE_LOOP) { PyErr_WarnEx(PyExc_DeprecationWarning, "'LINE_LOOP' is deprecated. Please use 'LINE_STRIP' and close the segment.", 1); } else if (prim_type.value_found == GPU_PRIM_TRI_FAN) { PyErr_WarnEx( PyExc_DeprecationWarning, "'TRI_FAN' is deprecated. Please use 'TRI_STRIP' or 'TRIS' and try modifying your " "vertices or indices to match the topology.", 1); } if (py_vertbuf == NULL) { PyErr_Format(PyExc_TypeError, exc_str_missing_arg, _keywords[1], 2); return NULL; } GPUBatch *batch = GPU_batch_create( prim_type.value_found, py_vertbuf->buf, py_indexbuf ? py_indexbuf->elem : NULL); BPyGPUBatch *ret = (BPyGPUBatch *)BPyGPUBatch_CreatePyObject(batch); #ifdef USE_GPU_PY_REFERENCES ret->references = PyList_New(py_indexbuf ? 2 : 1); PyList_SET_ITEM(ret->references, 0, (PyObject *)py_vertbuf); Py_INCREF(py_vertbuf); if (py_indexbuf != NULL) { PyList_SET_ITEM(ret->references, 1, (PyObject *)py_indexbuf); Py_INCREF(py_indexbuf); } BLI_assert(!PyObject_GC_IsTracked((PyObject *)ret)); PyObject_GC_Track(ret); #endif return (PyObject *)ret; } 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) { if (!BPyGPUVertBuf_Check(py_buf)) { PyErr_Format(PyExc_TypeError, "Expected a GPUVertBuf, got %s", Py_TYPE(py_buf)->tp_name); return NULL; } if (GPU_vertbuf_get_vertex_len(self->batch->verts[0]) != GPU_vertbuf_get_vertex_len(py_buf->buf)) { PyErr_Format(PyExc_TypeError, "Expected %d length, got %d", GPU_vertbuf_get_vertex_len(self->batch->verts[0]), GPU_vertbuf_get_vertex_len(py_buf->buf)); return NULL; } if (self->batch->verts[GPU_BATCH_VBO_MAX_LEN - 1] != NULL) { PyErr_SetString( PyExc_RuntimeError, "Maximum number of vertex buffers exceeded: " STRINGIFY(GPU_BATCH_VBO_MAX_LEN)); return NULL; } #ifdef USE_GPU_PY_REFERENCES /* Hold user */ PyList_Append(self->references, (PyObject *)py_buf); #endif GPU_batch_vertbuf_add(self->batch, py_buf->buf, false); Py_RETURN_NONE; } PyDoc_STRVAR( pygpu_batch_program_set_doc, ".. method:: program_set(program)\n" "\n" " Assign a shader to this batch that will be used for drawing when not overwritten later.\n" " Note: This method has to be called in the draw context that the batch will be drawn in.\n" " This function does not need to be called when you always\n" " set the shader when calling :meth:`gpu.types.GPUBatch.draw`.\n" "\n" " :arg program: The program/shader the batch will use in future draw calls.\n" " :type program: :class:`gpu.types.GPUShader`\n"); static PyObject *pygpu_batch_program_set(BPyGPUBatch *self, BPyGPUShader *py_shader) { //deprecation warning PyErr_WarnEx(PyExc_DeprecationWarning, "calls to 'gpu.types.GPUBatch.program_set' is deprecated'", 1); if (!BPyGPUShader_Check(py_shader)) { PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name); return NULL; } GPUShader *shader = py_shader->shader; GPU_batch_set_shader(self->batch, shader); #ifdef USE_GPU_PY_REFERENCES /* Remove existing user (if any), hold new user. */ int i = PyList_GET_SIZE(self->references); while (--i != -1) { PyObject *py_shader_test = PyList_GET_ITEM(self->references, i); if (BPyGPUShader_Check(py_shader_test)) { PyList_SET_ITEM(self->references, i, (PyObject *)py_shader); Py_INCREF(py_shader); Py_DECREF(py_shader_test); /* Only ever reference one shader. */ break; } } if (i != -1) { PyList_Append(self->references, (PyObject *)py_shader); } #endif Py_RETURN_NONE; } PyDoc_STRVAR(pygpu_batch_draw_doc, ".. method:: draw(program=None)\n" "\n" " Run the drawing program with the parameters assigned to the batch.\n" "\n" " :arg program: Program that performs the drawing operations.\n" " If ``None`` is passed, the last program set to this batch will run.\n" " :type program: :class:`gpu.types.GPUShader`\n"); static PyObject *pygpu_batch_draw(BPyGPUBatch *self, PyObject *args) { BPyGPUShader *py_program = NULL; if (!PyArg_ParseTuple(args, "|O!:GPUBatch.draw", &BPyGPUShader_Type, &py_program)) { return NULL; } if (py_program == NULL) { if (!pygpu_batch_is_program_or_error(self)) { return NULL; } } else if (self->batch->shader != py_program->shader) { GPU_batch_set_shader(self->batch, py_program->shader); } //deprecation warning if(args && self) { continue; } else { PyErr_WarnEx(PyExc_DeprecationWarning, "'GPU BATCH' program is deprecated. Please add a valid program parameter ", 1); } GPU_batch_draw(self->batch); Py_RETURN_NONE; } static PyObject *pygpu_batch_program_use_begin(BPyGPUBatch *self) { if (!pygpu_batch_is_program_or_error(self)) { return NULL; } GPU_shader_bind(self->batch->shader); Py_RETURN_NONE; } static PyObject *pygpu_batch_program_use_end(BPyGPUBatch *self) { if (!pygpu_batch_is_program_or_error(self)) { return NULL; } GPU_shader_unbind(); Py_RETURN_NONE; } static struct PyMethodDef pygpu_batch__tp_methods[] = { {"vertbuf_add", (PyCFunction)pygpu_batch_vertbuf_add, METH_O, pygpu_batch_vertbuf_add_doc}, {"program_set", (PyCFunction)pygpu_batch_program_set, METH_O, pygpu_batch_program_set_doc}, {"draw", (PyCFunction)pygpu_batch_draw, METH_VARARGS, pygpu_batch_draw_doc}, {"_program_use_begin", (PyCFunction)pygpu_batch_program_use_begin, METH_NOARGS, ""}, {"_program_use_end", (PyCFunction)pygpu_batch_program_use_end, METH_NOARGS, ""}, {NULL, NULL, 0, NULL}, }; #ifdef USE_GPU_PY_REFERENCES static int pygpu_batch__tp_traverse(BPyGPUBatch *self, visitproc visit, void *arg) { Py_VISIT(self->references); return 0; } static int pygpu_batch__tp_clear(BPyGPUBatch *self) { Py_CLEAR(self->references); return 0; } static int pygpu_batch__tp_is_gc(BPyGPUBatch *self) { return self->references != NULL; } #endif static void pygpu_batch__tp_dealloc(BPyGPUBatch *self) { GPU_batch_discard(self->batch); #ifdef USE_GPU_PY_REFERENCES PyObject_GC_UnTrack(self); if (self->references) { pygpu_batch__tp_clear(self); Py_XDECREF(self->references); } #endif Py_TYPE(self)->tp_free(self); } PyDoc_STRVAR( pygpu_batch__tp_doc, ".. class:: GPUBatch(type, buf, elem=None)\n" "\n" " Reusable container for drawable geometry.\n" "\n" " :arg type: The primitive type of geometry to be drawn.\n" " Possible values are `POINTS`, `LINES`, `TRIS`, `LINE_STRIP`, `LINE_LOOP`, `TRI_STRIP`, " "`TRI_FAN`, `LINES_ADJ`, `TRIS_ADJ` and `LINE_STRIP_ADJ`.\n" " :type type: str\n" " :arg buf: Vertex buffer containing all or some of the attributes required for drawing.\n" " :type buf: :class:`gpu.types.GPUVertBuf`\n" " :arg elem: An optional index buffer.\n" " :type elem: :class:`gpu.types.GPUIndexBuf`\n"); PyTypeObject BPyGPUBatch_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUBatch", .tp_basicsize = sizeof(BPyGPUBatch), .tp_dealloc = (destructor)pygpu_batch__tp_dealloc, #ifdef USE_GPU_PY_REFERENCES .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_doc = pygpu_batch__tp_doc, .tp_traverse = (traverseproc)pygpu_batch__tp_traverse, .tp_clear = (inquiry)pygpu_batch__tp_clear, .tp_is_gc = (inquiry)pygpu_batch__tp_is_gc, #else .tp_flags = Py_TPFLAGS_DEFAULT, #endif .tp_methods = pygpu_batch__tp_methods, .tp_new = pygpu_batch__tp_new,mm }; /** \} */ /* -------------------------------------------------------------------- */ /** \name Public API * \{ */ PyObject *BPyGPUBatch_CreatePyObject(GPUBatch *batch) { BPyGPUBatch *self; #ifdef USE_GPU_PY_REFERENCES self = (BPyGPUBatch *)_PyObject_GC_New(&BPyGPUBatch_Type); self->references = NULL; #else self = PyObject_New(BPyGPUBatch, &BPyGPUBatch_Type); #endif self->batch = batch; return (PyObject *)self; } /** \} */ #undef BPY_GPU_BATCH_CHECK_OBJ