/* 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 * Carl Worth * Chris Wilson * Eric Anholt * Alexandros Frantzis */ #include "cairoint.h" #include "cairo-error-private.h" #include "cairo-gl-private.h" #include "cairo-rtree-private.h" #if CAIRO_HAS_EVASGL_SURFACE #include "cairo-evas-gl.h" #endif #define MAX_MSAA_SAMPLES 4 cairo_int_status_t _cairo_gl_image_cache_init (cairo_gl_context_t *ctx, int width, int height, cairo_gl_image_cache_t **image_cache) { cairo_surface_t *cache_surface = _cairo_gl_surface_create_scratch (ctx, CAIRO_CONTENT_COLOR_ALPHA, width, height); if (unlikely (cache_surface->status)) { cairo_surface_destroy (cache_surface); return CAIRO_INT_STATUS_UNSUPPORTED; } _cairo_surface_release_device_reference (cache_surface); *image_cache = _cairo_malloc (sizeof (cairo_gl_image_cache_t)); if (*image_cache == NULL) return _cairo_error (CAIRO_STATUS_NO_MEMORY); (*image_cache)->surface = (cairo_gl_surface_t *)cache_surface; (*image_cache)->surface->supports_msaa = FALSE; _cairo_rtree_init (&((*image_cache)->rtree), width, height, IMAGE_CACHE_MIN_SIZE, sizeof (cairo_gl_image_t), _cairo_gl_image_node_destroy); (*image_cache)->copy_success = TRUE; return CAIRO_INT_STATUS_SUCCESS; } void _cairo_gl_image_cache_fini (cairo_gl_context_t *ctx) { if (ctx->image_cache) { _cairo_rtree_fini (&ctx->image_cache->rtree); cairo_surface_destroy (&ctx->image_cache->surface->base); } free (ctx->image_cache); } 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); _cairo_gl_context_reset (ctx); _disable_scissor_buffer (ctx); if (ctx->states_cache.blend_enabled == TRUE ) { ctx->dispatch.Disable (GL_BLEND); ctx->states_cache.blend_enabled = FALSE; } 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]); _cairo_gl_image_cache_fini (ctx); _gl_unlock (device); } static void _gl_destroy (void *device) { int n; cairo_gl_context_t *ctx = device; ctx->acquire (ctx); if(ctx->glyph_mask) { cairo_surface_destroy (&ctx->glyph_mask->base); ctx->glyph_mask = NULL; } for (n = 0; n < 2; n++) { if (ctx->source_scratch_surfaces[n]) cairo_surface_destroy (&ctx->source_scratch_surfaces[n]->base); if (ctx->mask_scratch_surfaces[n]) cairo_surface_destroy (&ctx->mask_scratch_surfaces[n]->base); if (ctx->shadow_scratch_surfaces[n]) cairo_surface_destroy (&ctx->shadow_scratch_surfaces[n]->base); } if (ctx->shadow_scratch_surfaces[2]) cairo_surface_destroy (&ctx->shadow_scratch_surfaces[2]->base); for (n = 0; n < 4; n++) { if (ctx->shadow_masks[n]) cairo_surface_destroy (&ctx->shadow_masks[n]->base); } 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); free (ctx->vb); if (ctx->vao) { ctx->dispatch.DeleteVertexArrays (1, &ctx->vao); } if (ctx->vbo) { ctx->dispatch.DeleteBuffers (1, &ctx->vbo); } if (ctx->ibo) { ctx->dispatch.DeleteBuffers (1, &ctx->ibo); } 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_context_t *ctx, 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_ES2 || gl_flavor == CAIRO_GL_FLAVOR_ES3); /* FIXME: always return false for GLES, this is tizen TV driver problem. Once that is resolved, we will return properly */ return FALSE; /* 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 (&ctx->dispatch, "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 (dispatch); cairo_gl_flavor_t gl_flavor = _cairo_gl_get_flavor (dispatch); int n; cairo_bool_t is_desktop = gl_flavor == CAIRO_GL_FLAVOR_DESKTOP; cairo_bool_t is_gles = (gl_flavor == CAIRO_GL_FLAVOR_ES2 || gl_flavor == CAIRO_GL_FLAVOR_ES3); if (gl_version >= CAIRO_GL_VERSION_ENCODE (3, 30) && gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) ctx->is_gl33 = TRUE; else ctx->is_gl33 = FALSE; _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; ctx->has_angle_multisampling = FALSE; 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 (gl_version >= CAIRO_GL_VERSION_ENCODE (3, 0) || _cairo_gl_has_extension (&ctx->dispatch, "GL_ARB_texture_non_power_of_two")) { ctx->tex_target = GL_TEXTURE_2D; ctx->has_npot_repeat = TRUE; } else if (_cairo_gl_has_extension (&ctx->dispatch, "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 (&ctx->dispatch, "GL_OES_texture_npot") || _cairo_gl_has_extension (&ctx->dispatch, "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 (&ctx->dispatch, "GL_ARB_pixel_buffer_object")) return _cairo_error (CAIRO_STATUS_DEVICE_ERROR); if (is_gles && ! _cairo_gl_has_extension (&ctx->dispatch, "GL_EXT_texture_format_BGRA8888")) return _cairo_error (CAIRO_STATUS_DEVICE_ERROR); ctx->has_map_buffer = is_desktop || (is_gles && _cairo_gl_has_extension (&ctx->dispatch, "GL_OES_mapbuffer")); ctx->can_read_bgra = test_can_read_bgra (ctx, gl_flavor); ctx->has_mesa_pack_invert = _cairo_gl_has_extension (&ctx->dispatch, "GL_MESA_pack_invert"); ctx->has_packed_depth_stencil = (is_desktop && (gl_version >= CAIRO_GL_VERSION_ENCODE (3, 0) || _cairo_gl_has_extension (&ctx->dispatch, "GL_EXT_packed_depth_stencil"))) || (is_gles && _cairo_gl_has_extension (&ctx->dispatch, "GL_OES_packed_depth_stencil")); ctx->num_samples = 1; ctx->msaa_type = CAIRO_GL_NONE_MULTISAMPLE_TO_TEXTURE; #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (is_desktop && ctx->has_packed_depth_stencil && (gl_version >= CAIRO_GL_VERSION_ENCODE (3, 0) || _cairo_gl_has_extension (&ctx->dispatch, "GL_ARB_framebuffer_object") || (_cairo_gl_has_extension (&ctx->dispatch, "GL_EXT_framebuffer_blit") && _cairo_gl_has_extension (&ctx->dispatch, "GL_EXT_framebuffer_multisample")))) { ctx->dispatch.GetIntegerv(GL_MAX_SAMPLES_EXT, &ctx->num_samples); } #endif #if (CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE) && GL_MAX_SAMPLES_EXT if (is_gles && ctx->has_packed_depth_stencil && _cairo_gl_has_extension (&ctx->dispatch, "GL_EXT_multisampled_render_to_texture")) { ctx->dispatch.GetIntegerv(GL_MAX_SAMPLES_EXT, &ctx->num_samples); ctx->msaa_type = CAIRO_GL_EXT_MULTISAMPLE_TO_TEXTURE; } #endif #if (CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE) && GL_MAX_SAMPLES_IMG if (ctx->msaa_type == CAIRO_GL_NONE_MULTISAMPLE_TO_TEXTURE && is_gles && ctx->has_packed_depth_stencil && _cairo_gl_has_extension (&ctx->dispatch, "GL_IMG_multisampled_render_to_texture")) { ctx->dispatch.GetIntegerv(GL_MAX_SAMPLES_IMG, &ctx->num_samples); ctx->msaa_type = CAIRO_GL_IMG_MULTISAMPLE_TO_TEXTURE; } #endif #if (CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE) && GL_MAX_SAMPLES_ANGLE if (ctx->msaa_type == CAIRO_GL_NONE_MULTISAMPLE_TO_TEXTURE && is_gles && ctx->has_packed_depth_stencil && _cairo_gl_has_extension (&ctx->dispatch, "GL_ANGLE_framebuffer_blit") && _cairo_gl_has_extension (&ctx->dispatch, "GL_ANGLE_framebuffer_multisample")) { ctx->dispatch.GetIntegerv(GL_MAX_SAMPLES_ANGLE, &ctx->num_samples); ctx->has_angle_multisampling = TRUE; } #endif #if CAIRO_HAS_GLESV3_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->msaa_type == CAIRO_GL_NONE_MULTISAMPLE_TO_TEXTURE && is_gles && ctx->has_packed_depth_stencil) { ctx->dispatch.GetIntegerv(GL_MAX_SAMPLES, &ctx->num_samples); /* this is work around for evasgl. At this moment, if we still get samples == 1, it means gles2 does not have any support for extensions we have supported */ if (gl_flavor == CAIRO_GL_FLAVOR_ES2) ctx->num_samples = 1; } #endif /* we always use renderbuffer for rendering in glesv3 */ if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES3 || (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2 && ctx->has_angle_multisampling)) ctx->supports_msaa = TRUE; else 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(); if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP && gl_version > CAIRO_GL_VERSION_ENCODE (3, 0)) { ctx->dispatch.GenVertexArrays (1, &ctx->vao); ctx->dispatch.BindVertexArray (ctx->vao); ctx->dispatch.GenBuffers (1, &ctx->vbo); ctx->dispatch.BindBuffer (GL_ARRAY_BUFFER, ctx->vbo); ctx->dispatch.BufferData (GL_ARRAY_BUFFER, ctx->vbo_size, NULL, GL_DYNAMIC_DRAW); ctx->dispatch.GenBuffers (1, &ctx->ibo); ctx->dispatch.BindBuffer (GL_ELEMENT_ARRAY_BUFFER, ctx->ibo); ctx->dispatch.BufferData (GL_ELEMENT_ARRAY_BUFFER, ctx->vbo_size * 2, NULL, GL_DYNAMIC_DRAW); ctx->states_cache.bound_vao = ctx->vao; ctx->states_cache.bound_vbo = ctx->vbo; ctx->states_cache.bound_ibo = ctx->ibo; } else { ctx->vbo = 0; ctx->vao = 0; ctx->ibo = 0; } ctx->vb = malloc (ctx->vbo_size); if (unlikely (ctx->vb == NULL)) { _cairo_cache_fini (&ctx->gradients); ctx->dispatch.DeleteVertexArrays (1, &ctx->vao); ctx->dispatch.DeleteBuffers (1, &ctx->vbo); ctx->dispatch.DeleteBuffers (1, &ctx->ibo); return _cairo_error (CAIRO_STATUS_NO_MEMORY); } ctx->primitive_type = CAIRO_GL_PRIMITIVE_TYPE_TRIANGLES; _cairo_array_init (&ctx->tristrip_indices, sizeof (unsigned short)); ctx->max_framebuffer_size = 0; ctx->dispatch.GetIntegerv (GL_MAX_RENDERBUFFER_SIZE, &ctx->max_framebuffer_size); ctx->max_texture_size = 0; ctx->dispatch.GetIntegerv (GL_MAX_TEXTURE_SIZE, &ctx->max_texture_size); ctx->max_textures = 0; ctx->dispatch.GetIntegerv (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]); ctx->image_cache = NULL; for (n = 0; n < 2; n++) { ctx->source_scratch_surfaces[n] = NULL; ctx->mask_scratch_surfaces[n] = NULL; ctx->shadow_scratch_surfaces[n] = NULL; } for (n = 0; n < 4; n++) ctx->shadow_masks[n] = NULL; ctx->source_scratch_in_use = FALSE; _cairo_gl_context_reset (ctx); 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); } if (ctx->states_cache.active_texture != ctx->max_textures - 1) { ctx->dispatch.ActiveTexture (ctx->max_textures - 1); ctx->states_cache.active_texture = ctx->max_textures - 1; } } else { if (ctx->states_cache.active_texture != GL_TEXTURE0 + tex_unit) { ctx->dispatch.ActiveTexture (GL_TEXTURE0 + tex_unit); ctx->states_cache.active_texture = 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 || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) return GL_DEPTH_STENCIL; #endif #if CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_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 || CAIRO_HAS_EVASGL_SURFACE return GL_DEPTH24_STENCIL8_OES; #elif CAIRO_HAS_GLESV3_SURFACE || CAIRO_HAS_EVASGL_SURFACE return GL_DEPTH24_STENCIL8; #endif } static void _cairo_gl_clear_framebuffer (cairo_gl_context_t *ctx, cairo_gl_surface_t *surface) { if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) return; if (_cairo_gl_surface_is_scratch (ctx, surface)) { _disable_scissor_buffer (ctx); _disable_stencil_buffer (ctx); ctx->dispatch.Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } } #if CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE static void _cairo_gl_ensure_msaa_gles_framebuffer (cairo_gl_context_t *ctx, cairo_gl_surface_t *surface) { if (ctx->has_angle_multisampling) return; 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 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 || CAIRO_HAS_EVASGL_SURFACE if (surface->supports_msaa && _cairo_gl_msaa_compositor_enabled () && ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2 && ! ctx->has_angle_multisampling) { _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 || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP && ctx->dispatch.DrawBuffer && ctx->dispatch.ReadBuffer) { ctx->dispatch.DrawBuffer (GL_COLOR_ATTACHMENT0); ctx->dispatch.ReadBuffer (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); } } static void _cairo_gl_ensure_multisampling (cairo_gl_context_t *ctx, cairo_gl_surface_t *surface) { GLenum rgba; if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2 && ! ctx->has_angle_multisampling) return; assert (surface->supports_msaa); 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. */ #if CAIRO_HAS_GLESV3_SURFACE || CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE #if CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_GLESV3_SURFACE rgba = GL_RGBA8; #else if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) rgba = GL_RGBA; else rgba = GL_RGBA8; #endif #else rgba = GL_RGBA; #endif ctx->dispatch.RenderbufferStorageMultisample (GL_RENDERBUFFER, ctx->num_samples, rgba, surface->width, surface->height); ctx->dispatch.FramebufferRenderbuffer (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, surface->msaa_rb); if (ctx->dispatch.CheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { ctx->dispatch.DeleteRenderbuffers (1, &surface->msaa_rb); surface->msaa_rb = 0; ctx->dispatch.DeleteRenderbuffers (1, &surface->msaa_fb); surface->msaa_fb = 0; return; } /* Cairo surfaces start out initialized to transparent (black) */ _disable_scissor_buffer (ctx); ctx->dispatch.ClearColor (0, 0, 0, 0); // reset cached clear colors memset (&ctx->states_cache.clear_red, 0, sizeof (GLclampf) * 4); ctx->dispatch.Clear (GL_COLOR_BUFFER_BIT); } 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); 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 || CAIRO_HAS_GLESV3_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP || ctx->gl_flavor == CAIRO_GL_FLAVOR_ES3) { dispatch->FramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, surface->msaa_depth_stencil); } #endif #if CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2) { 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 } static void bind_multisample_framebuffer (cairo_gl_context_t *ctx, cairo_gl_surface_t *surface) { #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_SURFACE cairo_bool_t stencil_test_enabled, scissor_test_enabled; cairo_bool_t has_stencil_cache; GLbitfield mask; if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) { has_stencil_cache = surface->clip_on_stencil_buffer ? TRUE : FALSE; mask = GL_COLOR_BUFFER_BIT; } #endif assert (surface->supports_msaa); _cairo_gl_ensure_framebuffer (ctx, surface); _cairo_gl_ensure_multisampling (ctx, surface); if (surface->msaa_active) { #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) ctx->dispatch.Enable (GL_MULTISAMPLE); #endif ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->msaa_fb); return; } _cairo_gl_composite_flush (ctx); #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_SURFACE /* we must disable scissor and stencil test */ if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) { stencil_test_enabled = ctx->states_cache.stencil_test_enabled; scissor_test_enabled = ctx->states_cache.scissor_test_enabled; _disable_stencil_buffer (ctx); _disable_scissor_buffer (ctx); ctx->dispatch.Enable (GL_MULTISAMPLE); if (has_stencil_cache) mask |= GL_STENCIL_BUFFER_BIT; /* 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, mask, GL_NEAREST); surface->content_synced = TRUE; } #endif ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->msaa_fb); #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) { /* re-enable stencil and scissor test */ if (stencil_test_enabled) _enable_stencil_buffer (ctx); if (scissor_test_enabled) _enable_scissor_buffer (ctx); } #endif } static void bind_singlesample_framebuffer (cairo_gl_context_t *ctx, cairo_gl_surface_t *surface) { cairo_bool_t has_stencil_cache = surface->clip_on_stencil_buffer ? TRUE : FALSE; cairo_bool_t stencil_test_enabled; cairo_bool_t scissor_test_enabled; GLbitfield mask = GL_COLOR_BUFFER_BIT; if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2 && ! ctx->has_angle_multisampling) return; _cairo_gl_ensure_framebuffer (ctx, surface); if (! surface->msaa_active) { #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_FLAVOR if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) ctx->dispatch.Disable (GL_MULTISAMPLE); #endif ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->fb); return; } _cairo_gl_composite_flush (ctx); stencil_test_enabled = ctx->states_cache.stencil_test_enabled; scissor_test_enabled = ctx->states_cache.scissor_test_enabled; _disable_stencil_buffer (ctx); _disable_scissor_buffer (ctx); #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_FLAVOR if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) ctx->dispatch.Disable (GL_MULTISAMPLE); #endif if (has_stencil_cache) mask |= GL_STENCIL_BUFFER_BIT; /* 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. */ #if CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2) { ctx->dispatch.BindFramebuffer (GL_DRAW_FRAMEBUFFER_ANGLE, surface->fb); ctx->dispatch.BindFramebuffer (GL_READ_FRAMEBUFFER_ANGLE, surface->msaa_fb); } #if CAIRO_HAS_EVASGL_SURFACE else { ctx->dispatch.BindFramebuffer (GL_DRAW_FRAMEBUFFER, surface->fb); ctx->dispatch.BindFramebuffer (GL_READ_FRAMEBUFFER, surface->msaa_fb); } #endif #else ctx->dispatch.BindFramebuffer (GL_DRAW_FRAMEBUFFER, surface->fb); ctx->dispatch.BindFramebuffer (GL_READ_FRAMEBUFFER, surface->msaa_fb); #endif ctx->dispatch.BlitFramebuffer (0, 0, surface->width, surface->height, 0, 0, surface->width, surface->height, mask, GL_NEAREST); ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->fb); surface->content_synced = TRUE; /* re-enable stencil and scissor test */ if (stencil_test_enabled) _enable_stencil_buffer (ctx); if (scissor_test_enabled) _enable_scissor_buffer (ctx); } 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_ES2 && ! ctx->has_angle_multisampling) { _cairo_gl_ensure_framebuffer (ctx, surface); ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, surface->fb); _cairo_gl_clear_framebuffer (ctx, surface); return; } if (multisampling) bind_multisample_framebuffer (ctx, surface); else bind_singlesample_framebuffer (ctx, surface); } else { #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_GLESV2_SURFACE || CAIRO_HAS_GLESV3_SURFACE ctx->dispatch.BindFramebuffer (GL_FRAMEBUFFER, 0); #endif #if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) { if (multisampling) ctx->dispatch.Enable (GL_MULTISAMPLE); else ctx->dispatch.Disable (GL_MULTISAMPLE); } #endif } if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP || ctx->gl_flavor == CAIRO_GL_FLAVOR_ES3 || (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES2 && ctx->has_angle_multisampling)) surface->msaa_active = multisampling; if (ctx->gl_flavor != CAIRO_GL_FLAVOR_DESKTOP && multisampling) _cairo_gl_clear_framebuffer (ctx, surface); } 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_ES2 && ! ctx->has_angle_multisampling) multisampling = surface->msaa_active; changing_surface = ctx->current_target != surface || surface->size_changed; changing_sampling = surface->msaa_active != multisampling; if (! changing_surface && ! changing_sampling) { if (surface->needs_update) _cairo_gl_composite_flush (ctx); 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 || CAIRO_HAS_EVASGL_SURFACE if (ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP && ctx->dispatch.DrawBuffer && ctx->dispatch.ReadBuffer) { ctx->dispatch.DrawBuffer (GL_BACK_LEFT); ctx->dispatch.ReadBuffer (GL_BACK_LEFT); } #endif } ctx->dispatch.Disable (GL_DITHER); if (ctx->states_cache.viewport_box.width != surface->width || ctx->states_cache.viewport_box.height != surface->height) { ctx->dispatch.Viewport (0, 0, surface->width, surface->height); ctx->states_cache.viewport_box.width = surface->width; ctx->states_cache.viewport_box.height = 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)||(cairo_device_status(device)!= CAIRO_STATUS_SUCCESS)) { fprintf (stderr, "cairo_gl_device_set_thread_aware(): cairo_device is NULL or not available\n"); _cairo_error_throw (CAIRO_STATUS_DEVICE_ERROR); return; } if (device->backend->type != CAIRO_DEVICE_TYPE_GL) { _cairo_error_throw (CAIRO_STATUS_DEVICE_TYPE_MISMATCH); return; } if(thread_aware == 0 || thread_aware == 1) { ((cairo_gl_context_t *) device)->thread_aware = thread_aware; } else { _cairo_device_set_error (device, CAIRO_STATUS_INVALID_STATUS); return; } } void _cairo_gl_context_reset (cairo_gl_context_t *ctx) { cairo_gl_shader_t *shader = ctx->current_shader; GLint current_program; /* reset current shader, this is because if cairo shares a same * context other extern libraries or applications, the current * program may well be changed, in this case, we must set cairo * to use cairo program */ if (shader) { ctx->dispatch.GetIntegerv (GL_CURRENT_PROGRAM, ¤t_program); if ((GLint)(ctx->current_shader->program) != current_program) _cairo_gl_set_shader (ctx, shader); } ctx->states_cache.viewport_box.width = 0; ctx->states_cache.viewport_box.height = 0; ctx->states_cache.clear_red = -1; ctx->states_cache.clear_green = -1; ctx->states_cache.clear_blue = -1; ctx->states_cache.clear_alpha = -1; ctx->states_cache.blend_enabled = FALSE; ctx->states_cache.src_color_factor = CAIRO_GL_ENUM_UNINITIALIZED; ctx->states_cache.dst_color_factor = CAIRO_GL_ENUM_UNINITIALIZED; ctx->states_cache.src_alpha_factor = CAIRO_GL_ENUM_UNINITIALIZED; ctx->states_cache.dst_alpha_factor = CAIRO_GL_ENUM_UNINITIALIZED; ctx->states_cache.active_texture = CAIRO_GL_ENUM_UNINITIALIZED; ctx->states_cache.depth_mask = FALSE; /* FIXME: this is hack to fix mali driver */ ctx->dispatch.Disable (GL_DITHER); ctx->states_cache.bound_vbo = 0; ctx->states_cache.bound_vao = 0; ctx->states_cache.bound_ibo = 0; }