Merge branch 'master' into blender2.8
[blender.git] / source / blender / gpu / intern / gpu_framebuffer.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  * The Original Code is Copyright (C) 2005 Blender Foundation.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): Brecht Van Lommel.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 #include "MEM_guardedalloc.h"
29
30 #include "BLI_blenlib.h"
31 #include "BLI_utildefines.h"
32 #include "BLI_math_base.h"
33 #include "BLI_math_vector.h"
34
35 #include "BKE_global.h"
36
37 #include "GPU_debug.h"
38 #include "GPU_glew.h"
39 #include "GPU_framebuffer.h"
40 #include "GPU_shader.h"
41 #include "GPU_texture.h"
42
43 static struct GPUFrameBufferGlobal {
44         GLuint currentfb;
45 } GG = {0};
46
47 /* Number of maximum output slots.
48  * We support 4 outputs for now (usually we wouldn't need more to preserve fill rate) */
49 #define GPU_FB_MAX_SLOTS 4
50
51 struct GPUFrameBuffer {
52         GLuint object;
53         GPUTexture *colortex[GPU_FB_MAX_SLOTS];
54         GPUTexture *depthtex;
55 };
56
57 static void GPU_print_framebuffer_error(GLenum status, char err_out[256])
58 {
59         const char *err = "unknown";
60
61         switch (status) {
62                 case GL_FRAMEBUFFER_COMPLETE_EXT:
63                         break;
64                 case GL_INVALID_OPERATION:
65                         err = "Invalid operation";
66                         break;
67                 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
68                         err = "Incomplete attachment";
69                         break;
70                 case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
71                         err = "Unsupported framebuffer format";
72                         break;
73                 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
74                         err = "Missing attachment";
75                         break;
76                 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
77                         err = "Attached images must have same dimensions";
78                         break;
79                 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
80                         err = "Attached images must have same format";
81                         break;
82                 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
83                         err = "Missing draw buffer";
84                         break;
85                 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
86                         err = "Missing read buffer";
87                         break;
88         }
89
90         if (err_out) {
91                 BLI_snprintf(err_out, 256, "GPUFrameBuffer: framebuffer incomplete error %d '%s'",
92                         (int)status, err);
93         }
94         else {
95                 fprintf(stderr, "GPUFrameBuffer: framebuffer incomplete error %d '%s'\n",
96                         (int)status, err);
97         }
98 }
99
100 /* GPUFrameBuffer */
101
102 GPUFrameBuffer *GPU_framebuffer_create(void)
103 {
104         GPUFrameBuffer *fb;
105
106         if (!(GLEW_VERSION_3_0 || GLEW_ARB_framebuffer_object ||
107               (GLEW_EXT_framebuffer_object && GLEW_EXT_framebuffer_blit)))
108         {
109                 return NULL;
110         }
111         
112         fb = MEM_callocN(sizeof(GPUFrameBuffer), "GPUFrameBuffer");
113         glGenFramebuffersEXT(1, &fb->object);
114
115         if (!fb->object) {
116                 fprintf(stderr, "GPUFFrameBuffer: framebuffer gen failed. %d\n",
117                         (int)glGetError());
118                 GPU_framebuffer_free(fb);
119                 return NULL;
120         }
121
122         /* make sure no read buffer is enabled, so completeness check will not fail. We set those at binding time */
123         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
124         glReadBuffer(GL_NONE);
125         glDrawBuffer(GL_NONE);
126         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
127         
128         return fb;
129 }
130
131 int GPU_framebuffer_texture_attach(GPUFrameBuffer *fb, GPUTexture *tex, int slot, char err_out[256])
132 {
133         GLenum attachment;
134         GLenum error;
135
136         if (slot >= GPU_FB_MAX_SLOTS) {
137                 fprintf(stderr,
138                         "Attaching to index %d framebuffer slot unsupported. "
139                         "Use at most %d\n", slot, GPU_FB_MAX_SLOTS);
140                 return 0;
141         }
142
143         if ((G.debug & G_DEBUG)) {
144                 if (GPU_texture_bound_number(tex) != -1) {
145                         fprintf(stderr,
146                                 "Feedback loop warning!: "
147                                 "Attempting to attach texture to framebuffer while still bound to texture unit for drawing!\n");
148                 }
149         }
150
151         if (GPU_texture_depth(tex))
152                 attachment = GL_DEPTH_ATTACHMENT_EXT;
153         else
154                 attachment = GL_COLOR_ATTACHMENT0_EXT + slot;
155
156         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
157         GG.currentfb = fb->object;
158
159         /* Clean glError buffer. */
160         while (glGetError() != GL_NO_ERROR) {}
161
162         glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, 
163                 GPU_texture_target(tex), GPU_texture_opengl_bindcode(tex), 0);
164
165         error = glGetError();
166
167         if (error == GL_INVALID_OPERATION) {
168                 GPU_framebuffer_restore();
169                 GPU_print_framebuffer_error(error, err_out);
170                 return 0;
171         }
172
173         if (GPU_texture_depth(tex))
174                 fb->depthtex = tex;
175         else
176                 fb->colortex[slot] = tex;
177
178         GPU_texture_framebuffer_set(tex, fb, slot);
179
180         return 1;
181 }
182
183 void GPU_framebuffer_texture_detach(GPUTexture *tex)
184 {
185         GLenum attachment;
186         GPUFrameBuffer *fb = GPU_texture_framebuffer(tex);
187         int fb_attachment = GPU_texture_framebuffer_attachment(tex);
188
189         if (!fb)
190                 return;
191
192         if (GG.currentfb != fb->object) {
193                 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
194                 GG.currentfb = fb->object;
195         }
196
197         if (GPU_texture_depth(tex)) {
198                 fb->depthtex = NULL;
199                 attachment = GL_DEPTH_ATTACHMENT_EXT;
200         }
201         else {
202                 BLI_assert(fb->colortex[fb_attachment] == tex);
203                 fb->colortex[fb_attachment] = NULL;
204                 attachment = GL_COLOR_ATTACHMENT0_EXT + fb_attachment;
205         }
206
207         glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, GPU_texture_target(tex), 0, 0);
208
209         GPU_texture_framebuffer_set(tex, NULL, -1);
210 }
211
212 void GPU_texture_bind_as_framebuffer(GPUTexture *tex)
213 {
214         GPUFrameBuffer *fb = GPU_texture_framebuffer(tex);
215         int fb_attachment = GPU_texture_framebuffer_attachment(tex);
216
217         if (!fb) {
218                 fprintf(stderr, "Error, texture not bound to framebuffer!\n");
219                 return;
220         }
221
222         /* push attributes */
223         glPushAttrib(GL_ENABLE_BIT | GL_VIEWPORT_BIT);
224         glDisable(GL_SCISSOR_TEST);
225
226         /* bind framebuffer */
227         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
228
229         if (GPU_texture_depth(tex)) {
230                 glDrawBuffer(GL_NONE);
231                 glReadBuffer(GL_NONE);
232         }
233         else {
234                 /* last bound prevails here, better allow explicit control here too */
235                 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + fb_attachment);
236                 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + fb_attachment);
237         }
238         
239         if (GPU_texture_target(tex) == GL_TEXTURE_2D_MULTISAMPLE) {
240                 glEnable(GL_MULTISAMPLE);
241         }
242
243         /* push matrices and set default viewport and matrix */
244         glViewport(0, 0, GPU_texture_width(tex), GPU_texture_height(tex));
245         GG.currentfb = fb->object;
246
247         glMatrixMode(GL_PROJECTION);
248         glPushMatrix();
249         glMatrixMode(GL_MODELVIEW);
250         glPushMatrix();
251 }
252
253 void GPU_framebuffer_slots_bind(GPUFrameBuffer *fb, int slot)
254 {
255         int numslots = 0, i;
256         GLenum attachments[4];
257         
258         if (!fb->colortex[slot]) {
259                 fprintf(stderr, "Error, framebuffer slot empty!\n");
260                 return;
261         }
262         
263         for (i = 0; i < 4; i++) {
264                 if (fb->colortex[i]) {
265                         attachments[numslots] = GL_COLOR_ATTACHMENT0_EXT + i;
266                         numslots++;
267                 }
268         }
269         
270         /* push attributes */
271         glPushAttrib(GL_ENABLE_BIT | GL_VIEWPORT_BIT);
272         glDisable(GL_SCISSOR_TEST);
273
274         /* bind framebuffer */
275         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
276
277         /* last bound prevails here, better allow explicit control here too */
278         glDrawBuffers(numslots, attachments);
279         glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + slot);
280
281         /* push matrices and set default viewport and matrix */
282         glViewport(0, 0, GPU_texture_width(fb->colortex[slot]), GPU_texture_height(fb->colortex[slot]));
283         GG.currentfb = fb->object;
284
285         glMatrixMode(GL_PROJECTION);
286         glPushMatrix();
287         glMatrixMode(GL_MODELVIEW);
288         glPushMatrix();
289 }
290
291
292 void GPU_framebuffer_texture_unbind(GPUFrameBuffer *UNUSED(fb), GPUTexture *UNUSED(tex))
293 {
294         /* restore matrix */
295         glMatrixMode(GL_PROJECTION);
296         glPopMatrix();
297         glMatrixMode(GL_MODELVIEW);
298         glPopMatrix();
299
300         /* restore attributes */
301         glPopAttrib();
302 }
303
304 void GPU_framebuffer_bind_no_save(GPUFrameBuffer *fb, int slot)
305 {
306         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
307         /* last bound prevails here, better allow explicit control here too */
308         glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + slot);
309         glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + slot);
310
311         /* push matrices and set default viewport and matrix */
312         glViewport(0, 0, GPU_texture_width(fb->colortex[slot]), GPU_texture_height(fb->colortex[slot]));
313         GG.currentfb = fb->object;
314         GG.currentfb = fb->object;
315 }
316
317 bool GPU_framebuffer_bound(GPUFrameBuffer *fb)
318 {
319         return fb->object == GG.currentfb;
320 }
321
322 bool GPU_framebuffer_check_valid(GPUFrameBuffer *fb, char err_out[256])
323 {
324         GLenum status;
325         
326         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
327         GG.currentfb = fb->object;
328         
329         /* Clean glError buffer. */
330         while (glGetError() != GL_NO_ERROR) {}
331         
332         status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
333         
334         if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
335                 GPU_framebuffer_restore();
336                 GPU_print_framebuffer_error(status, err_out);
337                 return false;
338         }
339         
340         return true;
341 }
342
343 void GPU_framebuffer_free(GPUFrameBuffer *fb)
344 {
345         int i;
346         if (fb->depthtex)
347                 GPU_framebuffer_texture_detach(fb->depthtex);
348
349         for (i = 0; i < GPU_FB_MAX_SLOTS; i++) {
350                 if (fb->colortex[i]) {
351                         GPU_framebuffer_texture_detach(fb->colortex[i]);
352                 }
353         }
354
355         if (fb->object) {
356                 glDeleteFramebuffersEXT(1, &fb->object);
357
358                 if (GG.currentfb == fb->object) {
359                         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
360                         GG.currentfb = 0;
361                 }
362         }
363
364         MEM_freeN(fb);
365 }
366
367 void GPU_framebuffer_restore(void)
368 {
369         if (GG.currentfb != 0) {
370                 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
371                 GG.currentfb = 0;
372         }
373 }
374
375 void GPU_framebuffer_blur(
376         GPUFrameBuffer *fb, GPUTexture *tex,
377         GPUFrameBuffer *blurfb, GPUTexture *blurtex)
378 {
379         const float scaleh[2] = {1.0f / GPU_texture_width(blurtex), 0.0f};
380         const float scalev[2] = {0.0f, 1.0f / GPU_texture_height(tex)};
381
382         GPUShader *blur_shader = GPU_shader_get_builtin_shader(GPU_SHADER_SEP_GAUSSIAN_BLUR);
383         int scale_uniform, texture_source_uniform;
384
385         if (!blur_shader)
386                 return;
387
388         scale_uniform = GPU_shader_get_uniform(blur_shader, "ScaleU");
389         texture_source_uniform = GPU_shader_get_uniform(blur_shader, "textureSource");
390                 
391         /* Blurring horizontally */
392
393         /* We do the bind ourselves rather than using GPU_framebuffer_texture_bind() to avoid
394          * pushing unnecessary matrices onto the OpenGL stack. */
395         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, blurfb->object);
396         glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
397         
398         /* avoid warnings from texture binding */
399         GG.currentfb = blurfb->object;
400
401         GPU_shader_bind(blur_shader);
402         GPU_shader_uniform_vector(blur_shader, scale_uniform, 2, 1, scaleh);
403         GPU_shader_uniform_texture(blur_shader, texture_source_uniform, tex);
404         glViewport(0, 0, GPU_texture_width(blurtex), GPU_texture_height(blurtex));
405
406         /* Peparing to draw quad */
407         glMatrixMode(GL_MODELVIEW);
408         glLoadIdentity();
409         glMatrixMode(GL_TEXTURE);
410         glLoadIdentity();
411         glMatrixMode(GL_PROJECTION);
412         glLoadIdentity();
413
414         glDisable(GL_DEPTH_TEST);
415
416         GPU_texture_bind(tex, 0);
417
418         /* Drawing quad */
419         glBegin(GL_QUADS);
420         glTexCoord2d(0, 0); glVertex2f(1, 1);
421         glTexCoord2d(1, 0); glVertex2f(-1, 1);
422         glTexCoord2d(1, 1); glVertex2f(-1, -1);
423         glTexCoord2d(0, 1); glVertex2f(1, -1);
424         glEnd();
425
426         /* Blurring vertically */
427
428         glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
429         glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
430         
431         GG.currentfb = fb->object;
432         
433         glViewport(0, 0, GPU_texture_width(tex), GPU_texture_height(tex));
434         GPU_shader_uniform_vector(blur_shader, scale_uniform, 2, 1, scalev);
435         GPU_shader_uniform_texture(blur_shader, texture_source_uniform, blurtex);
436         GPU_texture_bind(blurtex, 0);
437
438         glBegin(GL_QUADS);
439         glTexCoord2d(0, 0); glVertex2f(1, 1);
440         glTexCoord2d(1, 0); glVertex2f(-1, 1);
441         glTexCoord2d(1, 1); glVertex2f(-1, -1);
442         glTexCoord2d(0, 1); glVertex2f(1, -1);
443         glEnd();
444
445         GPU_shader_unbind();
446 }
447
448 /* GPUOffScreen */
449
450 struct GPUOffScreen {
451         GPUFrameBuffer *fb;
452         GPUTexture *color;
453         GPUTexture *depth;
454 };
455
456 GPUOffScreen *GPU_offscreen_create(int width, int height, int samples, char err_out[256])
457 {
458         GPUOffScreen *ofs;
459
460         ofs = MEM_callocN(sizeof(GPUOffScreen), "GPUOffScreen");
461
462         ofs->fb = GPU_framebuffer_create();
463         if (!ofs->fb) {
464                 GPU_offscreen_free(ofs);
465                 return NULL;
466         }
467
468         if (samples) {
469                 if (!GLEW_EXT_framebuffer_multisample ||
470                     !GLEW_ARB_texture_multisample ||
471                     /* Only needed for GPU_offscreen_read_pixels.
472                      * We could add an arg if we intend to use multi-sample
473                      * offscreen buffers w/o reading their pixels */
474                     !GLEW_EXT_framebuffer_blit ||
475                     /* This is required when blitting from a multi-sampled buffers,
476                      * even though we're not scaling. */
477                     !GLEW_EXT_framebuffer_multisample_blit_scaled)
478                 {
479                         samples = 0;
480                 }
481         }
482
483         ofs->depth = GPU_texture_create_depth_multisample(width, height, samples, err_out);
484         if (!ofs->depth) {
485                 GPU_offscreen_free(ofs);
486                 return NULL;
487         }
488
489         if (!GPU_framebuffer_texture_attach(ofs->fb, ofs->depth, 0, err_out)) {
490                 GPU_offscreen_free(ofs);
491                 return NULL;
492         }
493
494         ofs->color = GPU_texture_create_2D_multisample(width, height, NULL, GPU_HDR_NONE, samples, err_out);
495         if (!ofs->color) {
496                 GPU_offscreen_free(ofs);
497                 return NULL;
498         }
499
500         if (!GPU_framebuffer_texture_attach(ofs->fb, ofs->color, 0, err_out)) {
501                 GPU_offscreen_free(ofs);
502                 return NULL;
503         }
504         
505         /* check validity at the very end! */
506         if (!GPU_framebuffer_check_valid(ofs->fb, err_out)) {
507                 GPU_offscreen_free(ofs);
508                 return NULL;            
509         }
510
511         GPU_framebuffer_restore();
512
513         return ofs;
514 }
515
516 void GPU_offscreen_free(GPUOffScreen *ofs)
517 {
518         if (ofs->fb)
519                 GPU_framebuffer_free(ofs->fb);
520         if (ofs->color)
521                 GPU_texture_free(ofs->color);
522         if (ofs->depth)
523                 GPU_texture_free(ofs->depth);
524         
525         MEM_freeN(ofs);
526 }
527
528 void GPU_offscreen_bind(GPUOffScreen *ofs, bool save)
529 {
530         glDisable(GL_SCISSOR_TEST);
531         if (save)
532                 GPU_texture_bind_as_framebuffer(ofs->color);
533         else {
534                 GPU_framebuffer_bind_no_save(ofs->fb, 0);
535         }
536 }
537
538 void GPU_offscreen_unbind(GPUOffScreen *ofs, bool restore)
539 {
540         if (restore)
541                 GPU_framebuffer_texture_unbind(ofs->fb, ofs->color);
542         GPU_framebuffer_restore();
543         glEnable(GL_SCISSOR_TEST);
544 }
545
546 void GPU_offscreen_read_pixels(GPUOffScreen *ofs, int type, void *pixels)
547 {
548         const int w = GPU_texture_width(ofs->color);
549         const int h = GPU_texture_height(ofs->color);
550
551         if (GPU_texture_target(ofs->color) == GL_TEXTURE_2D_MULTISAMPLE) {
552                 /* For a multi-sample texture,
553                  * we need to create an intermediate buffer to blit to,
554                  * before its copied using 'glReadPixels' */
555
556                 /* not needed since 'ofs' needs to be bound to the framebuffer already */
557 // #define USE_FBO_CTX_SWITCH
558
559                 GLuint fbo_blit = 0;
560                 GLuint tex_blit = 0;
561                 GLenum status;
562
563                 /* create texture for new 'fbo_blit' */
564                 glGenTextures(1, &tex_blit);
565                 if (!tex_blit) {
566                         goto finally;
567                 }
568
569                 glBindTexture(GL_TEXTURE_2D, tex_blit);
570                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, type, 0);
571
572 #ifdef USE_FBO_CTX_SWITCH
573                 /* read from multi-sample buffer */
574                 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, ofs->color->fb->object);
575                 glFramebufferTexture2DEXT(
576                         GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + ofs->color->fb_attachment,
577                         GL_TEXTURE_2D_MULTISAMPLE, ofs->color->bindcode, 0);
578                 status = glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT);
579                 if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
580                         goto finally;
581                 }
582 #endif
583
584                 /* write into new single-sample buffer */
585                 glGenFramebuffersEXT(1, &fbo_blit);
586                 glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbo_blit);
587                 glFramebufferTexture2DEXT(
588                         GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
589                         GL_TEXTURE_2D, tex_blit, 0);
590                 status = glCheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER_EXT);
591                 if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
592                         goto finally;
593                 }
594
595                 /* perform the copy */
596                 glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
597
598                 /* read the results */
599                 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, fbo_blit);
600                 glReadPixels(0, 0, w, h, GL_RGBA, type, pixels);
601
602 #ifdef USE_FBO_CTX_SWITCH
603                 /* restore the original frame-bufer */
604                 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ofs->color->fb->object);
605 #undef USE_FBO_CTX_SWITCH
606 #endif
607
608
609 finally:
610                 /* cleanup */
611                 if (tex_blit) {
612                         glDeleteTextures(1, &tex_blit);
613                 }
614                 if (fbo_blit) {
615                         glDeleteFramebuffersEXT(1, &fbo_blit);
616                 }
617
618                 GPU_ASSERT_NO_GL_ERRORS("Read Multi-Sample Pixels");
619         }
620         else {
621                 glReadPixels(0, 0, w, h, GL_RGBA, type, pixels);
622         }
623 }
624
625 int GPU_offscreen_width(const GPUOffScreen *ofs)
626 {
627         return GPU_texture_width(ofs->color);
628 }
629
630 int GPU_offscreen_height(const GPUOffScreen *ofs)
631 {
632         return GPU_texture_height(ofs->color);
633 }
634
635 int GPU_offscreen_color_texture(const GPUOffScreen *ofs)
636 {
637         return GPU_texture_opengl_bindcode(ofs->color);
638 }
639