Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/gpu/intern/gpu_framebuffer.c')
-rw-r--r--source/blender/gpu/intern/gpu_framebuffer.c633
1 files changed, 633 insertions, 0 deletions
diff --git a/source/blender/gpu/intern/gpu_framebuffer.c b/source/blender/gpu/intern/gpu_framebuffer.c
new file mode 100644
index 00000000000..4ef7611a3f3
--- /dev/null
+++ b/source/blender/gpu/intern/gpu_framebuffer.c
@@ -0,0 +1,633 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2005 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Brecht Van Lommel.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_utildefines.h"
+#include "BLI_math_base.h"
+#include "BLI_math_vector.h"
+
+#include "BKE_global.h"
+
+#include "GPU_debug.h"
+#include "GPU_glew.h"
+#include "GPU_framebuffer.h"
+#include "GPU_shader.h"
+#include "GPU_texture.h"
+
+static struct GPUFrameBufferGlobal {
+ GLuint currentfb;
+} GG = {0};
+
+/* Number of maximum output slots. We support 4 outputs for now (usually we wouldn't need more to preserve fill rate) */
+#define GPU_FB_MAX_SLOTS 4
+
+struct GPUFrameBuffer {
+ GLuint object;
+ GPUTexture *colortex[GPU_FB_MAX_SLOTS];
+ GPUTexture *depthtex;
+};
+
+static void GPU_print_framebuffer_error(GLenum status, char err_out[256])
+{
+ const char *err = "unknown";
+
+ switch (status) {
+ case GL_FRAMEBUFFER_COMPLETE_EXT:
+ break;
+ case GL_INVALID_OPERATION:
+ err = "Invalid operation";
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
+ err = "Incomplete attachment";
+ break;
+ case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
+ err = "Unsupported framebuffer format";
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
+ err = "Missing attachment";
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
+ err = "Attached images must have same dimensions";
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
+ err = "Attached images must have same format";
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
+ err = "Missing draw buffer";
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
+ err = "Missing read buffer";
+ break;
+ }
+
+ if (err_out) {
+ BLI_snprintf(err_out, 256, "GPUFrameBuffer: framebuffer incomplete error %d '%s'",
+ (int)status, err);
+ }
+ else {
+ fprintf(stderr, "GPUFrameBuffer: framebuffer incomplete error %d '%s'\n",
+ (int)status, err);
+ }
+}
+
+/* GPUFrameBuffer */
+
+GPUFrameBuffer *GPU_framebuffer_create(void)
+{
+ GPUFrameBuffer *fb;
+
+ if (!(GLEW_VERSION_3_0 || GLEW_ARB_framebuffer_object || (GLEW_EXT_framebuffer_object && GLEW_EXT_framebuffer_blit)))
+ return NULL;
+
+ fb = MEM_callocN(sizeof(GPUFrameBuffer), "GPUFrameBuffer");
+ glGenFramebuffersEXT(1, &fb->object);
+
+ if (!fb->object) {
+ fprintf(stderr, "GPUFFrameBuffer: framebuffer gen failed. %d\n",
+ (int)glGetError());
+ GPU_framebuffer_free(fb);
+ return NULL;
+ }
+
+ /* make sure no read buffer is enabled, so completeness check will not fail. We set those at binding time */
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+ glReadBuffer(GL_NONE);
+ glDrawBuffer(GL_NONE);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
+ return fb;
+}
+
+int GPU_framebuffer_texture_attach(GPUFrameBuffer *fb, GPUTexture *tex, int slot, char err_out[256])
+{
+ GLenum attachment;
+ GLenum error;
+
+ if (slot >= GPU_FB_MAX_SLOTS) {
+ fprintf(stderr,
+ "Attaching to index %d framebuffer slot unsupported. "
+ "Use at most %d\n", slot, GPU_FB_MAX_SLOTS);
+ return 0;
+ }
+
+ if ((G.debug & G_DEBUG)) {
+ if (GPU_texture_bound_number(tex) != -1) {
+ fprintf(stderr,
+ "Feedback loop warning!: "
+ "Attempting to attach texture to framebuffer while still bound to texture unit for drawing!\n");
+ }
+ }
+
+ if (GPU_texture_depth(tex))
+ attachment = GL_DEPTH_ATTACHMENT_EXT;
+ else
+ attachment = GL_COLOR_ATTACHMENT0_EXT + slot;
+
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+ GG.currentfb = fb->object;
+
+ /* Clean glError buffer. */
+ while (glGetError() != GL_NO_ERROR) {}
+
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment,
+ GPU_texture_target(tex), GPU_texture_opengl_bindcode(tex), 0);
+
+ error = glGetError();
+
+ if (error == GL_INVALID_OPERATION) {
+ GPU_framebuffer_restore();
+ GPU_print_framebuffer_error(error, err_out);
+ return 0;
+ }
+
+ if (GPU_texture_depth(tex))
+ fb->depthtex = tex;
+ else
+ fb->colortex[slot] = tex;
+
+ GPU_texture_framebuffer_set(tex, fb, slot);
+
+ return 1;
+}
+
+void GPU_framebuffer_texture_detach(GPUTexture *tex)
+{
+ GLenum attachment;
+ GPUFrameBuffer *fb = GPU_texture_framebuffer(tex);
+ int fb_attachment = GPU_texture_framebuffer_attachment(tex);
+
+ if (!fb)
+ return;
+
+ if (GG.currentfb != fb->object) {
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+ GG.currentfb = fb->object;
+ }
+
+ if (GPU_texture_depth(tex)) {
+ fb->depthtex = NULL;
+ attachment = GL_DEPTH_ATTACHMENT_EXT;
+ }
+ else {
+ BLI_assert(fb->colortex[fb_attachment] == tex);
+ fb->colortex[fb_attachment] = NULL;
+ attachment = GL_COLOR_ATTACHMENT0_EXT + fb_attachment;
+ }
+
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, GPU_texture_target(tex), 0, 0);
+
+ GPU_texture_framebuffer_set(tex, NULL, -1);
+}
+
+void GPU_texture_bind_as_framebuffer(GPUTexture *tex)
+{
+ GPUFrameBuffer *fb = GPU_texture_framebuffer(tex);
+ int fb_attachment = GPU_texture_framebuffer_attachment(tex);
+
+ if (!fb) {
+ fprintf(stderr, "Error, texture not bound to framebuffer!\n");
+ return;
+ }
+
+ /* push attributes */
+ glPushAttrib(GL_ENABLE_BIT | GL_VIEWPORT_BIT);
+ glDisable(GL_SCISSOR_TEST);
+
+ /* bind framebuffer */
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+
+ if (GPU_texture_depth(tex)) {
+ glDrawBuffer(GL_NONE);
+ glReadBuffer(GL_NONE);
+ }
+ else {
+ /* last bound prevails here, better allow explicit control here too */
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + fb_attachment);
+ glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + fb_attachment);
+ }
+
+ if (GPU_texture_target(tex) == GL_TEXTURE_2D_MULTISAMPLE) {
+ glEnable(GL_MULTISAMPLE);
+ }
+
+ /* push matrices and set default viewport and matrix */
+ glViewport(0, 0, GPU_texture_width(tex), GPU_texture_height(tex));
+ GG.currentfb = fb->object;
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+}
+
+void GPU_framebuffer_slots_bind(GPUFrameBuffer *fb, int slot)
+{
+ int numslots = 0, i;
+ GLenum attachments[4];
+
+ if (!fb->colortex[slot]) {
+ fprintf(stderr, "Error, framebuffer slot empty!\n");
+ return;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (fb->colortex[i]) {
+ attachments[numslots] = GL_COLOR_ATTACHMENT0_EXT + i;
+ numslots++;
+ }
+ }
+
+ /* push attributes */
+ glPushAttrib(GL_ENABLE_BIT | GL_VIEWPORT_BIT);
+ glDisable(GL_SCISSOR_TEST);
+
+ /* bind framebuffer */
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+
+ /* last bound prevails here, better allow explicit control here too */
+ glDrawBuffers(numslots, attachments);
+ glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + slot);
+
+ /* push matrices and set default viewport and matrix */
+ glViewport(0, 0, GPU_texture_width(fb->colortex[slot]), GPU_texture_height(fb->colortex[slot]));
+ GG.currentfb = fb->object;
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+}
+
+
+void GPU_framebuffer_texture_unbind(GPUFrameBuffer *UNUSED(fb), GPUTexture *UNUSED(tex))
+{
+ /* restore matrix */
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+
+ /* restore attributes */
+ glPopAttrib();
+}
+
+void GPU_framebuffer_bind_no_save(GPUFrameBuffer *fb, int slot)
+{
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+ /* last bound prevails here, better allow explicit control here too */
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + slot);
+ glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + slot);
+
+ /* push matrices and set default viewport and matrix */
+ glViewport(0, 0, GPU_texture_width(fb->colortex[slot]), GPU_texture_height(fb->colortex[slot]));
+ GG.currentfb = fb->object;
+ GG.currentfb = fb->object;
+}
+
+bool GPU_framebuffer_bound(GPUFrameBuffer *fb)
+{
+ return fb->object == GG.currentfb;
+}
+
+bool GPU_framebuffer_check_valid(GPUFrameBuffer *fb, char err_out[256])
+{
+ GLenum status;
+
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+ GG.currentfb = fb->object;
+
+ /* Clean glError buffer. */
+ while (glGetError() != GL_NO_ERROR) {}
+
+ status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+
+ if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+ GPU_framebuffer_restore();
+ GPU_print_framebuffer_error(status, err_out);
+ return false;
+ }
+
+ return true;
+}
+
+void GPU_framebuffer_free(GPUFrameBuffer *fb)
+{
+ int i;
+ if (fb->depthtex)
+ GPU_framebuffer_texture_detach(fb->depthtex);
+
+ for (i = 0; i < GPU_FB_MAX_SLOTS; i++) {
+ if (fb->colortex[i]) {
+ GPU_framebuffer_texture_detach(fb->colortex[i]);
+ }
+ }
+
+ if (fb->object) {
+ glDeleteFramebuffersEXT(1, &fb->object);
+
+ if (GG.currentfb == fb->object) {
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ GG.currentfb = 0;
+ }
+ }
+
+ MEM_freeN(fb);
+}
+
+void GPU_framebuffer_restore(void)
+{
+ if (GG.currentfb != 0) {
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ GG.currentfb = 0;
+ }
+}
+
+void GPU_framebuffer_blur(GPUFrameBuffer *fb, GPUTexture *tex, GPUFrameBuffer *blurfb, GPUTexture *blurtex)
+{
+ const float scaleh[2] = {1.0f / GPU_texture_width(blurtex), 0.0f};
+ const float scalev[2] = {0.0f, 1.0f / GPU_texture_height(tex)};
+
+ GPUShader *blur_shader = GPU_shader_get_builtin_shader(GPU_SHADER_SEP_GAUSSIAN_BLUR);
+ int scale_uniform, texture_source_uniform;
+
+ if (!blur_shader)
+ return;
+
+ scale_uniform = GPU_shader_get_uniform(blur_shader, "ScaleU");
+ texture_source_uniform = GPU_shader_get_uniform(blur_shader, "textureSource");
+
+ /* Blurring horizontally */
+
+ /* We do the bind ourselves rather than using GPU_framebuffer_texture_bind() to avoid
+ * pushing unnecessary matrices onto the OpenGL stack. */
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, blurfb->object);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ /* avoid warnings from texture binding */
+ GG.currentfb = blurfb->object;
+
+ GPU_shader_bind(blur_shader);
+ GPU_shader_uniform_vector(blur_shader, scale_uniform, 2, 1, scaleh);
+ GPU_shader_uniform_texture(blur_shader, texture_source_uniform, tex);
+ glViewport(0, 0, GPU_texture_width(blurtex), GPU_texture_height(blurtex));
+
+ /* Peparing to draw quad */
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glMatrixMode(GL_TEXTURE);
+ glLoadIdentity();
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glDisable(GL_DEPTH_TEST);
+
+ GPU_texture_bind(tex, 0);
+
+ /* Drawing quad */
+ glBegin(GL_QUADS);
+ glTexCoord2d(0, 0); glVertex2f(1, 1);
+ glTexCoord2d(1, 0); glVertex2f(-1, 1);
+ glTexCoord2d(1, 1); glVertex2f(-1, -1);
+ glTexCoord2d(0, 1); glVertex2f(1, -1);
+ glEnd();
+
+ /* Blurring vertically */
+
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+
+ GG.currentfb = fb->object;
+
+ glViewport(0, 0, GPU_texture_width(tex), GPU_texture_height(tex));
+ GPU_shader_uniform_vector(blur_shader, scale_uniform, 2, 1, scalev);
+ GPU_shader_uniform_texture(blur_shader, texture_source_uniform, blurtex);
+ GPU_texture_bind(blurtex, 0);
+
+ glBegin(GL_QUADS);
+ glTexCoord2d(0, 0); glVertex2f(1, 1);
+ glTexCoord2d(1, 0); glVertex2f(-1, 1);
+ glTexCoord2d(1, 1); glVertex2f(-1, -1);
+ glTexCoord2d(0, 1); glVertex2f(1, -1);
+ glEnd();
+
+ GPU_shader_unbind();
+}
+
+/* GPUOffScreen */
+
+struct GPUOffScreen {
+ GPUFrameBuffer *fb;
+ GPUTexture *color;
+ GPUTexture *depth;
+};
+
+GPUOffScreen *GPU_offscreen_create(int width, int height, int samples, char err_out[256])
+{
+ GPUOffScreen *ofs;
+
+ ofs = MEM_callocN(sizeof(GPUOffScreen), "GPUOffScreen");
+
+ ofs->fb = GPU_framebuffer_create();
+ if (!ofs->fb) {
+ GPU_offscreen_free(ofs);
+ return NULL;
+ }
+
+ if (samples) {
+ if (!GLEW_EXT_framebuffer_multisample ||
+ !GLEW_ARB_texture_multisample ||
+ /* Only needed for GPU_offscreen_read_pixels.
+ * We could add an arg if we intend to use multi-sample
+ * offscreen buffers w/o reading their pixels */
+ !GLEW_EXT_framebuffer_blit ||
+ /* This is required when blitting from a multi-sampled buffers,
+ * even though we're not scaling. */
+ !GLEW_EXT_framebuffer_multisample_blit_scaled)
+ {
+ samples = 0;
+ }
+ }
+
+ ofs->depth = GPU_texture_create_depth_multisample(width, height, samples, err_out);
+ if (!ofs->depth) {
+ GPU_offscreen_free(ofs);
+ return NULL;
+ }
+
+ if (!GPU_framebuffer_texture_attach(ofs->fb, ofs->depth, 0, err_out)) {
+ GPU_offscreen_free(ofs);
+ return NULL;
+ }
+
+ ofs->color = GPU_texture_create_2D_multisample(width, height, NULL, GPU_HDR_NONE, samples, err_out);
+ if (!ofs->color) {
+ GPU_offscreen_free(ofs);
+ return NULL;
+ }
+
+ if (!GPU_framebuffer_texture_attach(ofs->fb, ofs->color, 0, err_out)) {
+ GPU_offscreen_free(ofs);
+ return NULL;
+ }
+
+ /* check validity at the very end! */
+ if (!GPU_framebuffer_check_valid(ofs->fb, err_out)) {
+ GPU_offscreen_free(ofs);
+ return NULL;
+ }
+
+ GPU_framebuffer_restore();
+
+ return ofs;
+}
+
+void GPU_offscreen_free(GPUOffScreen *ofs)
+{
+ if (ofs->fb)
+ GPU_framebuffer_free(ofs->fb);
+ if (ofs->color)
+ GPU_texture_free(ofs->color);
+ if (ofs->depth)
+ GPU_texture_free(ofs->depth);
+
+ MEM_freeN(ofs);
+}
+
+void GPU_offscreen_bind(GPUOffScreen *ofs, bool save)
+{
+ glDisable(GL_SCISSOR_TEST);
+ if (save)
+ GPU_texture_bind_as_framebuffer(ofs->color);
+ else {
+ GPU_framebuffer_bind_no_save(ofs->fb, 0);
+ }
+}
+
+void GPU_offscreen_unbind(GPUOffScreen *ofs, bool restore)
+{
+ if (restore)
+ GPU_framebuffer_texture_unbind(ofs->fb, ofs->color);
+ GPU_framebuffer_restore();
+ glEnable(GL_SCISSOR_TEST);
+}
+
+void GPU_offscreen_read_pixels(GPUOffScreen *ofs, int type, void *pixels)
+{
+ const int w = GPU_texture_width(ofs->color);
+ const int h = GPU_texture_height(ofs->color);
+
+ if (GPU_texture_target(ofs->color) == GL_TEXTURE_2D_MULTISAMPLE) {
+ /* For a multi-sample texture,
+ * we need to create an intermediate buffer to blit to,
+ * before its copied using 'glReadPixels' */
+
+ /* not needed since 'ofs' needs to be bound to the framebuffer already */
+// #define USE_FBO_CTX_SWITCH
+
+ GLuint fbo_blit = 0;
+ GLuint tex_blit = 0;
+ GLenum status;
+
+ /* create texture for new 'fbo_blit' */
+ glGenTextures(1, &tex_blit);
+ if (!tex_blit) {
+ goto finally;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, tex_blit);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, type, 0);
+
+#ifdef USE_FBO_CTX_SWITCH
+ /* read from multi-sample buffer */
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, ofs->color->fb->object);
+ glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + ofs->color->fb_attachment,
+ GL_TEXTURE_2D_MULTISAMPLE, ofs->color->bindcode, 0);
+ status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER_EXT);
+ if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+ goto finally;
+ }
+#endif
+
+ /* write into new single-sample buffer */
+ glGenFramebuffersEXT(1, &fbo_blit);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, fbo_blit);
+ glFramebufferTexture2DEXT(
+ GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, tex_blit, 0);
+ status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER_EXT);
+ if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+ goto finally;
+ }
+
+ /* perform the copy */
+ glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ /* read the results */
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, fbo_blit);
+ glReadPixels(0, 0, w, h, GL_RGBA, type, pixels);
+
+#ifdef USE_FBO_CTX_SWITCH
+ /* restore the original frame-bufer */
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ofs->color->fb->object);
+#undef USE_FBO_CTX_SWITCH
+#endif
+
+
+finally:
+ /* cleanup */
+ if (tex_blit) {
+ glDeleteTextures(1, &tex_blit);
+ }
+ if (fbo_blit) {
+ glDeleteFramebuffersEXT(1, &fbo_blit);
+ }
+
+ GPU_ASSERT_NO_GL_ERRORS("Read Multi-Sample Pixels");
+ }
+ else {
+ glReadPixels(0, 0, w, h, GL_RGBA, type, pixels);
+ }
+}
+
+int GPU_offscreen_width(const GPUOffScreen *ofs)
+{
+ return GPU_texture_width(ofs->color);
+}
+
+int GPU_offscreen_height(const GPUOffScreen *ofs)
+{
+ return GPU_texture_height(ofs->color);
+}
+
+int GPU_offscreen_color_texture(const GPUOffScreen *ofs)
+{
+ return GPU_texture_opengl_bindcode(ofs->color);
+}
+