diff --git a/src/cairo-gl-composite.c b/src/cairo-gl-composite.c
index 1a4467e..4b0ead8 100644
--- a/src/cairo-gl-composite.c
+++ b/src/cairo-gl-composite.c
@@ -458,9 +458,30 @@ _cairo_gl_operand_bind_to_shader (cairo_gl_context_t *ctx,
 	_cairo_gl_shader_bind_float  (ctx,
 				      uniform_name,
 				      operand->gradient.radius_0);
-        break;
+        /* fall through */
     case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
     case CAIRO_GL_OPERAND_TEXTURE:
+	/*
+	 * For GLES2 we use shaders to implement GL_CLAMP_TO_BORDER (used
+	 * with CAIRO_EXTEND_NONE). When bilinear filtering is enabled,
+	 * these shaders need the texture dimensions for their calculations.
+	 */
+	if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
+	    _cairo_gl_operand_get_extend (operand) == CAIRO_EXTEND_NONE &&
+	    _cairo_gl_operand_get_gl_filter (operand) == GL_LINEAR)
+	{
+	    float width, height;
+	    if (operand->type == CAIRO_GL_OPERAND_TEXTURE) {
+		width = operand->texture.surface->width;
+		height = operand->texture.surface->height;
+	    }
+	    else {
+		width = operand->gradient.gradient->cache_entry.size,
+		height = 1;
+	    }
+	    strcpy (custom_part, "_texdims");
+	    _cairo_gl_shader_bind_vec2 (ctx, uniform_name, width, height);
+	}
         break;
     }
 }
@@ -485,8 +506,14 @@ _cairo_gl_texture_set_extend (cairo_gl_context_t *ctx,
 
     switch (extend) {
     case CAIRO_EXTEND_NONE:
-	glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
-	glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+	if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES) {
+	    glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	    glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	}
+	else {
+	    glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+	    glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+	}
 	break;
     case CAIRO_EXTEND_PAD:
 	glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
diff --git a/src/cairo-gl-ext-def-private.h b/src/cairo-gl-ext-def-private.h
index 1653791..389d356 100644
--- a/src/cairo-gl-ext-def-private.h
+++ b/src/cairo-gl-ext-def-private.h
@@ -96,4 +96,8 @@
 #define GL_PACK_INVERT_MESA 0x8758
 #endif
 
+#ifndef GL_CLAMP_TO_BORDER
+#define GL_CLAMP_TO_BORDER 0x812D
+#endif
+
 #endif /* CAIRO_GL_EXT_DEF_PRIVATE_H */
diff --git a/src/cairo-gl-shaders.c b/src/cairo-gl-shaders.c
index c59f4f7..e35c256 100644
--- a/src/cairo-gl-shaders.c
+++ b/src/cairo-gl-shaders.c
@@ -315,13 +315,17 @@ typedef struct _cairo_shader_cache_entry {
     cairo_gl_operand_type_t mask;
     cairo_gl_operand_type_t dest;
     cairo_gl_shader_in_t in;
+    GLint src_gl_filter;
+    cairo_bool_t src_border_fade;
+    GLint mask_gl_filter;
+    cairo_bool_t mask_border_fade;
 
     cairo_gl_context_t *ctx; /* XXX: needed to destroy the program */
     cairo_gl_shader_t shader;
 } cairo_shader_cache_entry_t;
 
 static cairo_bool_t
-_cairo_gl_shader_cache_equal (const void *key_a, const void *key_b)
+_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;
@@ -332,6 +336,27 @@ _cairo_gl_shader_cache_equal (const void *key_a, const void *key_b)
            a->in   == b->in;
 }
 
