View3D offscreen buffer was interferring with view navigation
[blender.git] / source / blender / python / intern / gpu_offscreen.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Copyright 2015, Blender Foundation.
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/python/intern/gpu_offscreen.c
24  *  \ingroup pythonintern
25  *
26  * This file defines the offscreen functionalities of the 'gpu' module
27  * used for off-screen OpenGL rendering.
28  */
29
30 #include <Python.h>
31
32 #include "MEM_guardedalloc.h"
33
34 #include "BLI_utildefines.h"
35
36 #include "WM_types.h"
37
38 #include "ED_screen.h"
39
40 #include "GPU_extensions.h"
41 #include "GPU_compositing.h"
42
43 #include "../mathutils/mathutils.h"
44
45 #include "../generic/py_capi_utils.h"
46
47 #include "gpu.h"
48
49 #include "ED_view3d.h"
50
51 /* -------------------------------------------------------------------- */
52 /* GPU Offscreen PyObject */
53
54 typedef struct {
55         PyObject_HEAD
56         GPUOffScreen *ofs;
57 } BPy_GPUOffScreen;
58
59 static int bpy_gpu_offscreen_valid_check(BPy_GPUOffScreen *py_gpu_ofs)
60 {
61         if (UNLIKELY(py_gpu_ofs->ofs == NULL)) {
62                 PyErr_SetString(PyExc_ReferenceError, "GPU offscreen was freed, no further access is valid");
63                 return -1;
64         }
65         return 0;
66 }
67
68 #define BPY_GPU_OFFSCREEN_CHECK_OBJ(pygpu) { \
69         if (UNLIKELY(bpy_gpu_offscreen_valid_check(pygpu) == -1)) { \
70                 return NULL; \
71         } \
72 } ((void)0)
73
74 PyDoc_STRVAR(pygpu_offscreen_width_doc, "Texture width.\n\n:type: int");
75 static PyObject *pygpu_offscreen_width_get(BPy_GPUOffScreen *self, void *UNUSED(type))
76 {
77         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
78         return PyLong_FromLong(GPU_offscreen_width(self->ofs));
79 }
80
81 PyDoc_STRVAR(pygpu_offscreen_height_doc, "Texture height.\n\n:type: int");
82 static PyObject *pygpu_offscreen_height_get(BPy_GPUOffScreen *self, void *UNUSED(type))
83 {
84         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
85         return PyLong_FromLong(GPU_offscreen_height(self->ofs));
86 }
87
88 PyDoc_STRVAR(pygpu_offscreen_color_texture_doc, "Color texture.\n\n:type: int");
89 static PyObject *pygpu_offscreen_color_texture_get(BPy_GPUOffScreen *self, void *UNUSED(type))
90 {
91         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
92         return PyLong_FromLong(GPU_offscreen_color_texture(self->ofs));
93 }
94
95 PyDoc_STRVAR(pygpu_offscreen_bind_doc,
96 "bind(save=True)\n"
97 "\n"
98 "   Bind the offscreen object.\n"
99 "\n"
100 "   :param save: save OpenGL current states.\n"
101 "   :type save: bool\n"
102 );
103 static PyObject *pygpu_offscreen_bind(BPy_GPUOffScreen *self, PyObject *args, PyObject *kwds)
104 {
105         static const char *kwlist[] = {"save", NULL};
106         bool save = true;
107
108         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
109
110         if (!PyArg_ParseTupleAndKeywords(
111                 args, kwds, "|O&:bind", (char **)(kwlist),
112                 PyC_ParseBool, &save))
113         {
114                 return NULL;
115         }
116
117         GPU_offscreen_bind(self->ofs, save);
118         Py_RETURN_NONE;
119 }
120
121 PyDoc_STRVAR(pygpu_offscreen_unbind_doc,
122 "unbind(restore=True)\n"
123 "\n"
124 "   Unbind the offscreen object.\n"
125 "\n"
126 "   :param restore: restore OpenGL previous states.\n"
127 "   :type restore: bool\n"
128 );
129 static PyObject *pygpu_offscreen_unbind(BPy_GPUOffScreen *self, PyObject *args, PyObject *kwds)
130 {
131         static const char *kwlist[] = {"restore", NULL};
132         bool restore = true;
133
134         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
135
136         if (!PyArg_ParseTupleAndKeywords(
137                 args, kwds, "|O&:unbind", (char **)(kwlist),
138                 PyC_ParseBool, &restore))
139         {
140                 return NULL;
141         }
142
143         GPU_offscreen_unbind(self->ofs, restore);
144         Py_RETURN_NONE;
145 }
146
147 /**
148  * Use with PyArg_ParseTuple's "O&" formatting.
149  */
150 static int pygpu_offscreen_check_matrix(PyObject *o, void *p)
151 {
152         MatrixObject **pymat_p = p;
153         MatrixObject  *pymat = (MatrixObject *)o;
154
155         if (!MatrixObject_Check(pymat)) {
156                 PyErr_Format(PyExc_TypeError,
157                              "expected a mathutils.Matrix, not a %.200s",
158                              Py_TYPE(o)->tp_name);
159                 return 0;
160         }
161
162         if (BaseMath_ReadCallback(pymat) == -1) {
163                 return 0;
164         }
165
166         if ((pymat->num_col != 4) ||
167             (pymat->num_row != 4))
168         {
169                 PyErr_SetString(PyExc_ValueError, "matrix must be 4x4");
170                 return 0;
171         }
172
173         *pymat_p = pymat;
174         return 1;
175 }
176
177 PyDoc_STRVAR(pygpu_offscreen_draw_view3d_doc,
178 "draw_view3d(scene, view3d, region, modelview_matrix, projection_matrix)\n"
179 "\n"
180 "   Draw the 3d viewport in the offscreen object.\n"
181 "\n"
182 "   :param scene: Scene to draw.\n"
183 "   :type scene: :class:`bpy.types.Scene`\n"
184 "   :param view3d: 3D View to get the drawing settings from.\n"
185 "   :type view3d: :class:`bpy.types.SpaceView3D`\n"
186 "   :param region: Region of the 3D View.\n"
187 "   :type region: :class:`bpy.types.Region`\n"
188 "   :param modelview_matrix: ModelView Matrix.\n"
189 "   :type modelview_matrix: :class:`mathutils.Matrix`\n"
190 "   :param projection_matrix: Projection Matrix.\n"
191 "   :type projection_matrix: :class:`mathutils.Matrix`\n"
192 );
193 static PyObject *pygpu_offscreen_draw_view3d(BPy_GPUOffScreen *self, PyObject *args, PyObject *kwds)
194 {
195         static const char *kwlist[] = {"scene", "view3d", "region", "projection_matrix", "modelview_matrix", NULL};
196
197         MatrixObject *py_mat_modelview, *py_mat_projection;
198         PyObject *py_scene, *py_region, *py_view3d;
199
200         Scene *scene;
201         View3D *v3d;
202         ARegion *ar;
203         GPUFX *fx;
204         GPUFXSettings fx_settings;
205         void *rv3d_mats;
206
207         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
208
209         if (!PyArg_ParseTupleAndKeywords(
210                 args, kwds, "OOOO&O&:draw_view3d", (char **)(kwlist),
211                 &py_scene, &py_view3d, &py_region,
212                 pygpu_offscreen_check_matrix, &py_mat_projection,
213                 pygpu_offscreen_check_matrix, &py_mat_modelview) ||
214             (!(scene    = PyC_RNA_AsPointer(py_scene, "Scene")) ||
215              !(v3d      = PyC_RNA_AsPointer(py_view3d, "SpaceView3D")) ||
216              !(ar       = PyC_RNA_AsPointer(py_region, "Region"))))
217         {
218                 return NULL;
219         }
220
221         fx = GPU_fx_compositor_create();
222
223         fx_settings = v3d->fx_settings;  /* full copy */
224
225         ED_view3d_draw_offscreen_init(scene, v3d);
226
227         rv3d_mats = ED_view3d_mats_rv3d_backup(ar->regiondata);
228
229         GPU_offscreen_bind(self->ofs, true); /* bind */
230
231         ED_view3d_draw_offscreen(
232                 scene, v3d, ar, GPU_offscreen_width(self->ofs), GPU_offscreen_height(self->ofs),
233                 (float(*)[4])py_mat_modelview->matrix, (float(*)[4])py_mat_projection->matrix,
234                 false, true, true, "",
235                 fx, &fx_settings,
236                 self->ofs);
237
238         GPU_fx_compositor_destroy(fx);
239         GPU_offscreen_unbind(self->ofs, true); /* unbind */
240
241         ED_view3d_mats_rv3d_restore(ar->regiondata, rv3d_mats);
242         MEM_freeN(rv3d_mats);
243
244         Py_RETURN_NONE;
245 }
246
247 PyDoc_STRVAR(pygpu_offscreen_free_doc,
248 "free()\n"
249 "\n"
250 "   Free the offscreen object\n"
251 "   The framebuffer, texture and render objects will no longer be accessible.\n"
252 );
253 static PyObject *pygpu_offscreen_free(BPy_GPUOffScreen *self)
254 {
255         BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
256
257         GPU_offscreen_free(self->ofs);
258         self->ofs = NULL;
259         Py_RETURN_NONE;
260 }
261
262 static void BPy_GPUOffScreen__tp_dealloc(BPy_GPUOffScreen *self)
263 {
264         if (self->ofs)
265                 GPU_offscreen_free(self->ofs);
266         Py_TYPE(self)->tp_free((PyObject *)self);
267 }
268
269 static PyGetSetDef bpy_gpu_offscreen_getseters[] = {
270         {(char *)"color_texture", (getter)pygpu_offscreen_color_texture_get, (setter)NULL, pygpu_offscreen_color_texture_doc, NULL},
271         {(char *)"width", (getter)pygpu_offscreen_width_get, (setter)NULL, pygpu_offscreen_width_doc, NULL},
272         {(char *)"height", (getter)pygpu_offscreen_height_get, (setter)NULL, pygpu_offscreen_height_doc, NULL},
273         {NULL, NULL, NULL, NULL, NULL}  /* Sentinel */
274 };
275
276 static struct PyMethodDef bpy_gpu_offscreen_methods[] = {
277         {"bind", (PyCFunction)pygpu_offscreen_bind, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_bind_doc},
278         {"unbind", (PyCFunction)pygpu_offscreen_unbind, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_unbind_doc},
279         {"draw_view3d", (PyCFunction)pygpu_offscreen_draw_view3d, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_draw_view3d_doc},
280         {"free", (PyCFunction)pygpu_offscreen_free, METH_NOARGS, pygpu_offscreen_free_doc},
281         {NULL, NULL, 0, NULL}
282 };
283
284 PyDoc_STRVAR(py_gpu_offscreen_doc,
285 ".. class:: GPUOffscreen"
286 "\n"
287 "   This object gives access to off screen buffers.\n"
288 );
289 static PyTypeObject BPy_GPUOffScreen_Type = {
290         PyVarObject_HEAD_INIT(NULL, 0)
291         "GPUOffScreen",                              /* tp_name */
292         sizeof(BPy_GPUOffScreen),                      /* tp_basicsize */
293         0,                                           /* tp_itemsize */
294         /* methods */
295         (destructor)BPy_GPUOffScreen__tp_dealloc,      /* tp_dealloc */
296         NULL,                                        /* tp_print */
297         NULL,                                        /* tp_getattr */
298         NULL,                                        /* tp_setattr */
299         NULL,                                        /* tp_compare */
300         NULL,                                        /* tp_repr */
301         NULL,                                        /* tp_as_number */
302         NULL,                                        /* tp_as_sequence */
303         NULL,                                        /* tp_as_mapping */
304         NULL,                                        /* tp_hash */
305         NULL,                                        /* tp_call */
306         NULL,                                        /* tp_str */
307         NULL,                                        /* tp_getattro */
308         NULL,                                        /* tp_setattro */
309         NULL,                                        /* tp_as_buffer */
310         Py_TPFLAGS_DEFAULT,                          /* tp_flags */
311         py_gpu_offscreen_doc,                        /* Documentation string */
312         NULL,                                        /* tp_traverse */
313         NULL,                                        /* tp_clear */
314         NULL,                                        /* tp_richcompare */
315         0,                                           /* tp_weaklistoffset */
316         NULL,                                        /* tp_iter */
317         NULL,                                        /* tp_iternext */
318         bpy_gpu_offscreen_methods,                   /* tp_methods */
319         NULL,                                        /* tp_members */
320         bpy_gpu_offscreen_getseters,                 /* tp_getset */
321         NULL,                                        /* tp_base */
322         NULL,                                        /* tp_dict */
323         NULL,                                        /* tp_descr_get */
324         NULL,                                        /* tp_descr_set */
325         0,                                           /* tp_dictoffset */
326         0,                                           /* tp_init */
327         NULL,                                        /* tp_alloc */
328         NULL,                                        /* tp_new */
329         (freefunc)0,                                 /* tp_free */
330         NULL,                                        /* tp_is_gc */
331         NULL,                                        /* tp_bases */
332         NULL,                                        /* tp_mro */
333         NULL,                                        /* tp_cache */
334         NULL,                                        /* tp_subclasses */
335         NULL,                                        /* tp_weaklist */
336         (destructor) NULL                            /* tp_del */
337 };
338
339 /* -------------------------------------------------------------------- */
340 /* GPU offscreen methods */
341
342 static PyObject *BPy_GPU_OffScreen_CreatePyObject(GPUOffScreen *ofs)
343 {
344         BPy_GPUOffScreen *self;
345         self = PyObject_New(BPy_GPUOffScreen, &BPy_GPUOffScreen_Type);
346         self->ofs = ofs;
347         return (PyObject *)self;
348 }
349
350 PyDoc_STRVAR(pygpu_offscreen_new_doc,
351 "new(width, height, samples=0)\n"
352 "\n"
353 "   Return a GPUOffScreen.\n"
354 "\n"
355 "   :param width: Horizontal dimension of the buffer.\n"
356 "   :type width: int`\n"
357 "   :param height: Vertical dimension of the buffer.\n"
358 "   :type height: int`\n"
359 "   :param samples: OpenGL samples to use for MSAA or zero to disable.\n"
360 "   :type samples: int\n"
361 "   :return: Newly created off-screen buffer.\n"
362 "   :rtype: :class:`gpu.GPUOffscreen`\n"
363 );
364 static PyObject *pygpu_offscreen_new(PyObject *UNUSED(self), PyObject *args, PyObject *kwds)
365 {
366         static const char *kwlist[] = {"width", "height", "samples", NULL};
367
368         GPUOffScreen *ofs;
369         int width, height, samples = 0;
370         char err_out[256];
371
372         if (!PyArg_ParseTupleAndKeywords(
373                 args, kwds, "ii|i:new", (char **)(kwlist),
374                 &width, &height, &samples))
375         {
376                 return NULL;
377         }
378
379         ofs = GPU_offscreen_create(width, height, samples, err_out);
380
381         if (ofs == NULL) {
382                 PyErr_Format(PyExc_RuntimeError,
383                              "gpu.offscreen.new(...) failed with '%s'",
384                              err_out[0] ? err_out : "unknown error");
385                 return NULL;
386         }
387
388         return BPy_GPU_OffScreen_CreatePyObject(ofs);
389 }
390
391 static struct PyMethodDef BPy_GPU_offscreen_methods[] = {
392         {"new", (PyCFunction)pygpu_offscreen_new, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_new_doc},
393         {NULL, NULL, 0, NULL}
394 };
395
396 PyDoc_STRVAR(BPy_GPU_offscreen_doc,
397 "This module provides access to offscreen rendering functions."
398 );
399 static PyModuleDef BPy_GPU_offscreen_module_def = {
400         PyModuleDef_HEAD_INIT,
401         "gpu.offscreen",                             /* m_name */
402         BPy_GPU_offscreen_doc,                       /* m_doc */
403         0,                                           /* m_size */
404         BPy_GPU_offscreen_methods,                   /* m_methods */
405         NULL,                                        /* m_reload */
406         NULL,                                        /* m_traverse */
407         NULL,                                        /* m_clear */
408         NULL,                                        /* m_free */
409 };
410
411 PyObject *BPyInit_gpu_offscreen(void)
412 {
413         PyObject *submodule;
414
415         /* Register the 'GPUOffscreen' class */
416         if (PyType_Ready(&BPy_GPUOffScreen_Type)) {
417                 return NULL;
418         }
419
420         submodule = PyModule_Create(&BPy_GPU_offscreen_module_def);
421
422 #define MODULE_TYPE_ADD(s, t) \
423         PyModule_AddObject(s, t.tp_name, (PyObject *)&t); Py_INCREF((PyObject *)&t)
424
425         MODULE_TYPE_ADD(submodule, BPy_GPUOffScreen_Type);
426
427 #undef MODULE_TYPE_ADD
428
429         return submodule;
430 }
431
432 #undef BPY_GPU_OFFSCREEN_CHECK_OBJ