diff mbox

[2/2] gl: Provide a shader implementation of repeat wrap modes

Message ID 10ba5bf18ac1d603bffce71fca51d6b261d6f538.1336038723.git.alexandros.frantzis@linaro.org
State Accepted
Commit 6867383017fcea0b1d5a4671b32382037ba9be3f
Headers show

Commit Message

alexandros.frantzis@linaro.org May 3, 2012, 10:41 a.m. UTC
From: Alexandros Frantzis <alexandros.frantzis@linaro.org>

In OpenGL ES 2.0, repeat wrap modes (GL_REPEAT and GL_MIRRORED REPEAT) are
only available for NPOT textures if the GL_OES_texture_npot is supported.
This commit adds a shader implementation of these wrap modes for use by
devices that do not support GL_OES_texture_npot.
---
 src/cairo-gl-composite.c |   10 ++++--
 src/cairo-gl-device.c    |   16 ++++++---
 src/cairo-gl-private.h   |    1 +
 src/cairo-gl-shaders.c   |   84 +++++++++++++++++++++++++++++++++++++++-------
 4 files changed, 92 insertions(+), 19 deletions(-)
diff mbox

Patch

diff --git a/src/cairo-gl-composite.c b/src/cairo-gl-composite.c
index 633d229..1ab7557 100644
--- a/src/cairo-gl-composite.c
+++ b/src/cairo-gl-composite.c
@@ -168,10 +168,16 @@  _cairo_gl_texture_set_extend (cairo_gl_context_t *ctx,
 	wrap_mode = GL_CLAMP_TO_EDGE;
 	break;
     case CAIRO_EXTEND_REPEAT:
-	wrap_mode = GL_REPEAT;
+	if (ctx->has_npot_repeat)
+	    wrap_mode = GL_REPEAT;
+	else
+	    wrap_mode = GL_CLAMP_TO_EDGE;
 	break;
     case CAIRO_EXTEND_REFLECT:
-	wrap_mode = GL_MIRRORED_REPEAT;
+	if (ctx->has_npot_repeat)
+	    wrap_mode = GL_MIRRORED_REPEAT;
+	else
+	    wrap_mode = GL_CLAMP_TO_EDGE;
 	break;
     default:
 	wrap_mode = 0;
diff --git a/src/cairo-gl-device.c b/src/cairo-gl-device.c
index 47ee94e..3d3cc7e 100644
--- a/src/cairo-gl-device.c
+++ b/src/cairo-gl-device.c
@@ -187,18 +187,24 @@  _cairo_gl_context_init (cairo_gl_context_t *ctx)
 
     /* Check for required extensions */
     if (gl_flavor == CAIRO_GL_FLAVOR_DESKTOP) {
-	if (_cairo_gl_has_extension ("GL_ARB_texture_non_power_of_two"))
+	if (_cairo_gl_has_extension ("GL_ARB_texture_non_power_of_two")) {
 	    ctx->tex_target = GL_TEXTURE_2D;
-	else if (_cairo_gl_has_extension ("GL_ARB_texture_rectangle"))
+	    ctx->has_npot_repeat = TRUE;
+	}
+	else if (_cairo_gl_has_extension ("GL_ARB_texture_rectangle")) {
 	    ctx->tex_target = GL_TEXTURE_RECTANGLE;
-	else
+	    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"))
-	    ctx->tex_target = GL_TEXTURE_2D;
+	    ctx->has_npot_repeat = TRUE;
 	else
-	    return _cairo_error (CAIRO_STATUS_DEVICE_ERROR);
+	    ctx->has_npot_repeat = FALSE;
     }
 
     if (gl_flavor == CAIRO_GL_FLAVOR_DESKTOP &&
diff --git a/src/cairo-gl-private.h b/src/cairo-gl-private.h
index 3afdd70..9cbd3a9 100644
--- a/src/cairo-gl-private.h
+++ b/src/cairo-gl-private.h
@@ -319,6 +319,7 @@  struct _cairo_gl_context {
     cairo_gl_flavor_t gl_flavor;
     cairo_bool_t has_map_buffer;
     cairo_bool_t has_packed_depth_stencil;
+    cairo_bool_t has_npot_repeat;
 
     void (*acquire) (void *ctx);
     void (*release) (void *ctx);
diff --git a/src/cairo-gl-shaders.c b/src/cairo-gl-shaders.c
index b3af6f7..ea57efa 100644
--- a/src/cairo-gl-shaders.c
+++ b/src/cairo-gl-shaders.c
@@ -319,8 +319,10 @@  typedef struct _cairo_shader_cache_entry {
     cairo_gl_shader_in_t in;
     GLint src_gl_filter;
     cairo_bool_t src_border_fade;
+    cairo_extend_t src_extend;
     GLint mask_gl_filter;
     cairo_bool_t mask_border_fade;
+    cairo_extend_t mask_extend;
 
     cairo_gl_context_t *ctx; /* XXX: needed to destroy the program */
     cairo_gl_shader_t shader;
@@ -331,12 +333,16 @@  _cairo_gl_shader_cache_equal_desktop (const void *key_a, const void *key_b)
 {
     const cairo_shader_cache_entry_t *a = key_a;
     const cairo_shader_cache_entry_t *b = key_b;
+    cairo_bool_t both_have_npot_repeat =
+	a->ctx->has_npot_repeat && b->ctx->has_npot_repeat;
 
     return a->src  == b->src  &&
            a->mask == b->mask &&
            a->dest == b->dest &&
 	   a->use_coverage == b->use_coverage &&
-           a->in   == b->in;
+           a->in   == b->in &&
+	   (both_have_npot_repeat || a->src_extend == b->src_extend) &&
+	   (both_have_npot_repeat || a->mask_extend == b->mask_extend);
 }
 
 /*
@@ -349,6 +355,8 @@  _cairo_gl_shader_cache_equal_gles2 (const void *key_a, const void *key_b)
 {
     const cairo_shader_cache_entry_t *a = key_a;
     const cairo_shader_cache_entry_t *b = key_b;
+    cairo_bool_t both_have_npot_repeat =
+	a->ctx->has_npot_repeat && b->ctx->has_npot_repeat;
 
     return a->src  == b->src  &&
 	   a->mask == b->mask &&
@@ -357,8 +365,10 @@  _cairo_gl_shader_cache_equal_gles2 (const void *key_a, const void *key_b)
 	   a->in   == b->in   &&
 	   a->src_gl_filter == b->src_gl_filter &&
 	   a->src_border_fade == b->src_border_fade &&
+	   (both_have_npot_repeat || a->src_extend == b->src_extend) &&
 	   a->mask_gl_filter == b->mask_gl_filter &&
-	   a->mask_border_fade == b->mask_border_fade;
+	   a->mask_border_fade == b->mask_border_fade &&
+	   (both_have_npot_repeat || a->mask_extend == b->mask_extend);
 }
 
 static unsigned long
@@ -642,9 +652,9 @@  cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	else
 	{
 	    _cairo_output_stream_printf (stream,
-	        "    return texture2D%s (%s_sampler, %s_texcoords);\n"
+		"    return texture2D%s (%s_sampler, %s_wrap (%s_texcoords));\n"
 		"}\n",
-		rectstr, namestr, namestr);
+		rectstr, namestr, namestr, namestr);
 	}
         break;
     case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
@@ -669,9 +679,9 @@  cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	else
 	{
 	    _cairo_output_stream_printf (stream,
-		"    return texture2D%s (%s_sampler, vec2 (%s_texcoords.x, 0.5));\n"
+		"    return texture2D%s (%s_sampler, %s_wrap (vec2 (%s_texcoords.x, 0.5)));\n"
 		"}\n",
-		rectstr, namestr, namestr);
+		rectstr, namestr, namestr, namestr);
 	}
 	break;
     case CAIRO_GL_OPERAND_RADIAL_GRADIENT_A0:
@@ -706,9 +716,10 @@  cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	else
 	{
 	    _cairo_output_stream_printf (stream,
-		"    return mix (vec4 (0.0), texture2D%s (%s_sampler, vec2(t, 0.5)), is_valid);\n"
+		"    vec4 texel = texture2D%s (%s_sampler, %s_wrap (vec2 (t, 0.5)));\n"
+		"    return mix (vec4 (0.0), texel, is_valid);\n"
 		"}\n",
-		rectstr, namestr);
+		rectstr, namestr, namestr);
 	}
 	break;
     case CAIRO_GL_OPERAND_RADIAL_GRADIENT_NONE:
@@ -750,9 +761,10 @@  cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	else
 	{
 	    _cairo_output_stream_printf (stream,
-		"    return mix (vec4 (0.0), texture2D%s (%s_sampler, vec2 (upper_t, 0.5)), has_color);\n"
+		"    vec4 texel = texture2D%s (%s_sampler, %s_wrap (vec2(upper_t, 0.5)));\n"
+		"    return mix (vec4 (0.0), texel, has_color);\n"
 		"}\n",
-		rectstr, namestr);
+		rectstr, namestr, namestr);
 	}
 	break;
     case CAIRO_GL_OPERAND_RADIAL_GRADIENT_EXT:
@@ -778,11 +790,12 @@  cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	    "    float has_color = step (0., det) * max (is_valid.x, is_valid.y);\n"
 	    "    \n"
 	    "    float upper_t = mix (t.y, t.x, is_valid.x);\n"
-	    "    return mix (vec4 (0.0), texture2D%s (%s_sampler, vec2 (upper_t, 0.5)), has_color);\n"
+	    "    vec4 texel = texture2D%s (%s_sampler, %s_wrap (vec2(upper_t, 0.5)));\n"
+	    "    return mix (vec4 (0.0), texel, has_color);\n"
 	    "}\n",
 	    namestr, rectstr, namestr, namestr, namestr, namestr,
 	    namestr, namestr, namestr, namestr, namestr,
-	    namestr, namestr, namestr, rectstr, namestr);
+	    namestr, namestr, namestr, rectstr, namestr, namestr);
 	break;
     }
 }
@@ -838,6 +851,47 @@  _cairo_gl_shader_emit_border_fade (cairo_output_stream_t *stream,
     _cairo_output_stream_printf (stream, "}\n");
 }
 
+/*
+ * Emits the wrap function used by an operand.
+ *
+ * In OpenGL ES 2.0, repeat wrap modes (GL_REPEAT and GL_MIRRORED REPEAT) are
+ * only available for NPOT textures if the GL_OES_texture_npot is supported.
+ * If GL_OES_texture_npot is not supported, we need to implement the wrapping
+ * functionality in the shader.
+ */
+static void
+_cairo_gl_shader_emit_wrap (cairo_gl_context_t *ctx,
+			    cairo_output_stream_t *stream,
+			    cairo_gl_operand_t *operand,
+			    cairo_gl_tex_t name)
+{
+    const char *namestr = operand_names[name];
+    cairo_extend_t extend = _cairo_gl_operand_get_extend (operand);
+
+    _cairo_output_stream_printf (stream,
+	"vec2 %s_wrap(vec2 coords)\n"
+	"{\n",
+	namestr);
+
+    if (! ctx->has_npot_repeat &&
+	(extend == CAIRO_EXTEND_REPEAT || extend == CAIRO_EXTEND_REFLECT))
+    {
+	if (extend == CAIRO_EXTEND_REPEAT) {
+	    _cairo_output_stream_printf (stream,
+		"    return fract(coords);\n");
+	} else { /* CAIRO_EXTEND_REFLECT */
+	    _cairo_output_stream_printf (stream,
+		"    return mix(fract(coords), 1.0 - fract(coords), floor(mod(coords, 2.0)));\n");
+	}
+    }
+    else
+    {
+	_cairo_output_stream_printf (stream, "    return coords;\n");
+    }
+
+    _cairo_output_stream_printf (stream, "}\n");
+}
+
 static cairo_status_t
 cairo_gl_shader_get_fragment_source (cairo_gl_context_t *ctx,
                                      cairo_gl_shader_in_t in,
@@ -858,6 +912,9 @@  cairo_gl_shader_get_fragment_source (cairo_gl_context_t *ctx,
 	"precision mediump float;\n"
 	"#endif\n");
 
+    _cairo_gl_shader_emit_wrap (ctx, stream, src, CAIRO_GL_TEX_SOURCE);
+    _cairo_gl_shader_emit_wrap (ctx, stream, mask, CAIRO_GL_TEX_MASK);
+
     if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) {
 	if (_cairo_gl_shader_needs_border_fade (src))
 	    _cairo_gl_shader_emit_border_fade (stream, src, CAIRO_GL_TEX_SOURCE);
@@ -1065,6 +1122,7 @@  _cairo_gl_get_shader_by_type (cairo_gl_context_t *ctx,
     char *fs_source;
     cairo_status_t status;
 
+    lookup.ctx = ctx;
     lookup.src = source->type;
     lookup.mask = mask->type;
     lookup.dest = CAIRO_GL_OPERAND_NONE;
@@ -1072,8 +1130,10 @@  _cairo_gl_get_shader_by_type (cairo_gl_context_t *ctx,
     lookup.in = in;
     lookup.src_gl_filter = _cairo_gl_operand_get_gl_filter (source);
     lookup.src_border_fade = _cairo_gl_shader_needs_border_fade (source);
+    lookup.src_extend = _cairo_gl_operand_get_extend (source);
     lookup.mask_gl_filter = _cairo_gl_operand_get_gl_filter (mask);
     lookup.mask_border_fade = _cairo_gl_shader_needs_border_fade (mask);
+    lookup.mask_extend = _cairo_gl_operand_get_extend (mask);
     lookup.base.hash = _cairo_gl_shader_cache_hash (&lookup);
     lookup.base.size = 1;