| /* cairo - a vector graphics library with display and print output |
| * |
| * Copyright © 2009 Eric Anholt |
| * Copyright © 2009 Chris Wilson |
| * Copyright © 2005,2010 Red Hat, Inc |
| * Copyright © 2010 Linaro Limited |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it either under the terms of the GNU Lesser General Public |
| * License version 2.1 as published by the Free Software Foundation |
| * (the "LGPL") or, at your option, under the terms of the Mozilla |
| * Public License Version 1.1 (the "MPL"). If you do not alter this |
| * notice, a recipient may use your version of this file under either |
| * the MPL or the LGPL. |
| * |
| * You should have received a copy of the LGPL along with this library |
| * in the file COPYING-LGPL-2.1; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA |
| * You should have received a copy of the MPL along with this library |
| * in the file COPYING-MPL-1.1 |
| * |
| * The contents of this file are subject to the Mozilla Public License |
| * Version 1.1 (the "License"); you may not use this file except in |
| * compliance with the License. You may obtain a copy of the License at |
| * http://www.mozilla.org/MPL/ |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY |
| * OF ANY KIND, either express or implied. See the LGPL or the MPL for |
| * the specific language governing rights and limitations. |
| * |
| * The Original Code is the cairo graphics library. |
| * |
| * The Initial Developer of the Original Code is Red Hat, Inc. |
| * |
| * Contributor(s): |
| * Benjamin Otte <otte@gnome.org> |
| * Carl Worth <cworth@cworth.org> |
| * Chris Wilson <chris@chris-wilson.co.uk> |
| * Eric Anholt <eric@anholt.net> |
| * Alexandros Frantzis <alexandros.frantzis@linaro.org> |
| */ |
| |
| #include "cairoint.h" |
| |
| #include "cairo-error-private.h" |
| #include "cairo-gl-private.h" |
| |
| #define MAX_MSAA_SAMPLES 4 |
| |
| static void |
| _gl_lock (void *device) |
| { |
| cairo_gl_context_t *ctx = (cairo_gl_context_t *) device; |
| |
| ctx->acquire (ctx); |
| } |
| |
| static void |
| _gl_unlock (void *device) |
| { |
| cairo_gl_context_t *ctx = (cairo_gl_context_t *) device; |
| |
| ctx->release (ctx); |
| } |
| |
| static cairo_status_t |
| _gl_flush (void *device) |
| { |
| cairo_gl_context_t *ctx; |
| cairo_status_t status; |
| |
| status = _cairo_gl_context_acquire (device, &ctx); |
| if (unlikely (status)) |
| return status; |
| |
| _cairo_gl_composite_flush (ctx); |
| |
| _cairo_gl_context_destroy_operand (ctx, CAIRO_GL_TEX_SOURCE); |
| _cairo_gl_context_destroy_operand (ctx, CAIRO_GL_TEX_MASK); |
| |
| if (ctx->clip_region) { |
| cairo_region_destroy (ctx->clip_region); |
| ctx->clip_region = NULL; |
| } |
| |
| ctx->current_target = NULL; |
| ctx->current_operator = -1; |
| ctx->vertex_size = 0; |
| ctx->pre_shader = NULL; |
| _cairo_gl_set_shader (ctx, NULL); |
| |
| ctx->dispatch.BindBuffer (GL_ARRAY_BUFFER, 0); |
| |
| glDisable (GL_SCISSOR_TEST); |
| glDisable (GL_BLEND); |
| |
| return _cairo_gl_context_release (ctx, status); |
| } |
| |
| static void |
| _gl_finish (void *device) |
| { |
| cairo_gl_context_t *ctx = device; |
| int n; |
| |
| _gl_lock (device); |
| |
| _cairo_cache_fini (&ctx->gradients); |
| |
| _cairo_gl_context_fini_shaders (ctx); |
| |
| for (n = 0; n < ARRAY_LENGTH (ctx->glyph_cache); n++) |
| _cairo_gl_glyph_cache_fini (ctx, &ctx->glyph_cache[n]); |
| |
| _gl_unlock (device); |
| } |
| |
| static void |
| _gl_destroy (void *device) |
| { |
| cairo_gl_context_t *ctx = device; |
| |
| ctx->acquire (ctx); |
| |
| while (! cairo_list_is_empty (&ctx->fonts)) { |
| cairo_gl_font_t *font; |
| |
| font = cairo_list_first_entry (&ctx->fonts, |
| cairo_gl_font_t, |
| link); |
| |
| cairo_list_del (&font->base.link); |
| cairo_list_del (&font->link); |
| free (font); |
| } |
| |
| _cairo_array_fini (&ctx->tristrip_indices); |
| |
| cairo_region_destroy (ctx->clip_region); |
| _cairo_clip_destroy (ctx->clip); |
| |
| free (ctx->vb); |
| |
| ctx->destroy (ctx); |
| |
| free (ctx); |
| } |
| |
| static const cairo_device_backend_t _cairo_gl_device_backend = { |
| CAIRO_DEVICE_TYPE_GL, |
| |
| _gl_lock, |
| _gl_unlock, |
| |
| _gl_flush, /* flush */ |
| _gl_finish, |
| _gl_destroy, |
| }; |
| |
| static cairo_bool_t |
| _cairo_gl_msaa_compositor_enabled (void) |
| { |
| const char *env = getenv ("CAIRO_GL_COMPOSITOR"); |
| return env && strcmp(env, "msaa") == 0; |
| } |
| |
| static cairo_bool_t |
| test_can_read_bgra (cairo_gl_flavor_t gl_flavor) |
| { |
| /* Desktop GL always supports BGRA formats. */ |
| if (gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) |
| return TRUE; |
| |
| assert (gl_flavor == CAIRO_GL_FLAVOR_ES); |
| |
| /* For OpenGL ES we have to look for the specific extension and BGRA only |
| * matches cairo's integer packed bytes on little-endian machines. */ |
| if (!_cairo_is_little_endian()) |
| return FALSE; |
| return _cairo_gl_has_extension ("EXT_read_format_bgra"); |
| } |
| |
| cairo_status_t |
| _cairo_gl_context_init (cairo_gl_context_t *ctx) |
| { |
| cairo_status_t status; |
| cairo_gl_dispatch_t *dispatch = &ctx->dispatch; |
| int gl_version = _cairo_gl_get_version (); |
| cairo_gl_flavor_t gl_flavor = _cairo_gl_get_flavor (); |
| int n; |
| |
| cairo_bool_t is_desktop = gl_flavor == CAIRO_GL_FLAVOR_DESKTOP; |
| cairo_bool_t is_gles = gl_flavor == CAIRO_GL_FLAVOR_ES; |
| |
| _cairo_device_init (&ctx->base, &_cairo_gl_device_backend); |
| |
| /* XXX The choice of compositor should be made automatically at runtime. |
| * However, it is useful to force one particular compositor whilst |
| * testing. |
| */ |
| if (_cairo_gl_msaa_compositor_enabled ()) |
| ctx->compositor = _cairo_gl_msaa_compositor_get (); |
| else |
| ctx->compositor = _cairo_gl_span_compositor_get (); |
| |
| |
| ctx->thread_aware = TRUE; |
| |
| memset (ctx->glyph_cache, 0, sizeof (ctx->glyph_cache)); |
| cairo_list_init (&ctx->fonts); |
| |
| /* Support only GL version >= 1.3 */ |
| if (gl_version < CAIRO_GL_VERSION_ENCODE (1, 3)) |
| return _cairo_error (CAIRO_STATUS_DEVICE_ERROR); |
| |
| /* Check for required extensions */ |
| if (is_desktop) { |
| if (_cairo_gl_has_extension ("GL_ARB_texture_non_power_of_two")) { |
| ctx->tex_target = GL_TEXTURE_2D; |
| ctx->has_npot_repeat = TRUE; |
| } else if (_cairo_gl_has_extension ("GL_ARB_texture_rectangle")) { |
| ctx->tex_target = GL_TEXTURE_RECTANGLE; |
| ctx->has_npot_repeat = FALSE; |
| } else |
| return _cairo_error (CAIRO_STATUS_DEVICE_ERROR); |
| } else { |
| ctx->tex_target = GL_TEXTURE_2D; |
| if (_cairo_gl_has_extension ("GL_OES_texture_npot") || |
| _cairo_gl_has_extension ("GL_IMG_texture_npot")) |
| ctx->has_npot_repeat = TRUE; |
| else |
| ctx->has_npot_repeat = FALSE; |
| } |
| |
| if (is_desktop && gl_version < CAIRO_GL_VERSION_ENCODE (2, 1) && |
| ! _cairo_gl_has_extension ("GL_ARB_pixel_buffer_object")) |
| return _cairo_error (CAIRO_STATUS_DEVICE_ERROR); |
| |
| if (is_gles && ! _cairo_gl_has_extension ("GL_EXT_texture_format_BGRA8888")) |
| return _cairo_error (CAIRO_STATUS_DEVICE_ERROR); |
| |
| ctx->has_map_buffer = |
| is_desktop || (is_gles && _cairo_gl_has_extension ("GL_OES_mapbuffer")); |
| |
| ctx->can_read_bgra = test_can_read_bgra (gl_flavor); |
| |
| ctx->has_mesa_pack_invert = |
| _cairo_gl_has_extension ("GL_MESA_pack_invert"); |
| |
| ctx->has_packed_depth_stencil = |
| (is_desktop && _cairo_gl_has_extension ("GL_EXT_packed_depth_stencil")) || |
| (is_gles && _cairo_gl_has_extension ("GL_OES_packed_depth_stencil")); |
| |
| ctx->num_samples = 1; |
| |
| #if CAIRO_HAS_GL_SURFACE |
| if (is_desktop && ctx->has_packed_depth_stencil && |
| (gl_version >= CAIRO_GL_VERSION_ENCODE (3, 0) || |
| _cairo_gl_has_extension ("GL_ARB_framebuffer_object") || |
| (_cairo_gl_has_extension ("GL_EXT_framebuffer_blit") && |
| _cairo_gl_has_extension ("GL_EXT_framebuffer_multisample")))) { |
| glGetIntegerv(GL_MAX_SAMPLES_EXT, &ctx->num_samples); |
| } |
| #endif |
| |
| #if CAIRO_HAS_GLESV2_SURFACE && defined(GL_MAX_SAMPLES_EXT) |
| if (is_gles && ctx->has_packed_depth_stencil && |
| _cairo_gl_has_extension ("GL_EXT_multisampled_render_to_texture")) { |
| glGetIntegerv(GL_MAX_SAMPLES_EXT, &ctx->num_samples); |
| } |
| #endif |
| |
| #if CAIRO_HAS_GLESV2_SURFACE && defined(GL_MAX_SAMPLES_IMG) |
| if (is_gles && ctx->has_packed_depth_stencil && |
| _cairo_gl_has_extension ("GL_IMG_multisampled_render_to_texture")) { |
| glGetIntegerv(GL_MAX_SAMPLES_IMG, &ctx->num_samples); |
| } |
| #endif |
| |
| ctx->supports_msaa = ctx->num_samples > 1; |
| if (ctx->num_samples > MAX_MSAA_SAMPLES) |
| ctx->num_samples = MAX_MSAA_SAMPLES; |
| |
| |
| ctx->current_operator = -1; |
| ctx->gl_flavor = gl_flavor; |
| |
| status = _cairo_gl_context_init_shaders (ctx); |
| if (unlikely (status)) |
| return status; |
| |
| status = _cairo_cache_init (&ctx->gradients, |
| _cairo_gl_gradient_equal, |
| NULL, |
| (cairo_destroy_func_t) _cairo_gl_gradient_destroy, |
| CAIRO_GL_GRADIENT_CACHE_SIZE); |
| if (unlikely (status)) |
| return status; |
| |
| ctx->vbo_size = _cairo_gl_get_vbo_size(); |
| |
| ctx->vb = malloc (ctx->vbo_size); |
| if (unlikely (ctx->vb == NULL)) { |
| _cairo_cache_fini (&ctx->gradients); |
| return _cairo_error (CAIRO_STATUS_NO_MEMORY); |
| } |
| |
| ctx->primitive_type = CAIRO_GL_PRIMITIVE_TYPE_TRIANGLES; |
| _cairo_array_init (&ctx->tristrip_indices, sizeof (unsigned short)); |
| |
| /* PBO for any sort of texture upload */ |
| dispatch->GenBuffers (1, &ctx->texture_load_pbo); |
| |
| ctx->max_framebuffer_size = 0; |
| glGetIntegerv (GL_MAX_RENDERBUFFER_SIZE, &ctx->max_framebuffer_size); |
| ctx->max_texture_size = 0; |
| glGetIntegerv (GL_MAX_TEXTURE_SIZE, &ctx->max_texture_size); |
| ctx->max_textures = 0; |
| glGetIntegerv (GL_MAX_TEXTURE_IMAGE_UNITS, &ctx->max_textures); |
| |
| for (n = 0; n < ARRAY_LENGTH (ctx->glyph_cache); n++) |
| _cairo_gl_glyph_cache_init (&ctx->glyph_cache[n]); |
| |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| void |
| _cairo_gl_context_activate (cairo_gl_context_t *ctx, |
| cairo_gl_tex_t tex_unit) |
| { |
| if (ctx->max_textures <= (GLint) tex_unit) { |
| if (tex_unit < 2) { |
| _cairo_gl_composite_flush (ctx); |
| _cairo_gl_context_destroy_operand (ctx, ctx->max_textures - 1); |
| } |
| glActiveTexture (ctx->max_textures - 1); |
| } else { |
| glActiveTexture (GL_TEXTURE0 + tex_unit); |
| } |
| } |
| |
| static GLenum |
| _get_depth_stencil_format (cairo_gl_context_t *ctx) |
| { |
| /* This is necessary to properly handle the situation where both |
| OpenGL and OpenGLES are active and returning a sane default. */ |
| #if CAIRO_HAS_GL_SURFACE |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) |
| return GL_DEPTH_STENCIL; |
| #endif |
| |
| #if CAIRO_HAS_GLESV2_SURFACE |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) |
| return GL_DEPTH24_STENCIL8_OES; |
| #endif |
| |
| #if CAIRO_HAS_GL_SURFACE |
| return GL_DEPTH_STENCIL; |
| #elif CAIRO_HAS_GLESV2_SURFACE |
| return GL_DEPTH24_STENCIL8_OES; |
| #endif |
| } |
| |
| #if CAIRO_HAS_GLESV2_SURFACE |
| static void |
| _cairo_gl_ensure_msaa_gles_framebuffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| if (surface->msaa_active) |
| return; |
| |
| ctx->dispatch.FramebufferTexture2DMultisample(GL_FRAMEBUFFER, |
| GL_COLOR_ATTACHMENT0, |
| ctx->tex_target, |
| surface->tex, |
| 0, |
| ctx->num_samples); |
| |
| /* From now on MSAA will always be active on this surface. */ |
| surface->msaa_active = TRUE; |
| } |
| #endif |
| |
| static void |
| _cairo_gl_ensure_framebuffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| GLenum status; |
| cairo_gl_dispatch_t *dispatch = &ctx->dispatch; |
| |
| if (likely (surface->fb)) |
| return; |
| |
| /* Create a framebuffer object wrapping the texture so that we can render |
| * to it. |
| */ |
| dispatch->GenFramebuffers (1, &surface->fb); |
| dispatch->BindFramebuffer (GL_FRAMEBUFFER, surface->fb); |
| |
| /* Unlike for desktop GL we only maintain one multisampling framebuffer |
| for OpenGLES since the EXT_multisampled_render_to_texture extension |
| does not require an explicit multisample resolution. */ |
| #if CAIRO_HAS_GLESV2_SURFACE |
| if (surface->supports_msaa && _cairo_gl_msaa_compositor_enabled () && |
| ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) { |
| _cairo_gl_ensure_msaa_gles_framebuffer (ctx, surface); |
| } else |
| #endif |
| dispatch->FramebufferTexture2D (GL_FRAMEBUFFER, |
| GL_COLOR_ATTACHMENT0, |
| ctx->tex_target, |
| surface->tex, |
| 0); |
| |
| #if CAIRO_HAS_GL_SURFACE |
| glDrawBuffer (GL_COLOR_ATTACHMENT0); |
| glReadBuffer (GL_COLOR_ATTACHMENT0); |
| #endif |
| |
| status = dispatch->CheckFramebufferStatus (GL_FRAMEBUFFER); |
| if (status != GL_FRAMEBUFFER_COMPLETE) { |
| const char *str; |
| switch (status) { |
| //case GL_FRAMEBUFFER_UNDEFINED: str= "undefined"; break; |
| case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: str= "incomplete attachment"; break; |
| case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: str= "incomplete/missing attachment"; break; |
| case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: str= "incomplete draw buffer"; break; |
| case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: str= "incomplete read buffer"; break; |
| case GL_FRAMEBUFFER_UNSUPPORTED: str= "unsupported"; break; |
| case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: str= "incomplete multiple"; break; |
| default: str = "unknown error"; break; |
| } |
| |
| fprintf (stderr, |
| "destination is framebuffer incomplete: %s [%#x]\n", |
| str, status); |
| } |
| } |
| #if CAIRO_HAS_GL_SURFACE |
| static void |
| _cairo_gl_ensure_multisampling (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| assert (surface->supports_msaa); |
| assert (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP); |
| |
| if (surface->msaa_fb) |
| return; |
| |
| /* We maintain a separate framebuffer for multisampling operations. |
| This allows us to do a fast paint to the non-multisampling framebuffer |
| when mulitsampling is disabled. */ |
| ctx->dispatch.GenFramebuffers (1, &surface->msaa_fb); |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->msaa_fb); |
| ctx->dispatch.GenRenderbuffers (1, &surface->msaa_rb); |
| ctx->dispatch.BindRenderbuffer (GL_RENDERBUFFER, surface->msaa_rb); |
| |
| /* FIXME: For now we assume that textures passed from the outside have GL_RGBA |
| format, but eventually we need to expose a way for the API consumer to pass |
| this information. */ |
| ctx->dispatch.RenderbufferStorageMultisample (GL_RENDERBUFFER, |
| ctx->num_samples, |
| GL_RGBA, |
| surface->width, |
| surface->height); |
| ctx->dispatch.FramebufferRenderbuffer (GL_FRAMEBUFFER, |
| GL_COLOR_ATTACHMENT0, |
| GL_RENDERBUFFER, |
| surface->msaa_rb); |
| |
| /* Cairo surfaces start out initialized to transparent (black) */ |
| glDisable (GL_SCISSOR_TEST); |
| glClearColor (0, 0, 0, 0); |
| glClear (GL_COLOR_BUFFER_BIT); |
| } |
| #endif |
| |
| static cairo_bool_t |
| _cairo_gl_ensure_msaa_depth_stencil_buffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| cairo_gl_dispatch_t *dispatch = &ctx->dispatch; |
| if (surface->msaa_depth_stencil) |
| return TRUE; |
| |
| _cairo_gl_ensure_framebuffer (ctx, surface); |
| #if CAIRO_HAS_GL_SURFACE |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) |
| _cairo_gl_ensure_multisampling (ctx, surface); |
| #endif |
| |
| dispatch->GenRenderbuffers (1, &surface->msaa_depth_stencil); |
| dispatch->BindRenderbuffer (GL_RENDERBUFFER, |
| surface->msaa_depth_stencil); |
| |
| dispatch->RenderbufferStorageMultisample (GL_RENDERBUFFER, |
| ctx->num_samples, |
| _get_depth_stencil_format (ctx), |
| surface->width, |
| surface->height); |
| |
| #if CAIRO_HAS_GL_SURFACE |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) { |
| dispatch->FramebufferRenderbuffer (GL_FRAMEBUFFER, |
| GL_DEPTH_STENCIL_ATTACHMENT, |
| GL_RENDERBUFFER, |
| surface->msaa_depth_stencil); |
| } |
| #endif |
| |
| #if CAIRO_HAS_GLESV2_SURFACE |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) { |
| dispatch->FramebufferRenderbuffer (GL_FRAMEBUFFER, |
| GL_DEPTH_ATTACHMENT, |
| GL_RENDERBUFFER, |
| surface->msaa_depth_stencil); |
| dispatch->FramebufferRenderbuffer (GL_FRAMEBUFFER, |
| GL_STENCIL_ATTACHMENT, |
| GL_RENDERBUFFER, |
| surface->msaa_depth_stencil); |
| } |
| #endif |
| |
| if (dispatch->CheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { |
| dispatch->DeleteRenderbuffers (1, &surface->msaa_depth_stencil); |
| surface->msaa_depth_stencil = 0; |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static cairo_bool_t |
| _cairo_gl_ensure_depth_stencil_buffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| cairo_gl_dispatch_t *dispatch = &ctx->dispatch; |
| |
| if (surface->depth_stencil) |
| return TRUE; |
| |
| _cairo_gl_ensure_framebuffer (ctx, surface); |
| |
| dispatch->GenRenderbuffers (1, &surface->depth_stencil); |
| dispatch->BindRenderbuffer (GL_RENDERBUFFER, surface->depth_stencil); |
| dispatch->RenderbufferStorage (GL_RENDERBUFFER, |
| _get_depth_stencil_format (ctx), |
| surface->width, surface->height); |
| |
| dispatch->FramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
| GL_RENDERBUFFER, surface->depth_stencil); |
| dispatch->FramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
| GL_RENDERBUFFER, surface->depth_stencil); |
| if (dispatch->CheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { |
| dispatch->DeleteRenderbuffers (1, &surface->depth_stencil); |
| surface->depth_stencil = 0; |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| cairo_bool_t |
| _cairo_gl_ensure_stencil (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| if (! _cairo_gl_surface_is_texture (surface)) |
| return TRUE; /* best guess for now, will check later */ |
| if (! ctx->has_packed_depth_stencil) |
| return FALSE; |
| |
| if (surface->msaa_active) |
| return _cairo_gl_ensure_msaa_depth_stencil_buffer (ctx, surface); |
| else |
| return _cairo_gl_ensure_depth_stencil_buffer (ctx, surface); |
| } |
| |
| /* |
| * Stores a parallel projection transformation in matrix 'm', |
| * using column-major order. |
| * |
| * This is equivalent to: |
| * |
| * glLoadIdentity() |
| * gluOrtho2D() |
| * |
| * The calculation for the ortho tranformation was taken from the |
| * mesa source code. |
| */ |
| static void |
| _gl_identity_ortho (GLfloat *m, |
| GLfloat left, GLfloat right, |
| GLfloat bottom, GLfloat top) |
| { |
| #define M(row,col) m[col*4+row] |
| M(0,0) = 2.f / (right - left); |
| M(0,1) = 0.f; |
| M(0,2) = 0.f; |
| M(0,3) = -(right + left) / (right - left); |
| |
| M(1,0) = 0.f; |
| M(1,1) = 2.f / (top - bottom); |
| M(1,2) = 0.f; |
| M(1,3) = -(top + bottom) / (top - bottom); |
| |
| M(2,0) = 0.f; |
| M(2,1) = 0.f; |
| M(2,2) = -1.f; |
| M(2,3) = 0.f; |
| |
| M(3,0) = 0.f; |
| M(3,1) = 0.f; |
| M(3,2) = 0.f; |
| M(3,3) = 1.f; |
| #undef M |
| } |
| |
| #if CAIRO_HAS_GL_SURFACE |
| static void |
| bind_multisample_framebuffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| cairo_bool_t stencil_test_enabled; |
| cairo_bool_t scissor_test_enabled; |
| |
| assert (surface->supports_msaa); |
| assert (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP); |
| |
| _cairo_gl_ensure_framebuffer (ctx, surface); |
| _cairo_gl_ensure_multisampling (ctx, surface); |
| |
| if (surface->msaa_active) { |
| glEnable (GL_MULTISAMPLE); |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->msaa_fb); |
| return; |
| } |
| |
| _cairo_gl_composite_flush (ctx); |
| |
| stencil_test_enabled = glIsEnabled (GL_STENCIL_TEST); |
| scissor_test_enabled = glIsEnabled (GL_SCISSOR_TEST); |
| glDisable (GL_STENCIL_TEST); |
| glDisable (GL_SCISSOR_TEST); |
| |
| glEnable (GL_MULTISAMPLE); |
| |
| /* The last time we drew to the surface, we were not using multisampling, |
| so we need to blit from the non-multisampling framebuffer into the |
| multisampling framebuffer. */ |
| ctx->dispatch.BindFramebuffer (GL_DRAW_FRAMEBUFFER, surface->msaa_fb); |
| ctx->dispatch.BindFramebuffer (GL_READ_FRAMEBUFFER, surface->fb); |
| ctx->dispatch.BlitFramebuffer (0, 0, surface->width, surface->height, |
| 0, 0, surface->width, surface->height, |
| GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->msaa_fb); |
| |
| if (stencil_test_enabled) |
| glEnable (GL_STENCIL_TEST); |
| if (scissor_test_enabled) |
| glEnable (GL_SCISSOR_TEST); |
| } |
| #endif |
| |
| #if CAIRO_HAS_GL_SURFACE |
| static void |
| bind_singlesample_framebuffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface) |
| { |
| cairo_bool_t stencil_test_enabled; |
| cairo_bool_t scissor_test_enabled; |
| |
| assert (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP); |
| _cairo_gl_ensure_framebuffer (ctx, surface); |
| |
| if (! surface->msaa_active) { |
| glDisable (GL_MULTISAMPLE); |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->fb); |
| return; |
| } |
| |
| _cairo_gl_composite_flush (ctx); |
| |
| stencil_test_enabled = glIsEnabled (GL_STENCIL_TEST); |
| scissor_test_enabled = glIsEnabled (GL_SCISSOR_TEST); |
| glDisable (GL_STENCIL_TEST); |
| glDisable (GL_SCISSOR_TEST); |
| |
| glDisable (GL_MULTISAMPLE); |
| |
| /* The last time we drew to the surface, we were using multisampling, |
| so we need to blit from the multisampling framebuffer into the |
| non-multisampling framebuffer. */ |
| ctx->dispatch.BindFramebuffer (GL_DRAW_FRAMEBUFFER, surface->fb); |
| ctx->dispatch.BindFramebuffer (GL_READ_FRAMEBUFFER, surface->msaa_fb); |
| ctx->dispatch.BlitFramebuffer (0, 0, surface->width, surface->height, |
| 0, 0, surface->width, surface->height, |
| GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->fb); |
| |
| if (stencil_test_enabled) |
| glEnable (GL_STENCIL_TEST); |
| if (scissor_test_enabled) |
| glEnable (GL_SCISSOR_TEST); |
| } |
| #endif |
| |
| void |
| _cairo_gl_context_bind_framebuffer (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface, |
| cairo_bool_t multisampling) |
| { |
| if (_cairo_gl_surface_is_texture (surface)) { |
| /* OpenGL ES surfaces only have either a multisample framebuffer or a |
| * singlesample framebuffer, so we cannot switch back and forth. */ |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) { |
| _cairo_gl_ensure_framebuffer (ctx, surface); |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->fb); |
| return; |
| } |
| |
| #if CAIRO_HAS_GL_SURFACE |
| if (multisampling) |
| bind_multisample_framebuffer (ctx, surface); |
| else |
| bind_singlesample_framebuffer (ctx, surface); |
| #endif |
| } else { |
| ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, 0); |
| |
| #if CAIRO_HAS_GL_SURFACE |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) { |
| if (multisampling) |
| glEnable (GL_MULTISAMPLE); |
| else |
| glDisable (GL_MULTISAMPLE); |
| } |
| #endif |
| } |
| |
| surface->msaa_active = multisampling; |
| } |
| |
| void |
| _cairo_gl_context_set_destination (cairo_gl_context_t *ctx, |
| cairo_gl_surface_t *surface, |
| cairo_bool_t multisampling) |
| { |
| cairo_bool_t changing_surface, changing_sampling; |
| |
| /* The decision whether or not to use multisampling happens when |
| * we create an OpenGL ES surface, so we can never switch modes. */ |
| if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) |
| multisampling = surface->msaa_active; |
| |
| changing_surface = ctx->current_target != surface || surface->needs_update; |
| changing_sampling = surface->msaa_active != multisampling; |
| if (! changing_surface && ! changing_sampling) |
| return; |
| |
| if (! changing_surface) { |
| _cairo_gl_composite_flush (ctx); |
| _cairo_gl_context_bind_framebuffer (ctx, surface, multisampling); |
| return; |
| } |
| |
| _cairo_gl_composite_flush (ctx); |
| |
| ctx->current_target = surface; |
| surface->needs_update = FALSE; |
| |
| if (! _cairo_gl_surface_is_texture (surface)) { |
| ctx->make_current (ctx, surface); |
| } |
| |
| _cairo_gl_context_bind_framebuffer (ctx, surface, multisampling); |
| |
| if (! _cairo_gl_surface_is_texture (surface)) { |
| #if CAIRO_HAS_GL_SURFACE |
| glDrawBuffer (GL_BACK_LEFT); |
| glReadBuffer (GL_BACK_LEFT); |
| #endif |
| } |
| |
| glDisable (GL_DITHER); |
| glViewport (0, 0, surface->width, surface->height); |
| |
| if (_cairo_gl_surface_is_texture (surface)) |
| _gl_identity_ortho (ctx->modelviewprojection_matrix, |
| 0, surface->width, 0, surface->height); |
| else |
| _gl_identity_ortho (ctx->modelviewprojection_matrix, |
| 0, surface->width, surface->height, 0); |
| } |
| |
| void |
| cairo_gl_device_set_thread_aware (cairo_device_t *device, |
| cairo_bool_t thread_aware) |
| { |
| if (device->backend->type != CAIRO_DEVICE_TYPE_GL) { |
| _cairo_error_throw (CAIRO_STATUS_DEVICE_TYPE_MISMATCH); |
| return; |
| } |
| ((cairo_gl_context_t *) device)->thread_aware = thread_aware; |
| } |