+/*
+ * For GLES2 we use more complicated shaders to implement missing GL
+ * features. In this case we need more parameters to uniquely identify
+ * a shader (vs _cairo_gl_shader_cache_equal_desktop()).
+ */
+static cairo_bool_t
+_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;
+
+    return a->src  == b->src  &&
+	   a->mask == b->mask &&
+	   a->dest == b->dest &&
+	   a->in   == b->in   &&
+	   a->src_gl_filter == b->src_gl_filter &&
+	   a->src_border_fade == b->src_border_fade &&
+	   a->mask_gl_filter == b->mask_gl_filter &&
+	   a->mask_border_fade == b->mask_border_fade;
+}
+
 static unsigned long
 _cairo_gl_shader_cache_hash (const cairo_shader_cache_entry_t *entry)
 {
@@ -384,7 +409,9 @@ _cairo_gl_context_init_shaders (cairo_gl_context_t *ctx)
     memset (ctx->vertex_shaders, 0, sizeof (ctx->vertex_shaders));
 
     status = _cairo_cache_init (&ctx->shaders,
-                                _cairo_gl_shader_cache_equal,
+                                ctx->gl_flavor == CAIRO_GL_FLAVOR_DESKTOP ?
+				    _cairo_gl_shader_cache_equal_desktop :
+				    _cairo_gl_shader_cache_equal_gles2,
                                 NULL,
                                 _cairo_gl_shader_cache_destroy,
                                 CAIRO_GL_MAX_SHADERS_PER_CONTEXT);
@@ -534,6 +561,22 @@ cairo_gl_shader_get_vertex_source (cairo_gl_var_type_t src,
     return CAIRO_STATUS_SUCCESS;
 }
 
+/*
+ * Returns whether an operand needs a special border fade fragment shader
+ * to simulate the GL_CLAMP_TO_BORDER wrapping method that is missing in GLES2.
+ */
+static cairo_bool_t
+_cairo_gl_shader_needs_border_fade (cairo_gl_operand_t *operand)
+{
+    cairo_extend_t extend =_cairo_gl_operand_get_extend (operand);
+
+    return extend == CAIRO_EXTEND_NONE &&
+	   (operand->type == CAIRO_GL_OPERAND_TEXTURE ||
+	    operand->type == CAIRO_GL_OPERAND_LINEAR_GRADIENT ||
+	    operand->type == CAIRO_GL_OPERAND_RADIAL_GRADIENT_NONE ||
+	    operand->type == CAIRO_GL_OPERAND_RADIAL_GRADIENT_A0);
+}
+
 static void
 cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
                             cairo_gl_context_t *ctx,
@@ -566,29 +609,62 @@ cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
             namestr, namestr, namestr);
         break;
     case CAIRO_GL_OPERAND_TEXTURE:
-        _cairo_output_stream_printf (stream, 
-            "uniform sampler2D%s %s_sampler;\n"
-            "varying vec2 %s_texcoords;\n"
-            "vec4 get_%s()\n"
-            "{\n"
-            "    return texture2D%s(%s_sampler, %s_texcoords);\n"
-            "}\n",
-            rectstr, namestr, namestr, namestr, rectstr, namestr, namestr);
+	_cairo_output_stream_printf (stream,
+	     "uniform sampler2D%s %s_sampler;\n"
+	     "uniform vec2 %s_texdims;\n"
+	     "varying vec2 %s_texcoords;\n"
+	     "vec4 get_%s()\n"
+	     "{\n",
+	     rectstr, namestr, namestr, namestr, namestr);
+	if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
+	    _cairo_gl_shader_needs_border_fade (op))
+	{
+	    _cairo_output_stream_printf (stream,
+		"    vec2 border_fade = %s_border_fade (%s_texcoords, %s_texdims);\n"
+		"    vec4 texel = texture2D%s (%s_sampler, %s_texcoords);\n"
+		"    return texel * border_fade.x * border_fade.y;\n"
+		"}\n",
+		namestr, namestr, namestr, rectstr, namestr, namestr);
+	}
+	else
+	{
+	    _cairo_output_stream_printf (stream,
+	        "    return texture2D%s (%s_sampler, %s_texcoords);\n"
+		"}\n",
+		rectstr, namestr, namestr);
+	}
         break;
     case CAIRO_GL_OPERAND_LINEAR_GRADIENT:
