Mathutils types were always GC tracked even when it wasn't intended.
Not having to track objects speeds up Python execution.
In an isolated benchmark created to stress test the GC
creating 4-million vectors (re-assigning them 100 times), this gives
an overall ~2.5x speedup, see: P3221.
Details:
Since [0] (which added support for sub-classed mathutils types)
tp_alloc was called which defaults to PyType_GenericAlloc which always
GC tracked the resulting object when Py_TPFLAGS_HAVE_GC was set.
Avoid using PyType_GenericAlloc unless the type is sub-classed,
in that case the object is un-tracked.
Add asserts that the tracked state is as expected before tracking &
un-tracking, to ensure changes to object creation don't cause objects
to be tracked unintentionally.
Also assign the PyTypeObject.tp_is_gc callback so types optionally GC
track objects only do so when an object is referenced.
[0]: fbd9364944
715 lines
20 KiB
C
715 lines
20 KiB
C
/* 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 "BLI_utildefines.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "GPU_texture.h"
|
|
|
|
#include "../generic/py_capi_utils.h"
|
|
|
|
#include "gpu_py.h"
|
|
|
|
#include "gpu_py_buffer.h"
|
|
|
|
#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_Format(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_AsLong(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_AsLong(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:
|
|
case GPU_DATA_10_11_11_REV:
|
|
return "I";
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \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 = (BPyGPUBuffer *)_PyObject_GC_New(&BPyGPU_BufferType);
|
|
|
|
buffer->parent = NULL;
|
|
buffer->format = format;
|
|
buffer->shape_len = shape_len;
|
|
buffer->shape = MEM_mallocN(shape_len * sizeof(*buffer->shape), "BPyGPUBuffer shape");
|
|
memcpy(buffer->shape, shape, shape_len * sizeof(*buffer->shape));
|
|
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, int i)
|
|
{
|
|
if (i >= self->shape[0] || i < 0) {
|
|
PyErr_SetString(PyExc_IndexError, "array index out of range");
|
|
return NULL;
|
|
}
|
|
|
|
const char *formatstr = pygpu_buffer_formatstr(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:
|
|
case GPU_DATA_10_11_11_REV:
|
|
return Py_BuildValue(formatstr, self->buf.as_uint[i]);
|
|
}
|
|
}
|
|
else {
|
|
int offset = i * GPU_texture_dataformat_size(self->format);
|
|
for (int j = 1; j < self->shape_len; j++) {
|
|
offset *= self->shape[j];
|
|
}
|
|
|
|
return (PyObject *)pygpu_buffer_make_from_data((PyObject *)self,
|
|
self->format,
|
|
self->shape_len - 1,
|
|
self->shape + 1,
|
|
self->buf.as_byte + offset);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static PyObject *pygpu_buffer_to_list(BPyGPUBuffer *self)
|
|
{
|
|
int i, len = self->shape[0];
|
|
PyObject *list = PyList_New(len);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
PyList_SET_ITEM(list, i, pygpu_buffer__sq_item(self, i));
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
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 = (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;
|
|
}
|
|
|
|
static PyObject *pygpu_buffer_dimensions_get(BPyGPUBuffer *self, void *UNUSED(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 *UNUSED(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;
|
|
}
|
|
|
|
size_t size = shape_len * sizeof(*self->shape);
|
|
if (shape_len != self->shape_len) {
|
|
MEM_freeN(self->shape);
|
|
self->shape = MEM_mallocN(size, __func__);
|
|
}
|
|
|
|
self->shape_len = shape_len;
|
|
memcpy(self->shape, shape, size);
|
|
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 = NULL;
|
|
}
|
|
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_freeN(self->buf.as_void);
|
|
}
|
|
|
|
MEM_freeN(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, int 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;
|
|
|
|
if (begin < 0) {
|
|
begin = 0;
|
|
}
|
|
if (end > self->shape[0]) {
|
|
end = self->shape[0];
|
|
}
|
|
if (begin > end) {
|
|
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 *UNUSED(type), PyObject *args, PyObject *kwds)
|
|
{
|
|
PyObject *length_ob, *init = NULL;
|
|
BPyGPUBuffer *buffer = NULL;
|
|
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 NULL;
|
|
}
|
|
|
|
const struct 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 NULL;
|
|
}
|
|
|
|
if (!pygpu_buffer_pyobj_as_shape(length_ob, shape, &shape_len)) {
|
|
return NULL;
|
|
}
|
|
|
|
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 NULL;
|
|
}
|
|
|
|
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, pygpu_dataformat.value_found, shape_len, shape, pybuffer.buf);
|
|
}
|
|
|
|
PyBuffer_Release(&pybuffer);
|
|
}
|
|
else {
|
|
buffer = BPyGPU_Buffer_CreatePyObject(pygpu_dataformat.value_found, shape, shape_len, NULL);
|
|
if (init && pygpu_buffer_ass_slice(buffer, 0, shape[0], init)) {
|
|
Py_DECREF(buffer);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return (PyObject *)buffer;
|
|
}
|
|
|
|
static int pygpu_buffer__tp_is_gc(BPyGPUBuffer *self)
|
|
{
|
|
return self->parent != NULL;
|
|
}
|
|
|
|
/* BPyGPUBuffer sequence methods */
|
|
|
|
static int 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;
|
|
|
|
if (begin < 0) {
|
|
begin = 0;
|
|
}
|
|
if (end > self->shape[0]) {
|
|
end = self->shape[0];
|
|
}
|
|
if (begin > end) {
|
|
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, int 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 = (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:
|
|
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 NULL;
|
|
}
|
|
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 NULL;
|
|
}
|
|
|
|
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 NULL;
|
|
}
|
|
|
|
PyErr_Format(
|
|
PyExc_TypeError, "buffer indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static PyMethodDef pygpu_buffer__tp_methods[] = {
|
|
{"to_list",
|
|
(PyCFunction)pygpu_buffer_to_list_recursive,
|
|
METH_NOARGS,
|
|
"return the buffer as a list"},
|
|
{NULL, NULL, 0, NULL},
|
|
};
|
|
|
|
static PyGetSetDef pygpu_buffer_getseters[] = {
|
|
{"dimensions",
|
|
(getter)pygpu_buffer_dimensions_get,
|
|
(setter)pygpu_buffer_dimensions_set,
|
|
NULL,
|
|
NULL},
|
|
{NULL, NULL, NULL, NULL, NULL},
|
|
};
|
|
|
|
static PySequenceMethods pygpu_buffer__tp_as_sequence = {
|
|
(lenfunc)pygpu_buffer__sq_length, /* sq_length */
|
|
(binaryfunc)NULL, /* sq_concat */
|
|
(ssizeargfunc)NULL, /* sq_repeat */
|
|
(ssizeargfunc)pygpu_buffer__sq_item, /* sq_item */
|
|
(ssizessizeargfunc)NULL, /* sq_slice, deprecated, handled in pygpu_buffer__sq_item */
|
|
(ssizeobjargproc)pygpu_buffer__sq_ass_item, /* sq_ass_item */
|
|
(ssizessizeobjargproc)NULL, /* sq_ass_slice, deprecated handled in pygpu_buffer__sq_ass_item */
|
|
(objobjproc)NULL, /* sq_contains */
|
|
(binaryfunc)NULL, /* sq_inplace_concat */
|
|
(ssizeargfunc)NULL, /* sq_inplace_repeat */
|
|
};
|
|
|
|
static PyMappingMethods pygpu_buffer__tp_as_mapping = {
|
|
(lenfunc)pygpu_buffer__sq_length,
|
|
(binaryfunc)pygpu_buffer__mp_subscript,
|
|
(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)
|
|
{
|
|
r_strides[0] = GPU_texture_dataformat_size(format);
|
|
for (int i = 1; i < shape_len; i++) {
|
|
r_strides[i] = r_strides[i - 1] * shape[i - 1];
|
|
}
|
|
}
|
|
|
|
/* Here is the buffer interface function */
|
|
static int pygpu_buffer__bf_getbuffer(BPyGPUBuffer *self, Py_buffer *view, int flags)
|
|
{
|
|
if (view == NULL) {
|
|
PyErr_SetString(PyExc_ValueError, "NULL view in getbuffer");
|
|
return -1;
|
|
}
|
|
|
|
memset(view, 0, sizeof(*view));
|
|
|
|
view->obj = (PyObject *)self;
|
|
view->buf = (void *)self->buf.as_void;
|
|
view->len = bpygpu_Buffer_size(self);
|
|
view->readonly = 0;
|
|
view->itemsize = GPU_texture_dataformat_size(self->format);
|
|
if (flags & PyBUF_FORMAT) {
|
|
view->format = (char *)pygpu_buffer_formatstr(self->format);
|
|
}
|
|
if (flags & PyBUF_ND) {
|
|
view->ndim = self->shape_len;
|
|
view->shape = self->shape;
|
|
}
|
|
if (flags & PyBUF_STRIDES) {
|
|
view->strides = MEM_mallocN(view->ndim * sizeof(*view->strides), "BPyGPUBuffer strides");
|
|
pygpu_buffer_strides_calc(self->format, view->ndim, view->shape, view->strides);
|
|
}
|
|
view->suboffsets = NULL;
|
|
view->internal = NULL;
|
|
|
|
Py_INCREF(self);
|
|
return 0;
|
|
}
|
|
|
|
static void pygpu_buffer__bf_releasebuffer(PyObject *UNUSED(exporter), Py_buffer *view)
|
|
{
|
|
MEM_SAFE_FREE(view->strides);
|
|
}
|
|
|
|
static PyBufferProcs pygpu_buffer__tp_as_buffer = {
|
|
(getbufferproc)pygpu_buffer__bf_getbuffer,
|
|
(releasebufferproc)pygpu_buffer__bf_releasebuffer,
|
|
};
|
|
#endif
|
|
|
|
PyDoc_STRVAR(
|
|
pygpu_buffer__tp_doc,
|
|
".. class:: Buffer(format, dimensions, data)\n"
|
|
"\n"
|
|
" For Python access to GPU functions requiring a pointer.\n"
|
|
"\n"
|
|
" :arg format: Format type to interpret the buffer.\n"
|
|
" Possible values are `FLOAT`, `INT`, `UINT`, `UBYTE`, `UINT_24_8` and `10_11_11_REV`.\n"
|
|
" :type format: str\n"
|
|
" :arg dimensions: Array describing the dimensions.\n"
|
|
" :type dimensions: int\n"
|
|
" :arg data: Optional data array.\n"
|
|
" :type data: sequence\n");
|
|
PyTypeObject BPyGPU_BufferType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Buffer",
|
|
.tp_basicsize = sizeof(BPyGPUBuffer),
|
|
.tp_dealloc = (destructor)pygpu_buffer__tp_dealloc,
|
|
.tp_repr = (reprfunc)pygpu_buffer__tp_repr,
|
|
.tp_as_sequence = &pygpu_buffer__tp_as_sequence,
|
|
.tp_as_mapping = &pygpu_buffer__tp_as_mapping,
|
|
#ifdef PYGPU_BUFFER_PROTOCOL
|
|
.tp_as_buffer = &pygpu_buffer__tp_as_buffer,
|
|
#endif
|
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
|
|
.tp_doc = pygpu_buffer__tp_doc,
|
|
.tp_traverse = (traverseproc)pygpu_buffer__tp_traverse,
|
|
.tp_clear = (inquiry)pygpu_buffer__tp_clear,
|
|
.tp_methods = pygpu_buffer__tp_methods,
|
|
.tp_getset = pygpu_buffer_getseters,
|
|
.tp_new = pygpu_buffer__tp_new,
|
|
.tp_is_gc = (inquiry)pygpu_buffer__tp_is_gc,
|
|
};
|
|
|
|
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(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 == NULL) {
|
|
size_t size = pygpu_buffer_calc_size(format, shape_len, shape);
|
|
buffer = MEM_callocN(size, "BPyGPUBuffer buffer");
|
|
}
|
|
|
|
return pygpu_buffer_make_from_data(NULL, format, shape_len, shape, buffer);
|
|
}
|
|
|
|
/** \} */
|