Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../../editors/include
../../makesrna
)
set(INC_SYS
)
set(SRC
gpu_py.cc
gpu_py_api.cc
gpu_py_batch.cc
gpu_py_buffer.cc
gpu_py_capabilities.cc
gpu_py_compute.cc
gpu_py_element.cc
gpu_py_framebuffer.cc
gpu_py_matrix.cc
gpu_py_offscreen.cc
gpu_py_platform.cc
gpu_py_select.cc
gpu_py_shader.cc
gpu_py_shader_create_info.cc
gpu_py_state.cc
gpu_py_texture.cc
gpu_py_types.cc
gpu_py_uniformbuffer.cc
gpu_py_vertex_buffer.cc
gpu_py_vertex_format.cc
gpu_py.hh
gpu_py_api.hh
gpu_py_batch.hh
gpu_py_buffer.hh
gpu_py_capabilities.hh
gpu_py_compute.hh
gpu_py_element.hh
gpu_py_framebuffer.hh
gpu_py_matrix.hh
gpu_py_offscreen.hh
gpu_py_platform.hh
gpu_py_select.hh
gpu_py_shader.hh
gpu_py_state.hh
gpu_py_texture.hh
gpu_py_types.hh
gpu_py_uniformbuffer.hh
gpu_py_vertex_buffer.hh
gpu_py_vertex_format.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::guardedalloc
PRIVATE bf::windowmanager
PRIVATE bf::dependencies::optional::python
)
if(WITH_OPENGL_BACKEND)
list(APPEND LIB
PRIVATE bf::dependencies::epoxy
)
endif()
blender_add_lib(bf_python_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_init_exit.hh"
#include "GPU_primitive.hh"
#include "GPU_texture.hh"
#include "../generic/py_capi_utils.hh"
#include "gpu_py.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Enums
* \{ */
PyC_StringEnumItems bpygpu_primtype_items[] = {
{GPU_PRIM_POINTS, "POINTS"},
{GPU_PRIM_LINES, "LINES"},
{GPU_PRIM_TRIS, "TRIS"},
{GPU_PRIM_LINE_STRIP, "LINE_STRIP"},
{GPU_PRIM_LINE_LOOP, "LINE_LOOP"},
{GPU_PRIM_TRI_STRIP, "TRI_STRIP"},
{GPU_PRIM_TRI_FAN, "TRI_FAN"},
{GPU_PRIM_LINES_ADJ, "LINES_ADJ"},
{GPU_PRIM_TRIS_ADJ, "TRIS_ADJ"},
{GPU_PRIM_LINE_STRIP_ADJ, "LINE_STRIP_ADJ"},
{0, nullptr},
};
PyC_StringEnumItems bpygpu_dataformat_items[] = {
{GPU_DATA_FLOAT, "FLOAT"},
{GPU_DATA_INT, "INT"},
{GPU_DATA_UINT, "UINT"},
{GPU_DATA_UBYTE, "UBYTE"},
{GPU_DATA_UINT_24_8_DEPRECATED, "UINT_24_8"},
{GPU_DATA_10_11_11_REV, "10_11_11_REV"},
{0, nullptr},
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utilities
* \{ */
bool bpygpu_is_init_or_error()
{
if (!GPU_is_init()) {
PyErr_SetString(
PyExc_SystemError,
"GPU functions for drawing requires the gpu module to be initialized. See gpu.init.");
return false;
}
return true;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "../generic/py_capi_utils.hh"
namespace blender {
extern struct PyC_StringEnumItems bpygpu_primtype_items[];
extern struct PyC_StringEnumItems bpygpu_dataformat_items[];
/* Docstring Literal types for shared enums. */
#define PYDOC_PRIMTYPE_LITERAL \
"Literal[" \
"'POINTS', " \
"'LINES', " \
"'TRIS', " \
"'LINE_STRIP', " \
"'LINE_LOOP', " \
"'TRI_STRIP', " \
"'TRI_FAN', " \
"'LINES_ADJ', " \
"'TRIS_ADJ', " \
"'LINE_STRIP_ADJ']"
#define PYDOC_DATAFORMAT_LITERAL \
"Literal[" \
"'FLOAT', " \
"'INT', " \
"'UINT', " \
"'UBYTE', " \
"'UINT_24_8', " \
"'10_11_11_REV']"
[[nodiscard]] bool bpygpu_is_init_or_error();
#define BPYGPU_IS_INIT_OR_ERROR_OBJ \
if (UNLIKELY(!bpygpu_is_init_or_error())) { \
return NULL; \
} \
((void)0)
#define BPYGPU_IS_INIT_OR_ERROR_INT \
if (UNLIKELY(!bpygpu_is_init_or_error())) { \
return -1; \
} \
((void)0)
} // namespace blender

View File

@@ -0,0 +1,151 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* Experimental Python API, not considered public yet (called '_gpu'),
* we may re-expose as public later.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "../generic/py_capi_utils.hh"
#include "gpu_py_capabilities.hh"
#include "gpu_py_compute.hh"
#include "gpu_py_matrix.hh"
#include "gpu_py_platform.hh"
#include "gpu_py_select.hh"
#include "gpu_py_state.hh"
#include "gpu_py_types.hh"
#include "BKE_global.hh"
#include "GPU_context.hh"
#include "GPU_init_exit.hh"
#include "WM_api.hh"
#include "gpu_py_api.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Module
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_init_doc,
".. function:: init()\n"
"\n"
" Initializes the GPU module for background use.\n"
" If the initialization fails, a SystemError will be raised.\n");
static PyObject *pygpu_init(PyObject * /*self*/)
{
if (!G.background || GPU_is_init()) {
/* GPU is already initialized. */
Py_RETURN_NONE;
}
if (!GPU_backend_supported()) {
PyErr_SetString(PyExc_SystemError, "Failed to initialize GPU. GPU backend not supported");
return nullptr;
}
/* Cannot use GPU_init() as it requires a GPU context to have been created.
* See WM_init_gpu implementation. */
WM_init_gpu();
if (!GPU_is_init()) {
PyErr_SetString(PyExc_SystemError, "Failed to initialize GPU. Unexpected Error");
return nullptr;
}
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_tp_methods[] = {
{"init", reinterpret_cast<PyCFunction>(pygpu_init), METH_NOARGS, pygpu_init_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_doc,
"This module provides Python wrappers for the GPU implementation in Blender.\n"
"Some higher level functions can be found in the :mod:`gpu_extras` module.\n");
static PyModuleDef pygpu_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu",
/*m_doc*/ pygpu_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *BPyInit_gpu()
{
PyObject *sys_modules = PyImport_GetModuleDict();
PyObject *submodule;
PyObject *mod;
mod = PyModule_Create(&pygpu_module_def);
PyModule_AddObject(mod, "types", (submodule = bpygpu_types_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "capabilities", (submodule = bpygpu_capabilities_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "matrix", (submodule = bpygpu_matrix_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "platform", (submodule = bpygpu_platform_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "select", (submodule = bpygpu_select_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "shader", (submodule = bpygpu_shader_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "state", (submodule = bpygpu_state_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "texture", (submodule = bpygpu_texture_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "compute", (submodule = bpygpu_compute_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
return mod;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
/* Each type object could have a method for free GPU resources.
* However, it is currently of little use. */
// #define BPYGPU_USE_GPUOBJ_FREE_METHOD
[[nodiscard]] PyObject *BPyInit_gpu();
} // namespace blender

View File

@@ -0,0 +1,708 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \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 <Python.h>
#include "BLI_utildefines.h"
#include "GPU_batch.hh"
#include "GPU_state.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_element.hh"
#include "gpu_py_shader.hh"
#include "gpu_py_vertex_buffer.hh"
#include "gpu_py_batch.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \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 * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *exc_str_missing_arg = "GPUBatch.__new__() missing required argument '%s' (pos %d)";
PyC_StringEnum prim_type = {bpygpu_primtype_items, GPU_PRIM_NONE};
BPyGPUVertBuf *py_vertbuf = nullptr;
BPyGPUIndexBuf *py_indexbuf = nullptr;
PyC_TypeOrNone py_indexbuf_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPUIndexBuf_Type, &py_indexbuf);
static const char *_keywords[] = {"type", "buf", "elem", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O&" /* `type` */
"O!" /* `buf` */
"O&" /* `elem` */
":GPUBatch.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
PyC_ParseStringEnum,
&prim_type,
&BPyGPUVertBuf_Type,
&py_vertbuf,
PyC_ParseTypeOrNone,
&py_indexbuf_or_none))
{
return nullptr;
}
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 == nullptr) {
PyErr_Format(PyExc_TypeError, exc_str_missing_arg, _keywords[1], 2);
return nullptr;
}
gpu::Batch *batch = GPU_batch_create(GPUPrimType(prim_type.value_found),
py_vertbuf->buf,
py_indexbuf ? py_indexbuf->elem : nullptr);
BPyGPUBatch *ret = reinterpret_cast<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 != nullptr) {
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 reinterpret_cast<PyObject *>(ret);
}
PyDoc_STRVAR(
/* Wrap. */
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"
" :param 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 nullptr;
}
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 nullptr;
}
if (self->batch->verts[GPU_BATCH_VBO_MAX_LEN - 1] != nullptr) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum number of vertex buffers exceeded: " STRINGIFY(GPU_BATCH_VBO_MAX_LEN));
return nullptr;
}
#ifdef USE_GPU_PY_REFERENCES
/* Hold user */
PyList_Append(self->references, reinterpret_cast<PyObject *>(py_buf));
#endif
GPU_batch_vertbuf_add(self->batch, py_buf->buf, false);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
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"
" :param 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)
{
static bool deprecation_warning_issued = false;
/* Deprecation warning raised when calling `gpu.types.GPUBatch.program_set`. */
if (!deprecation_warning_issued) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calls to GPUBatch.program_set are deprecated."
"Please set the shader via the 'program' parameter when calling "
"GPUBatch.draw/draw_instanced/draw_range.",
1);
deprecation_warning_issued = true;
}
if (!BPyGPUShader_Check(py_shader)) {
PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name);
return nullptr;
}
gpu::Shader *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) {
/* No references set in the loop, so add it here. */
PyList_Append(self->references, reinterpret_cast<PyObject *>(py_shader));
}
#endif
Py_RETURN_NONE;
}
/**
* Verify if the Shader is compatible with the batch and can be used for rendering.
* Derived from `polyline_draw_workaround` in `gpu_immediate.cc`.
*/
static const char *pygpu_shader_check_compatibility(gpu::Batch *batch)
{
if (!batch->shader) {
return nullptr;
}
/* Currently only POLYLINE shaders are checked. */
if (!bpygpu_shader_is_polyline(batch->shader)) {
return nullptr;
}
/* Check batch compatibility with shader. */
for (auto *vert : Span(batch->verts, ARRAY_SIZE(batch->verts))) {
if (!vert) {
continue;
}
GPUVertFormat &format = vert->format;
if ((format.stride % 4) != 0) {
return "For POLYLINE shaders, only 4-byte aligned formats are supported";
}
int pos_attr_id = -1;
int col_attr_id = -1;
for (uint a_idx = 0; a_idx < format.attr_len; a_idx++) {
const GPUVertAttr *a = &format.attrs[a_idx];
if ((a->offset % 4) != 0) {
return "For POLYLINE shaders, only 4-byte aligned attributes are supported";
}
const StringRefNull name = GPU_vertformat_attr_name_get(&format, a, 0);
if (pos_attr_id == -1 && name == "pos") {
if (!ELEM(a->type.comp_type(), GPU_COMP_F32)) {
return "For POLYLINE shaders, the 'pos' attribute needs to be 'F32'";
}
if (!ELEM(a->type.fetch_mode(), GPU_FETCH_FLOAT)) {
return "For POLYLINE shaders, the 'pos' attribute must use the 'FLOAT' fetch type";
}
pos_attr_id = a_idx;
}
else if (col_attr_id == -1 && name == "color") {
if (!ELEM(a->type.comp_type(), GPU_COMP_F32, GPU_COMP_U8)) {
return "For POLYLINE shaders, the 'color' attribute needs to be 'F32' or 'U8'";
}
col_attr_id = a_idx;
}
if (pos_attr_id != -1 && col_attr_id != -1) {
break;
}
}
}
return nullptr;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_draw_doc,
".. method:: draw(shader=None)\n"
"\n"
" Run the drawing shader with the parameters assigned to the batch.\n"
"\n"
" :param shader: Shader that performs the drawing operations.\n"
" If ``None`` is passed, the last shader set to this batch will run.\n"
" :type shader: :class:`gpu.types.GPUShader` | None\n");
static PyObject *pygpu_batch_draw(BPyGPUBatch *self, PyObject *args, PyObject *kw)
{
static bool deprecation_warning_issued = false;
BPyGPUShader *py_shader = nullptr;
PyC_TypeOrNone py_shader_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPUShader_Type, &py_shader);
static const char *_keywords[] = {"shader", nullptr};
static _PyArg_Parser _parser = {
"|" /* Optional arguments. */
"O&" /* `shader` */
":GPUBatch.draw",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, PyC_ParseTypeOrNone, &py_shader_or_none))
{
return nullptr;
}
if (py_shader == nullptr) {
if (!deprecation_warning_issued) {
/* Deprecation warning raised when calling gpu.types.GPUBatch.draw without a valid GPUShader.
*/
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw without specifying a shader is deprecated. "
"Please provide a valid GPUShader as the 'shader' parameter.",
1);
deprecation_warning_issued = true;
}
if (!pygpu_batch_is_program_or_error(self)) {
return nullptr;
}
}
else if (self->batch->shader != py_shader->shader) {
GPU_batch_set_shader(self->batch, py_shader->shader);
}
/* Emit a warning when trying to draw wide lines as it is too late to automatically switch to a
* polyline shader. */
if (py_shader && py_shader->is_builtin &&
ELEM(self->batch->prim_type, GPU_PRIM_LINES, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINE_LOOP))
{
gpu::Shader *shader = py_shader->shader;
const float line_width = GPU_line_width_get();
const bool use_linesmooth = GPU_line_smooth_get();
if (line_width > 1.0f || use_linesmooth) {
if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_FLAT_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw wide or smooth lines with "
"GPU_SHADER_3D_FLAT_COLOR is deprecated. "
"Use GPU_SHADER_3D_POLYLINE_FLAT_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_SMOOTH_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw wide or smooth lines with "
"GPU_SHADER_3D_SMOOTH_COLOR is deprecated. "
"Use GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_UNIFORM_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw wide or smooth lines with "
"GPU_SHADER_3D_UNIFORM_COLOR is deprecated. "
"Use GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR instead.",
1);
}
}
}
/* Emit a warning when trying to draw points with a regular shader as it is too late to
* automatically switch to a point shader. */
if (py_shader && py_shader->is_builtin && self->batch->prim_type == GPU_PRIM_POINTS) {
gpu::Shader *shader = py_shader->shader;
if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_FLAT_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw points with "
"GPU_SHADER_3D_FLAT_COLOR is deprecated. "
"Use GPU_SHADER_3D_POINT_FLAT_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_SMOOTH_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw points with "
"GPU_SHADER_3D_SMOOTH_COLOR is deprecated. "
"Use GPU_SHADER_3D_POINT_FLAT_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_UNIFORM_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw points with "
"GPU_SHADER_3D_UNIFORM_COLOR is deprecated. "
"Use GPU_SHADER_3D_POINT_SMOOTH_COLOR instead.",
1);
}
}
if (const char *error = pygpu_shader_check_compatibility(self->batch)) {
PyErr_SetString(PyExc_RuntimeError, error);
return nullptr;
}
GPU_batch_draw(self->batch);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_draw_instanced_doc,
".. method:: draw_instanced(program, *, instance_start=0, instance_count=0)\n"
"\n"
" Draw multiple instances of the drawing program with the parameters assigned\n"
" to the batch. In the vertex shader, ``gl_InstanceID`` will contain the instance\n"
" number being drawn.\n"
"\n"
" :param program: Program that performs the drawing operations.\n"
" :type program: :class:`gpu.types.GPUShader`\n"
" :param instance_start: Number of the first instance to draw.\n"
" :type instance_start: int\n"
" :param instance_count: Number of instances to draw. When not provided or set to 0\n"
" the number of instances will be determined by the number of rows in the first\n"
" vertex buffer.\n"
" :type instance_count: int\n");
static PyObject *pygpu_batch_draw_instanced(BPyGPUBatch *self, PyObject *args, PyObject *kw)
{
BPyGPUShader *py_program = nullptr;
int instance_start = 0;
int instance_count = 0;
static const char *_keywords[] = {"program", "instance_start", "instance_count", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `program` */
"|$" /* Optional keyword only arguments. */
"i" /* `instance_start` */
"i" /* `instance_count` */
":GPUBatch.draw_instanced",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, &BPyGPUShader_Type, &py_program, &instance_start, &instance_count))
{
return nullptr;
}
GPU_batch_set_shader(self->batch, py_program->shader);
GPU_batch_draw_instance_range(self->batch, instance_start, instance_count);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_draw_range_doc,
".. method:: draw_range(program, *, elem_start=0, elem_count=0)\n"
"\n"
" Run the drawing program with the parameters assigned to the batch. "
"Only draw the ``elem_count`` elements of the index buffer starting at ``elem_start``.\n"
"\n"
" :param program: Program that performs the drawing operations.\n"
" :type program: :class:`gpu.types.GPUShader`\n"
" :param elem_start: First index to draw. When not provided or set to 0 drawing\n"
" will start from the first element of the index buffer.\n"
" :type elem_start: int\n"
" :param elem_count: Number of elements of the index buffer to draw. When not\n"
" provided or set to 0 all elements from ``elem_start`` to the end of the\n"
" index buffer will be drawn.\n"
" :type elem_count: int\n");
static PyObject *pygpu_batch_draw_range(BPyGPUBatch *self, PyObject *args, PyObject *kw)
{
BPyGPUShader *py_program = nullptr;
int elem_start = 0;
int elem_count = 0;
static const char *_keywords[] = {"program", "elem_start", "elem_count", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `program` */
"|$" /* Optional keyword only arguments. */
"i" /* `elem_start` */
"i" /* `elem_count` */
":GPUBatch.draw_range",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, &BPyGPUShader_Type, &py_program, &elem_start, &elem_count))
{
return nullptr;
}
GPU_batch_set_shader(self->batch, py_program->shader);
GPU_batch_draw_range(self->batch, elem_start, elem_count);
Py_RETURN_NONE;
}
static PyObject *pygpu_batch_program_use_begin(BPyGPUBatch *self)
{
if (!pygpu_batch_is_program_or_error(self)) {
return nullptr;
}
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 nullptr;
}
GPU_shader_unbind();
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_batch__tp_methods[] = {
{"vertbuf_add",
reinterpret_cast<PyCFunction>(pygpu_batch_vertbuf_add),
METH_O,
pygpu_batch_vertbuf_add_doc},
{"program_set",
reinterpret_cast<PyCFunction>(pygpu_batch_program_set),
METH_O,
pygpu_batch_program_set_doc},
{"draw",
reinterpret_cast<PyCFunction>(pygpu_batch_draw),
METH_VARARGS | METH_KEYWORDS,
pygpu_batch_draw_doc},
{"draw_instanced",
reinterpret_cast<PyCFunction>(pygpu_batch_draw_instanced),
METH_VARARGS | METH_KEYWORDS,
pygpu_batch_draw_instanced_doc},
{"draw_range",
reinterpret_cast<PyCFunction>(pygpu_batch_draw_range),
METH_VARARGS | METH_KEYWORDS,
pygpu_batch_draw_range_doc},
{"_program_use_begin",
reinterpret_cast<PyCFunction>(pygpu_batch_program_use_begin),
METH_NOARGS,
""},
{"_program_use_end",
reinterpret_cast<PyCFunction>(pygpu_batch_program_use_end),
METH_NOARGS,
""},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
#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 != nullptr;
}
#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(
/* Wrap. */
pygpu_batch__tp_doc,
".. class:: GPUBatch\n"
"\n"
" Reusable container for drawable geometry.\n"
"\n"
" .. method:: __init__(type, buf, elem=None)\n"
"\n"
" :param type: The primitive type of geometry to be drawn.\n"
" :type type: " PYDOC_PRIMTYPE_LITERAL
"\n"
" :param buf: Vertex buffer containing all or some of the attributes required for "
"drawing.\n"
" :type buf: :class:`gpu.types.GPUVertBuf`\n"
" :param elem: An optional index buffer.\n"
" :type elem: :class:`gpu.types.GPUIndexBuf` | None\n");
PyTypeObject BPyGPUBatch_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUBatch",
/*tp_basicsize*/ sizeof(BPyGPUBatch),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_batch__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
#ifdef USE_GPU_PY_REFERENCES
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
#else
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
#endif
/*tp_doc*/ pygpu_batch__tp_doc,
#ifdef USE_GPU_PY_REFERENCES
/*tp_traverse*/ reinterpret_cast<traverseproc>(pygpu_batch__tp_traverse),
#else
/*tp_traverse*/ nullptr,
#endif
#ifdef USE_GPU_PY_REFERENCES
/*tp_clear*/ reinterpret_cast<inquiry>(pygpu_batch__tp_clear),
#else
/*tp_clear*/ nullptr,
#endif
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_batch__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_batch__tp_new,
/*tp_free*/ nullptr,
#ifdef USE_GPU_PY_REFERENCES
/*tp_is_gc*/ reinterpret_cast<inquiry>(pygpu_batch__tp_is_gc),
#else
/*tp_is_gc*/ nullptr,
#endif
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUBatch_CreatePyObject(gpu::Batch *batch)
{
BPyGPUBatch *self;
#ifdef USE_GPU_PY_REFERENCES
self = reinterpret_cast<BPyGPUBatch *>(_PyObject_GC_New(&BPyGPUBatch_Type));
self->references = nullptr;
#else
self = PyObject_New(BPyGPUBatch, &BPyGPUBatch_Type);
#endif
self->batch = batch;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef BPY_GPU_BATCH_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class Batch;
}
#define USE_GPU_PY_REFERENCES
extern PyTypeObject BPyGPUBatch_Type;
#define BPyGPUBatch_Check(v) (Py_TYPE(v) == &BPyGPUBatch_Type)
struct BPyGPUBatch {
PyObject_HEAD
/* The batch is owned, we may support thin wrapped batches later. */
gpu::Batch *batch;
#ifdef USE_GPU_PY_REFERENCES
/* Just to keep a user to prevent freeing buffers we're using. */
PyObject *references;
#endif
};
[[nodiscard]] PyObject *BPyGPUBatch_CreatePyObject(gpu::Batch *batch) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,796 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.types.Buffer API.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include <algorithm>
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "GPU_texture.hh"
#include "../generic/py_capi_utils.hh"
#include "gpu_py.hh"
#include "gpu_py_buffer.hh"
namespace blender {
#define PYGPU_BUFFER_PROTOCOL
#define MAX_DIMENSIONS 64
/* -------------------------------------------------------------------- */
/** \name Utility Functions
* \{ */
static Py_ssize_t pygpu_buffer_dimensions_tot_elem(const Py_ssize_t *shape, Py_ssize_t shape_len)
{
Py_ssize_t tot = shape[0];
for (int i = 1; i < shape_len; i++) {
tot *= shape[i];
}
return tot;
}
static bool pygpu_buffer_dimensions_tot_len_compare(const Py_ssize_t *shape_a,
const Py_ssize_t shape_a_len,
const Py_ssize_t *shape_b,
const Py_ssize_t shape_b_len)
{
if (pygpu_buffer_dimensions_tot_elem(shape_a, shape_a_len) !=
pygpu_buffer_dimensions_tot_elem(shape_b, shape_b_len))
{
PyErr_SetString(PyExc_BufferError, "array size does not match");
return false;
}
return true;
}
static bool pygpu_buffer_pyobj_as_shape(PyObject *shape_obj,
Py_ssize_t r_shape[MAX_DIMENSIONS],
Py_ssize_t *r_shape_len)
{
Py_ssize_t shape_len = 0;
if (PyLong_Check(shape_obj)) {
shape_len = 1;
if ((r_shape[0] = PyLong_AsSsize_t(shape_obj)) < 1) {
PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1");
return false;
}
}
else if (PySequence_Check(shape_obj)) {
shape_len = PySequence_Size(shape_obj);
if (shape_len > MAX_DIMENSIONS) {
PyErr_SetString(PyExc_AttributeError,
"too many dimensions, max is " STRINGIFY(MAX_DIMENSIONS));
return false;
}
if (shape_len < 1) {
PyErr_SetString(PyExc_AttributeError, "sequence must have at least one dimension");
return false;
}
for (int i = 0; i < shape_len; i++) {
PyObject *ob = PySequence_GetItem(shape_obj, i);
if (!PyLong_Check(ob)) {
PyErr_Format(PyExc_TypeError,
"invalid dimension %i, expected an int, not a %.200s",
i,
Py_TYPE(ob)->tp_name);
Py_DECREF(ob);
return false;
}
r_shape[i] = PyLong_AsSsize_t(ob);
Py_DECREF(ob);
if (r_shape[i] < 1) {
PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1");
return false;
}
}
}
else {
PyErr_Format(PyExc_TypeError,
"invalid second argument expected a sequence "
"or an int, not a %.200s",
Py_TYPE(shape_obj)->tp_name);
}
*r_shape_len = shape_len;
return true;
}
static const char *pygpu_buffer_formatstr(eGPUDataFormat data_format)
{
switch (data_format) {
case GPU_DATA_FLOAT:
return "f";
case GPU_DATA_INT:
return "i";
case GPU_DATA_UINT:
return "I";
case GPU_DATA_UBYTE:
return "B";
case GPU_DATA_UINT_24_8_DEPRECATED:
case GPU_DATA_10_11_11_REV:
return "I";
default:
break;
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BPyGPUBuffer API
* \{ */
static BPyGPUBuffer *pygpu_buffer_make_from_data(PyObject *parent,
const eGPUDataFormat format,
const int shape_len,
const Py_ssize_t *shape,
void *buf)
{
BPyGPUBuffer *buffer = reinterpret_cast<BPyGPUBuffer *>(_PyObject_GC_New(&BPyGPU_BufferType));
buffer->parent = nullptr;
buffer->format = format;
buffer->shape_len = shape_len;
buffer->shape = MEM_new_array_uninitialized<Py_ssize_t>(size_t(shape_len), "BPyGPUBuffer shape");
memcpy(buffer->shape, shape, sizeof(*buffer->shape) * size_t(shape_len));
buffer->buf.as_void = buf;
if (parent) {
Py_INCREF(parent);
buffer->parent = parent;
BLI_assert(!PyObject_GC_IsTracked((PyObject *)buffer));
PyObject_GC_Track(buffer);
}
return buffer;
}
static PyObject *pygpu_buffer__sq_item(BPyGPUBuffer *self, Py_ssize_t i)
{
if (i >= self->shape[0] || i < 0) {
PyErr_SetString(PyExc_IndexError, "array index out of range");
return nullptr;
}
const char *formatstr = pygpu_buffer_formatstr(eGPUDataFormat(self->format));
if (self->shape_len == 1) {
switch (self->format) {
case GPU_DATA_FLOAT:
return Py_BuildValue(formatstr, self->buf.as_float[i]);
case GPU_DATA_INT:
return Py_BuildValue(formatstr, self->buf.as_int[i]);
case GPU_DATA_UBYTE:
return Py_BuildValue(formatstr, self->buf.as_byte[i]);
case GPU_DATA_UINT:
case GPU_DATA_UINT_24_8_DEPRECATED:
case GPU_DATA_10_11_11_REV:
return Py_BuildValue(formatstr, self->buf.as_uint[i]);
}
}
else {
int offset = i * GPU_texture_dataformat_size(eGPUDataFormat(self->format));
for (int j = 1; j < self->shape_len; j++) {
offset *= self->shape[j];
}
return reinterpret_cast<PyObject *>(
pygpu_buffer_make_from_data(reinterpret_cast<PyObject *>(self),
eGPUDataFormat(self->format),
self->shape_len - 1,
self->shape + 1,
self->buf.as_byte + offset));
}
return nullptr;
}
static PyObject *pygpu_buffer_to_list(BPyGPUBuffer *self)
{
const Py_ssize_t len = self->shape[0];
PyObject *list = PyList_New(len);
for (Py_ssize_t i = 0; i < len; i++) {
PyList_SET_ITEM(list, i, pygpu_buffer__sq_item(self, i));
}
return list;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_buffer_to_list_doc,
".. method:: to_list()\n"
"\n"
" Return the buffer as a list.\n"
"\n"
" :return: The buffer as a list.\n"
" :rtype: list\n");
static PyObject *pygpu_buffer_to_list_recursive(BPyGPUBuffer *self)
{
PyObject *list;
if (self->shape_len > 1) {
int i, len = self->shape[0];
list = PyList_New(len);
for (i = 0; i < len; i++) {
/* "BPyGPUBuffer *sub_tmp" is a temporary object created just to be read for nested lists.
* That is why it is decremented/freed soon after.
* TODO: For efficiency, avoid creating #BPyGPUBuffer when creating nested lists. */
BPyGPUBuffer *sub_tmp = reinterpret_cast<BPyGPUBuffer *>(pygpu_buffer__sq_item(self, i));
PyList_SET_ITEM(list, i, pygpu_buffer_to_list_recursive(sub_tmp));
Py_DECREF(sub_tmp);
}
}
else {
list = pygpu_buffer_to_list(self);
}
return list;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_buffer_dimensions_doc,
"The size of the buffer for each dimension.\n"
"\n"
"Setting the dimensions is supported when the total number of elements is unchanged.\n"
"\n"
":type: list[int]\n");
static PyObject *pygpu_buffer_dimensions_get(BPyGPUBuffer *self, void * /*arg*/)
{
PyObject *list = PyList_New(self->shape_len);
int i;
for (i = 0; i < self->shape_len; i++) {
PyList_SET_ITEM(list, i, PyLong_FromLong(self->shape[i]));
}
return list;
}
static int pygpu_buffer_dimensions_set(BPyGPUBuffer *self, PyObject *value, void * /*type*/)
{
Py_ssize_t shape[MAX_DIMENSIONS];
Py_ssize_t shape_len = 0;
if (!pygpu_buffer_pyobj_as_shape(value, shape, &shape_len)) {
return -1;
}
if (!pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, self->shape, self->shape_len)) {
return -1;
}
if (shape_len != self->shape_len) {
MEM_delete(self->shape);
self->shape = MEM_new_array_uninitialized<Py_ssize_t>(size_t(shape_len), __func__);
}
self->shape_len = shape_len;
memcpy(self->shape, shape, sizeof(*self->shape) * size_t(shape_len));
return 0;
}
static int pygpu_buffer__tp_traverse(BPyGPUBuffer *self, visitproc visit, void *arg)
{
Py_VISIT(self->parent);
return 0;
}
static int pygpu_buffer__tp_clear(BPyGPUBuffer *self)
{
if (self->parent) {
Py_CLEAR(self->parent);
self->buf.as_void = nullptr;
}
return 0;
}
static void pygpu_buffer__tp_dealloc(BPyGPUBuffer *self)
{
if (self->parent) {
PyObject_GC_UnTrack(self);
Py_CLEAR(self->parent);
}
else if (self->buf.as_void) {
MEM_delete_void(self->buf.as_void);
}
MEM_delete(self->shape);
PyObject_GC_Del(self);
}
static PyObject *pygpu_buffer__tp_repr(BPyGPUBuffer *self)
{
PyObject *repr;
PyObject *list = pygpu_buffer_to_list_recursive(self);
const char *typestr = PyC_StringEnum_FindIDFromValue(bpygpu_dataformat_items, self->format);
repr = PyUnicode_FromFormat("Buffer(%s, %R)", typestr, list);
Py_DECREF(list);
return repr;
}
static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, Py_ssize_t i, PyObject *v);
static int pygpu_buffer_ass_slice(BPyGPUBuffer *self,
Py_ssize_t begin,
Py_ssize_t end,
PyObject *seq)
{
PyObject *item;
int count, err = 0;
begin = std::max<Py_ssize_t>(begin, 0);
end = std::min(end, self->shape[0]);
begin = std::min(begin, end);
if (!PySequence_Check(seq)) {
PyErr_Format(PyExc_TypeError,
"buffer[:] = value, invalid assignment. "
"Expected a sequence, not an %.200s type",
Py_TYPE(seq)->tp_name);
return -1;
}
/* re-use count var */
if ((count = PySequence_Size(seq)) != (end - begin)) {
PyErr_Format(PyExc_TypeError,
"buffer[:] = value, size mismatch in assignment. "
"Expected: %d (given: %d)",
count,
end - begin);
return -1;
}
for (count = begin; count < end; count++) {
item = PySequence_GetItem(seq, count - begin);
if (item) {
err = pygpu_buffer__sq_ass_item(self, count, item);
Py_DECREF(item);
}
else {
err = -1;
}
if (err) {
break;
}
}
return err;
}
static PyObject *pygpu_buffer__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyObject *length_ob, *init = nullptr;
BPyGPUBuffer *buffer = nullptr;
Py_ssize_t shape[MAX_DIMENSIONS];
Py_ssize_t shape_len = 0;
if (kwds && PyDict_Size(kwds)) {
PyErr_SetString(PyExc_TypeError, "Buffer(): takes no keyword args");
return nullptr;
}
PyC_StringEnum pygpu_dataformat = {bpygpu_dataformat_items, GPU_DATA_FLOAT};
if (!PyArg_ParseTuple(
args, "O&O|O: Buffer", PyC_ParseStringEnum, &pygpu_dataformat, &length_ob, &init))
{
return nullptr;
}
if (pygpu_dataformat.value_found == GPU_DATA_UINT_24_8_DEPRECATED) {
PyErr_WarnEx(PyExc_DeprecationWarning, "`UINT_24_8` is deprecated, use `FLOAT` instead", 1);
}
if (!pygpu_buffer_pyobj_as_shape(length_ob, shape, &shape_len)) {
return nullptr;
}
if (init && PyObject_CheckBuffer(init)) {
Py_buffer pybuffer;
if (PyObject_GetBuffer(init, &pybuffer, PyBUF_ND | PyBUF_FORMAT) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return nullptr;
}
Py_ssize_t *pybuffer_shape = pybuffer.shape;
Py_ssize_t pybuffer_ndim = pybuffer.ndim;
if (!pybuffer_shape) {
pybuffer_shape = &pybuffer.len;
pybuffer_ndim = 1;
}
if (pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, pybuffer_shape, pybuffer_ndim)) {
buffer = pygpu_buffer_make_from_data(
init, eGPUDataFormat(pygpu_dataformat.value_found), shape_len, shape, pybuffer.buf);
}
PyBuffer_Release(&pybuffer);
}
else {
buffer = BPyGPU_Buffer_CreatePyObject(pygpu_dataformat.value_found, shape, shape_len, nullptr);
if (init && pygpu_buffer_ass_slice(buffer, 0, shape[0], init)) {
Py_DECREF(buffer);
return nullptr;
}
}
return reinterpret_cast<PyObject *>(buffer);
}
static int pygpu_buffer__tp_is_gc(BPyGPUBuffer *self)
{
return self->parent != nullptr;
}
/* BPyGPUBuffer sequence methods */
static Py_ssize_t pygpu_buffer__sq_length(BPyGPUBuffer *self)
{
return self->shape[0];
}
static PyObject *pygpu_buffer_slice(BPyGPUBuffer *self, Py_ssize_t begin, Py_ssize_t end)
{
PyObject *list;
Py_ssize_t count;
begin = std::max<Py_ssize_t>(begin, 0);
end = std::min(end, self->shape[0]);
begin = std::min(begin, end);
list = PyList_New(end - begin);
for (count = begin; count < end; count++) {
PyList_SET_ITEM(list, count - begin, pygpu_buffer__sq_item(self, count));
}
return list;
}
static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, Py_ssize_t i, PyObject *v)
{
if (i >= self->shape[0] || i < 0) {
PyErr_SetString(PyExc_IndexError, "array assignment index out of range");
return -1;
}
if (self->shape_len != 1) {
BPyGPUBuffer *row = reinterpret_cast<BPyGPUBuffer *>(pygpu_buffer__sq_item(self, i));
if (row) {
const int ret = pygpu_buffer_ass_slice(row, 0, self->shape[1], v);
Py_DECREF(row);
return ret;
}
return -1;
}
switch (self->format) {
case GPU_DATA_FLOAT:
return PyArg_Parse(v, "f:Expected floats", &self->buf.as_float[i]) ? 0 : -1;
case GPU_DATA_INT:
return PyArg_Parse(v, "i:Expected ints", &self->buf.as_int[i]) ? 0 : -1;
case GPU_DATA_UBYTE:
return PyArg_Parse(v, "b:Expected ints", &self->buf.as_byte[i]) ? 0 : -1;
case GPU_DATA_UINT:
case GPU_DATA_UINT_24_8_DEPRECATED:
case GPU_DATA_10_11_11_REV:
return PyArg_Parse(v, "I:Expected unsigned ints", &self->buf.as_uint[i]) ? 0 : -1;
default:
return 0; /* should never happen */
}
}
static PyObject *pygpu_buffer__mp_subscript(BPyGPUBuffer *self, PyObject *item)
{
if (PyIndex_Check(item)) {
Py_ssize_t i;
i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return nullptr;
}
if (i < 0) {
i += self->shape[0];
}
return pygpu_buffer__sq_item(self, i);
}
if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(item, self->shape[0], &start, &stop, &step, &slicelength) < 0) {
return nullptr;
}
if (slicelength <= 0) {
return PyTuple_New(0);
}
if (step == 1) {
return pygpu_buffer_slice(self, start, stop);
}
PyErr_SetString(PyExc_IndexError, "slice steps not supported with vectors");
return nullptr;
}
PyErr_Format(
PyExc_TypeError, "buffer indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
return nullptr;
}
static int pygpu_buffer__mp_ass_subscript(BPyGPUBuffer *self, PyObject *item, PyObject *value)
{
if (PyIndex_Check(item)) {
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return -1;
}
if (i < 0) {
i += self->shape[0];
}
return pygpu_buffer__sq_ass_item(self, i, value);
}
if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(item, self->shape[0], &start, &stop, &step, &slicelength) < 0) {
return -1;
}
if (step == 1) {
return pygpu_buffer_ass_slice(self, start, stop, value);
}
PyErr_SetString(PyExc_IndexError, "slice steps not supported with vectors");
return -1;
}
PyErr_Format(
PyExc_TypeError, "buffer indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
return -1;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_buffer__tp_methods[] = {
{"to_list",
reinterpret_cast<PyCFunction>(pygpu_buffer_to_list_recursive),
METH_NOARGS,
pygpu_buffer_to_list_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static PyGetSetDef pygpu_buffer_getseters[] = {
{"dimensions",
reinterpret_cast<getter>(pygpu_buffer_dimensions_get),
reinterpret_cast<setter>(pygpu_buffer_dimensions_set),
pygpu_buffer_dimensions_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr},
};
static PySequenceMethods pygpu_buffer__tp_as_sequence = {
/*sq_length*/ reinterpret_cast<lenfunc>(pygpu_buffer__sq_length),
/*sq_concat*/ nullptr,
/*sq_repeat*/ nullptr,
/*sq_item*/ reinterpret_cast<ssizeargfunc>(pygpu_buffer__sq_item),
/*was_sq_slice*/ nullptr, /* DEPRECATED. Handled by #pygpu_buffer__sq_item. */
/*sq_ass_item*/ reinterpret_cast<ssizeobjargproc>(pygpu_buffer__sq_ass_item),
/*was_sq_ass_slice*/ nullptr, /* DEPRECATED. Handled by #pygpu_buffer__sq_ass_item. */
/*sq_contains*/ nullptr,
/*sq_inplace_concat*/ nullptr,
/*sq_inplace_repeat*/ nullptr,
};
static PyMappingMethods pygpu_buffer__tp_as_mapping = {
/*mp_length*/ reinterpret_cast<lenfunc>(pygpu_buffer__sq_length),
/*mp_subscript*/ reinterpret_cast<binaryfunc>(pygpu_buffer__mp_subscript),
/*mp_ass_subscript*/ reinterpret_cast<objobjargproc>(pygpu_buffer__mp_ass_subscript),
};
#ifdef PYGPU_BUFFER_PROTOCOL
static void pygpu_buffer_strides_calc(const eGPUDataFormat format,
const int shape_len,
const Py_ssize_t *shape,
Py_ssize_t *r_strides)
{
Py_ssize_t stride = GPU_texture_dataformat_size(format);
for (int i = shape_len; i-- > 0;) {
r_strides[i] = stride;
stride *= shape[i];
}
}
/* Here is the buffer interface function */
static int pygpu_buffer__bf_getbuffer(BPyGPUBuffer *self, Py_buffer *view, int flags)
{
if (UNLIKELY(view == nullptr)) {
PyErr_SetString(PyExc_ValueError, "null view in get-buffer is obsolete");
return -1;
}
memset(view, 0, sizeof(*view));
view->obj = reinterpret_cast<PyObject *>(self);
view->buf = self->buf.as_void;
view->len = bpygpu_Buffer_size(self);
view->readonly = 0;
view->itemsize = GPU_texture_dataformat_size(eGPUDataFormat(self->format));
if (flags & PyBUF_FORMAT) {
view->format = const_cast<char *>(pygpu_buffer_formatstr(eGPUDataFormat(self->format)));
}
if (flags & PyBUF_ND) {
view->ndim = self->shape_len;
view->shape = self->shape;
}
if (flags & PyBUF_STRIDES) {
view->strides = MEM_new_array_uninitialized<Py_ssize_t>(size_t(view->ndim),
"BPyGPUBuffer strides");
pygpu_buffer_strides_calc(
eGPUDataFormat(self->format), view->ndim, view->shape, view->strides);
}
view->suboffsets = nullptr;
view->internal = nullptr;
Py_INCREF(self);
return 0;
}
static void pygpu_buffer__bf_releasebuffer(PyObject * /*exporter*/, Py_buffer *view)
{
MEM_SAFE_DELETE(view->strides);
}
static PyBufferProcs pygpu_buffer__tp_as_buffer = {
/*bf_getbuffer*/ reinterpret_cast<getbufferproc>(pygpu_buffer__bf_getbuffer),
/*bf_releasebuffer*/ static_cast<releasebufferproc>(pygpu_buffer__bf_releasebuffer),
};
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_buffer__tp_doc,
".. class:: Buffer\n"
"\n"
" For Python access to GPU functions requiring a pointer.\n"
"\n"
" .. method:: __init__(format, dimensions, data)\n"
"\n"
" :param format: Format type to interpret the buffer.\n"
" ``UINT_24_8`` is deprecated, use ``FLOAT`` instead.\n"
" :type format: " PYDOC_DATAFORMAT_LITERAL
"\n"
" :param dimensions: Array describing the dimensions.\n"
" :type dimensions: int | Sequence[int]\n"
" :param data: Optional data array.\n"
" :type data: Buffer | Sequence[float] | Sequence[int]\n");
PyTypeObject BPyGPU_BufferType = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "Buffer",
/*tp_basicsize*/ sizeof(BPyGPUBuffer),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_buffer__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_compare*/ nullptr,
/*tp_repr*/ reinterpret_cast<reprfunc>(pygpu_buffer__tp_repr),
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ &pygpu_buffer__tp_as_sequence,
/*tp_as_mapping*/ &pygpu_buffer__tp_as_mapping,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
#ifdef PYGPU_BUFFER_PROTOCOL
/*tp_as_buffer*/ &pygpu_buffer__tp_as_buffer,
#else
/*tp_as_buffer*/ nullptr,
#endif
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
/*tp_doc*/ pygpu_buffer__tp_doc,
/*tp_traverse*/ reinterpret_cast<traverseproc>(pygpu_buffer__tp_traverse),
/*tp_clear*/ reinterpret_cast<inquiry>(pygpu_buffer__tp_clear),
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_buffer__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_buffer_getseters,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_buffer__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ reinterpret_cast<inquiry>(pygpu_buffer__tp_is_gc),
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
static size_t pygpu_buffer_calc_size(const int format,
const int shape_len,
const Py_ssize_t *shape)
{
return pygpu_buffer_dimensions_tot_elem(shape, shape_len) *
GPU_texture_dataformat_size(eGPUDataFormat(format));
}
size_t bpygpu_Buffer_size(BPyGPUBuffer *buffer)
{
return pygpu_buffer_calc_size(buffer->format, buffer->shape_len, buffer->shape);
}
BPyGPUBuffer *BPyGPU_Buffer_CreatePyObject(const int format,
const Py_ssize_t *shape,
const int shape_len,
void *buffer)
{
if (buffer == nullptr) {
size_t size = pygpu_buffer_calc_size(format, shape_len, shape);
buffer = MEM_new_zeroed(size, "BPyGPUBuffer buffer");
}
return pygpu_buffer_make_from_data(nullptr, eGPUDataFormat(format), shape_len, shape, buffer);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_sys_types.h"
namespace blender {
extern PyTypeObject BPyGPU_BufferType;
#define BPyGPU_Buffer_Check(v) (Py_TYPE(v) == &BPyGPU_BufferType)
/**
* Buffer Object
*
* For Python access to GPU functions requiring a pointer.
*/
struct BPyGPUBuffer {
PyObject_HEAD
PyObject *parent;
int format;
int shape_len;
Py_ssize_t *shape;
union {
char *as_byte;
int *as_int;
uint *as_uint;
float *as_float;
void *as_void;
} buf;
};
[[nodiscard]] size_t bpygpu_Buffer_size(BPyGPUBuffer *buffer);
/**
* Create a buffer object
*
* \param shape: An array of `shape_len` integers representing the size of each dimension.
* \param buffer: When not NULL holds a contiguous buffer
* with the correct format from which the buffer will be initialized
*/
[[nodiscard]] BPyGPUBuffer *BPyGPU_Buffer_CreatePyObject(int format,
const Py_ssize_t *shape,
int shape_len,
void *buffer);
} // namespace blender

View File

@@ -0,0 +1,518 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_capabilities.hh"
#include "gpu_py.hh"
#include "gpu_py_capabilities.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Functions
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_texture_size_get_doc,
".. function:: max_texture_size_get()\n"
"\n"
" Get estimated maximum texture size to be able to handle.\n"
"\n"
" :return: Texture size.\n"
" :rtype: int\n");
static PyObject *pygpu_max_texture_size_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_texture_size());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_texture_layers_get_doc,
".. function:: max_texture_layers_get()\n"
"\n"
" Get maximum number of layers in texture.\n"
"\n"
" :return: Number of layers.\n"
" :rtype: int\n");
static PyObject *pygpu_max_texture_layers_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_texture_layers());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_get_doc,
".. function:: max_textures_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the vertex shader and the\n"
" fragment processor.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_vert_get_doc,
".. function:: max_textures_vert_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the vertex shader.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_vert_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_geom_get_doc,
".. function:: max_textures_geom_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the geometry shader.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_geom_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_frag_get_doc,
".. function:: max_textures_frag_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the fragment shader.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_frag_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_images_get_doc,
".. function:: max_images_get()\n"
"\n"
" Get maximum supported number of image units.\n"
"\n"
" :return: Number of image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_images_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_images());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_uniforms_vert_get_doc,
".. function:: max_uniforms_vert_get()\n"
"\n"
" Get maximum number of values held in uniform variable\n"
" storage for a vertex shader.\n"
"\n"
" :return: Number of values.\n"
" :rtype: int\n");
static PyObject *pygpu_max_uniforms_vert_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_uniforms_vert());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_uniforms_frag_get_doc,
".. function:: max_uniforms_frag_get()\n"
"\n"
" Get maximum number of values held in uniform variable\n"
" storage for a fragment shader.\n"
"\n"
" :return: Number of values.\n"
" :rtype: int\n");
static PyObject *pygpu_max_uniforms_frag_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_uniforms_frag());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_batch_indices_get_doc,
".. function:: max_batch_indices_get()\n"
"\n"
" Get maximum number of vertex array indices.\n"
"\n"
" :return: Number of indices.\n"
" :rtype: int\n");
static PyObject *pygpu_max_batch_indices_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_batch_indices());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_batch_vertices_get_doc,
".. function:: max_batch_vertices_get()\n"
"\n"
" Get maximum number of vertex array vertices.\n"
"\n"
" :return: Number of vertices.\n"
" :rtype: int\n");
static PyObject *pygpu_max_batch_vertices_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_batch_vertices());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_vertex_attribs_get_doc,
".. function:: max_vertex_attribs_get()\n"
"\n"
" Get maximum number of vertex attributes accessible to\n"
" a vertex shader.\n"
"\n"
" :return: Number of attributes.\n"
" :rtype: int\n");
static PyObject *pygpu_max_vertex_attribs_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_vertex_attribs());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_varying_floats_get_doc,
".. function:: max_varying_floats_get()\n"
"\n"
" Get maximum number of varying variables used by\n"
" vertex and fragment shaders.\n"
"\n"
" :return: Number of variables.\n"
" :rtype: int\n");
static PyObject *pygpu_max_varying_floats_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_varying_floats());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_extensions_get_doc,
".. function:: extensions_get()\n"
"\n"
" Get supported extensions in the current context.\n"
"\n"
" :return: Extensions.\n"
" :rtype: tuple[str, ...]\n");
static PyObject *pygpu_extensions_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int extensions_len = GPU_extensions_len();
PyObject *ret = PyTuple_New(extensions_len);
PyObject **ob_items = (reinterpret_cast<PyTupleObject *>(ret))->ob_item;
for (int i = 0; i < extensions_len; i++) {
ob_items[i] = PyUnicode_FromString(GPU_extension_get(i));
}
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_compute_shader_support_get_doc,
".. function:: compute_shader_support_get()\n"
"\n"
" Are compute shaders supported.\n"
"\n"
" :return: True when supported, False when not supported.\n"
" :rtype: bool\n");
static PyObject *pygpu_compute_shader_support_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
static bool deprecation_warning_issued = false;
if (!deprecation_warning_issued) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"compute_shader_support_get is deprecated. All platforms have support for "
"compute shaders.",
1);
deprecation_warning_issued = true;
}
return PyBool_FromLong(true);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_shader_image_load_store_support_get_doc,
".. function:: shader_image_load_store_support_get()\n"
"\n"
" Is image load/store supported.\n"
"\n"
" :return: True when supported, False when not supported.\n"
" :rtype: bool\n");
static PyObject *pygpu_shader_image_load_store_support_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
static bool deprecation_warning_issued = false;
if (!deprecation_warning_issued) {
PyErr_WarnEx(
PyExc_DeprecationWarning,
"shader_image_load_store_support_get is deprecated. All platforms have support for "
"image load store.",
1);
deprecation_warning_issued = true;
}
/* Now required to start Blender. */
return PyBool_FromLong(true);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_hdr_support_get_doc,
".. function:: hdr_support_get()\n"
"\n"
" Return whether GPU backend supports High Dynamic range for viewport.\n"
"\n"
" :return: HDR support available.\n"
" :rtype: bool\n");
static PyObject *pygpu_hdr_support_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyBool_FromLong(GPU_hdr_support());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_work_group_count_get_doc,
".. function:: max_work_group_count_get(index)\n"
"\n"
" Get maximum number of work groups that may be dispatched to a compute shader.\n"
"\n"
" :param index: Index of the dimension.\n"
" :type index: int\n"
" :return: Maximum number of work groups for the queried dimension.\n"
" :rtype: int\n");
static PyObject *pygpu_max_work_group_count_get(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int index;
if (!PyArg_ParseTuple(args, "i", &index)) {
return nullptr;
}
const int max_work_group_count = GPU_max_work_group_count(index);
return PyLong_FromLong(max_work_group_count);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_work_group_size_get_doc,
".. function:: max_work_group_size_get(index)\n"
"\n"
" Get maximum size of a work group that may be dispatched to a compute shader.\n"
"\n"
" :param index: Index of the dimension.\n"
" :type index: int\n"
" :return: Maximum size of a work group for the queried dimension.\n"
" :rtype: int\n");
static PyObject *pygpu_max_work_group_size_get(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int index;
if (!PyArg_ParseTuple(args, "i", &index)) {
return nullptr;
}
const int max_work_group_size = GPU_max_work_group_size(index);
return PyLong_FromLong(max_work_group_size);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_capabilities__tp_methods[] = {
{"max_texture_size_get",
reinterpret_cast<PyCFunction>(pygpu_max_texture_size_get),
METH_NOARGS,
pygpu_max_texture_size_get_doc},
{"max_texture_layers_get",
reinterpret_cast<PyCFunction>(pygpu_max_texture_layers_get),
METH_NOARGS,
pygpu_max_texture_layers_get_doc},
{"max_textures_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_get),
METH_NOARGS,
pygpu_max_textures_get_doc},
{"max_textures_vert_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_vert_get),
METH_NOARGS,
pygpu_max_textures_vert_get_doc},
{"max_textures_geom_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_geom_get),
METH_NOARGS,
pygpu_max_textures_geom_get_doc},
{"max_textures_frag_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_frag_get),
METH_NOARGS,
pygpu_max_textures_frag_get_doc},
{"max_images_get",
reinterpret_cast<PyCFunction>(pygpu_max_images_get),
METH_NOARGS,
pygpu_max_images_get_doc},
{"max_uniforms_vert_get",
reinterpret_cast<PyCFunction>(pygpu_max_uniforms_vert_get),
METH_NOARGS,
pygpu_max_uniforms_vert_get_doc},
{"max_uniforms_frag_get",
reinterpret_cast<PyCFunction>(pygpu_max_uniforms_frag_get),
METH_NOARGS,
pygpu_max_uniforms_frag_get_doc},
{"max_batch_indices_get",
reinterpret_cast<PyCFunction>(pygpu_max_batch_indices_get),
METH_NOARGS,
pygpu_max_batch_indices_get_doc},
{"max_batch_vertices_get",
reinterpret_cast<PyCFunction>(pygpu_max_batch_vertices_get),
METH_NOARGS,
pygpu_max_batch_vertices_get_doc},
{"max_vertex_attribs_get",
reinterpret_cast<PyCFunction>(pygpu_max_vertex_attribs_get),
METH_NOARGS,
pygpu_max_vertex_attribs_get_doc},
{"max_varying_floats_get",
reinterpret_cast<PyCFunction>(pygpu_max_varying_floats_get),
METH_NOARGS,
pygpu_max_varying_floats_get_doc},
{"extensions_get",
reinterpret_cast<PyCFunction>(pygpu_extensions_get),
METH_NOARGS,
pygpu_extensions_get_doc},
{"compute_shader_support_get",
reinterpret_cast<PyCFunction>(pygpu_compute_shader_support_get),
METH_NOARGS,
pygpu_compute_shader_support_get_doc},
{"shader_image_load_store_support_get",
reinterpret_cast<PyCFunction>(pygpu_shader_image_load_store_support_get),
METH_NOARGS,
pygpu_shader_image_load_store_support_get_doc},
{"hdr_support_get",
reinterpret_cast<PyCFunction>(pygpu_hdr_support_get),
METH_NOARGS,
pygpu_hdr_support_get_doc},
{
"max_work_group_count_get",
static_cast<PyCFunction>(pygpu_max_work_group_count_get),
METH_VARARGS,
pygpu_max_work_group_count_get_doc,
},
{
"max_work_group_size_get",
static_cast<PyCFunction>(pygpu_max_work_group_size_get),
METH_VARARGS,
pygpu_max_work_group_size_get_doc,
},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_capabilities__tp_doc,
"This module provides access to the GPU capabilities.");
static PyModuleDef pygpu_capabilities_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.capabilities",
/*m_doc*/ pygpu_capabilities__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_capabilities__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_capabilities_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_capabilities_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *bpygpu_capabilities_init();
} // namespace blender

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_capabilities.hh"
#include "GPU_compute.hh"
#include "GPU_state.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_compute.hh" /* own include */
#include "gpu_py_shader.hh"
namespace blender {
PyDoc_STRVAR(
/* Wrap. */
pygpu_compute_dispatch_doc,
".. function:: dispatch(shader, groups_x_len, groups_y_len, groups_z_len)\n"
"\n"
" Dispatches GPU compute.\n"
"\n"
" :param shader: The shader that you want to dispatch.\n"
" :type shader: :class:`gpu.types.GPUShader`\n"
" :param groups_x_len: Int for group x length:\n"
" :type groups_x_len: int\n"
" :param groups_y_len: Int for group y length:\n"
" :type groups_y_len: int\n"
" :param groups_z_len: Int for group z length:\n"
" :type groups_z_len: int\n");
static PyObject *pygpu_compute_dispatch(PyObject * /*self*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
BPyGPUShader *py_shader;
int groups_x_len;
int groups_y_len;
int groups_z_len;
static const char *_keywords[] = {
"shader", "groups_x_len", "groups_y_len", "groups_z_len", nullptr};
static _PyArg_Parser _parser = {
"O" /* `shader` */
"i" /* `groups_x_len` */
"i" /* `groups_y_len` */
"i" /* `groups_z_len` */
":dispatch",
_keywords,
nullptr,
};
if (_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &py_shader, &groups_x_len, &groups_y_len, &groups_z_len))
{
if (!BPyGPUShader_Check(py_shader)) {
PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name);
return nullptr;
}
/* Check that groups do not exceed #GPU_max_work_group_count(). */
const int max_work_group_count_x = GPU_max_work_group_count(0);
const int max_work_group_count_y = GPU_max_work_group_count(1);
const int max_work_group_count_z = GPU_max_work_group_count(2);
/* Report back to the user both the requested and the maximum supported value. */
if (groups_x_len > GPU_max_work_group_count(0)) {
PyErr_Format(PyExc_ValueError,
"groups_x_len (%d) exceeds maximum supported value (max work group count: %d)",
groups_x_len,
max_work_group_count_x);
return nullptr;
}
if (groups_y_len > GPU_max_work_group_count(1)) {
PyErr_Format(PyExc_ValueError,
"groups_y_len (%d) exceeds maximum supported value (max work group count: %d)",
groups_y_len,
max_work_group_count_y);
return nullptr;
}
if (groups_z_len > GPU_max_work_group_count(2)) {
PyErr_Format(PyExc_ValueError,
"groups_z_len (%d) exceeds maximum supported value (max work group count: %d)",
groups_z_len,
max_work_group_count_z);
return nullptr;
}
gpu::Shader *shader = py_shader->shader;
GPU_compute_dispatch(shader, groups_x_len, groups_y_len, groups_z_len);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
Py_RETURN_NONE;
}
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_compute__tp_methods[] = {
{"dispatch",
reinterpret_cast<PyCFunction>(pygpu_compute_dispatch),
METH_VARARGS | METH_KEYWORDS,
pygpu_compute_dispatch_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_compute__tp_doc,
"This module provides access to the global GPU compute functions.");
static PyModuleDef pygpu_compute_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.compute",
/*m_doc*/ pygpu_compute__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_compute__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_compute_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_compute_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *bpygpu_compute_init();
} // namespace blender

