Skip to content

Commit 07ce9ab

Browse files
committed
Improving C interface with capsule approach (like NumPy)
1 parent 5415d9e commit 07ce9ab

File tree

7 files changed

+304
-35
lines changed

7 files changed

+304
-35
lines changed

README.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,20 @@
22

33
[![build-docs action](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/emprice/pocky/gh-pages/endpoint.json)](https://github.com/emprice/pocky/actions/workflows/docs.yml)
44
[![License: MIT](https://img.shields.io/github/license/emprice/pocky?style=for-the-badge)](https://opensource.org/licenses/MIT)
5-
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/emprice/pocky/main?logo=codefactor&style=for-the-badge)
6-
![GitHub Repo stars](https://img.shields.io/github/stars/emprice/pocky?style=for-the-badge)
5+
![CodeFactor grade](https://img.shields.io/codefactor/grade/github/emprice/pocky/main?logo=codefactor&style=for-the-badge)
6+
![GitHub repo stars](https://img.shields.io/github/stars/emprice/pocky?style=for-the-badge)
7+
8+
## Example usage
9+
10+
```python
11+
import pocky
12+
13+
# Display all OpenCL platforms and devices available
14+
for plat in pocky.list_all_platforms():
15+
print(plat)
16+
print(pocky.list_all_devices(plat))
17+
print()
18+
19+
# Create a context for the default platform
20+
ctx = pocky.Context.default()
21+
```

src/pocky/ext/include/pocky_bufpair.h

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ typedef struct
88
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
99
pocky_context_object *context;
1010
PyObject *host;
11+
PyObject *dirty;
1112
cl_mem device;
1213
size_t host_size;
1314
size_t device_size;
@@ -22,12 +23,19 @@ extern int pocky_bufpair_init(pocky_bufpair_object *self,
2223
PyObject *args, PyObject *kwargs);
2324
extern void pocky_bufpair_dealloc(pocky_bufpair_object *self);
2425

26+
extern PyObject *pocky_bufpair_copy_to_device(pocky_bufpair_object *self, PyObject *args);
27+
extern PyObject *pocky_bufpair_copy_from_device(pocky_bufpair_object *self, PyObject *args);
28+
2529
extern PyObject *pocky_bufpair_array(pocky_bufpair_object *self, PyObject *noargs);
2630

2731
extern PyObject *pocky_bufpair_get_host(pocky_bufpair_object *self, void *closure);
2832
extern int pocky_bufpair_set_host(pocky_bufpair_object *self,
2933
PyObject *value, void *closure);
3034

35+
extern PyObject *pocky_bufpair_get_dirty(pocky_bufpair_object *self, void *closure);
36+
extern int pocky_bufpair_set_dirty(pocky_bufpair_object *self,
37+
PyObject *value, void *closure);
38+
3139
extern PyGetSetDef pocky_bufpair_getsetters[];
3240
extern PyMethodDef pocky_bufpair_methods[];
3341

src/pocky/ext/include/pocky_context.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ typedef struct
77
PyObject_HEAD
88
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
99
cl_context ctx; /**< OpenCL context handle */
10-
cl_program program; /**< OpenCL program handle */
1110
cl_uint num_queues; /**< Number of command queues */
1211
cl_command_queue *queues; /**< Array of command queues */
1312
}
@@ -17,6 +16,7 @@ pocky_context_object;
1716
extern PyTypeObject pocky_context_type;
1817

1918
extern PyMethodDef pocky_context_methods[];
19+
extern PyGetSetDef pocky_context_getsetters[];
2020

2121
/**
2222
* @brief Allocates and initializes an empty Python @c pocky.ext.Context object

src/pocky/ext/pocky.c

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ PyMODINIT_FUNC PyInit_ext(void)
6464
.tp_new = pocky_context_new,
6565
.tp_dealloc = (destructor) pocky_context_dealloc,
6666
.tp_methods = pocky_context_methods,
67+
.tp_getset = pocky_context_getsetters,
6768
};
6869

6970
/* Definition of the BufferPair type */

src/pocky/ext/pocky_bufpair.c

+159-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ PyObject *pocky_bufpair_new(PyTypeObject *type, PyObject *args, PyObject *kwargs
1313
if ((self = (pocky_bufpair_object *) type->tp_alloc(type, 0)))
1414
{
1515
self->host = NULL;
16+
self->dirty = NULL;
1617
self->context = NULL;
1718
self->device = NULL;
1819
self->host_size = 0;
@@ -52,13 +53,22 @@ int pocky_bufpair_init(pocky_bufpair_object *self,
5253
self->host = host;
5354
Py_XDECREF(tmp);
5455

56+
self->dirty = Py_NewRef(Py_True);
5557
self->host_size = PyArray_SIZE((PyArrayObject *) self->host);
5658
self->device_size = self->host_size;
5759

5860
if (self->device != NULL) clReleaseMemObject(self->device);
59-
self->device = clCreateBuffer(context->ctx, CL_MEM_READ_WRITE,
60-
self->device_size * sizeof(cl_float), NULL, &err);
61-
if (err != CL_SUCCESS) return -1;
61+
62+
if (self->device_size > 0)
63+
{
64+
self->device = clCreateBuffer(context->ctx, CL_MEM_READ_WRITE,
65+
self->device_size * sizeof(cl_float), NULL, &err);
66+
if (err != CL_SUCCESS) return -1;
67+
}
68+
else
69+
{
70+
self->device = NULL;
71+
}
6272

6373
return 0;
6474
}
@@ -67,6 +77,7 @@ void pocky_bufpair_dealloc(pocky_bufpair_object *self)
6777
{
6878
Py_XDECREF(self->context);
6979
Py_XDECREF(self->host);
80+
Py_XDECREF(self->dirty);
7081
Py_TYPE(self)->tp_free((PyObject *) self);
7182
}
7283

@@ -82,6 +93,118 @@ PyObject *pocky_bufpair_array(pocky_bufpair_object *self,
8293
return Py_NewRef(self->host);
8394
}
8495

96+
PyObject *pocky_bufpair_copy_to_device(pocky_bufpair_object *self, PyObject *args)
97+
{
98+
cl_int err;
99+
cl_uint idx;
100+
char buf[BUFSIZ];
101+
PyObject *device = NULL;
102+
cl_device_id dev = NULL;
103+
104+
if (!PyArg_ParseTuple(args, "|O!", pocky_device_type, &device)) return NULL;
105+
106+
if (device)
107+
{
108+
PyObject *cap;
109+
110+
cap = PyStructSequence_GetItem(device, 0);
111+
if (!PyCapsule_CheckExact(cap))
112+
{
113+
PyErr_SetString(PyExc_TypeError, pocky_ocl_msg_not_a_capsule);
114+
return NULL;
115+
}
116+
117+
dev = PyCapsule_GetPointer(cap, "DeviceID");
118+
}
119+
120+
for (idx = 0; idx < self->context->num_queues; ++idx)
121+
{
122+
cl_device_id qdev = NULL;
123+
cl_command_queue queue = self->context->queues[idx];
124+
125+
if (device)
126+
{
127+
err = clGetCommandQueueInfo(queue,
128+
CL_QUEUE_DEVICE, sizeof(cl_device_id), &qdev, NULL);
129+
if (err != CL_SUCCESS)
130+
{
131+
snprintf(buf, BUFSIZ, pocky_ocl_fmt_internal,
132+
pocky_opencl_error_to_string(err), err);
133+
PyErr_SetString(pocky_ocl_error, buf);
134+
return NULL;
135+
}
136+
}
137+
138+
if (qdev == dev)
139+
{
140+
err = clEnqueueWriteBuffer(queue, self->device,
141+
CL_TRUE, 0, self->host_size * sizeof(cl_float),
142+
PyArray_DATA((PyArrayObject *) self->host), 0, NULL, NULL);
143+
if (err != CL_SUCCESS)
144+
{
145+
snprintf(buf, BUFSIZ, pocky_ocl_fmt_internal,
146+
pocky_opencl_error_to_string(err), err);
147+
PyErr_SetString(pocky_ocl_error, buf);
148+
return NULL;
149+
}
150+
}
151+
}
152+
153+
return Py_NewRef(Py_None);
154+
}
155+
156+
PyObject *pocky_bufpair_copy_from_device(pocky_bufpair_object *self, PyObject *args)
157+
{
158+
cl_int err;
159+
cl_uint idx;
160+
char buf[BUFSIZ];
161+
PyObject *device, *cap;
162+
cl_device_id dev = NULL;
163+
164+
if (!PyArg_ParseTuple(args, "O!", pocky_device_type, &device)) return NULL;
165+
166+
cap = PyStructSequence_GetItem(device, 0);
167+
if (!PyCapsule_CheckExact(cap))
168+
{
169+
PyErr_SetString(PyExc_TypeError, pocky_ocl_msg_not_a_capsule);
170+
return NULL;
171+
}
172+
173+
dev = PyCapsule_GetPointer(cap, "DeviceID");
174+
175+
for (idx = 0; idx < self->context->num_queues; ++idx)
176+
{
177+
cl_device_id qdev = NULL;
178+
cl_command_queue queue = self->context->queues[idx];
179+
180+
err = clGetCommandQueueInfo(queue,
181+
CL_QUEUE_DEVICE, sizeof(cl_device_id), &qdev, NULL);
182+
if (err != CL_SUCCESS)
183+
{
184+
snprintf(buf, BUFSIZ, pocky_ocl_fmt_internal,
185+
pocky_opencl_error_to_string(err), err);
186+
PyErr_SetString(pocky_ocl_error, buf);
187+
return NULL;
188+
}
189+
190+
if (qdev == dev)
191+
{
192+
err = clEnqueueReadBuffer(queue, self->device,
193+
CL_TRUE, 0, self->host_size * sizeof(cl_float),
194+
PyArray_DATA((PyArrayObject *) self->host), 0, NULL, NULL);
195+
if (err != CL_SUCCESS)
196+
{
197+
snprintf(buf, BUFSIZ, pocky_ocl_fmt_internal,
198+
pocky_opencl_error_to_string(err), err);
199+
PyErr_SetString(pocky_ocl_error, buf);
200+
return NULL;
201+
}
202+
}
203+
}
204+
205+
return Py_NewRef(Py_None);
206+
}
207+
85208
PyObject *pocky_bufpair_get_host(pocky_bufpair_object *self, void *closure)
86209
{
87210
return Py_NewRef(self->host);
@@ -122,17 +245,50 @@ int pocky_bufpair_set_host(pocky_bufpair_object *self,
122245
return 0;
123246
}
124247

248+
PyObject *pocky_bufpair_get_dirty(pocky_bufpair_object *self, void *closure)
249+
{
250+
return Py_NewRef(self->dirty);
251+
}
252+
253+
int pocky_bufpair_set_dirty(pocky_bufpair_object *self,
254+
PyObject *value, void *closure)
255+
{
256+
if (value == NULL)
257+
{
258+
PyErr_SetString(PyExc_TypeError, "Cannot delete dirty marker");
259+
return -1;
260+
}
261+
262+
if (!PyBool_Check(value))
263+
{
264+
PyErr_SetString(PyExc_ValueError, "Dirty marker must be a boolean");
265+
return -1;
266+
}
267+
268+
Py_SETREF(self->dirty, Py_NewRef(value));
269+
return 0;
270+
}
271+
125272
PyGetSetDef pocky_bufpair_getsetters[] = {
126273
{ "host", (getter) pocky_bufpair_get_host, (setter) pocky_bufpair_set_host,
127274
"Exposes the underlying NumPy host array. The size of the array cannot "
128275
"be increased after the BufferPair is created, but the array can be "
129276
"replaced by one of the same dtype and equal or smaller size.", NULL },
277+
{ "dirty", (getter) pocky_bufpair_get_dirty, (setter) pocky_bufpair_set_dirty,
278+
"Indicates that the host array needs to be copied to the device (True) "
279+
"or that it is unchanged since the last copy (False). This can be used "
280+
"by external modules to avoid redundant copies, but it is not updated "
281+
"dynamically by pocky." },
130282
{ NULL } /* sentinel */
131283
};
132284

133285
PyMethodDef pocky_bufpair_methods[] = {
134286
{ "__array__", (PyCFunction) pocky_bufpair_array, METH_NOARGS,
135287
"Return the host NumPy array" },
288+
{ "copy_to_device", (PyCFunction) pocky_bufpair_copy_to_device, METH_VARARGS,
289+
"Copy the host data to the specified device" },
290+
{ "copy_from_device", (PyCFunction) pocky_bufpair_copy_from_device, METH_VARARGS,
291+
"Copy the specified device data to the host" },
136292
{ NULL, NULL, 0, NULL } /* sentinel */
137293
};
138294

0 commit comments

Comments
 (0)