-        _cairo_output_stream_printf (stream, 
-            "varying vec2 %s_texcoords;\n"
-            "uniform sampler1D %s_sampler;\n"
-            "\n"
-            "vec4 get_%s()\n"
-            "{\n"
-            "    return texture1D (%s_sampler, %s_texcoords.x);\n"
-            "}\n",
-	     namestr, namestr, namestr, namestr, namestr);
-        break;
+	_cairo_output_stream_printf (stream,
+	    "varying vec2 %s_texcoords;\n"
+	    "uniform vec2 %s_texdims;\n"
+	    "uniform sampler1D %s_sampler;\n"
+	    "\n"
+	    "vec4 get_%s()\n"
+	    "{\n",
+	    namestr, namestr, namestr, namestr);
+	if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
+	    _cairo_gl_shader_needs_border_fade (op))
+	{
+	    _cairo_output_stream_printf (stream,
+		"    float border_fade = %s_border_fade (%s_texcoords.x, %s_texdims.x);\n"
+		"    vec4 texel = texture1D (%s_sampler, %s_texcoords.x);\n"
+		"    return texel * border_fade;\n"
+		"}\n",
+		namestr, namestr, namestr, namestr, namestr);
+	}
+	else
+	{
+	    _cairo_output_stream_printf (stream,
+		"    return texture1D (%s_sampler, %s_texcoords.x);\n"
+		"}\n",
+		namestr, namestr);
+	}
+	break;
     case CAIRO_GL_OPERAND_RADIAL_GRADIENT_A0:
 	_cairo_output_stream_printf (stream,
 	    "varying vec2 %s_texcoords;\n"
+	    "uniform vec2 %s_texdims;\n"
 	    "uniform sampler1D %s_sampler;\n"
 	    "uniform vec3 %s_circle_d;\n"
 	    "uniform float %s_radius_0;\n"
@@ -601,15 +677,31 @@ cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	    "    float C = dot (pos, vec3 (pos.xy, -pos.z));\n"
 	    "    \n"
 	    "    float t = 0.5 * C / B;\n"
-	    "    float is_valid = step (-%s_radius_0, t * %s_circle_d.z);\n"
-	    "    return mix (vec4 (0.0), texture1D (%s_sampler, t), is_valid);\n"
-	    "}\n",
+	    "    float is_valid = step (-%s_radius_0, t * %s_circle_d.z);\n",
 	    namestr, namestr, namestr, namestr, namestr, namestr,
 	    namestr, namestr, namestr, namestr, namestr);
+	if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
+	    _cairo_gl_shader_needs_border_fade (op))
+	{
+	    _cairo_output_stream_printf (stream,
+		"    float border_fade = %s_border_fade (t, %s_texdims.x);\n"
+		"    vec4 texel = texture1D (%s_sampler, t);\n"
+		"    return mix (vec4 (0.0), texel * border_fade, is_valid);\n"
+		"}\n",
+		namestr, namestr, namestr);
+	}
+	else
+	{
+	    _cairo_output_stream_printf (stream,
+		"    return mix (vec4 (0.0), texture1D (%s_sampler, t), is_valid);\n"
+		"}\n",
+		namestr);
+	}
 	break;
     case CAIRO_GL_OPERAND_RADIAL_GRADIENT_NONE:
 	_cairo_output_stream_printf (stream,
 	    "varying vec2 %s_texcoords;\n"
+	    "uniform vec2 %s_texdims;\n"
 	    "uniform sampler1D %s_sampler;\n"
 	    "uniform vec3 %s_circle_d;\n"
 	    "uniform float %s_a;\n"
@@ -629,11 +721,26 @@ cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
 	    "    vec2 is_valid = step (vec2 (0.0), t) * step (t, vec2(1.0));\n"
 	    "    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), texture1D (%s_sampler, upper_t), has_color);\n"
-	    "}\n",
+	    "    float upper_t = mix (t.y, t.x, is_valid.x);\n",
 	    namestr, namestr, namestr, namestr, namestr, namestr,
 	    namestr, namestr, namestr, namestr, namestr, namestr);