View File

@@ -0,0 +1,266 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_index_buffer.hh"
#include "MEM_guardedalloc.h"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_element.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name IndexBuf Type
* \{ */
static PyObject *pygpu_IndexBuf__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *error_prefix = "IndexBuf.__new__";
bool ok = true;
PyC_StringEnum prim_type = {bpygpu_primtype_items, GPU_PRIM_NONE};
PyObject *seq;
uint verts_per_prim;
uint index_len;
GPUIndexBufBuilder builder;
static const char *_keywords[] = {"type", "seq", nullptr};
static _PyArg_Parser _parser = {
"$" /* Keyword only arguments. */
"O&" /* `type` */
"O" /* `seq` */
":IndexBuf.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, PyC_ParseStringEnum, &prim_type, &seq))
{
return nullptr;
}
verts_per_prim = GPU_indexbuf_primitive_len(GPUPrimType(prim_type.value_found));
if (verts_per_prim == -1) {
PyErr_SetString(PyExc_ValueError,
"The argument 'type' must be "
"'POINTS', 'LINES', 'TRIS', 'LINES_ADJ' or 'TRIS_ADJ'");
return nullptr;
}
if (PyObject_CheckBuffer(seq)) {
Py_buffer pybuffer;
if (PyObject_GetBuffer(seq, &pybuffer, PyBUF_FORMAT | PyBUF_ND) == -1) {
/* PyObject_GetBuffer already handles error messages. */
return nullptr;
}
if (pybuffer.ndim != 1 && pybuffer.shape[1] != verts_per_prim) {
PyErr_Format(PyExc_ValueError, "Each primitive must exactly %d indices", verts_per_prim);
PyBuffer_Release(&pybuffer);
return nullptr;
}
if (pybuffer.itemsize != 4 ||
PyC_StructFmt_type_is_float_any(PyC_StructFmt_type_from_str(pybuffer.format)))
{
PyErr_SetString(PyExc_ValueError, "Each index must be an 4-bytes integer value");
PyBuffer_Release(&pybuffer);
return nullptr;
}
index_len = pybuffer.shape[0];
if (pybuffer.ndim != 1) {
index_len *= pybuffer.shape[1];
}
/* The `vertex_len` parameter is only used for asserts in the Debug build. */
/* Not very useful in python since scripts are often tested in Release build. */
/* Use `INT_MAX` instead of the actual number of vertices. */
GPU_indexbuf_init(&builder, GPUPrimType(prim_type.value_found), index_len, INT_MAX);
uint *buf = static_cast<uint *>(pybuffer.buf);
for (uint i = index_len; i--; buf++) {
GPU_indexbuf_add_generic_vert(&builder, *buf);
}
PyBuffer_Release(&pybuffer);
}
else {
PyObject *seq_fast = PySequence_Fast(seq, error_prefix);
if (seq_fast == nullptr) {
return nullptr;
}
const uint seq_len = PySequence_Fast_GET_SIZE(seq_fast);
PyObject **seq_items = PySequence_Fast_ITEMS(seq_fast);
index_len = seq_len * verts_per_prim;
/* The `vertex_len` parameter is only used for asserts in the Debug build. */
/* Not very useful in python since scripts are often tested in Release build. */
/* Use `INT_MAX` instead of the actual number of vertices. */
GPU_indexbuf_init(&builder, GPUPrimType(prim_type.value_found), index_len, INT_MAX);
if (verts_per_prim == 1) {
for (uint i = 0; i < seq_len; i++) {
GPU_indexbuf_add_generic_vert(&builder, PyC_Long_AsU32(seq_items[i]));
}
}
else {
int values[4];
for (uint i = 0; i < seq_len; i++) {
PyObject *seq_fast_item = PySequence_Fast(seq_items[i], error_prefix);
if (seq_fast_item == nullptr) {
PyErr_Format(PyExc_TypeError,
"%s: expected a sequence, got %s",
error_prefix,
Py_TYPE(seq_items[i])->tp_name);
ok = false;
goto finally;
}
ok = PyC_AsArray_FAST(values,
sizeof(*values),
seq_fast_item,
verts_per_prim,
&PyLong_Type,
error_prefix) == 0;
if (ok) {
for (uint j = 0; j < verts_per_prim; j++) {
GPU_indexbuf_add_generic_vert(&builder, values[j]);
}
}
Py_DECREF(seq_fast_item);
}
}
if (PyErr_Occurred()) {
ok = false;
}
finally:
Py_DECREF(seq_fast);
}
if (ok == false) {
MEM_delete(builder.data);
return nullptr;
}
return BPyGPUIndexBuf_CreatePyObject(GPU_indexbuf_build(&builder));
}
static void pygpu_IndexBuf__tp_dealloc(BPyGPUIndexBuf *self)
{
GPU_indexbuf_discard(self->elem);
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_IndexBuf__tp_doc,
".. class:: GPUIndexBuf\n"
"\n"
" Contains an index buffer.\n"
"\n"
" .. method:: __init__(type, seq)\n"
"\n"
" :param type: The primitive type this index buffer is composed of.\n"
" :type type: Literal['POINTS', 'LINES', 'TRIS', 'LINES_ADJ', 'TRIS_ADJ']\n"
" :param seq: Indices this index buffer will contain.\n"
" Whether a 1D or 2D sequence is required depends on the type.\n"
" Optionally the sequence can support the buffer protocol.\n"
" :type seq: Buffer | Sequence[int] | Sequence[Sequence[int]]\n");
PyTypeObject BPyGPUIndexBuf_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUIndexBuf",
/*tp_basicsize*/ sizeof(BPyGPUIndexBuf),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_IndexBuf__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_IndexBuf__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ nullptr,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_IndexBuf__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUIndexBuf_CreatePyObject(gpu::IndexBuf *elem)
{
BPyGPUIndexBuf *self;
self = PyObject_New(BPyGPUIndexBuf, &BPyGPUIndexBuf_Type);
self->elem = elem;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
namespace gpu {
class IndexBuf;
}
extern PyTypeObject BPyGPUIndexBuf_Type;
#define BPyGPUIndexBuf_Check(v) (Py_TYPE(v) == &BPyGPUIndexBuf_Type)
struct BPyGPUIndexBuf {
PyObject_HEAD
gpu::IndexBuf *elem;
};
[[nodiscard]] PyObject *BPyGPUIndexBuf_CreatePyObject(gpu::IndexBuf *elem);
} // namespace blender

View File

@@ -0,0 +1,958 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \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.hh"
#include "GPU_framebuffer.hh"
#include "GPU_init_exit.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "../generic/python_utildefines.hh"
#include "../mathutils/mathutils.hh"
#include "gpu_py.hh"
#include "gpu_py_buffer.hh"
#include "gpu_py_framebuffer.hh" /* own include */
#include "gpu_py_texture.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name gpu::FrameBuffer Common Utilities
* \{ */
static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb)
{
if (UNLIKELY(bpygpu_fb->fb == nullptr)) {
PyErr_SetString(PyExc_ReferenceError, "GPU framebuffer was freed, no further access is valid");
return -1;
}
return 0;
}
#define PYGPU_FRAMEBUFFER_CHECK_OBJ(bpygpu) \
{ \
if (UNLIKELY(pygpu_framebuffer_valid_check(bpygpu) == -1)) { \
return nullptr; \
} \
} \
((void)0)
static void pygpu_framebuffer_free_if_possible(gpu::FrameBuffer *fb)
{
if (GPU_is_init()) {
GPU_framebuffer_free(fb);
}
else {
printf("PyFramebuffer freed after the context has been destroyed.\n");
}
}
static void pygpu_framebuffer_free_safe(BPyGPUFrameBuffer *self)
{
if (self->fb) {
#ifndef GPU_NO_USE_PY_REFERENCES
GPU_framebuffer_py_reference_set(self->fb, nullptr);
if (!self->shared_reference)
#endif
{
pygpu_framebuffer_free_if_possible(self->fb);
}
self->fb = nullptr;
}
}
/* Keep less than or equal to #FRAMEBUFFER_STACK_DEPTH */
#define GPU_PY_FRAMEBUFFER_STACK_LEN 16
static bool pygpu_framebuffer_stack_push_and_bind_or_error(gpu::FrameBuffer *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 pygpu_framebuffer_stack_pop_and_restore_or_error(gpu::FrameBuffer *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;
}
gpu::FrameBuffer *fb_prev = GPU_framebuffer_pop();
GPU_framebuffer_bind(fb_prev);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
struct PyFrameBufferStackContext {
PyObject_HEAD /* Required Python macro. */
BPyGPUFrameBuffer *py_fb;
int level;
};
static void pygpu_framebuffer_stack_context__tp_dealloc(PyFrameBufferStackContext *self)
{
Py_DECREF(self->py_fb);
PyObject_DEL(self);
}
static PyObject *pygpu_framebuffer_stack_context_enter(PyFrameBufferStackContext *self)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self->py_fb);
/* sanity - should never happen */
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return nullptr;
}
if (!pygpu_framebuffer_stack_push_and_bind_or_error(self->py_fb->fb)) {
return nullptr;
}
self->level = GPU_framebuffer_stack_level_get();
Py_RETURN_NONE;
}
static PyObject *pygpu_framebuffer_stack_context_exit(PyFrameBufferStackContext *self,
PyObject * /*args*/)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self->py_fb);
/* sanity - should never happen */
if (self->level == -1) {
fprintf(stderr, "Not yet in use\n");
return nullptr;
}
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 (!pygpu_framebuffer_stack_pop_and_restore_or_error(self->py_fb->fb)) {
return nullptr;
}
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_framebuffer_stack_context__tp_methods[] = {
{"__enter__",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_stack_context_enter),
METH_NOARGS},
{"__exit__",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_stack_context_exit),
METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static PyTypeObject FramebufferStackContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUFrameBufferStackContext",
/*tp_basicsize*/ sizeof(PyFrameBufferStackContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_framebuffer_stack_context__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ nullptr,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_framebuffer_stack_context__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_bind_doc,
".. method:: bind()\n"
"\n"
" Context manager to ensure balanced bind calls, even in the case of an error.\n");
static PyObject *pygpu_framebuffer_bind(BPyGPUFrameBuffer *self)
{
PyFrameBufferStackContext *ret = PyObject_New(PyFrameBufferStackContext,
&FramebufferStackContext_Type);
ret->py_fb = self;
ret->level = -1;
Py_INCREF(self);
return reinterpret_cast<PyObject *>(ret);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUFramebuffer Type
* \{ */
/* Fill in the GPUAttachment according to the PyObject parameter.
* PyObject *o can be nullptr, Py_None, BPyGPUTexture or a dictionary containing the keyword
* "texture" and the optional keywords "layer" and "mip". Returns false on error. In this case, a
* python message will be raised and GPUAttachment will not be touched. */
static bool pygpu_framebuffer_new_parse_arg(PyObject *o, GPUAttachment *r_attach)
{
GPUAttachment tmp_attach = GPU_ATTACHMENT_NONE;
if (!o || o == Py_None) {
/* Pass. */;
}
else if (BPyGPUTexture_Check(o)) {
if (!bpygpu_ParseTexture(o, &tmp_attach.tex)) {
return false;
}
}
else {
const char *c_texture = "texture";
const char *c_layer = "layer";
const char *c_mip = "mip";
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(o, &pos, &key, &value)) {
if (!PyUnicode_Check(key)) {
PyErr_SetString(PyExc_TypeError, "keywords must be strings");
return false;
}
if (c_texture && PyUnicode_CompareWithASCIIString(key, c_texture) == 0) {
/* Compare only once. */
c_texture = nullptr;
if (!bpygpu_ParseTexture(value, &tmp_attach.tex)) {
return false;
}
}
else if (c_layer && PyUnicode_CompareWithASCIIString(key, c_layer) == 0) {
/* Compare only once. */
c_layer = nullptr;
tmp_attach.layer = PyLong_AsLong(value);
if (tmp_attach.layer == -1 && PyErr_Occurred()) {
return false;
}
}
else if (c_mip && PyUnicode_CompareWithASCIIString(key, c_mip) == 0) {
/* Compare only once. */
c_mip = nullptr;
tmp_attach.mip = PyLong_AsLong(value);
if (tmp_attach.mip == -1 && PyErr_Occurred()) {
return false;
}
}
else {
PyErr_Format(
PyExc_TypeError, "'%U' is an invalid keyword argument for this attribute", key);
return false;
}
}
}
*r_attach = tmp_attach;
return true;
}
static PyObject *pygpu_framebuffer__tp_new(PyTypeObject * /*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 nullptr;
}
PyObject *depth_attachment = nullptr;
PyObject *color_attachements = nullptr;
static const char *_keywords[] = {"depth_slot", "color_slots", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O" /* `depth_slot` */
"O" /* `color_slots` */
":GPUFrameBuffer.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &depth_attachment, &color_attachements))
{
return nullptr;
}
/* Keep in sync with #GPU_FB_MAX_COLOR_ATTACHMENT.
* TODO: share the define. */
#define BPYGPU_FB_MAX_COLOR_ATTACHMENT 6
GPUAttachment config[BPYGPU_FB_MAX_COLOR_ATTACHMENT + 1];
if (!pygpu_framebuffer_new_parse_arg(depth_attachment, &config[0])) {
return nullptr;
}
if (config[0].tex && !GPU_texture_has_depth_format(config[0].tex)) {
PyErr_SetString(PyExc_ValueError, "Depth texture with incompatible format");
return nullptr;
}
int color_attachements_len = 0;
if (color_attachements && color_attachements != Py_None) {
if (PySequence_Check(color_attachements)) {
color_attachements_len = PySequence_Size(color_attachements);
if (color_attachements_len > BPYGPU_FB_MAX_COLOR_ATTACHMENT) {
PyErr_SetString(PyExc_AttributeError,
"too many attachments, max is " STRINGIFY(BPYGPU_FB_MAX_COLOR_ATTACHMENT));
return nullptr;
}
for (int i = 0; i < color_attachements_len; i++) {
PyObject *o = PySequence_GetItem(color_attachements, i);
bool ok = pygpu_framebuffer_new_parse_arg(o, &config[i + 1]);
Py_DECREF(o);
if (!ok) {
return nullptr;
}
}
}
else {
if (!pygpu_framebuffer_new_parse_arg(color_attachements, &config[1])) {
return nullptr;
}
color_attachements_len = 1;
}
}
gpu::FrameBuffer *fb_python = GPU_framebuffer_create("fb_python");
GPU_framebuffer_config_array(fb_python, config, color_attachements_len + 1);
return BPyGPUFrameBuffer_CreatePyObject(fb_python, false);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_is_bound_doc,
"Checks if this is the active frame-buffer in the context.\n"
"\n"
":type: bool\n");
static PyObject *pygpu_framebuffer_is_bound(BPyGPUFrameBuffer *self, void * /*type*/)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
return PyBool_FromLong(GPU_framebuffer_bound(self->fb));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_clear_doc,
".. method:: clear(*, color=None, depth=None, stencil=None)\n"
"\n"
" Fill color, depth and stencil textures with specific value.\n"
" Common values: color=(0.0, 0.0, 0.0, 1.0), depth=1.0, stencil=0.\n"
"\n"
" :param color: Sequence of 3 or 4 floats representing ``(r, g, b, a)``.\n"
" :type color: Sequence[float] | None\n"
" :param depth: depth value.\n"
" :type depth: float | None\n"
" :param stencil: stencil value.\n"
" :type stencil: int | None\n");
static PyObject *pygpu_framebuffer_clear(BPyGPUFrameBuffer *self, PyObject *args, PyObject *kwds)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
if (!GPU_framebuffer_bound(self->fb)) {
return nullptr;
}
PyObject *py_col = nullptr;
PyObject *py_depth = nullptr;
PyObject *py_stencil = nullptr;
static const char *_keywords[] = {"color", "depth", "stencil", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O" /* `color` */
"O" /* `depth` */
"O" /* `stencil` */
":clear",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &py_col, &py_depth, &py_stencil)) {
return nullptr;
}
GPUFrameBufferBits buffers = GPUFrameBufferBits(0);
float col[4] = {0.0f, 0.0f, 0.0f, 1.0f};
float depth = 1.0f;
uint stencil = 0;
if (py_col && py_col != Py_None) {
if (mathutils_array_parse(
col, 3, 4, py_col, "gpu::FrameBuffer.clear(), invalid 'color' arg") == -1)
{
return nullptr;
}
buffers |= GPU_COLOR_BIT;
}
if (py_depth && py_depth != Py_None) {
depth = PyFloat_AsDouble(py_depth);
if (PyErr_Occurred()) {
return nullptr;
}
buffers |= GPU_DEPTH_BIT;
}
if (py_stencil && py_stencil != Py_None) {
if ((stencil = PyC_Long_AsU32(py_stencil)) == uint(-1)) {
return nullptr;
}
buffers |= GPU_STENCIL_BIT;
}
GPU_framebuffer_clear(self->fb, buffers, double4(UNPACK4(col)), depth, stencil);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_viewport_set_doc,
".. method:: viewport_set(x, y, xsize, ysize)\n"
"\n"
" Set the viewport for this framebuffer object.\n"
" Note: The viewport state is not saved upon framebuffer rebind.\n"
"\n"
" :param x: Lower left corner x of the viewport rectangle, in pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y of the viewport rectangle, in pixels.\n"
" :type y: int\n"
" :param xsize: Width of the viewport.\n"
" :type xsize: int\n"
" :param ysize: Height of the viewport.\n"
" :type ysize: int\n");
static PyObject *pygpu_framebuffer_viewport_set(BPyGPUFrameBuffer *self, PyObject *args)
{
int x, y, xsize, ysize;
if (!PyArg_ParseTuple(args, "iiii:viewport_set", &x, &y, &xsize, &ysize)) {
return nullptr;
}
GPU_framebuffer_viewport_set(self->fb, x, y, xsize, ysize);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_viewport_get_doc,
".. method:: viewport_get()\n"
"\n"
" Returns position and dimension to current viewport.\n"
"\n"
" :return: The viewport as ``(x, y, width, height)``.\n"
" :rtype: tuple[int, int, int, int]\n");
static PyObject *pygpu_framebuffer_viewport_get(BPyGPUFrameBuffer *self)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
int viewport[4];
GPU_framebuffer_viewport_get(self->fb, viewport);
PyObject *ret = PyTuple_New(4);
PyTuple_SET_ITEMS(ret,
PyLong_FromLong(viewport[0]),
PyLong_FromLong(viewport[1]),
PyLong_FromLong(viewport[2]),
PyLong_FromLong(viewport[3]));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_read_color_doc,
".. method:: read_color(x, y, xsize, ysize, channels, slot, format, *, data=None)\n"
"\n"
" Read a block of pixels from the frame buffer.\n"
"\n"
" :param x: Lower left corner x of a rectangular block of pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y of a rectangular block of pixels.\n"
" :type y: int\n"
" :param xsize: Width of the pixel rectangle.\n"
" :type xsize: int\n"
" :param ysize: Height of the pixel rectangle.\n"
" :type ysize: int\n"
" :param channels: Number of components to read.\n"
" :type channels: int\n"
" :param slot: The framebuffer slot to read data from.\n"
" :type slot: int\n"
" :param format: The format that describes the content of a single channel.\n"
" ``UINT_24_8`` is deprecated, use ``FLOAT`` instead.\n"
" :type format: " PYDOC_DATAFORMAT_LITERAL
"\n"
" :param data: Optional Buffer object to fill with the pixels values.\n"
" :type data: :class:`gpu.types.Buffer` | None\n"
" :return: The Buffer with the read pixels.\n"
" :rtype: :class:`gpu.types.Buffer`\n");
static PyObject *pygpu_framebuffer_read_color(BPyGPUFrameBuffer *self,
PyObject *args,
PyObject *kwds)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
int x, y, w, h, channels;
uint slot;
PyC_StringEnum pygpu_dataformat = {bpygpu_dataformat_items,
int(gpu::TextureFormat::UNORM_8_8_8_8)};
BPyGPUBuffer *py_buffer = nullptr;
PyC_TypeOrNone py_buffer_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPU_BufferType, &py_buffer);
static const char *_keywords[] = {
"x", "y", "xsize", "ysize", "channels", "slot", "format", "data", nullptr};
static _PyArg_Parser _parser = {
"i" /* `x` */
"i" /* `y` */
"i" /* `xsize` */
"i" /* `ysize` */
"i" /* `channels` */
"I" /* `slot` */
"O&" /* `format` */
"|$" /* Optional keyword only arguments. */
"O&" /* `data` */
":read_color",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
&x,
&y,
&w,
&h,
&channels,
&slot,
PyC_ParseStringEnum,
&pygpu_dataformat,
PyC_ParseTypeOrNone,
&py_buffer_or_none))
{
return nullptr;
}
if (pygpu_dataformat.value_found == GPU_DATA_UINT_24_8_DEPRECATED) {
PyErr_WarnEx(PyExc_DeprecationWarning, "`UINT_24_8` is deprecated, use `FLOAT` instead", 1);
}
if (!IN_RANGE_INCL(channels, 1, 4)) {
PyErr_SetString(PyExc_AttributeError, "Color channels must be 1, 2, 3 or 4");
return nullptr;
}
if (slot >= BPYGPU_FB_MAX_COLOR_ATTACHMENT) {
PyErr_SetString(PyExc_ValueError, "slot overflow");
return nullptr;
}
int2 extent = GPU_framebuffer_extent_get(self->fb);
if (x < 0 || w < 0 || x + w > extent.x || y < 0 || h < 0 || y + h > extent.y) {
PyErr_SetString(PyExc_ValueError,
"Trying to read color outside the extent of the framebuffer");
return nullptr;
}
if (py_buffer) {
if (pygpu_dataformat.value_found != py_buffer->format) {
PyErr_SetString(PyExc_AttributeError,
"the format of the buffer is different from that specified");
return nullptr;
}
size_t size_curr = bpygpu_Buffer_size(py_buffer);
size_t size_expected = w * h * channels *
GPU_texture_dataformat_size(
eGPUDataFormat(pygpu_dataformat.value_found));
if (size_curr < size_expected) {
PyErr_SetString(PyExc_BufferError, "the buffer size is smaller than expected");
return nullptr;
}
Py_INCREF(py_buffer);
}
else {
const Py_ssize_t shape[3] = {h, w, channels};
py_buffer = BPyGPU_Buffer_CreatePyObject(pygpu_dataformat.value_found, shape, 3, nullptr);
BLI_assert(bpygpu_Buffer_size(py_buffer) ==
size_t(w) * size_t(h) * size_t(channels) *
GPU_texture_dataformat_size(eGPUDataFormat(pygpu_dataformat.value_found)));
}
GPU_framebuffer_read_color(self->fb,
x,
y,
w,
h,
channels,
int(slot),
eGPUDataFormat(pygpu_dataformat.value_found),
py_buffer->buf.as_void);
return reinterpret_cast<PyObject *>(py_buffer);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_read_depth_doc,
".. method:: read_depth(x, y, xsize, ysize, *, data=None)\n"
"\n"
" Read a pixel depth block from the frame buffer.\n"
"\n"
" :param x: Lower left corner x of a rectangular block of pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y of a rectangular block of pixels.\n"
" :type y: int\n"
" :param xsize: Width of the pixel rectangle.\n"
" :type xsize: int\n"
" :param ysize: Height of the pixel rectangle.\n"
" :type ysize: int\n"
" :param data: Optional Buffer object to fill with the pixels values.\n"
" :type data: :class:`gpu.types.Buffer` | None\n"
" :return: The Buffer with the read pixels.\n"
" :rtype: :class:`gpu.types.Buffer`\n");
static PyObject *pygpu_framebuffer_read_depth(BPyGPUFrameBuffer *self,
PyObject *args,
PyObject *kwds)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
int x, y, w, h;
BPyGPUBuffer *py_buffer = nullptr;
PyC_TypeOrNone py_buffer_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPU_BufferType, &py_buffer);
static const char *_keywords[] = {"x", "y", "xsize", "ysize", "data", nullptr};
static _PyArg_Parser _parser = {
"i" /* `x` */
"i" /* `y` */
"i" /* `xsize` */
"i" /* `ysize` */
"|$" /* Optional keyword only arguments. */
"O&" /* `data` */
":read_depth",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &x, &y, &w, &h, PyC_ParseTypeOrNone, &py_buffer_or_none))
{
return nullptr;
}
int2 extent = GPU_framebuffer_extent_get(self->fb);
if (x < 0 || w < 0 || x + w > extent.x || y < 0 || h < 0 || y + h > extent.y) {
PyErr_SetString(PyExc_ValueError,
"Trying to read depth outside the extent of the framebuffer");
return nullptr;
}
if (py_buffer) {
if (py_buffer->format != GPU_DATA_FLOAT) {
PyErr_SetString(PyExc_AttributeError, "the format of the buffer must be 'GPU_DATA_FLOAT'");
return nullptr;
}
size_t size_curr = bpygpu_Buffer_size(py_buffer);
size_t size_expected = w * h * GPU_texture_dataformat_size(GPU_DATA_FLOAT);
if (size_curr < size_expected) {
PyErr_SetString(PyExc_BufferError, "the buffer size is smaller than expected");
return nullptr;
}
Py_INCREF(py_buffer);
}
else {
const Py_ssize_t shape[2] = {h, w};
py_buffer = BPyGPU_Buffer_CreatePyObject(GPU_DATA_FLOAT, shape, 2, nullptr);
BLI_assert(bpygpu_Buffer_size(py_buffer) ==
w * h * GPU_texture_dataformat_size(GPU_DATA_FLOAT));
}
GPU_framebuffer_read_depth(self->fb, x, y, w, h, GPU_DATA_FLOAT, py_buffer->buf.as_void);
return reinterpret_cast<PyObject *>(py_buffer);
}
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_free_doc,
".. method:: free()\n"
"\n"
" Free the framebuffer object.\n"
" The framebuffer will no longer be accessible.\n");
static PyObject *pygpu_framebuffer_free(BPyGPUFrameBuffer *self)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
pygpu_framebuffer_free_safe(self);
Py_RETURN_NONE;
}
#endif
static void BPyGPUFrameBuffer__tp_dealloc(BPyGPUFrameBuffer *self)
{
pygpu_framebuffer_free_safe(self);
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
static PyGetSetDef pygpu_framebuffer__tp_getseters[] = {
{"is_bound",
reinterpret_cast<getter>(pygpu_framebuffer_is_bound),
static_cast<setter>(nullptr),
pygpu_framebuffer_is_bound_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_framebuffer__tp_methods[] = {
{"bind",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_bind),
METH_NOARGS,
pygpu_framebuffer_bind_doc},
{"clear",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_clear),
METH_VARARGS | METH_KEYWORDS,
pygpu_framebuffer_clear_doc},
{"viewport_set",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_viewport_set),
METH_VARARGS,
pygpu_framebuffer_viewport_set_doc},
{"viewport_get",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_viewport_get),
METH_NOARGS,
pygpu_framebuffer_viewport_get_doc},
{"read_color",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_read_color),
METH_VARARGS | METH_KEYWORDS,
pygpu_framebuffer_read_color_doc},
{"read_depth",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_read_depth),
METH_VARARGS | METH_KEYWORDS,
pygpu_framebuffer_read_depth_doc},
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
{"free", (PyCFunction)pygpu_framebuffer_free, METH_NOARGS, pygpu_framebuffer_free_doc},
#endif
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
/* Ideally type aliases would de-duplicate:
* `GPUTexture | dict[str, int | GPUTexture]` in this docstring. */
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer__tp_doc,
".. class:: GPUFrameBuffer\n"
"\n"
" This object gives access to framebuffer functionalities.\n"
" When a 'layer' is specified in a argument, a single layer of a 3D or array "
"texture is attached to the frame-buffer.\n"
" For cube map textures, layer is translated into a cube map face.\n"
"\n"
" .. method:: __init__(*, depth_slot=None, color_slots=None)\n"
"\n"
" :param depth_slot: GPUTexture to attach or a ``dict`` containing keywords: "
"'texture', 'layer' and 'mip'.\n"
" :type depth_slot: :class:`gpu.types.GPUTexture` | dict[str, int | "
":class:`gpu.types.GPUTexture`] | None\n"
" :param color_slots: Tuple where each item can be a GPUTexture or a ``dict`` "
"containing keywords: 'texture', 'layer' and 'mip'.\n"
" :type color_slots: :class:`gpu.types.GPUTexture` | "
"dict[str, int | :class:`gpu.types.GPUTexture`] | "
"Sequence[:class:`gpu.types.GPUTexture` | dict[str, int | "
":class:`gpu.types.GPUTexture`]] | "
"None\n");
PyTypeObject BPyGPUFrameBuffer_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUFrameBuffer",
/*tp_basicsize*/ sizeof(BPyGPUFrameBuffer),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPyGPUFrameBuffer__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_framebuffer__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_framebuffer__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_framebuffer__tp_getseters,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_framebuffer__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUFrameBuffer_CreatePyObject(gpu::FrameBuffer *fb, bool shared_reference)
{
BPyGPUFrameBuffer *self;
#ifndef GPU_NO_USE_PY_REFERENCES
if (shared_reference) {
void **ref = GPU_framebuffer_py_reference_get(fb);
if (ref) {
/* Retrieve BPyGPUFrameBuffer reference. */
self = reinterpret_cast<BPyGPUFrameBuffer *> POINTER_OFFSET(
ref, -offsetof(BPyGPUFrameBuffer, fb));
BLI_assert(self->fb == fb);
Py_INCREF(self);
return reinterpret_cast<PyObject *>(self);
}
}
#else
UNUSED_VARS(shared_reference);
#endif
self = PyObject_New(BPyGPUFrameBuffer, &BPyGPUFrameBuffer_Type);
self->fb = fb;
#ifndef GPU_NO_USE_PY_REFERENCES
self->shared_reference = shared_reference;
BLI_assert(GPU_framebuffer_py_reference_get(fb) == nullptr);
GPU_framebuffer_py_reference_set(fb, reinterpret_cast<void **>(&self->fb));
#endif
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef PYGPU_FRAMEBUFFER_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,37 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class FrameBuffer;
} // namespace gpu
extern PyTypeObject BPyGPUFrameBuffer_Type;
#define BPyGPUFrameBuffer_Check(v) (Py_TYPE(v) == &BPyGPUFrameBuffer_Type)
struct BPyGPUFrameBuffer {
PyObject_HEAD
gpu::FrameBuffer *fb;
#ifndef GPU_NO_USE_PY_REFERENCES
bool shared_reference;
#endif
};
[[nodiscard]] PyObject *BPyGPUFrameBuffer_CreatePyObject(gpu::FrameBuffer *fb,
bool shared_reference) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,732 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.matrix stack API.
*
* \warning While these functions attempt to ensure correct stack usage.
* Mixing Python and C functions may still crash on invalid use.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "BLI_utildefines.h"
#include "../mathutils/mathutils.hh"
#define USE_GPU_PY_MATRIX_API
#include "GPU_matrix.hh"
#undef USE_GPU_PY_MATRIX_API
#include "gpu_py.hh"
#include "gpu_py_matrix.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Helper Functions
* \{ */
static bool pygpu_stack_is_push_model_view_ok_or_error()
{
if (GPU_matrix_stack_level_get_model_view() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum model-view stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
return false;
}
return true;
}
static bool pygpu_stack_is_push_projection_ok_or_error()
{
if (GPU_matrix_stack_level_get_projection() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum projection stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
return false;
}
return true;
}
static bool pygpu_stack_is_pop_model_view_ok_or_error()
{
if (GPU_matrix_stack_level_get_model_view() == 0) {
PyErr_SetString(PyExc_RuntimeError, "Minimum model-view stack depth reached");
return false;
}
return true;
}
static bool pygpu_stack_is_pop_projection_ok_or_error()
{
if (GPU_matrix_stack_level_get_projection() == 0) {
PyErr_SetString(PyExc_RuntimeError, "Minimum projection stack depth reached");
return false;
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manage Stack
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_doc,
".. function:: push()\n"
"\n"
" Add to the model-view matrix stack.\n");
static PyObject *pygpu_matrix_push(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_push_model_view_ok_or_error()) {
return nullptr;
}
GPU_matrix_push();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_pop_doc,
".. function:: pop()\n"
"\n"
" Remove the last model-view matrix from the stack.\n");
static PyObject *pygpu_matrix_pop(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_pop_model_view_ok_or_error()) {
return nullptr;
}
GPU_matrix_pop();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_projection_doc,
".. function:: push_projection()\n"
"\n"
" Add to the projection matrix stack.\n");
static PyObject *pygpu_matrix_push_projection(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_push_projection_ok_or_error()) {
return nullptr;
}
GPU_matrix_push_projection();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_pop_projection_doc,
".. function:: pop_projection()\n"
"\n"
" Remove the last projection matrix from the stack.\n");
static PyObject *pygpu_matrix_pop_projection(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_pop_projection_ok_or_error()) {
return nullptr;
}
GPU_matrix_pop_projection();
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
struct BPyGPU_MatrixStackContext {
PyObject_HEAD /* Required Python macro. */
int type;
int level;
};
enum {
PYGPU_MATRIX_TYPE_MODEL_VIEW = 1,
PYGPU_MATRIX_TYPE_PROJECTION = 2,
};
static PyObject *pygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *self);
static PyObject *pygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self, PyObject *args);
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_matrix_stack_context__tp_methods[] = {
{"__enter__", reinterpret_cast<PyCFunction>(pygpu_matrix_stack_context_enter), METH_NOARGS},
{"__exit__", reinterpret_cast<PyCFunction>(pygpu_matrix_stack_context_exit), METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_stack_context__tp_doc,
"Context manager for matrix stack push/pop.");
PyTypeObject PyGPUMatrixStackContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "MatrixStackContext",
/*tp_basicsize*/ sizeof(BPyGPU_MatrixStackContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ nullptr,
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_matrix_stack_context__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_matrix_stack_context__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
static PyObject *pygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *self)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
/* sanity - should never happen */
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return nullptr;
}
if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
if (!pygpu_stack_is_push_model_view_ok_or_error()) {
return nullptr;
}
GPU_matrix_push();
self->level = GPU_matrix_stack_level_get_model_view();
}
else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
if (!pygpu_stack_is_push_projection_ok_or_error()) {
return nullptr;
}
GPU_matrix_push_projection();
self->level = GPU_matrix_stack_level_get_projection();
}
else {
BLI_assert_unreachable();
}
Py_RETURN_NONE;
}
static PyObject *pygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self,
PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
/* sanity - should never happen */
if (self->level == -1) {
fprintf(stderr, "Not yet in use\n");
goto finally;
}
if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
const int level = GPU_matrix_stack_level_get_model_view();
if (level != self->level) {
fprintf(stderr, "Level push/pop mismatch, expected %d, got %d\n", self->level, level);
}
if (level != 0) {
GPU_matrix_pop();
}
}
else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
const int level = GPU_matrix_stack_level_get_projection();
if (level != self->level) {
fprintf(stderr, "Level push/pop mismatch, expected %d, got %d", self->level, level);
}
if (level != 0) {
GPU_matrix_pop_projection();
}
}
else {
BLI_assert_unreachable();
}
finally:
Py_RETURN_NONE;
}
static PyObject *pygpu_matrix_push_pop_impl(int type)
{
BPyGPU_MatrixStackContext *ret = PyObject_New(BPyGPU_MatrixStackContext,
&PyGPUMatrixStackContext_Type);
ret->type = type;
ret->level = -1;
return reinterpret_cast<PyObject *>(ret);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_pop_doc,
".. function:: push_pop()\n"
"\n"
" Context manager to ensure balanced push/pop calls, even in the case of an error.\n"
"\n"
" :return: The context manager.\n"
" :rtype: :class:`gpu.types.MatrixStackContext`\n");
static PyObject *pygpu_matrix_push_pop(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return pygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_MODEL_VIEW);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_pop_projection_doc,
".. function:: push_pop_projection()\n"
"\n"
" Context manager to ensure balanced push/pop calls, even in the case of an error.\n"
"\n"
" :return: The context manager.\n"
" :rtype: :class:`gpu.types.MatrixStackContext`\n");
static PyObject *pygpu_matrix_push_pop_projection(PyObject * /*self*/)
{
return pygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_PROJECTION);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manipulate State
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_multiply_matrix_doc,
".. function:: multiply_matrix(matrix)\n"
"\n"
" Multiply the current stack matrix.\n"
"\n"
" :param matrix: A 4x4 matrix.\n"
" :type matrix: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_multiply_matrix(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
MatrixObject *pymat;
if (!Matrix_Parse4x4(value, &pymat)) {
return nullptr;
}
GPU_matrix_mul(pymat->matrix);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_scale_doc,
".. function:: scale(scale)\n"
"\n"
" Scale the current stack matrix.\n"
"\n"
" :param scale: Scale the current stack matrix with 2 or 3 floats.\n"
" :type scale: Sequence[float]\n");
static PyObject *pygpu_matrix_scale(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float scale[3];
int len;
if ((len = mathutils_array_parse(
scale, 2, 3, value, "gpu.matrix.scale(): invalid vector arg")) == -1)
{
return nullptr;
}
if (len == 2) {
GPU_matrix_scale_2fv(scale);
}
else {
GPU_matrix_scale_3fv(scale);
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_scale_uniform_doc,
".. function:: scale_uniform(scale)\n"
"\n"
" Scale the current stack matrix uniformly.\n"
"\n"
" :param scale: Uniform scale factor.\n"
" :type scale: float\n");
static PyObject *pygpu_matrix_scale_uniform(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float scalar;
if ((scalar = PyFloat_AsDouble(value)) == -1.0f && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError, "expected a number, not %.200s", Py_TYPE(value)->tp_name);
return nullptr;
}
GPU_matrix_scale_1f(scalar);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_translate_doc,
".. function:: translate(offset)\n"
"\n"
" Translate the current stack matrix.\n"
"\n"
" :param offset: Translate the current stack matrix with 2 or 3 floats.\n"
" :type offset: Sequence[float]\n");
static PyObject *pygpu_matrix_translate(PyObject * /*self*/, PyObject *value)
{
float offset[3];
int len;
if ((len = mathutils_array_parse(
offset, 2, 3, value, "gpu.matrix.translate(): invalid vector arg")) == -1)
{
return nullptr;
}
if (len == 2) {
GPU_matrix_translate_2fv(offset);
}
else {
GPU_matrix_translate_3fv(offset);
}
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Write State
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_reset_doc,
".. function:: reset()\n"
"\n"
" Empty stack and set to identity.\n");
static PyObject *pygpu_matrix_reset(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPU_matrix_reset();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_load_identity_doc,
".. function:: load_identity()\n"
"\n"
" Load an identity matrix into the stack.\n");
static PyObject *pygpu_matrix_load_identity(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPU_matrix_identity_set();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_load_matrix_doc,
".. function:: load_matrix(matrix)\n"
"\n"
" Load a matrix into the stack.\n"
"\n"
" :param matrix: A 4x4 matrix.\n"
" :type matrix: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_load_matrix(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
MatrixObject *pymat;
if (!Matrix_Parse4x4(value, &pymat)) {
return nullptr;
}
GPU_matrix_set(pymat->matrix);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_load_projection_matrix_doc,
".. function:: load_projection_matrix(matrix)\n"
"\n"
" Load a projection matrix into the stack.\n"
"\n"
" :param matrix: A 4x4 matrix.\n"
" :type matrix: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_load_projection_matrix(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
MatrixObject *pymat;
if (!Matrix_Parse4x4(value, &pymat)) {
return nullptr;
}
GPU_matrix_projection_set(pymat->matrix);
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Read State
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_get_projection_matrix_doc,
".. function:: get_projection_matrix()\n"
"\n"
" Return a copy of the projection matrix.\n"
"\n"
" :return: A 4x4 projection matrix.\n"
" :rtype: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_get_projection_matrix(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float matrix[4][4];
GPU_matrix_projection_get(matrix);
return Matrix_CreatePyObject(&matrix[0][0], 4, 4, nullptr);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_get_model_view_matrix_doc,
".. function:: get_model_view_matrix()\n"
"\n"
" Return a copy of the model-view matrix.\n"
"\n"
" :return: A 4x4 view matrix.\n"
" :rtype: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_get_model_view_matrix(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float matrix[4][4];
GPU_matrix_model_view_get(matrix);
return Matrix_CreatePyObject(&matrix[0][0], 4, 4, nullptr);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_get_normal_matrix_doc,
".. function:: get_normal_matrix()\n"
"\n"
" Return a copy of the normal matrix.\n"
"\n"
" :return: A 3x3 normal matrix.\n"
" :rtype: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_get_normal_matrix(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float matrix[3][3];
GPU_matrix_normal_get(matrix);
return Matrix_CreatePyObject(&matrix[0][0], 3, 3, nullptr);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_matrix__tp_methods[] = {
/* Manage Stack */
{"push", reinterpret_cast<PyCFunction>(pygpu_matrix_push), METH_NOARGS, pygpu_matrix_push_doc},
{"pop", reinterpret_cast<PyCFunction>(pygpu_matrix_pop), METH_NOARGS, pygpu_matrix_pop_doc},
{"push_projection",
reinterpret_cast<PyCFunction>(pygpu_matrix_push_projection),
METH_NOARGS,
pygpu_matrix_push_projection_doc},
{"pop_projection",
reinterpret_cast<PyCFunction>(pygpu_matrix_pop_projection),
METH_NOARGS,
pygpu_matrix_pop_projection_doc},
/* Stack (Context Manager) */
{"push_pop",
reinterpret_cast<PyCFunction>(pygpu_matrix_push_pop),
METH_NOARGS,
pygpu_matrix_push_pop_doc},
{"push_pop_projection",
reinterpret_cast<PyCFunction>(pygpu_matrix_push_pop_projection),
METH_NOARGS,
pygpu_matrix_push_pop_projection_doc},
/* Manipulate State */
{"multiply_matrix",
static_cast<PyCFunction>(pygpu_matrix_multiply_matrix),
METH_O,
pygpu_matrix_multiply_matrix_doc},
{"scale", static_cast<PyCFunction>(pygpu_matrix_scale), METH_O, pygpu_matrix_scale_doc},
{"scale_uniform",
static_cast<PyCFunction>(pygpu_matrix_scale_uniform),
METH_O,
pygpu_matrix_scale_uniform_doc},
{"translate",
static_cast<PyCFunction>(pygpu_matrix_translate),
METH_O,
pygpu_matrix_translate_doc},
/* TODO */
#if 0
{"rotate", (PyCFunction)pygpu_matrix_rotate, METH_O, pygpu_matrix_rotate_doc},
{"rotate_axis", (PyCFunction)pygpu_matrix_rotate_axis, METH_O, pygpu_matrix_rotate_axis_doc},
{"look_at", (PyCFunction)pygpu_matrix_look_at, METH_O, pygpu_matrix_look_at_doc},
#endif
/* Write State */
{"reset",
reinterpret_cast<PyCFunction>(pygpu_matrix_reset),
METH_NOARGS,
pygpu_matrix_reset_doc},
{"load_identity",
reinterpret_cast<PyCFunction>(pygpu_matrix_load_identity),
METH_NOARGS,
pygpu_matrix_load_identity_doc},
{"load_matrix",
static_cast<PyCFunction>(pygpu_matrix_load_matrix),
METH_O,
pygpu_matrix_load_matrix_doc},
{"load_projection_matrix",
static_cast<PyCFunction>(pygpu_matrix_load_projection_matrix),
METH_O,
pygpu_matrix_load_projection_matrix_doc},
/* Read State */
{"get_projection_matrix",
reinterpret_cast<PyCFunction>(pygpu_matrix_get_projection_matrix),
METH_NOARGS,
pygpu_matrix_get_projection_matrix_doc},
{"get_model_view_matrix",
reinterpret_cast<PyCFunction>(pygpu_matrix_get_model_view_matrix),
METH_NOARGS,
pygpu_matrix_get_model_view_matrix_doc},
{"get_normal_matrix",
reinterpret_cast<PyCFunction>(pygpu_matrix_get_normal_matrix),
METH_NOARGS,
pygpu_matrix_get_normal_matrix_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix__tp_doc,
"This module provides access to the matrix stack.");
static PyModuleDef pygpu_matrix_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.matrix",
/*m_doc*/ pygpu_matrix__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_matrix__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_matrix_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_matrix_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
extern PyTypeObject PyGPUMatrixStackContext_Type;
[[nodiscard]] PyObject *bpygpu_matrix_init();
} // namespace blender

View File

@@ -0,0 +1,689 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the offscreen 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 "BLI_string_utf8.h"
#include "BKE_global.hh"
#include "BKE_lib_id.hh" /* For #BKE_id_is_in_global_main. */
#include "BKE_scene.hh"
#include "DNA_view3d_types.h"
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "GPU_viewport.hh"
#include "ED_view3d_offscreen.hh"
#include "../mathutils/mathutils.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_texture.hh"
#include "gpu_py_offscreen.hh" /* own include */
namespace blender {
/* Define the free method to avoid breakage. */
#define BPYGPU_USE_GPUOBJ_FREE_METHOD
/* -------------------------------------------------------------------- */
/** \name GPUOffScreen Common Utilities
* \{ */
static const PyC_StringEnumItems pygpu_framebuffer_color_texture_formats[] = {
{int(gpu::TextureFormat::UNORM_8_8_8_8), "RGBA8"},
{int(gpu::TextureFormat::UNORM_16_16_16_16), "RGBA16"},
{int(gpu::TextureFormat::SFLOAT_16_16_16_16), "RGBA16F"},
{int(gpu::TextureFormat::SFLOAT_32_32_32_32), "RGBA32F"},
{0, nullptr},
};
static int pygpu_offscreen_valid_check(BPyGPUOffScreen *py_ofs)
{
if (UNLIKELY(py_ofs->ofs == nullptr)) {
PyErr_SetString(PyExc_ReferenceError,
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
"GPU offscreen was freed, no further access is valid"
#else
"GPU offscreen: internal error"
#endif
);
return -1;
}
return 0;
}
#define BPY_GPU_OFFSCREEN_CHECK_OBJ(bpygpu) \
{ \
if (UNLIKELY(pygpu_offscreen_valid_check(bpygpu) == -1)) { \
return nullptr; \
} \
} \
((void)0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
struct OffScreenStackContext {
PyObject_HEAD /* Required Python macro. */
BPyGPUOffScreen *py_offscreen;
int level;
bool is_explicitly_bound; /* Bound by "bind" method. */
};
static void pygpu_offscreen_stack_context__tp_dealloc(OffScreenStackContext *self)
{
Py_DECREF(self->py_offscreen);
PyObject_DEL(self);
}
static PyObject *pygpu_offscreen_stack_context_enter(OffScreenStackContext *self)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self->py_offscreen);
if (!self->is_explicitly_bound) {
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return nullptr;
}
GPU_offscreen_bind(self->py_offscreen->ofs, true);
self->level = GPU_framebuffer_stack_level_get();
}
Py_RETURN_NONE;
}
static PyObject *pygpu_offscreen_stack_context_exit(OffScreenStackContext *self,
PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self->py_offscreen);
if (self->level == -1) {
PyErr_SetString(PyExc_RuntimeError, "Not yet in use\n");
return nullptr;
}
const int level = GPU_framebuffer_stack_level_get();
if (level != self->level) {
PyErr_Format(
PyExc_RuntimeError, "Level of bind mismatch, expected %d, got %d\n", self->level, level);
}
GPU_offscreen_unbind(self->py_offscreen->ofs, true);
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_offscreen_stack_context__tp_methods[] = {
{"__enter__", reinterpret_cast<PyCFunction>(pygpu_offscreen_stack_context_enter), METH_NOARGS},
{"__exit__", reinterpret_cast<PyCFunction>(pygpu_offscreen_stack_context_exit), METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_stack_context__tp_doc,
"Context manager for off-screen framebuffer binding.");
PyTypeObject PyGPUOffscreenStackContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "OffScreenStackContext",
/*tp_basicsize*/ sizeof(OffScreenStackContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_offscreen_stack_context__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_offscreen_stack_context__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_offscreen_stack_context__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_bind_doc,
".. method:: bind()\n"
"\n"
" Context manager to ensure balanced bind calls, even in the case of an error.\n"
"\n"
" :return: A context manager for the off-screen binding.\n"
" :rtype: :class:`gpu.types.OffScreenStackContext`\n");
static PyObject *pygpu_offscreen_bind(BPyGPUOffScreen *self)
{
OffScreenStackContext *ret = PyObject_New(OffScreenStackContext,
&PyGPUOffscreenStackContext_Type);
ret->py_offscreen = self;
ret->level = -1;
ret->is_explicitly_bound = false;
Py_INCREF(self);
pygpu_offscreen_stack_context_enter(ret);
ret->is_explicitly_bound = true;
return reinterpret_cast<PyObject *>(ret);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_unbind_doc,
".. method:: unbind(*, restore=True)\n"
"\n"
" Unbind the offscreen object.\n"
"\n"
" :param restore: Restore the OpenGL state, can only be used when the state has been "
"saved before.\n"
" :type restore: bool\n");
static PyObject *pygpu_offscreen_unbind(BPyGPUOffScreen *self, PyObject *args, PyObject *kwds)
{
bool restore = true;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
static const char *_keywords[] = {"restore", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O&" /* `restore` */
":unbind",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, PyC_ParseBool, &restore)) {
return nullptr;
}
GPU_offscreen_unbind(self->ofs, restore);
GPU_apply_state();
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUOffscreen Type
* \{ */
static PyObject *pygpu_offscreen__tp_new(PyTypeObject * /*self*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPUOffScreen *ofs = nullptr;
int width, height;
PyC_StringEnum pygpu_textureformat = {pygpu_framebuffer_color_texture_formats,
int(gpu::TextureFormat::UNORM_8_8_8_8)};
char err_out[256];
static const char *_keywords[] = {"width", "height", "format", nullptr};
static _PyArg_Parser _parser = {
"i" /* `width` */
"i" /* `height` */
"|$" /* Optional keyword only arguments. */
"O&" /* `format` */
":GPUOffScreen.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &width, &height, PyC_ParseStringEnum, &pygpu_textureformat))
{
return nullptr;
}
if (GPU_context_active_get()) {
ofs = GPU_offscreen_create(width,
height,
true,
gpu::TextureFormat(pygpu_textureformat.value_found),
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_HOST_READ,
false,
err_out);
}
else {
STRNCPY_UTF8(err_out, "No active GPU context found");
}
if (ofs == nullptr) {
PyErr_Format(PyExc_RuntimeError,
"gpu.offscreen.new(...) failed with '%s'",
err_out[0] ? err_out : "unknown error");
return nullptr;
}
return BPyGPUOffScreen_CreatePyObject(ofs);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_width_doc,
"Width of the texture.\n"
"\n"
":type: int\n");
static PyObject *pygpu_offscreen_width_get(BPyGPUOffScreen *self, void * /*type*/)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
return PyLong_FromLong(GPU_offscreen_width(self->ofs));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_height_doc,
"Height of the texture.\n"
"\n"
":type: int\n");
static PyObject *pygpu_offscreen_height_get(BPyGPUOffScreen *self, void * /*type*/)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
return PyLong_FromLong(GPU_offscreen_height(self->ofs));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_texture_color_doc,
"The color texture attached.\n"
"\n"
":type: :class:`gpu.types.GPUTexture`\n");
static PyObject *pygpu_offscreen_texture_color_get(BPyGPUOffScreen *self, void * /*type*/)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
gpu::Texture *texture = GPU_offscreen_color_texture(self->ofs);
return BPyGPUTexture_CreatePyObject(texture, true);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_draw_view3d_doc,
".. method:: draw_view3d(scene, view_layer, view3d, region, view_matrix, projection_matrix, "
"*, do_color_management=False, draw_background=True)\n"
"\n"
" Draw the 3d viewport in the offscreen object.\n"
"\n"
" :param scene: Scene to draw.\n"
" :type scene: :class:`bpy.types.Scene`\n"
" :param view_layer: View layer to draw.\n"
" :type view_layer: :class:`bpy.types.ViewLayer`\n"
" :param view3d: 3D View to get the drawing settings from.\n"
" :type view3d: :class:`bpy.types.SpaceView3D`\n"
" :param region: Region of the 3D View (required as temporary draw target).\n"
" :type region: :class:`bpy.types.Region`\n"
" :param view_matrix: View Matrix (e.g. ``camera.matrix_world.inverted()``).\n"
" :type view_matrix: :class:`mathutils.Matrix`\n"
" :param projection_matrix: Projection Matrix (e.g. ``camera.calc_matrix_camera(...)``).\n"
" :type projection_matrix: :class:`mathutils.Matrix`\n"
" :param do_color_management: Color manage the output.\n"
" :type do_color_management: bool\n"
" :param draw_background: Draw background.\n"
" :type draw_background: bool\n");
static PyObject *pygpu_offscreen_draw_view3d(BPyGPUOffScreen *self, PyObject *args, PyObject *kwds)
{
MatrixObject *py_mat_view, *py_mat_projection;
PyObject *py_scene, *py_view_layer, *py_region, *py_view3d;
Depsgraph *depsgraph;
Scene *scene;
ViewLayer *view_layer;
View3D *v3d;
ARegion *region;
bool do_color_management = false;
bool draw_background = true;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
static const char *_keywords[] = {
"scene",
"view_layer",
"view3d",
"region",
"view_matrix",
"projection_matrix",
"do_color_management",
"draw_background",
nullptr,
};
static _PyArg_Parser _parser = {
"O" /* `scene` */
"O" /* `view_layer` */
"O" /* `view3d` */
"O" /* `region` */
"O&" /* `view_matrix` */
"O&" /* `projection_matrix` */
"|$" /* Optional keyword only arguments. */
"O&" /* `do_color_management` */
"O&" /* `draw_background` */
":draw_view3d",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
&py_scene,
&py_view_layer,
&py_view3d,
&py_region,
Matrix_Parse4x4,
&py_mat_view,
Matrix_Parse4x4,
&py_mat_projection,
PyC_ParseBool,
&do_color_management,
PyC_ParseBool,
&draw_background) ||
(!(scene = static_cast<Scene *>(PyC_RNA_AsPointer(py_scene, "Scene"))) ||
!(view_layer = static_cast<ViewLayer *>(PyC_RNA_AsPointer(py_view_layer, "ViewLayer"))) ||
!(v3d = static_cast<View3D *>(PyC_RNA_AsPointer(py_view3d, "SpaceView3D"))) ||
!(region = static_cast<ARegion *>(PyC_RNA_AsPointer(py_region, "Region")))))
{
return nullptr;
}
if (ED_view3d_draw_offscreen_check_nested()) {
/* NOTE(@ideasman42): Nested draw calls could be supported.
* Adding support for this looks to be possible but non-trivial. */
PyErr_SetString(PyExc_RuntimeError, "Nested off-screen drawing not supported");
return nullptr;
}
BLI_assert(BKE_id_is_in_global_main(&scene->id));
depsgraph = BKE_scene_ensure_depsgraph(G_MAIN, scene, view_layer);
GPU_offscreen_bind(self->ofs, true);
/* Cache the #GPUViewport so the frame-buffers and associated textures are
* not reallocated each time, see: #89204 */
if (!self->viewport) {
self->viewport = GPU_viewport_create();
}
else {
GPU_viewport_tag_update(self->viewport);
}
ED_view3d_draw_offscreen(depsgraph,
scene,
eDrawType(v3d->shading.type),
v3d,
region,
nullptr,
GPU_offscreen_width(self->ofs),
GPU_offscreen_height(self->ofs),
reinterpret_cast<const float (*)[4]>(py_mat_view->matrix),
reinterpret_cast<const float (*)[4]>(py_mat_projection->matrix),
true,
draw_background,
"",
do_color_management,
true,
self->ofs,
self->viewport);
GPU_offscreen_unbind(self->ofs, true);
Py_RETURN_NONE;
}
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_free_doc,
".. method:: free()\n"
"\n"
" Free the offscreen object.\n"
" The framebuffer, texture and render objects will no longer be accessible.\n");
static PyObject *pygpu_offscreen_free(BPyGPUOffScreen *self)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
if (self->viewport) {
GPU_viewport_free(self->viewport);
self->viewport = nullptr;
}
GPU_offscreen_free(self->ofs);
self->ofs = nullptr;
Py_RETURN_NONE;
}
#endif
static void BPyGPUOffScreen__tp_dealloc(BPyGPUOffScreen *self)
{
if (self->viewport) {
GPU_viewport_free(self->viewport);
}
if (self->ofs) {
GPU_offscreen_free(self->ofs);
}
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
static PyGetSetDef pygpu_offscreen__tp_getseters[] = {
{"texture_color",
reinterpret_cast<getter>(pygpu_offscreen_texture_color_get),
static_cast<setter>(nullptr),
pygpu_offscreen_texture_color_doc,
nullptr},
{"width",
reinterpret_cast<getter>(pygpu_offscreen_width_get),
static_cast<setter>(nullptr),
pygpu_offscreen_width_doc,
nullptr},
{"height",
reinterpret_cast<getter>(pygpu_offscreen_height_get),
static_cast<setter>(nullptr),
pygpu_offscreen_height_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_offscreen__tp_methods[] = {
{"bind",
reinterpret_cast<PyCFunction>(pygpu_offscreen_bind),
METH_NOARGS,
pygpu_offscreen_bind_doc},
{"unbind",
reinterpret_cast<PyCFunction>(pygpu_offscreen_unbind),
METH_VARARGS | METH_KEYWORDS,
pygpu_offscreen_unbind_doc},
{"draw_view3d",
reinterpret_cast<PyCFunction>(pygpu_offscreen_draw_view3d),
METH_VARARGS | METH_KEYWORDS,
pygpu_offscreen_draw_view3d_doc},
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
{"free",
reinterpret_cast<PyCFunction>(pygpu_offscreen_free),
METH_NOARGS,
pygpu_offscreen_free_doc},
#endif
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen__tp_doc,
".. class:: GPUOffScreen\n"
"\n"
" This object gives access to off screen buffers.\n"
"\n"
" .. method:: __init__(width, height, *, format='RGBA8')\n"
"\n"
" :param width: Horizontal dimension of the buffer.\n"
" :type width: int\n"
" :param height: Vertical dimension of the buffer.\n"
" :type height: int\n"
" :param format: Internal data format inside GPU memory for color attachment texture.\n"
" :type format: Literal['RGBA8', 'RGBA16', 'RGBA16F', 'RGBA32F']\n");
PyTypeObject BPyGPUOffScreen_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUOffScreen",
/*tp_basicsize*/ sizeof(BPyGPUOffScreen),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPyGPUOffScreen__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_compare*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_offscreen__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_offscreen__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_offscreen__tp_getseters,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_offscreen__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUOffScreen_CreatePyObject(GPUOffScreen *ofs)
{
BPyGPUOffScreen *self;
self = PyObject_New(BPyGPUOffScreen, &BPyGPUOffScreen_Type);
self->ofs = ofs;
self->viewport = nullptr;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef BPY_GPU_OFFSCREEN_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
struct GPUOffScreen;
struct GPUViewport;
extern PyTypeObject BPyGPUOffScreen_Type;
extern PyTypeObject PyGPUOffscreenStackContext_Type;
#define BPyGPUOffScreen_Check(v) (Py_TYPE(v) == &BPyGPUOffScreen_Type)
struct BPyGPUOffScreen {
PyObject_HEAD
GPUOffScreen *ofs;
GPUViewport *viewport;
};
[[nodiscard]] PyObject *BPyGPUOffScreen_CreatePyObject(GPUOffScreen *ofs) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,419 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_context.hh"
#include "GPU_platform.hh"
#include "gpu_py.hh"
#include "gpu_py_platform.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Device Type
* \{ */
PyDoc_STRVAR(pygpu_device_index_doc,
"Device index.\n"
"\n"
":type: int\n");
static PyObject *pygpu_device_index_get(BPyGPUDevice *self, void * /*closure*/)
{
return PyLong_FromLong(self->index);
}
PyDoc_STRVAR(pygpu_device_identifier_doc,
"Device identifier.\n"
"\n"
":type: str\n");
static PyObject *pygpu_device_identifier_get(BPyGPUDevice *self, void * /*closure*/)
{
return PyUnicode_FromString(self->identifier);
}
PyDoc_STRVAR(pygpu_device_name_doc,
"Device name.\n"
"\n"
":type: str\n");
static PyObject *pygpu_device_name_get(BPyGPUDevice *self, void * /*closure*/)
{
return PyUnicode_FromString(self->name);
}
/* Property descriptors */
static PyGetSetDef pygpu_device_getseters[] = {
{"index",
reinterpret_cast<getter>(pygpu_device_index_get),
nullptr,
pygpu_device_index_doc,
nullptr},
{"identifier",
reinterpret_cast<getter>(pygpu_device_identifier_get),
nullptr,
pygpu_device_identifier_doc,
nullptr},
{"name",
reinterpret_cast<getter>(pygpu_device_name_get),
nullptr,
pygpu_device_name_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr},
};
/* Representation */
static PyObject *pygpu_device__tp_repr(BPyGPUDevice *self)
{
return PyUnicode_FromFormat("<GPUDevice index=%d identifier=\"%s\" name=\"%s\">",
self->index,
self->identifier,
self->name);
}
/** Rich comparison for GPUDevice types, compares by index. */
static PyObject *pygpu_device__tp_richcmp(BPyGPUDevice *self, PyObject *other, int op)
{
if (!Py_IS_TYPE(other, &BPyGPU_DeviceType)) {
Py_RETURN_NOTIMPLEMENTED;
}
BPyGPUDevice *other_device = reinterpret_cast<BPyGPUDevice *>(other);
switch (op) {
case Py_LT:
return PyBool_FromLong(self->index < other_device->index);
case Py_LE:
return PyBool_FromLong(self->index <= other_device->index);
case Py_EQ:
return PyBool_FromLong(self->index == other_device->index);
case Py_NE:
return PyBool_FromLong(self->index != other_device->index);
case Py_GT:
return PyBool_FromLong(self->index > other_device->index);
case Py_GE:
return PyBool_FromLong(self->index >= other_device->index);
}
Py_RETURN_NOTIMPLEMENTED;
}
/* Type definition */
PyDoc_STRVAR(pygpu_device__tp_doc,
".. class:: GPUDevice\n"
"\n"
" Represents a GPU device.\n"
"\n"
" :ivar index: Device index.\n"
" :type index: int\n"
" :ivar identifier: Device identifier.\n"
" :type identifier: str\n"
" :ivar name: Device name.\n"
" :type name: str\n");
PyTypeObject BPyGPU_DeviceType = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "gpu.platform.GPUDevice",
/*tp_basicsize*/ sizeof(BPyGPUDevice),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ nullptr,
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_compare*/ nullptr,
/*tp_repr*/ reinterpret_cast<reprfunc>(pygpu_device__tp_repr),
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_device__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ reinterpret_cast<richcmpfunc>(pygpu_device__tp_richcmp),
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ nullptr,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_device_getseters,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
static BPyGPUDevice *pygpu_device_new(int index, const char *identifier, const char *name)
{
BPyGPUDevice *self = reinterpret_cast<BPyGPUDevice *>(
BPyGPU_DeviceType.tp_alloc(&BPyGPU_DeviceType, 0));
if (self != nullptr) {
self->index = index;
self->identifier = identifier;
self->name = name;
}
return self;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Functions
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_vendor_get_doc,
".. function:: vendor_get()\n"
"\n"
" Get GPU vendor.\n"
"\n"
" :return: Vendor name.\n"
" :rtype: str\n");
static PyObject *pygpu_platform_vendor_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyUnicode_FromString(GPU_platform_vendor());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_renderer_get_doc,
".. function:: renderer_get()\n"
"\n"
" Get GPU to be used for rendering.\n"
"\n"
" :return: GPU name.\n"
" :rtype: str\n");
static PyObject *pygpu_platform_renderer_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyUnicode_FromString(GPU_platform_renderer());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_version_get_doc,
".. function:: version_get()\n"
"\n"
" Get GPU driver version.\n"
"\n"
" :return: Driver version.\n"
" :rtype: str\n");
static PyObject *pygpu_platform_version_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyUnicode_FromString(GPU_platform_version());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_device_type_get_doc,
".. function:: device_type_get()\n"
"\n"
" Get GPU device type.\n"
"\n"
" :return: Device type ('APPLE', 'NVIDIA', 'AMD', 'INTEL', 'SOFTWARE', 'QUALCOMM', "
"'UNKNOWN').\n"
" :rtype: str\n");
static PyObject *pygpu_platform_device_type_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *device;
if (GPU_type_matches(GPU_DEVICE_APPLE, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "APPLE";
}
else if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "NVIDIA";
}
else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "AMD";
}
else if (GPU_type_matches(GPU_DEVICE_INTEL | GPU_DEVICE_INTEL_UHD, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "INTEL";
}
else if (GPU_type_matches(GPU_DEVICE_SOFTWARE, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "SOFTWARE";
}
/* Right now we can only detect Qualcomm GPUs on Windows, not other OSes */
else if (GPU_type_matches(GPU_DEVICE_QUALCOMM, GPU_OS_WIN, GPU_DRIVER_ANY)) {
device = "QUALCOMM";
}
else {
device = "UNKNOWN";
}
return PyUnicode_FromString(device);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_backend_type_get_doc,
".. function:: backend_type_get()\n"
"\n"
" Get active GPU backend.\n"
"\n"
" :return: Backend type ('OPENGL', 'VULKAN', 'METAL', 'NONE', 'UNKNOWN').\n"
" :rtype: str\n");
static PyObject *pygpu_platform_backend_type_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *backend = "UNKNOWN";
switch (GPU_backend_get_type()) {
case GPU_BACKEND_VULKAN: {
backend = "VULKAN";
break;
}
case GPU_BACKEND_METAL: {
backend = "METAL";
break;
}
case GPU_BACKEND_NONE: {
backend = "NONE";
break;
}
case GPU_BACKEND_OPENGL: {
backend = "OPENGL";
break;
}
case GPU_BACKEND_ANY:
break;
}
return PyUnicode_FromString(backend);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_devices_get_doc,
".. function:: devices_get()\n"
"\n"
" Get all available GPU devices.\n"
"\n"
" :return: List of :class:`GPUDevice` objects for each device.\n"
" :rtype: list\n");
static PyObject *pygpu_platform_devices_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
Span<GPUDevice> devices = GPU_platform_devices_list();
PyObject *list = PyList_New(devices.size());
for (int i = 0; i < devices.size(); i++) {
const GPUDevice &dev = devices[i];
PyObject *item = reinterpret_cast<PyObject *>(
pygpu_device_new(dev.index, dev.identifier.c_str(), dev.name.c_str()));
PyList_SET_ITEM(list, i, item);
}
/* Sort by index (first attribute) for deterministic ordering. */
PyList_Sort(list);
return list;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_platform__tp_methods[] = {
{"vendor_get",
reinterpret_cast<PyCFunction>(pygpu_platform_vendor_get),
METH_NOARGS,
pygpu_platform_vendor_get_doc},
{"renderer_get",
reinterpret_cast<PyCFunction>(pygpu_platform_renderer_get),
METH_NOARGS,
pygpu_platform_renderer_get_doc},
{"version_get",
reinterpret_cast<PyCFunction>(pygpu_platform_version_get),
METH_NOARGS,
pygpu_platform_version_get_doc},
{"device_type_get",
reinterpret_cast<PyCFunction>(pygpu_platform_device_type_get),
METH_NOARGS,
pygpu_platform_device_type_get_doc},
{"backend_type_get",
reinterpret_cast<PyCFunction>(pygpu_platform_backend_type_get),
METH_NOARGS,
pygpu_platform_backend_type_get_doc},
{"devices_get",
reinterpret_cast<PyCFunction>(pygpu_platform_devices_get),
METH_NOARGS,
pygpu_platform_devices_get_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform__tp_doc,
"This module provides access to GPU Platform definitions.");
static PyModuleDef pygpu_platform_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.platform",
/*m_doc*/ pygpu_platform__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_platform__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_platform_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_platform_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
/* GPU Device Python object structure */
struct BPyGPUDevice {
PyObject_HEAD
int index;
const char *identifier;
const char *name;
};
extern PyTypeObject BPyGPU_DeviceType;
[[nodiscard]] PyObject *bpygpu_platform_init();
} // namespace blender

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.select API.
*
* \note Currently only used for gizmo selection,
* will need to add begin/end and a way to access the hits.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "../generic/py_capi_utils.hh"
#include "GPU_select.hh"
#include "gpu_py.hh"
#include "gpu_py_select.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Methods
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_select_load_id_doc,
".. function:: load_id(id)\n"
"\n"
" Set the selection ID.\n"
"\n"
" :param id: Number (32-bit uint).\n"
" :type id: int\n");
static PyObject *pygpu_select_load_id(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
uint id;
if ((id = PyC_Long_AsU32(value)) == uint(-1)) {
return nullptr;
}
GPU_select_load_id(id);
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
static PyMethodDef pygpu_select__tp_methods[] = {
/* Manage Stack */
{"load_id", static_cast<PyCFunction>(pygpu_select_load_id), METH_O, pygpu_select_load_id_doc},
{nullptr, nullptr, 0, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_select__tp_doc,
"This module provides access to selection.");
static PyModuleDef pygpu_select_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.select",
/*m_doc*/ pygpu_select__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_select__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_select_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_select_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *bpygpu_select_init();
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
struct GPUShaderCreateInfo;
struct GPUStageInterfaceInfo;
namespace blender {
#ifndef __cplusplus
# include "../generic/py_capi_utils.hh"
#endif
namespace gpu {
class Shader;
} // namespace gpu
/* Make sure that there is always a reference count for PyObjects of type String as the strings are
* passed by reference in the #GPUStageInterfaceInfo and #GPUShaderCreateInfo APIs. */
#define USE_GPU_PY_REFERENCES
/* `gpu_py_shader.cc` */
extern PyTypeObject BPyGPUShader_Type;
#define BPyGPUShader_Check(v) (Py_TYPE(v) == &BPyGPUShader_Type)
struct BPyGPUShader {
PyObject_HEAD
gpu::Shader *shader;
bool is_builtin;
};
[[nodiscard]] PyObject *BPyGPUShader_CreatePyObject(gpu::Shader *shader, bool is_builtin);
[[nodiscard]] PyObject *bpygpu_shader_init();
/* gpu_py_shader_create_info.cc */
extern const struct PyC_StringEnumItems pygpu_attrtype_items[];
extern PyTypeObject BPyGPUShaderCreateInfo_Type;
extern PyTypeObject BPyGPUStageInterfaceInfo_Type;
#define BPyGPUShaderCreateInfo_Check(v) (Py_TYPE(v) == &BPyGPUShaderCreateInfo_Type)
#define BPyGPUStageInterfaceInfo_Check(v) (Py_TYPE(v) == &BPyGPUStageInterfaceInfo_Type)
struct BPyGPUStageInterfaceInfo {
PyObject_HEAD
GPUStageInterfaceInfo *interface;
#ifdef USE_GPU_PY_REFERENCES
/* Just to keep a user to prevent freeing buffers we're using. */
PyObject *references;
#endif
};
struct BPyGPUShaderCreateInfo {
PyObject_HEAD
GPUShaderCreateInfo *info;
#ifdef USE_GPU_PY_REFERENCES
/* Just to keep a user to prevent freeing buffers we're using. */
PyObject *vertex_source;
PyObject *fragment_source;
PyObject *compute_source;
PyObject *typedef_source;
PyObject *references;
#endif
size_t constants_total_size;
};
[[nodiscard]] PyObject *BPyGPUStageInterfaceInfo_CreatePyObject(GPUStageInterfaceInfo *interface);
[[nodiscard]] PyObject *BPyGPUShaderCreateInfo_CreatePyObject(GPUShaderCreateInfo *info);
[[nodiscard]] bool bpygpu_shader_is_polyline(gpu::Shader *shader);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,688 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.state API.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_framebuffer.hh"
#include "GPU_state.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_utildefines.hh"
#include "gpu_py.hh"
#include "gpu_py_framebuffer.hh"
#include "gpu_py_state.hh" /* own include */
/* Docstring Literal types. */
#define PYDOC_BLEND_LITERAL \
"Literal['NONE', 'ALPHA', 'ALPHA_PREMULT', 'ADDITIVE', " \
"'ADDITIVE_PREMULT', 'MULTIPLY', 'SUBTRACT', 'INVERT']"
#define PYDOC_DEPTHTEST_LITERAL \
"Literal['NONE', 'ALWAYS', 'LESS', 'LESS_EQUAL', 'EQUAL', 'GREATER', 'GREATER_EQUAL']"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Helper Functions
* \{ */
static const PyC_StringEnumItems pygpu_state_blend_items[] = {
{GPU_BLEND_NONE, "NONE"},
{GPU_BLEND_ALPHA, "ALPHA"},
{GPU_BLEND_ALPHA_PREMULT, "ALPHA_PREMULT"},
{GPU_BLEND_ADDITIVE, "ADDITIVE"},
{GPU_BLEND_ADDITIVE_PREMULT, "ADDITIVE_PREMULT"},
{GPU_BLEND_MULTIPLY, "MULTIPLY"},
{GPU_BLEND_SUBTRACT, "SUBTRACT"},
{GPU_BLEND_INVERT, "INVERT"},
/**
* These are quite special cases used inside the draw manager.
* {GPU_BLEND_OIT, "OIT"},
* {GPU_BLEND_BACKGROUND, "BACKGROUND"},
* {GPU_BLEND_CUSTOM, "CUSTOM"},
*/
{0, nullptr},
};
static const PyC_StringEnumItems pygpu_state_depthtest_items[] = {
{GPU_DEPTH_NONE, "NONE"},
{GPU_DEPTH_ALWAYS, "ALWAYS"},
{GPU_DEPTH_LESS, "LESS"},
{GPU_DEPTH_LESS_EQUAL, "LESS_EQUAL"},
{GPU_DEPTH_EQUAL, "EQUAL"},
{GPU_DEPTH_GREATER, "GREATER"},
{GPU_DEPTH_GREATER_EQUAL, "GREATER_EQUAL"},
{0, nullptr},
};
static const PyC_StringEnumItems pygpu_state_faceculling_items[] = {
{GPU_CULL_NONE, "NONE"},
{GPU_CULL_FRONT, "FRONT"},
{GPU_CULL_BACK, "BACK"},
{0, nullptr},
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manage Stack
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_blend_set_doc,
".. function:: blend_set(mode)\n"
"\n"
" Defines the fixed pipeline blending equation.\n"
"\n"
" :param mode: The type of blend mode.\n"
"\n"
" * ``NONE`` No blending.\n"
" * ``ALPHA`` The original color channels are interpolated according to the alpha "
"value.\n"
" * ``ALPHA_PREMULT`` The original color channels are interpolated according to the "
"alpha value with the new colors pre-multiplied by this value.\n"
" * ``ADDITIVE`` The original color channels are added by the corresponding ones.\n"
" * ``ADDITIVE_PREMULT`` The original color channels are added by the corresponding ones "
"that are pre-multiplied by the alpha value.\n"
" * ``MULTIPLY`` The original color channels are multiplied by the corresponding ones.\n"
" * ``SUBTRACT`` The original color channels are subtracted by the corresponding ones.\n"
" * ``INVERT`` The original color channels are replaced by its complementary color.\n"
//" * ``OIT``.\n"
//" * ``BACKGROUND`` .\n"
//" * ``CUSTOM`` .\n"
" :type mode: " PYDOC_BLEND_LITERAL "\n");
static PyObject *pygpu_state_blend_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyC_StringEnum pygpu_blend = {pygpu_state_blend_items};
if (!PyC_ParseStringEnum(value, &pygpu_blend)) {
return nullptr;
}
GPU_blend(GPUBlend(pygpu_blend.value_found));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_blend_get_doc,
".. function:: blend_get()\n"
"\n"
" Current blending equation.\n"
"\n"
" :return: The current blend mode.\n"
" :rtype: str\n");
static PyObject *pygpu_state_blend_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPUBlend blend = GPU_blend_get();
return PyUnicode_FromString(PyC_StringEnum_FindIDFromValue(pygpu_state_blend_items, blend));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_clip_distances_set_doc,
".. function:: clip_distances_set(distances_enabled)\n"
"\n"
" Sets the number of ``gl_ClipDistance`` planes used for clip geometry.\n"
"\n"
" :param distances_enabled: Number of clip distances enabled.\n"
" :type distances_enabled: int\n");
static PyObject *pygpu_state_clip_distances_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int distances_enabled = int(PyLong_AsUnsignedLong(value));
if (distances_enabled == -1) {
return nullptr;
}
if (distances_enabled > 6) {
PyErr_SetString(PyExc_ValueError, "too many distances enabled, max is 6");
}
GPU_clip_distances(distances_enabled);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_test_set_doc,
".. function:: depth_test_set(mode)\n"
"\n"
" Defines the depth_test equation.\n"
"\n"
" :param mode: The depth test equation name.\n"
" :type mode: " PYDOC_DEPTHTEST_LITERAL "\n");
static PyObject *pygpu_state_depth_test_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyC_StringEnum pygpu_depth_test = {pygpu_state_depthtest_items};
if (!PyC_ParseStringEnum(value, &pygpu_depth_test)) {
return nullptr;
}
GPU_depth_test(GPUDepthTest(pygpu_depth_test.value_found));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_test_get_doc,
".. function:: depth_test_get()\n"
"\n"
" Current depth_test equation.\n"
"\n"
" :return: The current depth test mode.\n"
" :rtype: str\n");
static PyObject *pygpu_state_depth_test_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPUDepthTest test = GPU_depth_test_get();
return PyUnicode_FromString(PyC_StringEnum_FindIDFromValue(pygpu_state_depthtest_items, test));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_mask_set_doc,
".. function:: depth_mask_set(value)\n"
"\n"
" Write to depth component.\n"
"\n"
" :param value: True for writing to the depth component.\n"
" :type value: bool\n");
static PyObject *pygpu_state_depth_mask_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool write_to_depth;
if (!PyC_ParseBool(value, &write_to_depth)) {
return nullptr;
}
GPU_depth_mask(write_to_depth);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_mask_get_doc,
".. function:: depth_mask_get()\n"
"\n"
" Writing status in the depth component.\n"
"\n"
" :return: True if writing to the depth component is enabled.\n"
" :rtype: bool\n");
static PyObject *pygpu_state_depth_mask_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyBool_FromLong(GPU_depth_mask_get());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_viewport_set_doc,
".. function:: viewport_set(x, y, xsize, ysize)\n"
"\n"
" Specifies the viewport of the active framebuffer.\n"
" Note: The viewport state is not saved upon framebuffer rebind.\n"
"\n"
" :param x: Lower left corner x coordinate, in pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y coordinate, in pixels.\n"
" :type y: int\n"
" :param xsize: Width of the viewport.\n"
" :type xsize: int\n"
" :param ysize: Height of the viewport.\n"
" :type ysize: int\n");
static PyObject *pygpu_state_viewport_set(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int x, y, xsize, ysize;
if (!PyArg_ParseTuple(args, "iiii:viewport_set", &x, &y, &xsize, &ysize)) {
return nullptr;
}
GPU_viewport(x, y, xsize, ysize);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_viewport_get_doc,
".. function:: viewport_get()\n"
"\n"
" Viewport of the active framebuffer.\n"
"\n"
" :return: The viewport as a tuple (x, y, xsize, ysize).\n"
" :rtype: tuple[int, int, int, int]\n");
static PyObject *pygpu_state_viewport_get(PyObject * /*self*/, PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int viewport[4];
GPU_viewport_size_get_i(viewport);
PyObject *ret = PyTuple_New(4);
PyTuple_SET_ITEMS(ret,
PyLong_FromLong(viewport[0]),
PyLong_FromLong(viewport[1]),
PyLong_FromLong(viewport[2]),
PyLong_FromLong(viewport[3]));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_scissor_set_doc,
".. function:: scissor_set(x, y, xsize, ysize)\n"
"\n"
" Specifies the scissor area of the active framebuffer.\n"
" Note: The scissor state is not saved upon framebuffer rebind.\n"
"\n"
" :param x: Lower left corner x coordinate, in pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y coordinate, in pixels.\n"
" :type y: int\n"
" :param xsize: Width of the scissor rectangle.\n"
" :type xsize: int\n"
" :param ysize: Height of the scissor rectangle.\n"
" :type ysize: int\n");
static PyObject *pygpu_state_scissor_set(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int x, y, xsize, ysize;
if (!PyArg_ParseTuple(args, "iiii:scissor_set", &x, &y, &xsize, &ysize)) {
return nullptr;
}
GPU_scissor(x, y, xsize, ysize);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_scissor_get_doc,
".. function:: scissor_get()\n"
"\n"
" Retrieve the scissors of the active framebuffer.\n"
" Note: Only valid between 'scissor_set' and a framebuffer rebind.\n"
"\n"
" :return: The scissor of the active framebuffer as a tuple\n"
" (x, y, xsize, ysize).\n"
" x, y: lower left corner of the scissor rectangle, in pixels.\n"
" xsize, ysize: width and height of the scissor rectangle.\n"
" :rtype: tuple[int, int, int, int]\n");
static PyObject *pygpu_state_scissor_get(PyObject * /*self*/, PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int scissor[4];
GPU_scissor_get(scissor);
PyObject *ret = PyTuple_New(4);
PyTuple_SET_ITEMS(ret,
PyLong_FromLong(scissor[0]),
PyLong_FromLong(scissor[1]),
PyLong_FromLong(scissor[2]),
PyLong_FromLong(scissor[3]));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_scissor_test_set_doc,
".. function:: scissor_test_set(enable)\n"
"\n"
" Enable/disable scissor testing on the active framebuffer.\n"
"\n"
" :param enable:\n"
" True - enable scissor testing.\n"
" False - disable scissor testing.\n"
" :type enable: bool\n");
static PyObject *pygpu_state_scissor_test_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool enabled;
if (!PyC_ParseBool(value, &enabled)) {
return nullptr;
}
GPU_scissor_test(enabled);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_line_width_set_doc,
".. function:: line_width_set(width)\n"
"\n"
" Specify the width of rasterized lines.\n"
"\n"
" :param width: New width.\n"
" :type width: float\n");
static PyObject *pygpu_state_line_width_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float width = float(PyFloat_AsDouble(value));
if (PyErr_Occurred()) {
return nullptr;
}
GPU_line_width(width);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_line_width_get_doc,
".. function:: line_width_get()\n"
"\n"
" Current width of rasterized lines.\n"
"\n"
" :return: The current line width.\n"
" :rtype: float\n");
static PyObject *pygpu_state_line_width_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float width = GPU_line_width_get();
return PyFloat_FromDouble(double(width));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_point_size_set_doc,
".. function:: point_size_set(size)\n"
"\n"
" Specify the diameter of rasterized points.\n"
"\n"
" When ``program_point_size_set(False)`` is set, this sets the ``size``\n"
" uniform of point shaders. Any value set by ``uniform_float(\"size\", ...)``\n"
" on a point shader will be overwritten by this function. Use\n"
" ``program_point_size_set(True)`` to disable this override and control\n"
" point size via ``gl_PointSize`` in the vertex shader.\n"
"\n"
" :param size: New diameter.\n"
" :type size: float\n");
static PyObject *pygpu_state_point_size_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float size = float(PyFloat_AsDouble(value));
if (PyErr_Occurred()) {
return nullptr;
}
GPU_point_size(size);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_color_mask_set_doc,
".. function:: color_mask_set(r, g, b, a)\n"
"\n"
" Enable or disable writing of frame buffer color components.\n"
"\n"
" :param r: Red component.\n"
" :type r: bool\n"
" :param g: Green component.\n"
" :type g: bool\n"
" :param b: Blue component.\n"
" :type b: bool\n"
" :param a: Alpha component.\n"
" :type a: bool\n");
static PyObject *pygpu_state_color_mask_set(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int r, g, b, a;
if (!PyArg_ParseTuple(args, "pppp:color_mask_set", &r, &g, &b, &a)) {
return nullptr;
}
GPU_color_mask(bool(r), bool(g), bool(b), bool(a));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_face_culling_set_doc,
".. function:: face_culling_set(culling)\n"
"\n"
" Specify whether none, front-facing or back-facing facets can be culled.\n"
"\n"
" :param culling: The face culling mode.\n"
" :type culling: Literal['NONE', 'FRONT', 'BACK']\n");
static PyObject *pygpu_state_face_culling_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyC_StringEnum pygpu_faceculling = {pygpu_state_faceculling_items};
if (!PyC_ParseStringEnum(value, &pygpu_faceculling)) {
return nullptr;
}
GPU_face_culling(GPUFaceCullTest(pygpu_faceculling.value_found));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_front_facing_set_doc,
".. function:: front_facing_set(invert)\n"
"\n"
" Specifies the orientation of front-facing polygons.\n"
"\n"
" :param invert: True for clockwise polygons as front-facing.\n"
" :type invert: bool\n");
static PyObject *pygpu_state_front_facing_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool invert;
if (!PyC_ParseBool(value, &invert)) {
return nullptr;
}
GPU_front_facing(invert);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_program_point_size_set_doc,
".. function:: program_point_size_set(enable)\n"
"\n"
" When enabled (True), point size is taken from the shader builtin\n"
" ``gl_PointSize``. When disabled (False), the global state value can\n"
" be applied to the shader ``size`` uniform, overwriting any user-set\n"
" value.\n"
"\n"
" :param enable: True to use shader ``gl_PointSize``, False for global state.\n"
" :type enable: bool\n");
static PyObject *pygpu_state_program_point_size_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool enable;
if (!PyC_ParseBool(value, &enable)) {
return nullptr;
}
GPU_program_point_size(enable);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_active_framebuffer_get_doc,
".. function:: active_framebuffer_get()\n"
"\n"
" Return the active frame-buffer in context.\n"
"\n"
" :return: The active framebuffer.\n"
" :rtype: :class:`gpu.types.GPUFrameBuffer`\n");
static PyObject *pygpu_state_active_framebuffer_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
gpu::FrameBuffer *fb = GPU_framebuffer_active_get();
return BPyGPUFrameBuffer_CreatePyObject(fb, true);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_state__tp_methods[] = {
/* Manage Stack */
{"blend_set",
static_cast<PyCFunction>(pygpu_state_blend_set),
METH_O,
pygpu_state_blend_set_doc},
{"blend_get",
reinterpret_cast<PyCFunction>(pygpu_state_blend_get),
METH_NOARGS,
pygpu_state_blend_get_doc},
{"clip_distances_set",
static_cast<PyCFunction>(pygpu_state_clip_distances_set),
METH_O,
pygpu_state_clip_distances_set_doc},
{"depth_test_set",
static_cast<PyCFunction>(pygpu_state_depth_test_set),
METH_O,
pygpu_state_depth_test_set_doc},
{"depth_test_get",
reinterpret_cast<PyCFunction>(pygpu_state_depth_test_get),
METH_NOARGS,
pygpu_state_depth_test_get_doc},
{"depth_mask_set",
static_cast<PyCFunction>(pygpu_state_depth_mask_set),
METH_O,
pygpu_state_depth_mask_set_doc},
{"depth_mask_get",
reinterpret_cast<PyCFunction>(pygpu_state_depth_mask_get),
METH_NOARGS,
pygpu_state_depth_mask_get_doc},
{"viewport_set",
static_cast<PyCFunction>(pygpu_state_viewport_set),
METH_VARARGS,
pygpu_state_viewport_set_doc},
{"viewport_get",
static_cast<PyCFunction>(pygpu_state_viewport_get),
METH_NOARGS,
pygpu_state_viewport_get_doc},
{"scissor_set",
static_cast<PyCFunction>(pygpu_state_scissor_set),
METH_VARARGS,
pygpu_state_scissor_set_doc},
{"scissor_get",
static_cast<PyCFunction>(pygpu_state_scissor_get),
METH_NOARGS,
pygpu_state_scissor_get_doc},
{"scissor_test_set",
static_cast<PyCFunction>(pygpu_state_scissor_test_set),
METH_O,
pygpu_state_scissor_test_set_doc},
{"line_width_set",
static_cast<PyCFunction>(pygpu_state_line_width_set),
METH_O,
pygpu_state_line_width_set_doc},
{"line_width_get",
reinterpret_cast<PyCFunction>(pygpu_state_line_width_get),
METH_NOARGS,
pygpu_state_line_width_get_doc},
{"point_size_set",
static_cast<PyCFunction>(pygpu_state_point_size_set),
METH_O,
pygpu_state_point_size_set_doc},
{"color_mask_set",
static_cast<PyCFunction>(pygpu_state_color_mask_set),
METH_VARARGS,
pygpu_state_color_mask_set_doc},
{"face_culling_set",
static_cast<PyCFunction>(pygpu_state_face_culling_set),
METH_O,
pygpu_state_face_culling_set_doc},
{"front_facing_set",
static_cast<PyCFunction>(pygpu_state_front_facing_set),
METH_O,
pygpu_state_front_facing_set_doc},
{"program_point_size_set",
static_cast<PyCFunction>(pygpu_state_program_point_size_set),
METH_O,
pygpu_state_program_point_size_set_doc},
{"active_framebuffer_get",
reinterpret_cast<PyCFunction>(pygpu_state_active_framebuffer_get),
METH_NOARGS,
pygpu_state_active_framebuffer_get_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_state__tp_doc,
"This module provides access to the gpu state.");
static PyModuleDef pygpu_state_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.state",
/*m_doc*/ pygpu_state__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_state__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_state_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_state_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *bpygpu_state_init();
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class Texture;
}
extern PyTypeObject BPyGPUTexture_Type;
/**
* GPU_DEPTH24_STENCIL8 and GPU_DEPTH_COMPONENT24 are deprecated in Blender 5.0. These formats are
* automatically converted to their 32F variant.
*/
constexpr int GPU_DEPTH24_STENCIL8_DEPRECATED = -1;
constexpr int GPU_DEPTH_COMPONENT24_DEPRECATED = -2;
extern const struct PyC_StringEnumItems pygpu_textureformat_items[];
#define BPyGPUTexture_Check(v) (Py_TYPE(v) == &BPyGPUTexture_Type)
struct BPyGPUTexture {
PyObject_HEAD
gpu::Texture *tex;
};
[[nodiscard]] int bpygpu_ParseTexture(PyObject *o, void *p);
[[nodiscard]] PyObject *bpygpu_texture_init();
[[nodiscard]] PyObject *BPyGPUTexture_CreatePyObject(gpu::Texture *tex, bool shared_reference)
ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,109 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "gpu_py_matrix.hh"
#include "gpu_py_offscreen.hh"
#include "gpu_py_types.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Types Module
* \{ */
static PyModuleDef pygpu_types_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.types",
/*m_doc*/ nullptr,
/*m_size*/ 0,
/*m_methods*/ nullptr,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_types_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_types_module_def);
if (PyType_Ready(&BPyGPU_BufferType) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUVertFormat_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUVertBuf_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUIndexBuf_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUBatch_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUOffScreen_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUShader_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUTexture_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUFrameBuffer_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUUniformBuf_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUShaderCreateInfo_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUStageInterfaceInfo_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&PyGPUMatrixStackContext_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&PyGPUOffscreenStackContext_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPU_DeviceType) < 0) {
return nullptr;
}
PyModule_AddType(submodule, &BPyGPU_BufferType);
PyModule_AddType(submodule, &BPyGPUVertFormat_Type);
PyModule_AddType(submodule, &BPyGPUVertBuf_Type);
PyModule_AddType(submodule, &BPyGPUIndexBuf_Type);
PyModule_AddType(submodule, &BPyGPUBatch_Type);
PyModule_AddType(submodule, &BPyGPUOffScreen_Type);
PyModule_AddType(submodule, &BPyGPUShader_Type);
PyModule_AddType(submodule, &BPyGPUTexture_Type);
PyModule_AddType(submodule, &BPyGPUFrameBuffer_Type);
PyModule_AddType(submodule, &BPyGPUUniformBuf_Type);
PyModule_AddType(submodule, &BPyGPUShaderCreateInfo_Type);
PyModule_AddType(submodule, &BPyGPUStageInterfaceInfo_Type);
PyModule_AddType(submodule, &PyGPUMatrixStackContext_Type);
PyModule_AddType(submodule, &PyGPUOffscreenStackContext_Type);
PyModule_AddType(submodule, &BPyGPU_DeviceType);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include "gpu_py_buffer.hh" // IWYU pragma: export
#include "gpu_py_batch.hh" // IWYU pragma: export
#include "gpu_py_compute.hh" // IWYU pragma: export
#include "gpu_py_element.hh" // IWYU pragma: export
#include "gpu_py_framebuffer.hh" // IWYU pragma: export
#include "gpu_py_offscreen.hh" // IWYU pragma: export
#include "gpu_py_platform.hh" // IWYU pragma: export
#include "gpu_py_shader.hh" // IWYU pragma: export
#include "gpu_py_texture.hh" // IWYU pragma: export
#include "gpu_py_uniformbuffer.hh" // IWYU pragma: export
#include "gpu_py_vertex_buffer.hh" // IWYU pragma: export
#include "gpu_py_vertex_format.hh" // IWYU pragma: export
namespace blender {
[[nodiscard]] PyObject *bpygpu_types_init();
} // namespace blender

