diff options
author | Germano Cavalcante <mano-wii> | 2022-04-13 00:28:27 +0300 |
---|---|---|
committer | Germano Cavalcante <germano.costa@ig.com.br> | 2022-04-13 00:50:56 +0300 |
commit | 9bc678969aaef5e2343d9362648e9a633d1b6e5e (patch) | |
tree | 6967bc7469d26d91925bddc618d4ad69432d12fc /source/blender/python/gpu | |
parent | 359b6baf325a701328732598ecc04b68a9a335d9 (diff) |
pyGPU: Port 'StageInterfaceInfo' and 'ShaderCreateInfo' types
In order to allow GLSL Cross Compilation across platforms, expose in
Python the `GPUShaderCreateInfo` strategy as detailed in
https://wiki.blender.org/wiki/EEVEE_%26_Viewport/GPU_Module/GLSL_Cross_Compilation
The new features can be listed as follows:
```
>>> gpu.types.GPUShaderCreateInfo.
define(
fragment_out(
fragment_source(
push_constant(
sampler(
typedef_source(
uniform_buf(
vertex_in(
vertex_out(
vertex_source(
>>> gpu.types.GPUStageInterfaceInfo.
flat(
name
no_perspective(
smooth(
>>> gpu.shader.create_from_info(
```
Reviewed By: fclem, campbellbarton
Differential Revision: https://developer.blender.org/D14497
Diffstat (limited to 'source/blender/python/gpu')
-rw-r--r-- | source/blender/python/gpu/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_shader.c | 36 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_shader.h | 47 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_shader_create_info.cc | 1095 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_types.c | 8 |
5 files changed, 1187 insertions, 0 deletions
diff --git a/source/blender/python/gpu/CMakeLists.txt b/source/blender/python/gpu/CMakeLists.txt index 644bbd18173..e726cb7883d 100644 --- a/source/blender/python/gpu/CMakeLists.txt +++ b/source/blender/python/gpu/CMakeLists.txt @@ -29,6 +29,7 @@ set(SRC gpu_py_offscreen.c gpu_py_platform.c gpu_py_select.c + gpu_py_shader_create_info.cc gpu_py_shader.c gpu_py_state.c gpu_py_texture.c diff --git a/source/blender/python/gpu/gpu_py_shader.c b/source/blender/python/gpu/gpu_py_shader.c index c74b3e173d1..cdcf22df7a1 100644 --- a/source/blender/python/gpu/gpu_py_shader.c +++ b/source/blender/python/gpu/gpu_py_shader.c @@ -831,6 +831,38 @@ static PyObject *pygpu_shader_code_from_builtin(BPyGPUShader *UNUSED(self), PyOb return r_dict; } +PyDoc_STRVAR(pygpu_shader_create_from_info_doc, + ".. function:: create_from_info(shader_info)\n" + "\n" + " Create shader from a GPUShaderCreateInfo.\n" + "\n" + " :param shader_info: GPUShaderCreateInfo\n" + " :type shader_info: :class:`bpy.types.GPUShaderCreateInfo`\n" + " :return: Shader object corresponding to the given name.\n" + " :rtype: :class:`bpy.types.GPUShader`\n"); +static PyObject *pygpu_shader_create_from_info(BPyGPUShader *UNUSED(self), + BPyGPUShaderCreateInfo *o) +{ + if (!BPyGPUShaderCreateInfo_Check(o)) { + PyErr_Format(PyExc_TypeError, "Expected a GPUShaderCreateInfo, got %s", Py_TYPE(o)->tp_name); + return NULL; + } + + char error[128]; + if (!GPU_shader_create_info_check_error(o->info, error)) { + PyErr_SetString(PyExc_Exception, error); + return NULL; + } + + GPUShader *shader = GPU_shader_create_from_info(o->info); + if (!shader) { + PyErr_SetString(PyExc_Exception, "Shader Compile Error, see console for more details"); + return NULL; + } + + return BPyGPUShader_CreatePyObject(shader, false); +} + static struct PyMethodDef pygpu_shader_module__tp_methods[] = { {"unbind", (PyCFunction)pygpu_shader_unbind, METH_NOARGS, pygpu_shader_unbind_doc}, {"from_builtin", @@ -841,6 +873,10 @@ static struct PyMethodDef pygpu_shader_module__tp_methods[] = { (PyCFunction)pygpu_shader_code_from_builtin, METH_O, pygpu_shader_code_from_builtin_doc}, + {"create_from_info", + (PyCFunction)pygpu_shader_create_from_info, + METH_O, + pygpu_shader_create_from_info_doc}, {NULL, NULL, 0, NULL}, }; diff --git a/source/blender/python/gpu/gpu_py_shader.h b/source/blender/python/gpu/gpu_py_shader.h index eb4810efeac..b5944c4b3a0 100644 --- a/source/blender/python/gpu/gpu_py_shader.h +++ b/source/blender/python/gpu/gpu_py_shader.h @@ -6,6 +6,12 @@ #pragma once +/* 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.c */ + extern PyTypeObject BPyGPUShader_Type; #define BPyGPUShader_Check(v) (Py_TYPE(v) == &BPyGPUShader_Type) @@ -18,3 +24,44 @@ typedef struct BPyGPUShader { PyObject *BPyGPUShader_CreatePyObject(struct GPUShader *shader, bool is_builtin); PyObject *bpygpu_shader_init(void); + +#ifdef __cplusplus +extern "C" { +#endif + +/* gpu_py_shader_create_info.cc */ + +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) + +typedef struct BPyGPUStageInterfaceInfo { + PyObject_VAR_HEAD + struct GPUStageInterfaceInfo *interface; +#ifdef USE_GPU_PY_REFERENCES + /* Just to keep a user to prevent freeing buf's we're using. */ + PyObject *references; +#endif +} BPyGPUStageInterfaceInfo; + +typedef struct BPyGPUShaderCreateInfo { + PyObject_VAR_HEAD + struct GPUShaderCreateInfo *info; +#ifdef USE_GPU_PY_REFERENCES + /* Just to keep a user to prevent freeing buf's we're using. */ + PyObject *vertex_source; + PyObject *fragment_source; + PyObject *typedef_source; + PyObject *references; +#endif + size_t constants_total_size; +} BPyGPUShaderCreateInfo; + +PyObject *BPyGPUStageInterfaceInfo_CreatePyObject(struct GPUStageInterfaceInfo *interface); +PyObject *BPyGPUShaderCreateInfo_CreatePyObject(struct GPUShaderCreateInfo *info); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/python/gpu/gpu_py_shader_create_info.cc b/source/blender/python/gpu/gpu_py_shader_create_info.cc new file mode 100644 index 00000000000..caf71ad5980 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_shader_create_info.cc @@ -0,0 +1,1095 @@ +/* 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 "BLI_utildefines.h" + +#include "GPU_shader.h" +#include "intern/gpu_shader_create_info.hh" + +#include "../generic/py_capi_utils.h" +#include "../generic/python_utildefines.h" + +#include "gpu_py_shader.h" /* own include */ + +//#define USE_PYGPU_SHADER_INFO_IMAGE_METHOD + +using blender::gpu::shader::DualBlend; +using blender::gpu::shader::Frequency; +using blender::gpu::shader::ImageType; +using blender::gpu::shader::ShaderCreateInfo; +using blender::gpu::shader::StageInterfaceInfo; +using blender::gpu::shader::Type; + +#ifdef USE_PYGPU_SHADER_INFO_IMAGE_METHOD +using blender::gpu::shader::Qualifier; + +# define PYDOC_QUALIFIERS \ + "- ``NO_RESTRICT``\n" \ + "- ``READ``\n" \ + "- ``WRITE``\n" +static const struct PyC_FlagSet pygpu_qualifiers[] = { + {(int)Qualifier::NO_RESTRICT, "NO_RESTRICT"}, + {(int)Qualifier::READ, "READ"}, + {(int)Qualifier::WRITE, "WRITE"}, + {0, nullptr}, +}; +#endif + +#define PYDOC_TYPE_LIST \ + "- ``FLOAT``\n" \ + "- ``VEC2``\n" \ + "- ``VEC3``\n" \ + "- ``VEC4``\n" \ + "- ``MAT3``\n" \ + "- ``MAT4``\n" \ + "- ``UINT``\n" \ + "- ``UVEC2``\n" \ + "- ``UVEC3``\n" \ + "- ``UVEC4``\n" \ + "- ``INT``\n" \ + "- ``IVEC2``\n" \ + "- ``IVEC3``\n" \ + "- ``IVEC4``\n" \ + "- ``BOOL``\n" +static const struct PyC_StringEnumItems pygpu_attrtype_items[] = { + {(int)Type::FLOAT, "FLOAT"}, + {(int)Type::VEC2, "VEC2"}, + {(int)Type::VEC3, "VEC3"}, + {(int)Type::VEC4, "VEC4"}, + {(int)Type::MAT3, "MAT3"}, + {(int)Type::MAT4, "MAT4"}, + {(int)Type::UINT, "UINT"}, + {(int)Type::UVEC2, "UVEC2"}, + {(int)Type::UVEC3, "UVEC3"}, + {(int)Type::UVEC4, "UVEC4"}, + {(int)Type::INT, "INT"}, + {(int)Type::IVEC2, "IVEC2"}, + {(int)Type::IVEC3, "IVEC3"}, + {(int)Type::IVEC4, "IVEC4"}, + {(int)Type::BOOL, "BOOL"}, + {0, nullptr}, +}; + +#define PYDOC_IMAGE_TYPES \ + "- ``FLOAT_BUFFER``\n" \ + "- ``FLOAT_1D``\n" \ + "- ``FLOAT_1D_ARRAY``\n" \ + "- ``FLOAT_2D``\n" \ + "- ``FLOAT_2D_ARRAY``\n" \ + "- ``FLOAT_3D``\n" \ + "- ``FLOAT_CUBE``\n" \ + "- ``FLOAT_CUBE_ARRAY``\n" \ + "- ``INT_BUFFER``\n" \ + "- ``INT_1D``\n" \ + "- ``INT_1D_ARRAY``\n" \ + "- ``INT_2D``\n" \ + "- ``INT_2D_ARRAY``\n" \ + "- ``INT_3D``\n" \ + "- ``INT_CUBE``\n" \ + "- ``INT_CUBE_ARRAY``\n" \ + "- ``UINT_BUFFER``\n" \ + "- ``UINT_1D``\n" \ + "- ``UINT_1D_ARRAY``\n" \ + "- ``UINT_2D``\n" \ + "- ``UINT_2D_ARRAY``\n" \ + "- ``UINT_3D``\n" \ + "- ``UINT_CUBE``\n" \ + "- ``UINT_CUBE_ARRAY``\n" \ + "- ``SHADOW_2D``\n" \ + "- ``SHADOW_2D_ARRAY``\n" \ + "- ``SHADOW_CUBE``\n" \ + "- ``SHADOW_CUBE_ARRAY``\n" \ + "- ``DEPTH_2D``\n" \ + "- ``DEPTH_2D_ARRAY``\n" \ + "- ``DEPTH_CUBE``\n" \ + "- ``DEPTH_CUBE_ARRAY``\n" +static const struct PyC_StringEnumItems pygpu_imagetype_items[] = { + {(int)ImageType::FLOAT_BUFFER, "FLOAT_BUFFER"}, + {(int)ImageType::FLOAT_1D, "FLOAT_1D"}, + {(int)ImageType::FLOAT_1D_ARRAY, "FLOAT_1D_ARRAY"}, + {(int)ImageType::FLOAT_2D, "FLOAT_2D"}, + {(int)ImageType::FLOAT_2D_ARRAY, "FLOAT"}, + {(int)ImageType::FLOAT_3D, "FLOAT_2D_ARRAY"}, + {(int)ImageType::FLOAT_CUBE, "FLOAT_CUBE"}, + {(int)ImageType::FLOAT_CUBE_ARRAY, "FLOAT_CUBE_ARRAY"}, + {(int)ImageType::INT_BUFFER, "INT_BUFFER"}, + {(int)ImageType::INT_1D, "INT_1D"}, + {(int)ImageType::INT_1D_ARRAY, "INT_1D_ARRAY"}, + {(int)ImageType::INT_2D, "INT_2D"}, + {(int)ImageType::INT_2D_ARRAY, "INT_2D_ARRAY"}, + {(int)ImageType::INT_3D, "INT_3D"}, + {(int)ImageType::INT_CUBE, "INT_CUBE"}, + {(int)ImageType::INT_CUBE_ARRAY, "INT_CUBE_ARRAY"}, + {(int)ImageType::UINT_BUFFER, "UINT_BUFFER"}, + {(int)ImageType::UINT_1D, "UINT_1D"}, + {(int)ImageType::UINT_1D_ARRAY, "UINT_1D_ARRAY"}, + {(int)ImageType::UINT_2D, "UINT_2D"}, + {(int)ImageType::UINT_2D_ARRAY, "UINT_2D_ARRAY"}, + {(int)ImageType::UINT_3D, "UINT_3D"}, + {(int)ImageType::UINT_CUBE, "UINT_CUBE"}, + {(int)ImageType::UINT_CUBE_ARRAY, "UINT_CUBE_ARRAY"}, + {(int)ImageType::SHADOW_2D, "SHADOW_2D"}, + {(int)ImageType::SHADOW_2D_ARRAY, "SHADOW_2D_ARRAY"}, + {(int)ImageType::SHADOW_CUBE, "SHADOW_CUBE"}, + {(int)ImageType::SHADOW_CUBE_ARRAY, "SHADOW_CUBE_ARRAY"}, + {(int)ImageType::DEPTH_2D, "DEPTH_2D"}, + {(int)ImageType::DEPTH_2D_ARRAY, "DEPTH_2D_ARRAY"}, + {(int)ImageType::DEPTH_CUBE, "DEPTH_CUBE"}, + {(int)ImageType::DEPTH_CUBE_ARRAY, "DEPTH_CUBE_ARRAY"}, + {0, nullptr}, +}; + +static const struct PyC_StringEnumItems pygpu_dualblend_items[] = { + {(int)DualBlend::NONE, "NONE"}, + {(int)DualBlend::SRC_0, "SRC_0"}, + {(int)DualBlend::SRC_1, "SRC_1"}, + {0, nullptr}, +}; + +/* -------------------------------------------------------------------- */ +/** \name GPUStageInterfaceInfo Methods + * \{ */ + +static bool pygpu_interface_info_get_args(BPyGPUStageInterfaceInfo *self, + PyObject *args, + const char *format, + Type *r_type, + const char **r_name) +{ + struct PyC_StringEnum pygpu_type = {pygpu_attrtype_items}; + PyObject *py_name; + + if (!PyArg_ParseTuple(args, format, PyC_ParseStringEnum, &pygpu_type, &py_name)) { + return false; + } + + const char *name = PyUnicode_AsUTF8(py_name); + if (name == nullptr) { + return false; + } + +#ifdef USE_GPU_PY_REFERENCES + PyList_Append(self->references, (PyObject *)py_name); +#endif + + *r_type = (Type)pygpu_type.value_found; + *r_name = name; + return true; +} + +PyDoc_STRVAR(pygpu_interface_info_smooth_doc, + ".. method:: smooth(type, name)\n" + "\n" + " Add an attribute with qualifier of type `smooth` to the interface block.\n" + "\n" + " :param type: One of these types:\n" + "\n" PYDOC_TYPE_LIST + " :type type: str\n" + " :param name: name of the attribute.\n" + " :type name: str\n"); +static PyObject *pygpu_interface_info_smooth(BPyGPUStageInterfaceInfo *self, PyObject *args) +{ + Type type; + const char *name; + if (!pygpu_interface_info_get_args(self, args, "O&O:smooth", &type, &name)) { + return nullptr; + } + + StageInterfaceInfo *interface = reinterpret_cast<StageInterfaceInfo *>(self->interface); + interface->smooth(type, name); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(pygpu_interface_info_flat_doc, + ".. method:: flat(type, name)\n" + "\n" + " Add an attribute with qualifier of type `flat` to the interface block.\n" + "\n" + " :param type: One of these types:\n" + "\n" PYDOC_TYPE_LIST + " :type type: str\n" + " :param name: name of the attribute.\n" + " :type name: str\n"); +static PyObject *pygpu_interface_info_flat(BPyGPUStageInterfaceInfo *self, PyObject *args) +{ + Type type; + const char *name; + if (!pygpu_interface_info_get_args(self, args, "O&O:flat", &type, &name)) { + return nullptr; + } + + StageInterfaceInfo *interface = reinterpret_cast<StageInterfaceInfo *>(self->interface); + interface->flat(type, name); + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + pygpu_interface_info_no_perspective_doc, + ".. method:: no_perspective(type, name)\n" + "\n" + " Add an attribute with qualifier of type `no_perspective` to the interface block.\n" + "\n" + " :param type: One of these types:\n" + "\n" PYDOC_TYPE_LIST + " :type type: str\n" + " :param name: name of the attribute.\n" + " :type name: str\n"); +static PyObject *pygpu_interface_info_no_perspective(BPyGPUStageInterfaceInfo *self, + PyObject *args) +{ + Type type; + const char *name; + if (!pygpu_interface_info_get_args(self, args, "O&O:no_perspective", &type, &name)) { + return nullptr; + } + + StageInterfaceInfo *interface = reinterpret_cast<StageInterfaceInfo *>(self->interface); + interface->no_perspective(type, name); + Py_RETURN_NONE; +} + +static struct PyMethodDef pygpu_interface_info__tp_methods[] = { + {"smooth", + (PyCFunction)pygpu_interface_info_smooth, + METH_VARARGS, + pygpu_interface_info_smooth_doc}, + {"flat", (PyCFunction)pygpu_interface_info_flat, METH_VARARGS, pygpu_interface_info_flat_doc}, + {"no_perspective", + (PyCFunction)pygpu_interface_info_no_perspective, + METH_VARARGS, + pygpu_interface_info_no_perspective_doc}, + {nullptr, nullptr, 0, nullptr}, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GPUStageInterfaceInfo Getters and Setters + * \{ */ + +PyDoc_STRVAR(pygpu_interface_info_name_doc, + "Name of the interface block.\n" + "\n" + ":type: str"); +static PyObject *pygpu_interface_info_name_get(BPyGPUStageInterfaceInfo *self, + void *UNUSED(closure)) +{ + StageInterfaceInfo *interface = reinterpret_cast<StageInterfaceInfo *>(self->interface); + return PyUnicode_FromString(interface->name.c_str()); +} + +static PyGetSetDef pygpu_interface_info__tp_getseters[] = { + {"name", + (getter)pygpu_interface_info_name_get, + (setter) nullptr, + pygpu_interface_info_name_doc, + nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */ +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GPUStageInterfaceInfo Type + * \{ */ + +static PyObject *pygpu_interface_info__tp_new(PyTypeObject *UNUSED(type), + PyObject *args, + PyObject *kwds) +{ + if (kwds) { + PyErr_SetString(PyExc_TypeError, "no keywords are expected"); + return nullptr; + } + + const char *name; + if (!PyArg_ParseTuple(args, "s:GPUStageInterfaceInfo.__new__*", &name)) { + return nullptr; + } + + StageInterfaceInfo *interface = new StageInterfaceInfo(name, ""); + GPUStageInterfaceInfo *interface_info = reinterpret_cast<GPUStageInterfaceInfo *>(interface); + + auto self = BPyGPUStageInterfaceInfo_CreatePyObject(interface_info); + +#ifdef USE_GPU_PY_REFERENCES + PyObject *py_name = PyTuple_GET_ITEM(args, 0); + PyList_Append(((BPyGPUStageInterfaceInfo *)self)->references, py_name); +#endif + + return self; +} + +#ifdef USE_GPU_PY_REFERENCES + +static int pygpu_interface_info__tp_traverse(PyObject *self, visitproc visit, void *arg) +{ + BPyGPUStageInterfaceInfo *py_interface = reinterpret_cast<BPyGPUStageInterfaceInfo *>(self); + Py_VISIT(py_interface->references); + return 0; +} + +static int pygpu_interface_info__tp_clear(PyObject *self) +{ + BPyGPUStageInterfaceInfo *py_interface = reinterpret_cast<BPyGPUStageInterfaceInfo *>(self); + Py_CLEAR(py_interface->references); + return 0; +} + +#endif + +static void pygpu_interface_info__tp_dealloc(PyObject *self) +{ + BPyGPUStageInterfaceInfo *py_interface = reinterpret_cast<BPyGPUStageInterfaceInfo *>(self); + StageInterfaceInfo *interface = reinterpret_cast<StageInterfaceInfo *>(py_interface->interface); + delete interface; + +#ifdef USE_GPU_PY_REFERENCES + PyObject_GC_UnTrack(self); + if (py_interface->references) { + pygpu_interface_info__tp_clear(self); + Py_CLEAR(py_interface->references); + } +#endif + + Py_TYPE(self)->tp_free((PyObject *)self); +} + +PyDoc_STRVAR(pygpu_interface_info__tp_doc, + ".. class:: GPUStageInterfaceInfo(name)\n" + "\n" + " List of varyings between shader stages.\n\n" + "\n" + " :param name: Name of the interface block.\n" + " :type value: str\n"); +constexpr PyTypeObject pygpu_interface_info_type() +{ + PyTypeObject pytype = {PyVarObject_HEAD_INIT(nullptr, 0)}; + pytype.tp_name = "GPUStageInterfaceInfo"; + pytype.tp_basicsize = sizeof(BPyGPUStageInterfaceInfo); + pytype.tp_dealloc = pygpu_interface_info__tp_dealloc; + pytype.tp_doc = pygpu_interface_info__tp_doc; +#ifdef USE_GPU_PY_REFERENCES + pytype.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC; + pytype.tp_traverse = pygpu_interface_info__tp_traverse; + pytype.tp_clear = pygpu_interface_info__tp_clear; +#else + pytype.tp_flags = Py_TPFLAGS_DEFAULT, +#endif + pytype.tp_methods = pygpu_interface_info__tp_methods; + pytype.tp_getset = pygpu_interface_info__tp_getseters; + pytype.tp_new = pygpu_interface_info__tp_new; + return pytype; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GPUShaderCreateInfo Methods + * \{ */ + +PyDoc_STRVAR(pygpu_shader_info_vertex_in_doc, + ".. method:: vertex_in(slot, type, name)\n" + "\n" + " Add a vertex shader input attribute.\n" + "\n" + " :param slot: The attribute index.\n" + " :type slot: int\n" + " :param type: One of these types:\n" + "\n" PYDOC_TYPE_LIST + " :type type: str\n" + " :param name: name of the attribute.\n" + " :type name: str\n"); +static PyObject *pygpu_shader_info_vertex_in(BPyGPUShaderCreateInfo *self, PyObject *args) +{ + int slot; + struct PyC_StringEnum pygpu_type = {pygpu_attrtype_items}; + const char *param; + + if (!PyArg_ParseTuple(args, "iO&s:vertex_in", &slot, PyC_ParseStringEnum, &pygpu_type, ¶m)) { + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyObject *py_name = PyTuple_GET_ITEM(args, 2); + PyList_Append(self->references, py_name); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->vertex_in(slot, (Type)pygpu_type.value_found, param); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(pygpu_shader_info_vertex_out_doc, + ".. method:: vertex_out(interface)\n" + "\n" + " Add a vertex shader output interface block.\n" + "\n" + " :param interface: Object describing the block.\n" + " :type interface: :class:`gpu.types.GPUStageInterfaceInfo`\n"); +static PyObject *pygpu_shader_info_vertex_out(BPyGPUShaderCreateInfo *self, + BPyGPUStageInterfaceInfo *o) +{ + if (!BPyGPUStageInterfaceInfo_Check(o)) { + PyErr_Format(PyExc_TypeError, "Expected a GPUStageInterfaceInfo, got %s", Py_TYPE(o)->tp_name); + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyList_Append(self->references, (PyObject *)o); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + StageInterfaceInfo *interface = reinterpret_cast<StageInterfaceInfo *>(o->interface); + info->vertex_out(*interface); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(pygpu_shader_info_fragment_out_doc, + ".. method:: fragment_out(slot, type, name, blend='NONE')\n" + "\n" + " Specify a fragment output corresponding to a framebuffer target slot.\n" + "\n" + " :param slot: The attribute index.\n" + " :type slot: int\n" + " :param type: One of these types:\n" + "\n" PYDOC_TYPE_LIST + " :type type: str\n" + " :param name: Name of the attribute.\n" + " :type name: str\n" + " :param blend: Dual Source Blending Index. It can be 'NONE', 'SRC_0' or 'SRC_1'.\n" + " :type blend: str\n"); +static PyObject *pygpu_shader_info_fragment_out(BPyGPUShaderCreateInfo *self, + PyObject *args, + PyObject *kwds) +{ + int slot; + struct PyC_StringEnum pygpu_type = {pygpu_attrtype_items}; + const char *name; + struct PyC_StringEnum blend_type = {pygpu_dualblend_items, (int)DualBlend::NONE}; + + static const char *_keywords[] = {"slot", "type", "name", "blend", nullptr}; + static _PyArg_Parser _parser = {"iO&s|$O&:fragment_out", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast(args, + kwds, + &_parser, + &slot, + PyC_ParseStringEnum, + &pygpu_type, + &name, + PyC_ParseStringEnum, + &blend_type)) { + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyObject *py_name = PyTuple_GET_ITEM(args, 2); + PyList_Append(self->references, py_name); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->fragment_out(slot, (Type)pygpu_type.value_found, name, (DualBlend)blend_type.value_found); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + pygpu_shader_info_uniform_buf_doc, + ".. method:: uniform_buf(slot, type_name, name)\n" + "\n" + " Specify a uniform variable whose type can be one of those declared in `typedef_source`.\n" + "\n" + " :param slot: The uniform variable index.\n" + " :type slot: int\n" + " :param type_name: Name of the data type. It can be a struct type defined in the source " + "passed through the :meth:`gpu.types.GPUShaderCreateInfo.typedef_source`.\n" + " :type type_name: str\n" + " :param name: The uniform variable name.\n" + " :type name: str\n"); +static PyObject *pygpu_shader_info_uniform_buf(BPyGPUShaderCreateInfo *self, PyObject *args) +{ + int slot; + const char *type_name; + const char *name; + + if (!PyArg_ParseTuple(args, "iss:uniform_buf", &slot, &type_name, &name)) { + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyList_Append(self->references, PyTuple_GET_ITEM(args, 1)); /* type_name */ + PyList_Append(self->references, PyTuple_GET_ITEM(args, 2)); /* name */ +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->uniform_buf(slot, type_name, name); + + Py_RETURN_NONE; +} + +#ifdef USE_PYGPU_SHADER_INFO_IMAGE_METHOD +PyDoc_STRVAR( + pygpu_shader_info_image_doc, + ".. method:: image(slot, format, type, name, qualifiers={'NO_RESTRICT'})\n" + "\n" + " Specify an image resource used for arbitrary load and store operations.\n" + "\n" + " :param slot: The image resource index.\n" + " :type slot: int\n" + " :param format: The GPUTexture format that is passed to the shader. Possible values are:\n" + "" PYDOC_TEX_FORMAT_ITEMS + " :type format: str\n" + " :param type: The data type describing how the image is to be read in the shader. " + "Possible values are:\n" + "" PYDOC_IMAGE_TYPES + " :type type: str\n" + " :param name: The image resource name.\n" + " :type name: str\n" + " :param qualifiers: Set containing values that describe how the image resource is to be " + "read or written. Possible values are:\n" + "" PYDOC_QUALIFIERS + "" + " :type qualifiers: set\n"); +static PyObject *pygpu_shader_info_image(BPyGPUShaderCreateInfo *self, + PyObject *args, + PyObject *kwds) +{ + int slot; + struct PyC_StringEnum pygpu_texformat = {pygpu_textureformat_items}; + struct PyC_StringEnum pygpu_imagetype = {pygpu_imagetype_items}; + const char *name; + PyObject *py_qualifiers = nullptr; + Qualifier qualifier = Qualifier::NO_RESTRICT; + + static const char *_keywords[] = {"slot", "format", "type", "name", "qualifiers", nullptr}; + static _PyArg_Parser _parser = {"iO&O&s|$O:image", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast(args, + kwds, + &_parser, + &slot, + PyC_ParseStringEnum, + &pygpu_texformat, + PyC_ParseStringEnum, + &pygpu_imagetype, + &name, + &py_qualifiers)) { + return nullptr; + } + + if (py_qualifiers && + PyC_FlagSet_ToBitfield( + pygpu_qualifiers, py_qualifiers, (int *)&qualifier, "shader_info.image") == -1) { + return nullptr; + } + +# ifdef USE_GPU_PY_REFERENCES + PyList_Append(self->references, PyTuple_GET_ITEM(args, 3)); /* name */ +# endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->image(slot, + (eGPUTextureFormat)pygpu_texformat.value_found, + qualifier, + (ImageType)pygpu_imagetype.value_found, + name); + + Py_RETURN_NONE; +} +#endif + +PyDoc_STRVAR( + pygpu_shader_info_sampler_doc, + ".. method:: sampler(slot, type, name)\n" + "\n" + " Specify an image texture sampler.\n" + "\n" + " :param slot: The image texture sampler index.\n" + " :type slot: int\n" + " :param type: The data type describing the format of each sampler unit. Possible values " + "are:\n" + "" PYDOC_IMAGE_TYPES + " :type type: str\n" + " :param name: The image texture sampler name.\n" + " :type name: str\n"); +static PyObject *pygpu_shader_info_sampler(BPyGPUShaderCreateInfo *self, PyObject *args) +{ + int slot; + struct PyC_StringEnum pygpu_samplertype = {pygpu_imagetype_items}; + const char *name; + + if (!PyArg_ParseTuple( + args, "iO&s:sampler", &slot, PyC_ParseStringEnum, &pygpu_samplertype, &name)) { + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyList_Append(self->references, PyTuple_GET_ITEM(args, 2)); /* name */ +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->sampler(slot, (ImageType)pygpu_samplertype.value_found, name); + + Py_RETURN_NONE; +} + +static int constant_type_size(Type type) +{ + switch (type) { + case Type::BOOL: + case Type::FLOAT: + case Type::INT: + case Type::UINT: + return 4; + break; + case Type::VEC2: + case Type::UVEC2: + case Type::IVEC2: + return 8; + break; + case Type::VEC3: + case Type::UVEC3: + case Type::IVEC3: + return 12; + break; + case Type::VEC4: + case Type::UVEC4: + case Type::IVEC4: + return 16; + break; + case Type::MAT3: + return 72 + 3 * 4; + case Type::MAT4: + return 128; + break; + } + BLI_assert(false); + return -1; +} + +static int constants_calc_size(ShaderCreateInfo *info) +{ + int size_prev = 0; + int size_last = 0; + for (const ShaderCreateInfo::PushConst &uniform : info->push_constants_) { + int pad = 0; + int size = constant_type_size(uniform.type); + if (size_last && size_last != size) { + /* Calc pad. */ + int pack = (size == 8) ? 8 : 16; + if (size_last < size) { + pad = pack - (size_last % pack); + } + else { + pad = size_prev % pack; + } + } + else if (size == 12) { + /* It is still unclear how Vulkan handles padding for `vec3` constants. For now let's follow + * the rules of the `std140` layout. */ + pad = 4; + } + size_prev += pad + size * std::max(1, uniform.array_size); + size_last = size; + } + return size_prev + (size_prev % 16); +} + +PyDoc_STRVAR(pygpu_shader_info_push_constant_doc, + ".. method:: push_constant(type, name, size=0)\n" + "\n" + " Specify a global access constant.\n" + "\n" + " :param type: One of these types:\n" + "\n" PYDOC_TYPE_LIST + " :type type: str\n" + " :param name: Name of the constant.\n" + " :type name: str\n" + " :param size: If not zero, indicates that the constant is an array with the " + "specified size.\n" + " :type size: uint\n"); +static PyObject *pygpu_shader_info_push_constant(BPyGPUShaderCreateInfo *self, + PyObject *args, + PyObject *kwds) +{ + struct PyC_StringEnum pygpu_type = {pygpu_attrtype_items}; + const char *name = nullptr; + int array_size = 0; + + static const char *_keywords[] = {"type", "name", "size", nullptr}; + static _PyArg_Parser _parser = {"O&s|I:push_constant", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kwds, &_parser, PyC_ParseStringEnum, &pygpu_type, &name, &array_size)) { + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyObject *py_name = PyTuple_GET_ITEM(args, 1); + PyList_Append(self->references, py_name); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->push_constant((Type)pygpu_type.value_found, name, array_size); + +#define VULKAN_LIMIT 128 + int size = constants_calc_size(info); + if (size > VULKAN_LIMIT) { + printf("Push constants have a minimum supported size of " + STRINGIFY(VULKAN_LIMIT) + " bytes, however the constants added so far already reach %d bytes. Consider using UBO.\n", size); + } +#undef VULKAN_LIMIT + + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + pygpu_shader_info_vertex_source_doc, + ".. method:: vertex_source(source)\n" + "\n" + " Vertex shader source code written in GLSL.\n" + "\n" + " Example:\n" + "\n" + ".. code-block:: python\n" + "\n" + " \"void main {gl_Position = vec4(pos, 1.0);}\"\n" + "\n" + ".. seealso:: `GLSL Cross Compilation " + "<https://wiki.blender.org/wiki/EEVEE_%26_Viewport/GPU_Module/GLSL_Cross_Compilation>`__" + "\n" + " :param source: The vertex shader source code.\n" + " :type source: str\n"); +static PyObject *pygpu_shader_info_vertex_source(BPyGPUShaderCreateInfo *self, PyObject *o) +{ + const char *vertex_source = PyUnicode_AsUTF8(o); + if (vertex_source == nullptr) { + PyErr_Format(PyExc_ValueError, "expected a string, got %s", Py_TYPE(o)->tp_name); + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + if (self->vertex_source) { + Py_DECREF(self->vertex_source); + } + + self->vertex_source = o; + Py_INCREF(o); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->vertex_source("common_colormanagement_lib.glsl"); + info->vertex_source_generated = vertex_source; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + pygpu_shader_info_fragment_source_doc, + ".. method:: fragment_source(source)\n" + "\n" + " Fragment shader source code written in GLSL.\n" + "\n" + " Example:\n" + "\n" + ".. code-block:: python\n" + "\n" + " \"void main {fragColor = vec4(0.0, 0.0, 0.0, 1.0);}\"\n" + "\n" + ".. seealso:: `GLSL Cross Compilation " + "<https://wiki.blender.org/wiki/EEVEE_%26_Viewport/GPU_Module/GLSL_Cross_Compilation>`__" + "\n" + " :param source: The fragment shader source code.\n" + " :type source: str\n"); +static PyObject *pygpu_shader_info_fragment_source(BPyGPUShaderCreateInfo *self, PyObject *o) +{ + const char *fragment_source = PyUnicode_AsUTF8(o); + if (fragment_source == nullptr) { + PyErr_Format(PyExc_ValueError, "expected a string, got %s", Py_TYPE(o)->tp_name); + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + if (self->fragment_source) { + Py_DECREF(self->fragment_source); + } + + self->fragment_source = o; + Py_INCREF(o); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + info->fragment_source("common_colormanagement_lib.glsl"); + info->fragment_source_generated = fragment_source; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(pygpu_shader_info_typedef_source_doc, + ".. method:: typedef_source(source)\n" + "\n" + " Source code included before resource declaration. " + "Useful for defining structs used by Uniform Buffers.\n" + "\n" + " Example:\n" + "\n" + ".. code-block:: python\n" + "\n" + " \"struct MyType {int foo; float bar;};\"\n" + "\n" + " :param source: The source code defining types.\n" + " :type source: str\n"); +static PyObject *pygpu_shader_info_typedef_source(BPyGPUShaderCreateInfo *self, PyObject *o) +{ + const char *typedef_source = PyUnicode_AsUTF8(o); + if (typedef_source == nullptr) { + PyErr_Format(PyExc_ValueError, "expected a string, got %s", Py_TYPE(o)->tp_name); + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + if (self->typedef_source) { + Py_DECREF(self->typedef_source); + } + + self->typedef_source = o; + Py_INCREF(o); +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); +#if 0 + if (info->typedef_sources_.is_empty()) { + info->typedef_source("GPU_shader_shared_utils.h"); + } +#endif + info->typedef_source_generated = typedef_source; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(pygpu_shader_info_define_doc, + ".. method:: define(name, value)\n" + "\n" + " Add a preprocessing define directive. In GLSL it would be something like:\n" + "\n" + ".. code-block:: glsl\n" + "\n" + " #define name value\n" + "\n" + " :param name: Token name.\n" + " :type name: str\n" + " :param value: Text that replaces token occurrences.\n" + " :type value: str\n"); +static PyObject *pygpu_shader_info_define(BPyGPUShaderCreateInfo *self, PyObject *args) +{ + const char *name; + const char *value = nullptr; + + if (!PyArg_ParseTuple(args, "s|s:define", &name, &value)) { + return nullptr; + } + +#ifdef USE_GPU_PY_REFERENCES + PyList_Append(self->references, PyTuple_GET_ITEM(args, 0)); /* name */ + if (value) { + PyList_Append(self->references, PyTuple_GET_ITEM(args, 1)); /* value */ + } +#endif + + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(self->info); + if (value) { + info->define(name, value); + } + else { + info->define(name); + } + + Py_RETURN_NONE; +} + +static struct PyMethodDef pygpu_shader_info__tp_methods[] = { + {"vertex_in", + (PyCFunction)pygpu_shader_info_vertex_in, + METH_VARARGS, + pygpu_shader_info_vertex_in_doc}, + {"vertex_out", + (PyCFunction)pygpu_shader_info_vertex_out, + METH_O, + pygpu_shader_info_vertex_out_doc}, + {"fragment_out", + (PyCFunction)(void *)pygpu_shader_info_fragment_out, + METH_VARARGS | METH_KEYWORDS, + pygpu_shader_info_fragment_out_doc}, + {"uniform_buf", + (PyCFunction)(void *)pygpu_shader_info_uniform_buf, + METH_VARARGS, + pygpu_shader_info_uniform_buf_doc}, +#ifdef USE_PYGPU_SHADER_INFO_IMAGE_METHOD + {"image", + (PyCFunction)(void *)pygpu_shader_info_image, + METH_VARARGS | METH_KEYWORDS, + pygpu_shader_info_image_doc}, +#endif + {"sampler", + (PyCFunction)pygpu_shader_info_sampler, + METH_VARARGS, + pygpu_shader_info_sampler_doc}, + {"push_constant", + (PyCFunction)(void *)pygpu_shader_info_push_constant, + METH_VARARGS | METH_KEYWORDS, + pygpu_shader_info_push_constant_doc}, + {"vertex_source", + (PyCFunction)pygpu_shader_info_vertex_source, + METH_O, + pygpu_shader_info_vertex_source_doc}, + {"fragment_source", + (PyCFunction)pygpu_shader_info_fragment_source, + METH_O, + pygpu_shader_info_fragment_source_doc}, + {"typedef_source", + (PyCFunction)pygpu_shader_info_typedef_source, + METH_O, + pygpu_shader_info_typedef_source_doc}, + {"define", (PyCFunction)pygpu_shader_info_define, METH_VARARGS, pygpu_shader_info_define_doc}, + {nullptr, nullptr, 0, nullptr}, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GPUShaderCreateInfo Init + * \{ */ + +static PyObject *pygpu_shader_info__tp_new(PyTypeObject *UNUSED(type), + PyObject *args, + PyObject *kwds) +{ + if (PyTuple_Size(args) || kwds) { + PyErr_SetString(PyExc_TypeError, "no args or keywords are expected"); + return nullptr; + } + + ShaderCreateInfo *info = new ShaderCreateInfo("pyGPU_Shader"); + GPUShaderCreateInfo *shader_info = reinterpret_cast<GPUShaderCreateInfo *>(info); + + return BPyGPUShaderCreateInfo_CreatePyObject(shader_info); +} + +#ifdef USE_GPU_PY_REFERENCES + +static int pygpu_shader_info__tp_traverse(PyObject *self, visitproc visit, void *arg) +{ + BPyGPUShaderCreateInfo *py_info = reinterpret_cast<BPyGPUShaderCreateInfo *>(self); + Py_VISIT(py_info->vertex_source); + Py_VISIT(py_info->fragment_source); + Py_VISIT(py_info->references); + return 0; +} + +static int pygpu_shader_info__tp_clear(PyObject *self) +{ + BPyGPUShaderCreateInfo *py_info = reinterpret_cast<BPyGPUShaderCreateInfo *>(self); + Py_CLEAR(py_info->vertex_source); + Py_CLEAR(py_info->fragment_source); + Py_CLEAR(py_info->references); + return 0; +} + +#endif + +static void pygpu_shader_info__tp_dealloc(PyObject *self) +{ + BPyGPUShaderCreateInfo *py_info = reinterpret_cast<BPyGPUShaderCreateInfo *>(self); + ShaderCreateInfo *info = reinterpret_cast<ShaderCreateInfo *>(py_info->info); + delete info; + +#ifdef USE_GPU_PY_REFERENCES + PyObject_GC_UnTrack(self); + if (py_info->references || py_info->vertex_source || py_info->fragment_source) { + pygpu_shader_info__tp_clear(self); + Py_XDECREF(py_info->vertex_source); + Py_XDECREF(py_info->fragment_source); + Py_XDECREF(py_info->references); + } +#endif + + Py_TYPE(self)->tp_free((PyObject *)self); +} + +PyDoc_STRVAR(pygpu_shader_info__tp_doc, + ".. class:: GPUShaderCreateInfo()\n" + "\n" + " Stores and describes types and variables that are used in shader sources.\n"); +constexpr PyTypeObject pygpu_shader_info_type() +{ + PyTypeObject pytype = {PyVarObject_HEAD_INIT(nullptr, 0)}; + pytype.tp_name = "GPUShaderCreateInfo"; + pytype.tp_basicsize = sizeof(BPyGPUShaderCreateInfo); + pytype.tp_dealloc = pygpu_shader_info__tp_dealloc; + pytype.tp_doc = pygpu_shader_info__tp_doc; +#ifdef USE_GPU_PY_REFERENCES + pytype.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC; + pytype.tp_traverse = pygpu_shader_info__tp_traverse; + pytype.tp_clear = pygpu_shader_info__tp_clear; +#else + pytype.tp_flags = Py_TPFLAGS_DEFAULT, +#endif + pytype.tp_methods = pygpu_shader_info__tp_methods; + pytype.tp_new = pygpu_shader_info__tp_new; + return pytype; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public API + * \{ */ + +PyTypeObject BPyGPUStageInterfaceInfo_Type = pygpu_interface_info_type(); +PyTypeObject BPyGPUShaderCreateInfo_Type = pygpu_shader_info_type(); + +PyObject *BPyGPUStageInterfaceInfo_CreatePyObject(GPUStageInterfaceInfo *interface) +{ + BPyGPUStageInterfaceInfo *self; + +#ifdef USE_GPU_PY_REFERENCES + self = (BPyGPUStageInterfaceInfo *)_PyObject_GC_New(&BPyGPUStageInterfaceInfo_Type); + self->references = PyList_New(0); +#else + self = PyObject_New(BPyGPUStageInterfaceInfo, &BPyGPUStageInterfaceInfo_Type); +#endif + + self->interface = interface; + + return (PyObject *)self; +} + +PyObject *BPyGPUShaderCreateInfo_CreatePyObject(GPUShaderCreateInfo *info) +{ + BPyGPUShaderCreateInfo *self; + +#ifdef USE_GPU_PY_REFERENCES + self = (BPyGPUShaderCreateInfo *)_PyObject_GC_New(&BPyGPUShaderCreateInfo_Type); + self->vertex_source = nullptr; + self->fragment_source = nullptr; + self->typedef_source = nullptr; + self->references = PyList_New(0); +#else + self = PyObject_New(BPyGPUShaderCreateInfo, &BPyGPUShaderCreateInfo_Type); +#endif + + self->info = info; + self->constants_total_size = 0; + + return (PyObject *)self; +} + +/** \} */ diff --git a/source/blender/python/gpu/gpu_py_types.c b/source/blender/python/gpu/gpu_py_types.c index 6dff70ad530..65201df4a9e 100644 --- a/source/blender/python/gpu/gpu_py_types.c +++ b/source/blender/python/gpu/gpu_py_types.c @@ -59,6 +59,12 @@ PyObject *bpygpu_types_init(void) if (PyType_Ready(&BPyGPUUniformBuf_Type) < 0) { return NULL; } + if (PyType_Ready(&BPyGPUShaderCreateInfo_Type) < 0) { + return NULL; + } + if (PyType_Ready(&BPyGPUStageInterfaceInfo_Type) < 0) { + return NULL; + } PyModule_AddType(submodule, &BPyGPU_BufferType); PyModule_AddType(submodule, &BPyGPUVertFormat_Type); @@ -70,6 +76,8 @@ PyObject *bpygpu_types_init(void) 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); return submodule; } |