+	if (ctx->gl_flavor == CAIRO_GL_FLAVOR_ES &&
+	    _cairo_gl_shader_needs_border_fade (op))
+	{
+	    _cairo_output_stream_printf (stream,
+		"    float border_fade = %s_border_fade (upper_t, %s_texdims.x);\n"
+		"    vec4 texel = texture1D (%s_sampler, upper_t);\n"
+		"    return mix (vec4 (0.0), texel * border_fade, has_color);\n"
+		"}\n",
+		namestr, namestr, namestr);
+	}
+	else
+	{
+	    _cairo_output_stream_printf (stream,
+		"    return mix (vec4 (0.0), texture1D (%s_sampler, upper_t), has_color);\n"
+		"}\n",
+		namestr);
+	}
 	break;
     case CAIRO_GL_OPERAND_RADIAL_GRADIENT_EXT:
 	_cairo_output_stream_printf (stream,
@@ -676,6 +783,57 @@ cairo_gl_shader_emit_color (cairo_output_stream_t *stream,
     }
 }
 
+/*
+ * Emits the border fade functions used by an operand.
+ *
+ * If bilinear filtering is used, the emitted function performs a linear
+ * fade to transparency effect in the intervals [-1/2n, 1/2n] and
+ * [1 - 1/2n, 1 + 1/2n] (n: texture size).
+ *
+ * If nearest filtering is used, the emitted function just returns
+ * 0.0 for all values outside [0, 1).
+ */
+static void
+_cairo_gl_shader_emit_border_fade (cairo_output_stream_t *stream,
+				   cairo_gl_operand_t *operand,
+				   cairo_gl_tex_t name)
+{
+    const char *namestr = operand_names[name];
+    GLint gl_filter = _cairo_gl_operand_get_gl_filter (operand);
+
+    /* 2D version */
+    _cairo_output_stream_printf (stream,
+	"vec2 %s_border_fade (vec2 coords, vec2 dims)\n"
+	"{\n",
+	namestr);
+
+    if (gl_filter == GL_LINEAR)
+	_cairo_output_stream_printf (stream,
+	    "    return clamp(-abs(dims * (coords - 0.5)) + (dims + vec2(1.0)) * 0.5, 0.0, 1.0);\n");
+    else
+	_cairo_output_stream_printf (stream,
+	    "    bvec2 in_tex1 = greaterThanEqual (coords, vec2 (0.0));\n"
+	    "    bvec2 in_tex2 = lessThan (coords, vec2 (1.0));\n"
+	    "    return vec2 (float (all (in_tex1) && all (in_tex2)));\n");
+
+    _cairo_output_stream_printf (stream, "}\n");
+
+    /* 1D version */
+    _cairo_output_stream_printf (stream,
+	"float %s_border_fade (float x, float dim)\n"
+	"{\n",
+	namestr);
+    if (gl_filter == GL_LINEAR)
+	_cairo_output_stream_printf (stream,
+	    "    return clamp(-abs(dim * (x - 0.5)) + (dim + 1.0) * 0.5, 0.0, 1.0);\n");
+    else
+	_cairo_output_stream_printf (stream,
+	    "    bool in_tex = x >= 0.0 && x < 1.0;\n"
+	    "    return float (in_tex);\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,
@@ -689,6 +847,13 @@ cairo_gl_shader_get_fragment_source (cairo_gl_context_t *ctx,
     unsigned long length;
     cairo_status_t status;
 
+    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);
+	if (_cairo_gl_shader_needs_border_fade (mask))
+	    _cairo_gl_shader_emit_border_fade (stream, mask, CAIRO_GL_TEX_MASK);
+    }
+
     cairo_gl_shader_emit_color (stream, ctx, src, CAIRO_GL_TEX_SOURCE);
     cairo_gl_shader_emit_color (stream, ctx, mask, CAIRO_GL_TEX_MASK);
 
@@ -880,6 +1045,10 @@ _cairo_gl_get_shader_by_type (cairo_gl_context_t *ctx,
     lookup.mask = mask->type;
     lookup.dest = CAIRO_GL_OPERAND_NONE;
     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.mask_gl_filter = _cairo_gl_operand_get_gl_filter (mask);
+    lookup.mask_border_fade = _cairo_gl_shader_needs_border_fade (mask);
     lookup.base.hash = _cairo_gl_shader_cache_hash (&lookup);
     lookup.base.size = 1;
 