View File

@@ -0,0 +1,257 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the uniform buffer functionalities of the 'gpu' module
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "BLI_string_utf8.h"
#include "GPU_context.hh"
#include "GPU_uniform_buffer.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_uniformbuffer.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name gpu::UniformBuf Common Utilities
* \{ */
static int pygpu_uniformbuffer_valid_check(BPyGPUUniformBuf *bpygpu_ub)
{
if (UNLIKELY(bpygpu_ub->ubo == nullptr)) {
PyErr_SetString(PyExc_ReferenceError,
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
"GPU uniform buffer was freed, no further access is valid");
#else
"GPU uniform buffer: internal error");
#endif
return -1;
}
return 0;
}
#define BPYGPU_UNIFORMBUF_CHECK_OBJ(bpygpu) \
{ \
if (UNLIKELY(pygpu_uniformbuffer_valid_check(bpygpu) == -1)) { \
return nullptr; \
} \
} \
((void)0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name gpu::UniformBuf Type
* \{ */
static PyObject *pygpu_uniformbuffer__tp_new(PyTypeObject * /*self*/,
PyObject *args,
PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
gpu::UniformBuf *ubo = nullptr;
PyObject *pybuffer_obj;
char err_out[256] = "unknown error. See console";
static const char *_keywords[] = {"data", nullptr};
static _PyArg_Parser _parser = {
"O" /* `data` */
":GPUUniformBuf.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &pybuffer_obj)) {
return nullptr;
}
if (!GPU_context_active_get()) {
STRNCPY_UTF8(err_out, "No active GPU context found");
}
else {
Py_buffer pybuffer;
if (PyObject_GetBuffer(pybuffer_obj, &pybuffer, PyBUF_SIMPLE) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return nullptr;
}
if ((pybuffer.len % 16) != 0) {
STRNCPY_UTF8(err_out, "UBO is not padded to size of vec4");
}
else {
ubo = GPU_uniformbuf_create_ex(pybuffer.len, pybuffer.buf, "python_uniformbuffer");
}
PyBuffer_Release(&pybuffer);
}
if (ubo == nullptr) {
PyErr_Format(PyExc_RuntimeError, "GPUUniformBuf.__new__(...) failed with '%s'", err_out);
return nullptr;
}
return BPyGPUUniformBuf_CreatePyObject(ubo);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_uniformbuffer_update_doc,
".. method:: update(data)\n"
"\n"
" Update the data of the uniform buffer object.\n"
"\n"
" :param data: Data to fill the buffer.\n"
" :type data: Buffer\n");
static PyObject *pygpu_uniformbuffer_update(BPyGPUUniformBuf *self, PyObject *obj)
{
BPYGPU_UNIFORMBUF_CHECK_OBJ(self);
Py_buffer pybuffer;
if (PyObject_GetBuffer(obj, &pybuffer, PyBUF_SIMPLE) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return nullptr;
}
GPU_uniformbuf_update(self->ubo, pybuffer.buf);
PyBuffer_Release(&pybuffer);
Py_RETURN_NONE;
}
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
PyDoc_STRVAR(
/* Wrap. */
pygpu_uniformbuffer_free_doc,
".. method:: free()\n"
"\n"
" Free the uniform buffer object.\n"
" The uniform buffer object will no longer be accessible.\n");
static PyObject *pygpu_uniformbuffer_free(BPyGPUUniformBuf *self)
{
BPYGPU_UNIFORMBUF_CHECK_OBJ(self);
GPU_uniformbuf_free(self->ubo);
self->ubo = nullptr;
Py_RETURN_NONE;
}
#endif
static void BPyGPUUniformBuf__tp_dealloc(BPyGPUUniformBuf *self)
{
if (self->ubo) {
GPU_uniformbuf_free(self->ubo);
}
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
static PyGetSetDef pygpu_uniformbuffer__tp_getseters[] = {
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
static PyMethodDef pygpu_uniformbuffer__tp_methods[] = {
{"update",
reinterpret_cast<PyCFunction>(pygpu_uniformbuffer_update),
METH_O,
pygpu_uniformbuffer_update_doc},
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
{"free", (PyCFunction)pygpu_uniformbuffer_free, METH_NOARGS, pygpu_uniformbuffer_free_doc},
#endif
{nullptr, nullptr, 0, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_uniformbuffer__tp_doc,
".. class:: GPUUniformBuf\n"
"\n"
" This object gives access to uniform buffers.\n"
"\n"
" .. method:: __init__(data)\n"
"\n"
" :param data: Data to fill the buffer.\n"
" :type data: Buffer\n");
PyTypeObject BPyGPUUniformBuf_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUUniformBuf",
/*tp_basicsize*/ sizeof(BPyGPUUniformBuf),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPyGPUUniformBuf__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_uniformbuffer__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_uniformbuffer__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_uniformbuffer__tp_getseters,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_uniformbuffer__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUUniformBuf_CreatePyObject(gpu::UniformBuf *ubo)
{
BPyGPUUniformBuf *self;
self = PyObject_New(BPyGPUUniformBuf, &BPyGPUUniformBuf_Type);
self->ubo = ubo;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef BPYGPU_UNIFORMBUF_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class UniformBuf;
} // namespace gpu
extern PyTypeObject BPyGPUUniformBuf_Type;
#define BPyGPUUniformBuf_Check(v) (Py_TYPE(v) == &BPyGPUUniformBuf_Type)
struct BPyGPUUniformBuf {
PyObject_HEAD
gpu::UniformBuf *ubo;
};
[[nodiscard]] PyObject *BPyGPUUniformBuf_CreatePyObject(gpu::UniformBuf *ubo) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,434 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_vertex_buffer.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_vertex_buffer.hh" /* own include */
#include "gpu_py_vertex_format.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Utility Functions
* \{ */
#define PYGPU_AS_NATIVE_SWITCH(attr) \
switch (attr->type.comp_type()) { \
case GPU_COMP_I8: { \
PY_AS_NATIVE(int8_t, PyC_Long_AsI8); \
break; \
} \
case GPU_COMP_U8: { \
PY_AS_NATIVE(uint8_t, PyC_Long_AsU8); \
break; \
} \
case GPU_COMP_I16: { \
PY_AS_NATIVE(int16_t, PyC_Long_AsI16); \
break; \
} \
case GPU_COMP_U16: { \
PY_AS_NATIVE(uint16_t, PyC_Long_AsU16); \
break; \
} \
case GPU_COMP_I32: { \
PY_AS_NATIVE(int32_t, PyC_Long_AsI32); \
break; \
} \
case GPU_COMP_U32: { \
PY_AS_NATIVE(uint32_t, PyC_Long_AsU32); \
break; \
} \
case GPU_COMP_F32: { \
PY_AS_NATIVE(float, PyFloat_AsDouble); \
break; \
} \
default: \
BLI_assert_unreachable(); \
break; \
} \
((void)0)
/* No error checking, callers must run PyErr_Occurred */
static void pygpu_fill_format_elem(void *data_dst_void, PyObject *py_src, const GPUVertAttr *attr)
{
#define PY_AS_NATIVE(ty_dst, py_as_native) \
{ \
ty_dst *data_dst = static_cast<ty_dst *>(data_dst_void); \
*data_dst = py_as_native(py_src); \
} \
((void)0)
PYGPU_AS_NATIVE_SWITCH(attr);
#undef PY_AS_NATIVE
}
/* No error checking, callers must run PyErr_Occurred */
static void pygpu_fill_format_sequence(void *data_dst_void,
PyObject *py_seq_fast,
const GPUVertAttr *attr)
{
const uint len = attr->type.comp_len();
PyObject **value_fast_items = PySequence_Fast_ITEMS(py_seq_fast);
/**
* Args are constants, so range checks will be optimized out if they're no-op's.
*/
#define PY_AS_NATIVE(ty_dst, py_as_native) \
ty_dst *data_dst = static_cast<ty_dst *>(data_dst_void); \
for (uint i = 0; i < len; i++) { \
data_dst[i] = py_as_native(value_fast_items[i]); \
} \
((void)0)
PYGPU_AS_NATIVE_SWITCH(attr);
#undef PY_AS_NATIVE
}
#undef PYGPU_AS_NATIVE_SWITCH
#undef WARN_TYPE_LIMIT_PUSH
#undef WARN_TYPE_LIMIT_POP
static bool pygpu_vertbuf_fill_impl(gpu::VertBuf *vbo,
uint data_id,
PyObject *seq,
const char *error_prefix)
{
const char *exc_str_size_mismatch = "Expected a %s of size %d, got %u";
bool ok = true;
const GPUVertAttr *attr = &GPU_vertbuf_get_format(vbo)->attrs[data_id];
uint vert_len = GPU_vertbuf_get_vertex_len(vbo);
if (PyObject_CheckBuffer(seq)) {
Py_buffer pybuffer;
if (PyObject_GetBuffer(seq, &pybuffer, PyBUF_STRIDES | PyBUF_ND) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return false;
}
const uint comp_len = pybuffer.ndim == 1 ? 1 : uint(pybuffer.shape[1]);
if (pybuffer.shape[0] != vert_len) {
PyErr_Format(
PyExc_ValueError, exc_str_size_mismatch, "sequence", vert_len, pybuffer.shape[0]);
ok = false;
}
else if (comp_len != attr->type.comp_len()) {
PyErr_Format(
PyExc_ValueError, exc_str_size_mismatch, "component", attr->type.comp_len(), comp_len);
ok = false;
}
else {
GPU_vertbuf_attr_fill_stride(vbo, data_id, pybuffer.strides[0], pybuffer.buf);
}
PyBuffer_Release(&pybuffer);
}
else {
GPUVertBufRaw data_step;
GPU_vertbuf_attr_get_raw_data(vbo, data_id, &data_step);
PyObject *seq_fast = PySequence_Fast(seq, "Vertex buffer fill");
if (seq_fast == nullptr) {
return false;
}
const uint seq_len = PySequence_Fast_GET_SIZE(seq_fast);
if (seq_len != vert_len) {
PyErr_Format(PyExc_ValueError, exc_str_size_mismatch, "sequence", vert_len, seq_len);
}
PyObject **seq_items = PySequence_Fast_ITEMS(seq_fast);
if (attr->type.comp_len() == 1) {
for (uint i = 0; i < seq_len; i++) {
uchar *data = static_cast<uchar *>(GPU_vertbuf_raw_step(&data_step));
PyObject *item = seq_items[i];
pygpu_fill_format_elem(data, item, attr);
}
}
else {
for (uint i = 0; i < seq_len; i++) {
uchar *data = static_cast<uchar *>(GPU_vertbuf_raw_step(&data_step));
PyObject *seq_fast_item = PySequence_Fast(seq_items[i], error_prefix);
if (seq_fast_item == nullptr) {
ok = false;
goto finally;
}
if (PySequence_Fast_GET_SIZE(seq_fast_item) != attr->type.comp_len()) {
PyErr_Format(PyExc_ValueError,
exc_str_size_mismatch,
"sequence",
attr->type.comp_len(),
PySequence_Fast_GET_SIZE(seq_fast_item));
ok = false;
Py_DECREF(seq_fast_item);
goto finally;
}
/* May trigger error, check below */
pygpu_fill_format_sequence(data, seq_fast_item, attr);
Py_DECREF(seq_fast_item);
}
}
if (PyErr_Occurred()) {
ok = false;
}
finally:
Py_DECREF(seq_fast);
}
return ok;
}
static int pygpu_vertbuf_fill(gpu::VertBuf *buf,
int id,
PyObject *py_seq_data,
const char *error_prefix)
{
if (id < 0 || id >= GPU_vertbuf_get_format(buf)->attr_len) {
PyErr_Format(PyExc_ValueError, "Format id %d out of range", id);
return 0;
}
if (buf->data<char>().data() == nullptr) {
PyErr_SetString(PyExc_ValueError, "Can't fill, static buffer already in use");
return 0;
}
if (!pygpu_vertbuf_fill_impl(buf, uint(id), py_seq_data, error_prefix)) {
return 0;
}
return 1;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name VertBuf Type
* \{ */
static PyObject *pygpu_vertbuf__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
struct {
PyObject *py_fmt;
uint len;
} params;
static const char *_keywords[] = {"format", "len", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `format` */
"I" /* `len` */
":GPUVertBuf.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &BPyGPUVertFormat_Type, &params.py_fmt, &params.len))
{
return nullptr;
}
const GPUVertFormat &fmt = (reinterpret_cast<BPyGPUVertFormat *>(params.py_fmt))->fmt;
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(fmt);
GPU_vertbuf_data_alloc(*vbo, params.len);
return BPyGPUVertBuf_CreatePyObject(vbo);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertbuf_attr_fill_doc,
".. method:: attr_fill(id, data)\n"
"\n"
" Insert data into the buffer for a single attribute.\n"
"\n"
" :param id: Either the name or the id of the attribute.\n"
" :type id: int | str\n"
" :param data: Buffer or sequence of data that should be stored in the buffer\n"
" :type data: Buffer | "
"Sequence[float] | Sequence[int] | Sequence[Sequence[float]] | Sequence[Sequence[int]]\n");
static PyObject *pygpu_vertbuf_attr_fill(BPyGPUVertBuf *self, PyObject *args, PyObject *kwds)
{
PyObject *data;
PyObject *identifier;
static const char *_keywords[] = {"id", "data", nullptr};
static _PyArg_Parser _parser = {
"O" /* `id` */
"O" /* `data` */
":attr_fill",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &identifier, &data)) {
return nullptr;
}
int id;
if (PyLong_Check(identifier)) {
id = PyLong_AsLong(identifier);
}
else if (PyUnicode_Check(identifier)) {
const GPUVertFormat *format = GPU_vertbuf_get_format(self->buf);
const char *name = PyUnicode_AsUTF8(identifier);
id = GPU_vertformat_attr_id_get(format, name);
if (id == -1) {
PyErr_Format(PyExc_ValueError, "Unknown attribute '%s'", name);
return nullptr;
}
}
else {
PyErr_SetString(PyExc_TypeError, "expected int or str type as identifier");
return nullptr;
}
if (!pygpu_vertbuf_fill(self->buf, id, data, "GPUVertBuf.attr_fill")) {
return nullptr;
}
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_vertbuf__tp_methods[] = {
{"attr_fill",
reinterpret_cast<PyCFunction>(pygpu_vertbuf_attr_fill),
METH_VARARGS | METH_KEYWORDS,
pygpu_vertbuf_attr_fill_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static void pygpu_vertbuf__tp_dealloc(BPyGPUVertBuf *self)
{
GPU_vertbuf_discard(self->buf);
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertbuf__tp_doc,
".. class:: GPUVertBuf\n"
"\n"
" Contains a VBO.\n"
"\n"
" .. method:: __init__(format, len)\n"
"\n"
" :param format: Vertex format.\n"
" :type format: :class:`gpu.types.GPUVertFormat`\n"
" :param len: Amount of vertices that will fit into this buffer.\n"
" :type len: int\n");
PyTypeObject BPyGPUVertBuf_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUVertBuf",
/*tp_basicsize*/ sizeof(BPyGPUVertBuf),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_vertbuf__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_vertbuf__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_vertbuf__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_vertbuf__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUVertBuf_CreatePyObject(gpu::VertBuf *buf)
{
BPyGPUVertBuf *self;
self = PyObject_New(BPyGPUVertBuf, &BPyGPUVertBuf_Type);
self->buf = buf;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class VertBuf;
}
extern PyTypeObject BPyGPUVertBuf_Type;
#define BPyGPUVertBuf_Check(v) (Py_TYPE(v) == &BPyGPUVertBuf_Type)
struct BPyGPUVertBuf {
PyObject_HEAD
/* The buf is owned, we may support thin wrapped batches later. */
gpu::VertBuf *buf;
};
[[nodiscard]] PyObject *BPyGPUVertBuf_CreatePyObject(gpu::VertBuf *buf) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_vertex_format.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Enum Conversion
*
* Use with PyArg_ParseTuple's "O&" formatting.
* \{ */
static PyC_StringEnumItems pygpu_vertcomptype_items[] = {
{GPU_COMP_I8, "I8"},
{GPU_COMP_U8, "U8"},
{GPU_COMP_I16, "I16"},
{GPU_COMP_U16, "U16"},
{GPU_COMP_I32, "I32"},
{GPU_COMP_U32, "U32"},
{GPU_COMP_F32, "F32"},
{GPU_COMP_I10, "I10"},
{0, nullptr},
};
static PyC_StringEnumItems pygpu_vertfetchmode_items[] = {
{GPU_FETCH_FLOAT, "FLOAT"},
{GPU_FETCH_INT, "INT"},
{GPU_FETCH_INT_TO_FLOAT_UNIT, "INT_TO_FLOAT_UNIT"},
{0, nullptr},
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name VertFormat Type
* \{ */
static PyObject *pygpu_vertformat__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (PyTuple_GET_SIZE(args) || (kwds && PyDict_Size(kwds))) {
PyErr_SetString(PyExc_ValueError, "This function takes no arguments");
return nullptr;
}
return BPyGPUVertFormat_CreatePyObject(nullptr);
}
static uint attr_size(GPUVertCompType type, int len)
{
if (type == GPU_COMP_I10) {
return 4; /* Always packed as 10_10_10_2. */
}
BLI_assert(type <= GPU_COMP_F32); /* Other types have irregular sizes (not bytes). */
const uint sizes[] = {1, 1, 2, 2, 4, 4, 4};
return len * sizes[type];
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertformat_attr_add_doc,
".. method:: attr_add(id, comp_type, len, fetch_mode)\n"
"\n"
" Add a new attribute to the format.\n"
"\n"
" :param id: Name of the attribute. Often ``position``, ``normal``, ...\n"
" :type id: str\n"
" :param comp_type: The data type that will be used to store the value in memory.\n"
" :type comp_type: Literal['I8', 'U8', 'I16', 'U16', 'I32', 'U32', 'F32', 'I10']\n"
" :param len: How many individual values the attribute consists of\n"
" (e.g. 2 for uv coordinates).\n"
" :type len: int\n"
" :param fetch_mode: How values from memory will be converted when used in the shader.\n"
" This is mainly useful for memory optimizations when you want to store values with\n"
" reduced precision. E.g. you can store a float in only 1 byte but it will be\n"
" converted to a normal 4 byte float when used.\n"
" :type fetch_mode: Literal['FLOAT', 'INT', 'INT_TO_FLOAT_UNIT']\n");
static PyObject *pygpu_vertformat_attr_add(BPyGPUVertFormat *self, PyObject *args, PyObject *kwds)
{
const char *id;
uint len;
PyC_StringEnum comp_type = {pygpu_vertcomptype_items, GPU_COMP_I8};
PyC_StringEnum fetch_mode = {pygpu_vertfetchmode_items, GPU_FETCH_FLOAT};
if (self->fmt.attr_len == GPU_VERT_ATTR_MAX_LEN) {
PyErr_SetString(PyExc_ValueError, "Maximum attr reached " STRINGIFY(GPU_VERT_ATTR_MAX_LEN));
return nullptr;
}
static const char *_keywords[] = {"id", "comp_type", "len", "fetch_mode", nullptr};
static _PyArg_Parser _parser = {
"$" /* Keyword only arguments. */
"s" /* `id` */
"O&" /* `comp_type` */
"I" /* `len` */
"O&" /* `fetch_mode` */
":attr_add",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
&id,
PyC_ParseStringEnum,
&comp_type,
&len,
PyC_ParseStringEnum,
&fetch_mode))
{
return nullptr;
}
GPUVertCompType comp_type_enum = GPUVertCompType(comp_type.value_found);
GPUVertFetchMode fetch_mode_enum = GPUVertFetchMode(fetch_mode.value_found);
if (len > 4) {
PyErr_WarnEx(
PyExc_DeprecationWarning,
"Using GPUVertFormat.attr_add(...) with component count greater than 4 is deprecated. "
"Use several attributes for each matrix columns instead.",
1);
}
if (attr_size(comp_type_enum, len) % 4 != 0) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Using GPUVertFormat.attr_add(...) with a format that is not 4 bytes aligned is "
"deprecated. Add padding components and/or higher precision integers.",
1);
}
uint attr_id = GPU_vertformat_attr_add_legacy(
&self->fmt, id, comp_type_enum, len, fetch_mode_enum);
return PyLong_FromLong(attr_id);
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef pygpu_vertformat__tp_methods[] = {
{"attr_add",
reinterpret_cast<PyCFunction>(pygpu_vertformat_attr_add),
METH_VARARGS | METH_KEYWORDS,
pygpu_vertformat_attr_add_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static void pygpu_vertformat__tp_dealloc(BPyGPUVertFormat *self)
{
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertformat__tp_doc,
".. class:: GPUVertFormat()\n"
"\n"
" This object contains information about the structure of a vertex buffer.\n");
PyTypeObject BPyGPUVertFormat_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUVertFormat",
/*tp_basicsize*/ sizeof(BPyGPUVertFormat),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_vertformat__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_vertformat__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_vertformat__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ pygpu_vertformat__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUVertFormat_CreatePyObject(GPUVertFormat *fmt)
{
BPyGPUVertFormat *self;
self = PyObject_New(BPyGPUVertFormat, &BPyGPUVertFormat_Type);
if (fmt) {
self->fmt = *fmt;
}
else {
memset(&self->fmt, 0, sizeof(self->fmt));
}
return reinterpret_cast<PyObject *>(self);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "GPU_vertex_format.hh"
namespace blender {
extern PyTypeObject BPyGPUVertFormat_Type;
#define BPyGPUVertFormat_Check(v) (Py_TYPE(v) == &BPyGPUVertFormat_Type)
struct BPyGPUVertFormat {
PyObject_HEAD
GPUVertFormat fmt;
};
[[nodiscard]] PyObject *BPyGPUVertFormat_CreatePyObject(GPUVertFormat *fmt);
} // namespace blender