mutter/cogl/cogl-framebuffer.c
Robert Bragg 944423a8d9 cogl: deprecate cogl_draw_buffer API and replace with a cogl_framebuffer API
cogl_push_draw_buffer, cogl_set_draw_buffer and cogl_pop_draw_buffer are now
deprecated and new code should use the new cogl_framebuffer_* API instead.

Code that previously did:
    cogl_push_draw_buffer ();
    cogl_set_draw_buffer (COGL_OFFSCREEN_BUFFER, buffer);
    /* draw */
    cogl_pop_draw_buffer ();
should now be re-written as:
    cogl_push_framebuffer (buffer);
    /* draw */
    cogl_pop_framebuffer ();

As can be seen from the example above the rename has been used as an
opportunity to remove the redundant target argument from
cogl_set_draw_buffer; it now only takes one call to redirect to an offscreen
buffer, and finally the term framebuffer may be a bit more familiar to
anyone coming from an OpenGL background.
2009-11-26 19:33:14 +00:00

619 lines
17 KiB
C

/*
* Cogl
*
* An object oriented GL/GLES Abstraction/Utility Layer
*
* Copyright (C) 2007,2008,2009 Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "cogl.h"
#include "cogl-internal.h"
#include "cogl-context.h"
#include "cogl-handle.h"
#include "cogl-util.h"
#include "cogl-texture-private.h"
#include "cogl-framebuffer-private.h"
#include "cogl-clip-stack.h"
#ifdef HAVE_COGL_GLES2
#include "../gles/cogl-gles2-wrapper.h"
#else
#define glGenRenderbuffers ctx->drv.pf_glGenRenderbuffers
#define glDeleteRenderbuffers ctx->drv.pf_glDeleteRenderbuffers
#define glBindRenderbuffer ctx->drv.pf_glBindRenderbuffer
#define glRenderbufferStorage ctx->drv.pf_glRenderbufferStorage
#define glGenFramebuffers ctx->drv.pf_glGenFramebuffers
#define glBindFramebuffer ctx->drv.pf_glBindFramebuffer
#define glFramebufferTexture2D ctx->drv.pf_glFramebufferTexture2D
#define glFramebufferRenderbuffer ctx->drv.pf_glFramebufferRenderbuffer
#define glCheckFramebufferStatus ctx->drv.pf_glCheckFramebufferStatus
#define glDeleteFramebuffers ctx->drv.pf_glDeleteFramebuffers
#endif
#ifndef GL_FRAMEBUFFER
#define GL_FRAMEBUFFER 0x8D40
#endif
#ifndef GL_RENDERBUFFER
#define GL_RENDERBUFFER 0x8D41
#endif
#ifndef GL_STENCIL_ATTACHMENT
#define GL_STENCIL_ATTACHMENT 0x8D00
#endif
#ifndef GL_COLOR_ATTACHMENT0
#define GL_COLOR_ATTACHMENT0 0x8CE0
#endif
#ifndef GL_FRAMEBUFFER_COMPLETE
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#endif
#ifndef GL_STENCIL_INDEX8
#define GL_STENCIL_INDEX8 0x8D48
#endif
static void _cogl_framebuffer_free (CoglFramebuffer *framebuffer);
static void _cogl_onscreen_free (CoglOnscreen *onscreen);
static void _cogl_offscreen_free (CoglOffscreen *offscreen);
COGL_HANDLE_DEFINE (Onscreen, onscreen);
COGL_HANDLE_DEFINE (Offscreen, offscreen);
/* XXX:
* The CoglHandle macros don't support any form of inheritance, so for
* now we implement the CoglHandle support for the CoglFramebuffer
* abstract class manually.
*/
gboolean
cogl_is_framebuffer (CoglHandle handle)
{
CoglHandleObject *obj = (CoglHandleObject *)handle;
if (handle == COGL_INVALID_HANDLE)
return FALSE;
return obj->klass->type == _cogl_handle_onscreen_get_type ()
|| obj->klass->type == _cogl_handle_offscreen_get_type ();
}
static void
_cogl_framebuffer_init (CoglFramebuffer *framebuffer,
CoglFramebufferType type,
int width,
int height)
{
framebuffer->type = type;
framebuffer->width = width;
framebuffer->height = height;
framebuffer->viewport_x = 0;
framebuffer->viewport_y = 0;
framebuffer->viewport_width = width;
framebuffer->viewport_height = height;
framebuffer->modelview_stack = _cogl_matrix_stack_new ();
framebuffer->projection_stack = _cogl_matrix_stack_new ();
/* Initialise the clip stack */
_cogl_clip_stack_state_init (&framebuffer->clip_state);
}
void
_cogl_framebuffer_free (CoglFramebuffer *framebuffer)
{
_cogl_clip_stack_state_destroy (&framebuffer->clip_state);
_cogl_matrix_stack_destroy (framebuffer->modelview_stack);
framebuffer->modelview_stack = NULL;
_cogl_matrix_stack_destroy (framebuffer->projection_stack);
framebuffer->projection_stack = NULL;
}
int
_cogl_framebuffer_get_width (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->width;
}
int
_cogl_framebuffer_get_height (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->height;
}
CoglClipStackState *
_cogl_framebuffer_get_clip_state (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return &framebuffer->clip_state;
}
void
_cogl_framebuffer_set_viewport (CoglHandle handle,
int x,
int y,
int width,
int height)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (framebuffer->viewport_x == x &&
framebuffer->viewport_y == y &&
framebuffer->viewport_width == width &&
framebuffer->viewport_height == height)
return;
_cogl_journal_flush ();
framebuffer->viewport_x = x;
framebuffer->viewport_y = y;
framebuffer->viewport_width = width;
framebuffer->viewport_height = height;
if (_cogl_get_framebuffer () == framebuffer)
ctx->dirty_gl_viewport = TRUE;
}
int
_cogl_framebuffer_get_viewport_x (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->viewport_x;
}
int
_cogl_framebuffer_get_viewport_y (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->viewport_y;
}
int
_cogl_framebuffer_get_viewport_width (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->viewport_width;
}
int
_cogl_framebuffer_get_viewport_height (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->viewport_height;
}
void
_cogl_framebuffer_get_viewport4fv (CoglHandle handle, int *viewport)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
viewport[0] = framebuffer->viewport_x;
viewport[1] = framebuffer->viewport_y;
viewport[2] = framebuffer->viewport_width;
viewport[3] = framebuffer->viewport_height;
}
CoglMatrixStack *
_cogl_framebuffer_get_modelview_stack (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->modelview_stack;
}
CoglMatrixStack *
_cogl_framebuffer_get_projection_stack (CoglHandle handle)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (handle);
return framebuffer->projection_stack;
}
CoglHandle
cogl_offscreen_new_to_texture (CoglHandle texhandle)
{
CoglOffscreen *offscreen;
int width;
int height;
GLuint tex_gl_handle;
GLenum tex_gl_target;
GLuint fbo_gl_handle;
GLuint gl_stencil_handle;
GLenum status;
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
if (!cogl_features_available (COGL_FEATURE_OFFSCREEN))
return COGL_INVALID_HANDLE;
/* Make texhandle is a valid texture object */
if (!cogl_is_texture (texhandle))
return COGL_INVALID_HANDLE;
/* The texture must not be sliced */
if (cogl_texture_is_sliced (texhandle))
return COGL_INVALID_HANDLE;
/* Pick the single texture slice width, height and GL id */
width = cogl_texture_get_width (texhandle);
height = cogl_texture_get_height (texhandle);
if (!cogl_texture_get_gl_texture (texhandle, &tex_gl_handle, &tex_gl_target))
return COGL_INVALID_HANDLE;
if (tex_gl_target != GL_TEXTURE_2D &&
tex_gl_target != GL_TEXTURE_RECTANGLE_ARB)
return COGL_INVALID_HANDLE;
/* Create a renderbuffer for stenciling */
GE (glGenRenderbuffers (1, &gl_stencil_handle));
GE (glBindRenderbuffer (GL_RENDERBUFFER, gl_stencil_handle));
GE (glRenderbufferStorage (GL_RENDERBUFFER, GL_STENCIL_INDEX8,
cogl_texture_get_width (texhandle),
cogl_texture_get_height (texhandle)));
GE (glBindRenderbuffer (GL_RENDERBUFFER, 0));
/* We are about to generate and bind a new fbo, so when next flushing the
* journal, we will need to rebind the current framebuffer... */
ctx->dirty_bound_framebuffer = 1;
/* Generate framebuffer */
glGenFramebuffers (1, &fbo_gl_handle);
GE (glBindFramebuffer (GL_FRAMEBUFFER, fbo_gl_handle));
GE (glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex_gl_target, tex_gl_handle, 0));
GE (glFramebufferRenderbuffer (GL_FRAMEBUFFER,
GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, gl_stencil_handle));
/* XXX: The framebuffer_object spec isn't clear in defining whether attaching
* a texture as a renderbuffer with mipmap filtering enabled while the
* mipmaps have not been uploaded should result in an incomplete framebuffer
* object. (different drivers make different decisions)
*
* To avoid an error with drivers that do consider this a problem we
* explicitly set non mipmapped filters here. These will later be reset when
* the texture is actually used for rendering according to the filters set on
* the corresponding CoglMaterial.
*/
_cogl_texture_set_filters (texhandle, GL_NEAREST, GL_NEAREST);
/* Make sure it's complete */
status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
/* Stencil renderbuffers aren't always supported. Try again
without the stencil buffer */
GE (glFramebufferRenderbuffer (GL_FRAMEBUFFER,
GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER,
0));
GE (glDeleteRenderbuffers (1, &gl_stencil_handle));
gl_stencil_handle = 0;
status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
/* Still failing, so give up */
GE (glDeleteFramebuffers (1, &fbo_gl_handle));
GE (glBindFramebuffer (GL_FRAMEBUFFER, 0));
return COGL_INVALID_HANDLE;
}
}
offscreen = g_new0 (CoglOffscreen, 1);
_cogl_framebuffer_init (COGL_FRAMEBUFFER (offscreen),
COGL_FRAMEBUFFER_TYPE_OFFSCREEN,
width,
height);
offscreen->fbo_handle = fbo_gl_handle;
offscreen->gl_stencil_handle = gl_stencil_handle;
/* XXX: Can we get a away with removing this? It wasn't documented, and most
* users of the API are hopefully setting up the modelview from scratch
* anyway */
#if 0
cogl_matrix_translate (&framebuffer->modelview, -1.0f, -1.0f, 0.0f);
cogl_matrix_scale (&framebuffer->modelview,
2.0f / framebuffer->width, 2.0f / framebuffer->height, 1.0f);
#endif
return _cogl_offscreen_handle_new (offscreen);
}
static void
_cogl_offscreen_free (CoglOffscreen *offscreen)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* Chain up to parent */
_cogl_framebuffer_free (COGL_FRAMEBUFFER (offscreen));
if (offscreen->gl_stencil_handle)
GE (glDeleteRenderbuffers (1, &offscreen->gl_stencil_handle));
GE (glDeleteFramebuffers (1, &offscreen->fbo_handle));
g_free (offscreen);
}
CoglHandle
_cogl_onscreen_new (void)
{
CoglOnscreen *onscreen;
/* XXX: Until we have full winsys support in Cogl then we can't fully
* implement CoglOnscreen framebuffers, since we can't, e.g. keep track of
* the window size. */
onscreen = g_new0 (CoglOnscreen, 1);
_cogl_framebuffer_init (COGL_FRAMEBUFFER (onscreen),
COGL_FRAMEBUFFER_TYPE_ONSCREEN,
0xdeadbeef, /* width */
0xdeadbeef); /* height */
return _cogl_onscreen_handle_new (onscreen);
}
static void
_cogl_onscreen_free (CoglOnscreen *onscreen)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* Chain up to parent */
_cogl_framebuffer_free (COGL_FRAMEBUFFER (onscreen));
g_free (onscreen);
}
void
_cogl_onscreen_clutter_backend_set_size (int width, int height)
{
CoglFramebuffer *framebuffer;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
framebuffer = COGL_FRAMEBUFFER (ctx->window_buffer);
if (framebuffer->width == width && framebuffer->height == height)
return;
framebuffer->width = width;
framebuffer->height = height;
/* We'll need to recalculate the GL viewport state derived
* from the Cogl viewport */
ctx->dirty_gl_viewport = 1;
}
GSList *
_cogl_create_framebuffer_stack (void)
{
GSList *stack = NULL;
return g_slist_prepend (stack, COGL_INVALID_HANDLE);
}
void
_cogl_free_framebuffer_stack (GSList *stack)
{
GSList *l;
for (l = stack; l != NULL; l = l->next)
{
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (l->data);
if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_OFFSCREEN)
_cogl_offscreen_free (COGL_OFFSCREEN (framebuffer));
else
_cogl_onscreen_free (COGL_ONSCREEN (framebuffer));
}
g_slist_free (stack);
}
/* Set the current framebuffer without checking if it's already the
* current framebuffer. This is used by cogl_pop_framebuffer while
* the top of the stack is currently not up to date. */
static void
_cogl_set_framebuffer_real (CoglFramebuffer *framebuffer)
{
CoglHandle *entry;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
cogl_flush ();
entry = &ctx->framebuffer_stack->data;
ctx->dirty_bound_framebuffer = 1;
ctx->dirty_gl_viewport = 1;
if (framebuffer != COGL_INVALID_HANDLE)
cogl_handle_ref (framebuffer);
if (*entry != COGL_INVALID_HANDLE)
cogl_handle_unref (*entry);
*entry = framebuffer;
/* We've effectively just switched the current modelview and
* projection matrix stacks and clip state so we need to dirty
* them to ensure they get flushed for the next batch of geometry
* we flush */
_cogl_matrix_stack_dirty (framebuffer->modelview_stack);
_cogl_matrix_stack_dirty (framebuffer->projection_stack);
_cogl_clip_stack_state_dirty (&framebuffer->clip_state);
}
void
cogl_set_framebuffer (CoglHandle handle)
{
g_return_if_fail (cogl_is_framebuffer (handle));
if (_cogl_get_framebuffer () != handle)
_cogl_set_framebuffer_real (COGL_FRAMEBUFFER (handle));
}
/* XXX: deprecated API */
void
cogl_set_draw_buffer (CoglBufferTarget target, CoglHandle handle)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (target == COGL_WINDOW_BUFFER)
handle = ctx->window_buffer;
cogl_set_framebuffer (handle);
}
CoglHandle
_cogl_get_framebuffer (void)
{
_COGL_GET_CONTEXT (ctx, NULL);
g_assert (ctx->framebuffer_stack);
return (CoglHandle)ctx->framebuffer_stack->data;
}
void
cogl_push_framebuffer (CoglHandle buffer)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
g_return_if_fail (cogl_is_framebuffer (buffer));
g_assert (ctx->framebuffer_stack);
cogl_flush ();
ctx->framebuffer_stack =
g_slist_prepend (ctx->framebuffer_stack, COGL_INVALID_HANDLE);
cogl_set_framebuffer (buffer);
}
/* XXX: deprecated API */
void
cogl_push_draw_buffer (void)
{
cogl_push_framebuffer (_cogl_get_framebuffer ());
}
void
cogl_pop_framebuffer (void)
{
CoglHandle to_pop;
CoglHandle to_restore;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
g_assert (ctx->framebuffer_stack != NULL);
g_assert (ctx->framebuffer_stack->next != NULL);
to_pop = ctx->framebuffer_stack->data;
to_restore = ctx->framebuffer_stack->next->data;
cogl_flush ();
cogl_handle_unref (to_pop);
ctx->framebuffer_stack =
g_slist_remove_link (ctx->framebuffer_stack,
ctx->framebuffer_stack);
/* If the framebuffer has changed as a result of popping the top
* then re-assert the current buffer so as to dirty state as
* necessary. */
if (to_pop != to_restore)
_cogl_set_framebuffer_real (to_restore);
}
/* XXX: deprecated API */
void
cogl_pop_draw_buffer (void)
{
cogl_pop_framebuffer ();
}
void
_cogl_framebuffer_flush_state (CoglHandle handle,
CoglFramebufferFlushFlags flags)
{
CoglFramebuffer *framebuffer;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
framebuffer = COGL_FRAMEBUFFER (handle);
if (cogl_features_available (COGL_FEATURE_OFFSCREEN) &&
ctx->dirty_bound_framebuffer)
{
if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_OFFSCREEN)
{
GE (glBindFramebuffer (GL_FRAMEBUFFER,
COGL_OFFSCREEN (framebuffer)->fbo_handle));
}
else
GE (glBindFramebuffer (GL_FRAMEBUFFER, 0));
ctx->dirty_bound_framebuffer = FALSE;
}
if (ctx->dirty_gl_viewport)
{
int gl_viewport_y;
/* Convert the Cogl viewport y offset to an OpenGL viewport y offset
* NB: OpenGL defines its window and viewport origins to be bottom
* left, while Cogl defines them to be top left.
* NB: We render upside down to offscreen framebuffers so we don't
* need to convert the y offset in this case. */
if (cogl_is_offscreen (framebuffer))
gl_viewport_y = framebuffer->viewport_y;
else
gl_viewport_y = framebuffer->height -
(framebuffer->viewport_y + framebuffer->viewport_height);
GE (glViewport (framebuffer->viewport_x,
gl_viewport_y,
framebuffer->viewport_width,
framebuffer->viewport_height));
ctx->dirty_gl_viewport = FALSE;
}
/* XXX: Flushing clip state may trash the modelview and projection
* matrices so we must do it before flushing the matrices...
*/
_cogl_flush_clip_state (&framebuffer->clip_state);
if (!(flags & COGL_FRAMEBUFFER_FLUSH_SKIP_MODELVIEW))
_cogl_matrix_stack_flush_to_gl (framebuffer->modelview_stack,
COGL_MATRIX_MODELVIEW);
_cogl_matrix_stack_flush_to_gl (framebuffer->projection_stack,
COGL_MATRIX_PROJECTION);
}