summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/res
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/wr/webrender/res
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/wr/webrender/res')
-rw-r--r--gfx/wr/webrender/res/Proggy.ttfbin0 -> 5284 bytes
-rw-r--r--gfx/wr/webrender/res/area-lut.tgabin0 -> 65580 bytes
-rw-r--r--gfx/wr/webrender/res/base.glsl53
-rw-r--r--gfx/wr/webrender/res/brush.glsl226
-rw-r--r--gfx/wr/webrender/res/brush_blend.glsl301
-rw-r--r--gfx/wr/webrender/res/brush_conic_gradient.glsl95
-rw-r--r--gfx/wr/webrender/res/brush_image.glsl423
-rw-r--r--gfx/wr/webrender/res/brush_linear_gradient.glsl81
-rw-r--r--gfx/wr/webrender/res/brush_mix_blend.glsl291
-rw-r--r--gfx/wr/webrender/res/brush_opacity.glsl113
-rw-r--r--gfx/wr/webrender/res/brush_radial_gradient.glsl94
-rw-r--r--gfx/wr/webrender/res/brush_solid.glsl92
-rw-r--r--gfx/wr/webrender/res/brush_yuv_image.glsl249
-rw-r--r--gfx/wr/webrender/res/clip_shared.glsl86
-rw-r--r--gfx/wr/webrender/res/composite.glsl272
-rw-r--r--gfx/wr/webrender/res/cs_blur.glsl200
-rw-r--r--gfx/wr/webrender/res/cs_border_segment.glsl450
-rw-r--r--gfx/wr/webrender/res/cs_border_solid.glsl176
-rw-r--r--gfx/wr/webrender/res/cs_clip_box_shadow.glsl139
-rw-r--r--gfx/wr/webrender/res/cs_clip_image.glsl83
-rw-r--r--gfx/wr/webrender/res/cs_clip_rectangle.glsl172
-rw-r--r--gfx/wr/webrender/res/cs_gradient.glsl56
-rw-r--r--gfx/wr/webrender/res/cs_line_decoration.glsl163
-rw-r--r--gfx/wr/webrender/res/cs_scale.glsl67
-rw-r--r--gfx/wr/webrender/res/cs_svg_filter.glsl590
-rw-r--r--gfx/wr/webrender/res/debug_color.glsl24
-rw-r--r--gfx/wr/webrender/res/debug_font.glsl30
-rw-r--r--gfx/wr/webrender/res/ellipse.glsl81
-rw-r--r--gfx/wr/webrender/res/gpu_cache.glsl138
-rw-r--r--gfx/wr/webrender/res/gpu_cache_update.glsl27
-rw-r--r--gfx/wr/webrender/res/gradient_shared.glsl127
-rw-r--r--gfx/wr/webrender/res/pf_vector_cover.glsl77
-rw-r--r--gfx/wr/webrender/res/pf_vector_stencil.glsl111
-rw-r--r--gfx/wr/webrender/res/prim_shared.glsl284
-rw-r--r--gfx/wr/webrender/res/ps_clear.glsl25
-rw-r--r--gfx/wr/webrender/res/ps_split_composite.glsl149
-rw-r--r--gfx/wr/webrender/res/ps_text_run.glsl302
-rw-r--r--gfx/wr/webrender/res/rect.glsl42
-rw-r--r--gfx/wr/webrender/res/render_task.glsl118
-rw-r--r--gfx/wr/webrender/res/shared.glsl192
-rw-r--r--gfx/wr/webrender/res/shared_other.glsl33
-rw-r--r--gfx/wr/webrender/res/transform.glsl129
-rw-r--r--gfx/wr/webrender/res/yuv.glsl176
43 files changed, 6537 insertions, 0 deletions
diff --git a/gfx/wr/webrender/res/Proggy.ttf b/gfx/wr/webrender/res/Proggy.ttf
new file mode 100644
index 0000000000..308d3e1ac9
--- /dev/null
+++ b/gfx/wr/webrender/res/Proggy.ttf
Binary files differ
diff --git a/gfx/wr/webrender/res/area-lut.tga b/gfx/wr/webrender/res/area-lut.tga
new file mode 100644
index 0000000000..5edcddc3d1
--- /dev/null
+++ b/gfx/wr/webrender/res/area-lut.tga
Binary files differ
diff --git a/gfx/wr/webrender/res/base.glsl b/gfx/wr/webrender/res/base.glsl
new file mode 100644
index 0000000000..eb343c019b
--- /dev/null
+++ b/gfx/wr/webrender/res/base.glsl
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(GL_ES)
+ #if GL_ES == 1
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
+ precision highp sampler2DArray;
+ #else
+ precision mediump sampler2DArray;
+ #endif
+
+ // Sampler default precision is lowp on mobile GPUs.
+ // This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880).
+ // Define highp precision macro to allow lossless FLOAT texture sampling.
+ #define HIGHP_SAMPLER_FLOAT highp
+
+ // Default int precision in GLES 3 is highp (32 bits) in vertex shaders
+ // and mediump (16 bits) in fragment shaders. If an int is being used as
+ // a texel address in a fragment shader it, and therefore requires > 16
+ // bits, it must be qualified with this.
+ #define HIGHP_FS_ADDRESS highp
+
+ // texelFetchOffset is buggy on some Android GPUs (see issue #1694).
+ // Fallback to texelFetch on mobile GPUs.
+ #define TEXEL_FETCH(sampler, position, lod, offset) texelFetch(sampler, position + offset, lod)
+ #else
+ #define HIGHP_SAMPLER_FLOAT
+ #define HIGHP_FS_ADDRESS
+ #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset)
+ #endif
+#else
+ #define HIGHP_SAMPLER_FLOAT
+ #define HIGHP_FS_ADDRESS
+ #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset)
+#endif
+
+#ifdef WR_VERTEX_SHADER
+ #ifdef SWGL
+ // Annotate a vertex attribute as being flat per each drawn primitive instance.
+ // SWGL can use this information to avoid redundantly loading the attribute in all SIMD lanes.
+ #define PER_INSTANCE flat
+ #else
+ #define PER_INSTANCE
+ #endif
+
+ #define varying out
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+ precision highp float;
+ #define varying in
+#endif
diff --git a/gfx/wr/webrender/res/brush.glsl b/gfx/wr/webrender/res/brush.glsl
new file mode 100644
index 0000000000..9f21741be2
--- /dev/null
+++ b/gfx/wr/webrender/res/brush.glsl
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// # Brush vertex shaders memory layout
+///
+/// The overall memory layout is the same for all brush shaders.
+///
+/// The vertex shader receives a minimal amount of data from vertex attributes (packed into a single
+/// ivec4 per instance) and the rest is fetched from various uniform samplers using offsets decoded
+/// from the vertex attributes.
+///
+/// The diagram below shows the the various pieces of data fectched in the vertex shader:
+///
+///```ascii
+/// (sPrimitiveHeadersI)
+/// (VBO) +-----------------------+
+/// +----------------------------+ +----------------------------> | Int header |
+/// | Instance vertex attributes | | (sPrimitiveHeadersF) | |
+/// | | | +---------------------+ | z |
+/// | x: prim_header_address +-------+---> | Float header | | specific_address +-----+
+/// | y: picture_task_address +---------+ | | | transform_address +---+ |
+/// | clip_address +-----+ | | local_rect | | user_data | | |
+/// | z: flags | | | | local_clip_rect | +-----------------------+ | |
+/// | segment_index | | | +---------------------+ | |
+/// | w: resource_address +--+ | | | |
+/// +----------------------------+ | | | (sGpuCache) | |
+/// | | | (sGpuCache) +------------+ | |
+/// | | | +---------------+ | Transform | <--------+ |
+/// (sGpuCache) | | +-> | Picture task | +------------+ |
+/// +-------------+ | | | | |
+/// | Resource | <---+ | | ... | |
+/// | | | +---------------+ +--------------------------------+
+/// | | | |
+/// +-------------+ | (sGpuCache) v (sGpuCache)
+/// | +---------------+ +--------------+---------------+-+-+
+/// +-----> | Clip area | | Brush data | Segment data | | |
+/// | | | | | | |
+/// | ... | | ... | ... | | | ...
+/// +---------------+ +--------------+---------------+-+-+
+///```
+///
+/// - Segment data address is obtained by combining the address stored in the int header and the
+/// segment index decoded from the vertex attributes.
+/// - Resource data is optional, some brush types (such as images) store some extra data there while
+/// other brush types don't use it.
+///
+
+#ifdef WR_VERTEX_SHADER
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 segment_data
+);
+
+// Forward-declare the text vertex shader entry point which is currently
+// different from other brushes.
+void text_shader_main(
+ Instance instance,
+ PrimitiveHeader ph,
+ Transform transform,
+ PictureTask task,
+ ClipArea clip_area
+);
+
+#define VECS_PER_SEGMENT 2
+
+#define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION 1
+#define BRUSH_FLAG_SEGMENT_RELATIVE 2
+#define BRUSH_FLAG_SEGMENT_REPEAT_X 4
+#define BRUSH_FLAG_SEGMENT_REPEAT_Y 8
+#define BRUSH_FLAG_SEGMENT_REPEAT_X_ROUND 16
+#define BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND 32
+#define BRUSH_FLAG_SEGMENT_NINEPATCH_MIDDLE 64
+#define BRUSH_FLAG_TEXEL_RECT 128
+
+#define INVALID_SEGMENT_INDEX 0xffff
+
+void brush_shader_main_vs(
+ Instance instance,
+ PrimitiveHeader ph,
+ Transform transform,
+ PictureTask pic_task,
+ ClipArea clip_area
+) {
+ int edge_flags = instance.flags & 0xff;
+ int brush_flags = (instance.flags >> 8) & 0xff;
+
+ // Fetch the segment of this brush primitive we are drawing.
+ vec4 segment_data;
+ RectWithSize segment_rect;
+ if (instance.segment_index == INVALID_SEGMENT_INDEX) {
+ segment_rect = ph.local_rect;
+ segment_data = vec4(0.0);
+ } else {
+ int segment_address = ph.specific_prim_address +
+ VECS_PER_SPECIFIC_BRUSH +
+ instance.segment_index * VECS_PER_SEGMENT;
+
+ vec4[2] segment_info = fetch_from_gpu_cache_2(segment_address);
+ segment_rect = RectWithSize(segment_info[0].xy, segment_info[0].zw);
+ segment_rect.p0 += ph.local_rect.p0;
+ segment_data = segment_info[1];
+ }
+
+ VertexInfo vi;
+
+ // Write the normal vertex information out.
+ if (transform.is_axis_aligned) {
+
+ // Select the corner of the local rect that we are processing.
+ vec2 local_pos = segment_rect.p0 + segment_rect.size * aPosition.xy;
+
+ vi = write_vertex(
+ local_pos,
+ ph.local_clip_rect,
+ ph.z,
+ transform,
+ pic_task
+ );
+
+ // TODO(gw): transform bounds may be referenced by
+ // the fragment shader when running in
+ // the alpha pass, even on non-transformed
+ // items. For now, just ensure it has no
+ // effect. We can tidy this up as we move
+ // more items to be brush shaders.
+#ifdef WR_FEATURE_ALPHA_PASS
+ init_transform_vs(vec4(vec2(-1.0e16), vec2(1.0e16)));
+#endif
+ } else {
+ bvec4 edge_mask = notEqual(edge_flags & ivec4(1, 2, 4, 8), ivec4(0));
+
+ vi = write_transform_vertex(
+ segment_rect,
+ ph.local_rect,
+ ph.local_clip_rect,
+ mix(vec4(0.0), vec4(1.0), edge_mask),
+ ph.z,
+ transform,
+ pic_task
+ );
+ }
+
+ // For brush instances in the alpha pass, always write
+ // out clip information.
+ // TODO(gw): It's possible that we might want alpha
+ // shaders that don't clip in the future,
+ // but it's reasonable to assume that one
+ // implies the other, for now.
+#ifdef WR_FEATURE_ALPHA_PASS
+ write_clip(
+ vi.world_pos,
+ clip_area,
+ pic_task
+ );
+#endif
+
+ // Run the specific brush VS code to write interpolators.
+ brush_vs(
+ vi,
+ ph.specific_prim_address,
+ ph.local_rect,
+ segment_rect,
+ ph.user_data,
+ instance.resource_address,
+ transform.m,
+ pic_task,
+ brush_flags,
+ segment_data
+ );
+}
+
+#ifndef WR_VERTEX_SHADER_MAIN_FUNCTION
+// If the entry-point was not overridden before including the brush shader,
+// use the default one.
+#define WR_VERTEX_SHADER_MAIN_FUNCTION brush_shader_main_vs
+#endif
+
+void main(void) {
+
+ Instance instance = decode_instance_attributes();
+ PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address);
+ Transform transform = fetch_transform(ph.transform_id);
+ PictureTask task = fetch_picture_task(instance.picture_task_address);
+ ClipArea clip_area = fetch_clip_area(instance.clip_address);
+
+ WR_VERTEX_SHADER_MAIN_FUNCTION(instance, ph, transform, task, clip_area);
+}
+
+#endif // WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+
+Fragment brush_fs();
+
+void main(void) {
+#ifdef WR_FEATURE_DEBUG_OVERDRAW
+ oFragColor = WR_DEBUG_OVERDRAW_COLOR;
+#else
+
+ Fragment frag = brush_fs();
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ // Apply the clip mask
+ float clip_alpha = do_clip();
+
+ frag.color *= clip_alpha;
+
+ #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+ oFragBlend = frag.blend * clip_alpha;
+ #endif
+#endif
+
+ write_output(frag.color);
+#endif
+}
+#endif
diff --git a/gfx/wr/webrender/res/brush_blend.glsl b/gfx/wr/webrender/res/brush_blend.glsl
new file mode 100644
index 0000000000..67d422a81b
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_blend.glsl
@@ -0,0 +1,301 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 3
+#define WR_FEATURE_TEXTURE_2D
+
+#define COMPONENT_TRANSFER_IDENTITY 0
+#define COMPONENT_TRANSFER_TABLE 1
+#define COMPONENT_TRANSFER_DISCRETE 2
+#define COMPONENT_TRANSFER_LINEAR 3
+#define COMPONENT_TRANSFER_GAMMA 4
+
+// Must be kept in sync with `Filter::as_int` in internal_types.rs
+// Not all filters are defined here because some filter use different shaders.
+#define FILTER_CONTRAST 0
+#define FILTER_GRAYSCALE 1
+#define FILTER_HUE_ROTATE 2
+#define FILTER_INVERT 3
+#define FILTER_SATURATE 4
+#define FILTER_SEPIA 5
+#define FILTER_BRIGHTNESS 6
+#define FILTER_COLOR_MATRIX 7
+#define FILTER_SRGB_TO_LINEAR 8
+#define FILTER_LINEAR_TO_SRGB 9
+#define FILTER_FLOOD 10
+#define FILTER_COMPONENT_TRANSFER 11
+
+#include shared,prim_shared,brush
+
+// Interpolated UV coordinates to sample.
+varying vec2 v_uv;
+varying vec2 v_local_pos;
+
+// Normalized bounds of the source image in the texture, adjusted to avoid
+// sampling artifacts.
+flat varying vec4 v_uv_sample_bounds;
+
+flat varying vec4 v_color_offset;
+
+// Flag to allow perspective interpolation of UV.
+flat varying float v_perspective;
+
+flat varying float v_amount;
+
+flat varying int v_op;
+flat varying int v_table_address;
+
+flat varying mat4 vColorMat;
+
+flat varying int vFuncs[4];
+
+#ifdef WR_VERTEX_SHADER
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 unused
+) {
+ ImageResource res = fetch_image_resource(prim_user_data.x);
+ vec2 uv0 = res.uv_rect.p0;
+ vec2 uv1 = res.uv_rect.p1;
+
+ vec2 inv_texture_size = vec2(1.0) / vec2(textureSize(sColor0, 0).xy);
+ vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
+ f = get_image_quad_uv(prim_user_data.x, f);
+ vec2 uv = mix(uv0, uv1, f);
+ float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;
+
+ v_uv = uv * inv_texture_size * mix(vi.world_pos.w, 1.0, perspective_interpolate);
+ v_perspective = perspective_interpolate;
+
+ v_uv_sample_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) * inv_texture_size.xyxy;
+
+ v_local_pos = vi.local_pos;
+
+ float lumR = 0.2126;
+ float lumG = 0.7152;
+ float lumB = 0.0722;
+ float oneMinusLumR = 1.0 - lumR;
+ float oneMinusLumG = 1.0 - lumG;
+ float oneMinusLumB = 1.0 - lumB;
+
+ float amount = float(prim_user_data.z) / 65536.0;
+ float invAmount = 1.0 - amount;
+
+ v_op = prim_user_data.y & 0xffff;
+ v_amount = amount;
+
+ // This assignment is only used for component transfer filters but this
+ // assignment has to be done here and not in the component transfer case
+ // below because it doesn't get executed on Windows because of a suspected
+ // miscompile of this shader on Windows. See
+ // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows
+ // default: just to satisfy angle_shader_validation.rs which needs one
+ // default: for every switch, even in comments.
+ vFuncs[0] = (prim_user_data.y >> 28) & 0xf; // R
+ vFuncs[1] = (prim_user_data.y >> 24) & 0xf; // G
+ vFuncs[2] = (prim_user_data.y >> 20) & 0xf; // B
+ vFuncs[3] = (prim_user_data.y >> 16) & 0xf; // A
+
+ if (v_op == FILTER_GRAYSCALE) {
+ vColorMat = mat4(
+ vec4(lumR + oneMinusLumR * invAmount, lumR - lumR * invAmount, lumR - lumR * invAmount, 0.0),
+ vec4(lumG - lumG * invAmount, lumG + oneMinusLumG * invAmount, lumG - lumG * invAmount, 0.0),
+ vec4(lumB - lumB * invAmount, lumB - lumB * invAmount, lumB + oneMinusLumB * invAmount, 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0)
+ );
+ v_color_offset = vec4(0.0);
+ } else if (v_op == FILTER_HUE_ROTATE) {
+ float c = cos(amount);
+ float s = sin(amount);
+ vColorMat = mat4(
+ vec4(lumR + oneMinusLumR * c - lumR * s, lumR - lumR * c + 0.143 * s, lumR - lumR * c - oneMinusLumR * s, 0.0),
+ vec4(lumG - lumG * c - lumG * s, lumG + oneMinusLumG * c + 0.140 * s, lumG - lumG * c + lumG * s, 0.0),
+ vec4(lumB - lumB * c + oneMinusLumB * s, lumB - lumB * c - 0.283 * s, lumB + oneMinusLumB * c + lumB * s, 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0)
+ );
+ v_color_offset = vec4(0.0);
+ } else if (v_op == FILTER_SATURATE) {
+ vColorMat = mat4(
+ vec4(invAmount * lumR + amount, invAmount * lumR, invAmount * lumR, 0.0),
+ vec4(invAmount * lumG, invAmount * lumG + amount, invAmount * lumG, 0.0),
+ vec4(invAmount * lumB, invAmount * lumB, invAmount * lumB + amount, 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0)
+ );
+ v_color_offset = vec4(0.0);
+ } else if (v_op == FILTER_SEPIA) {
+ vColorMat = mat4(
+ vec4(0.393 + 0.607 * invAmount, 0.349 - 0.349 * invAmount, 0.272 - 0.272 * invAmount, 0.0),
+ vec4(0.769 - 0.769 * invAmount, 0.686 + 0.314 * invAmount, 0.534 - 0.534 * invAmount, 0.0),
+ vec4(0.189 - 0.189 * invAmount, 0.168 - 0.168 * invAmount, 0.131 + 0.869 * invAmount, 0.0),
+ vec4(0.0, 0.0, 0.0, 1.0)
+ );
+ v_color_offset = vec4(0.0);
+ } else if (v_op == FILTER_COLOR_MATRIX) {
+ vec4 mat_data[4] = fetch_from_gpu_cache_4(prim_user_data.z);
+ vec4 offset_data = fetch_from_gpu_cache_1(prim_user_data.z + 4);
+ vColorMat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]);
+ v_color_offset = offset_data;
+ } else if (v_op == FILTER_COMPONENT_TRANSFER) {
+ v_table_address = prim_user_data.z;
+ } else if (v_op == FILTER_FLOOD) {
+ v_color_offset = fetch_from_gpu_cache_1(prim_user_data.z);
+ }
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec3 Contrast(vec3 Cs, float amount) {
+ return Cs.rgb * amount - 0.5 * amount + 0.5;
+}
+
+vec3 Invert(vec3 Cs, float amount) {
+ return mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount);
+}
+
+vec3 Brightness(vec3 Cs, float amount) {
+ // Apply the brightness factor.
+ // Resulting color needs to be clamped to output range
+ // since we are pre-multiplying alpha in the shader.
+ return clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
+}
+
+// Based on the Gecko's implementation in
+// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24
+// These could be made faster by sampling a lookup table stored in a float texture
+// with linear interpolation.
+
+vec3 SrgbToLinear(vec3 color) {
+ vec3 c1 = color / 12.92;
+ vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4));
+ return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2);
+}
+
+vec3 LinearToSrgb(vec3 color) {
+ vec3 c1 = color * 12.92;
+ vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
+ return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
+}
+
+// This function has to be factored out due to the following issue:
+// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones
+// (and now the words "default: default:" so angle_shader_validation.rs passes)
+vec4 ComponentTransfer(vec4 colora) {
+ // We push a different amount of data to the gpu cache depending on the
+ // function type.
+ // Identity => 0 blocks
+ // Table/Discrete => 64 blocks (256 values)
+ // Linear => 1 block (2 values)
+ // Gamma => 1 block (3 values)
+ // We loop through the color components and increment the offset (for the
+ // next color component) into the gpu cache based on how many blocks that
+ // function type put into the gpu cache.
+ // Table/Discrete use a 256 entry look up table.
+ // Linear/Gamma are a simple calculation.
+ int offset = 0;
+ vec4 texel;
+ int k;
+
+ for (int i = 0; i < 4; i++) {
+ switch (vFuncs[i]) {
+ case COMPONENT_TRANSFER_IDENTITY:
+ break;
+ case COMPONENT_TRANSFER_TABLE:
+ case COMPONENT_TRANSFER_DISCRETE: {
+ // fetch value from lookup table
+ k = int(floor(colora[i]*255.0));
+ texel = fetch_from_gpu_cache_1(v_table_address + offset + k/4);
+ colora[i] = clamp(texel[k % 4], 0.0, 1.0);
+ // offset plus 256/4 blocks
+ offset = offset + 64;
+ break;
+ }
+ case COMPONENT_TRANSFER_LINEAR: {
+ // fetch the two values for use in the linear equation
+ texel = fetch_from_gpu_cache_1(v_table_address + offset);
+ colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
+ // offset plus 1 block
+ offset = offset + 1;
+ break;
+ }
+ case COMPONENT_TRANSFER_GAMMA: {
+ // fetch the three values for use in the gamma equation
+ texel = fetch_from_gpu_cache_1(v_table_address + offset);
+ colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
+ // offset plus 1 block
+ offset = offset + 1;
+ break;
+ }
+ default:
+ // shouldn't happen
+ break;
+ }
+ }
+ return colora;
+}
+
+Fragment brush_fs() {
+ float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective);
+ vec2 uv = v_uv * perspective_divisor;
+ // Clamp the uvs to avoid sampling artifacts.
+ uv = clamp(uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);
+
+ vec4 Cs = texture(sColor0, uv);
+
+ // Un-premultiply the input.
+ float alpha = Cs.a;
+ vec3 color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb;
+
+ switch (v_op) {
+ case FILTER_CONTRAST:
+ color = Contrast(color, v_amount);
+ break;
+ case FILTER_INVERT:
+ color = Invert(color, v_amount);
+ break;
+ case FILTER_BRIGHTNESS:
+ color = Brightness(color, v_amount);
+ break;
+ case FILTER_SRGB_TO_LINEAR:
+ color = SrgbToLinear(color);
+ break;
+ case FILTER_LINEAR_TO_SRGB:
+ color = LinearToSrgb(color);
+ break;
+ case FILTER_COMPONENT_TRANSFER: {
+ // Get the unpremultiplied color with alpha.
+ vec4 colora = vec4(color, alpha);
+ colora = ComponentTransfer(colora);
+ color = colora.rgb;
+ alpha = colora.a;
+ break;
+ }
+ case FILTER_FLOOD:
+ color = v_color_offset.rgb;
+ alpha = v_color_offset.a;
+ break;
+ default:
+ // Color matrix type filters (sepia, hue-rotate, etc...)
+ vec4 result = vColorMat * vec4(color, alpha) + v_color_offset;
+ result = clamp(result, vec4(0.0), vec4(1.0));
+ color = result.rgb;
+ alpha = result.a;
+ }
+
+ #ifdef WR_FEATURE_ALPHA_PASS
+ alpha *= init_transform_fs(v_local_pos);
+ // Pre-multiply the alpha into the output value.
+ #endif
+
+ return Fragment(alpha * vec4(color, 1.0));
+}
+#endif
diff --git a/gfx/wr/webrender/res/brush_conic_gradient.glsl b/gfx/wr/webrender/res/brush_conic_gradient.glsl
new file mode 100644
index 0000000000..cdc5a8a241
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_conic_gradient.glsl
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 2
+
+#include shared,prim_shared,brush,gradient_shared
+
+flat varying vec2 v_center;
+flat varying float v_start_offset;
+flat varying float v_offset_scale;
+flat varying float v_angle;
+
+#define PI 3.141592653589793
+
+#ifdef WR_VERTEX_SHADER
+
+struct ConicGradient {
+ vec2 center_point;
+ vec2 start_end_offset;
+ float angle;
+ int extend_mode;
+ vec2 stretch_size;
+};
+
+ConicGradient fetch_gradient(int address) {
+ vec4 data[2] = fetch_from_gpu_cache_2(address);
+ return ConicGradient(
+ data[0].xy,
+ data[0].zw,
+ float(data[1].x),
+ int(data[1].y),
+ data[1].zw
+ );
+}
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 texel_rect
+) {
+ ConicGradient gradient = fetch_gradient(prim_address);
+
+ write_gradient_vertex(
+ vi,
+ local_rect,
+ segment_rect,
+ prim_user_data,
+ brush_flags,
+ texel_rect,
+ gradient.extend_mode,
+ gradient.stretch_size
+ );
+
+ v_center = gradient.center_point;
+ v_angle = PI / 2.0 - gradient.angle;
+ v_start_offset = gradient.start_end_offset.x;
+ if (gradient.start_end_offset.x != gradient.start_end_offset.y) {
+ // Store 1/scale where scale = end_offset - start_offset
+ v_offset_scale = 1.0 / (gradient.start_end_offset.y - gradient.start_end_offset.x);
+ } else {
+ // If scale = 0, we can't get its reciprocal. Instead, just use a zero scale.
+ v_offset_scale = 0.0;
+ }
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment brush_fs() {
+ vec2 pos = compute_gradient_pos();
+
+ // Rescale UV to actual repetition size. This can't be done in the vertex
+ // shader due to the use of atan() below.
+ pos *= v_repeated_size;
+
+ vec2 current_dir = pos - v_center;
+ float current_angle = atan(current_dir.y, current_dir.x) + v_angle;
+ float offset = (fract(current_angle / (2.0 * PI)) - v_start_offset) * v_offset_scale;
+
+ vec4 color = sample_gradient(offset);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ color *= init_transform_fs(v_local_pos);
+#endif
+
+ return Fragment(color);
+}
+#endif
diff --git a/gfx/wr/webrender/res/brush_image.glsl b/gfx/wr/webrender/res/brush_image.glsl
new file mode 100644
index 0000000000..47a6e8bdcc
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_image.glsl
@@ -0,0 +1,423 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 3
+
+#include shared,prim_shared,brush
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 v_local_pos;
+#endif
+
+// Interpolated UV coordinates to sample.
+varying vec2 v_uv;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+flat varying vec4 v_color;
+flat varying vec2 v_mask_swizzle;
+flat varying vec2 v_tile_repeat;
+#endif
+
+// Normalized bounds of the source image in the texture.
+flat varying vec4 v_uv_bounds;
+// Normalized bounds of the source image in the texture, adjusted to avoid
+// sampling artifacts.
+flat varying vec4 v_uv_sample_bounds;
+// x: Layer index to sample.
+// y: Flag to allow perspective interpolation of UV.
+flat varying vec2 v_layer_and_perspective;
+
+#ifdef WR_VERTEX_SHADER
+
+// Must match the AlphaType enum.
+#define BLEND_MODE_ALPHA 0
+#define BLEND_MODE_PREMUL_ALPHA 1
+
+struct ImageBrushData {
+ vec4 color;
+ vec4 background_color;
+ vec2 stretch_size;
+};
+
+ImageBrushData fetch_image_data(int address) {
+ vec4[3] raw_data = fetch_from_gpu_cache_3(address);
+ ImageBrushData data = ImageBrushData(
+ raw_data[0],
+ raw_data[1],
+ raw_data[2].xy
+ );
+ return data;
+}
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize prim_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 segment_data
+) {
+ ImageBrushData image_data = fetch_image_data(prim_address);
+
+ // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
+ // non-normalized texture coordinates.
+#ifdef WR_FEATURE_TEXTURE_RECT
+ vec2 texture_size = vec2(1, 1);
+#else
+ vec2 texture_size = vec2(textureSize(sColor0, 0));
+#endif
+
+ ImageResource res = fetch_image_resource(specific_resource_address);
+ vec2 uv0 = res.uv_rect.p0;
+ vec2 uv1 = res.uv_rect.p1;
+
+ RectWithSize local_rect = prim_rect;
+ vec2 stretch_size = image_data.stretch_size;
+ if (stretch_size.x < 0.0) {
+ stretch_size = local_rect.size;
+ }
+
+ // If this segment should interpolate relative to the
+ // segment, modify the parameters for that.
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
+ local_rect = segment_rect;
+ stretch_size = local_rect.size;
+
+ if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
+ // If the extra data is a texel rect, modify the UVs.
+ vec2 uv_size = res.uv_rect.p1 - res.uv_rect.p0;
+ uv0 = res.uv_rect.p0 + segment_data.xy * uv_size;
+ uv1 = res.uv_rect.p0 + segment_data.zw * uv_size;
+ }
+
+ #ifdef WR_FEATURE_REPETITION
+ // TODO(bug 1609893): Move this logic to the CPU as well as other sources of
+ // branchiness in this shader.
+ if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
+ // Value of the stretch size with repetition. We have to compute it for
+ // both axis even if we only repeat on one axis because the value for
+ // each axis depends on what the repeated value would have been for the
+ // other axis.
+ vec2 repeated_stretch_size = stretch_size;
+ // Size of the uv rect of the segment we are considering when computing
+ // the repetitions. For the fill area it is a tad more complicated as we
+ // have to use the uv size of the top-middle segment to drive horizontal
+ // repetitions, and the size of the left-middle segment to drive vertical
+ // repetitions. So we track the reference sizes for both axis separately
+ // even though in the common case (the border segments) they are the same.
+ vec2 horizontal_uv_size = uv1 - uv0;
+ vec2 vertical_uv_size = uv1 - uv0;
+ // We use top and left sizes by default and fall back to bottom and right
+ // when a size is empty.
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_NINEPATCH_MIDDLE) != 0) {
+ repeated_stretch_size = segment_rect.p0 - prim_rect.p0;
+
+ float epsilon = 0.001;
+
+ // Adjust the the referecne uv size to compute vertical repetitions for
+ // the fill area.
+ vertical_uv_size.x = uv0.x - res.uv_rect.p0.x;
+ if (vertical_uv_size.x < epsilon || repeated_stretch_size.x < epsilon) {
+ vertical_uv_size.x = res.uv_rect.p1.x - uv1.x;
+ repeated_stretch_size.x = prim_rect.p0.x + prim_rect.size.x
+ - segment_rect.p0.x - segment_rect.size.x;
+ }
+
+ // Adjust the the referecne uv size to compute horizontal repetitions
+ // for the fill area.
+ horizontal_uv_size.y = uv0.y - res.uv_rect.p0.y;
+ if (horizontal_uv_size.y < epsilon || repeated_stretch_size.y < epsilon) {
+ horizontal_uv_size.y = res.uv_rect.p1.y - uv1.y;
+ repeated_stretch_size.y = prim_rect.p0.y + prim_rect.size.y
+ - segment_rect.p0.y - segment_rect.size.y;
+ }
+ }
+
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
+ float uv_ratio = horizontal_uv_size.x / horizontal_uv_size.y;
+ stretch_size.x = repeated_stretch_size.y * uv_ratio;
+ }
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
+ float uv_ratio = vertical_uv_size.y / vertical_uv_size.x;
+ stretch_size.y = repeated_stretch_size.x * uv_ratio;
+ }
+
+ } else {
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
+ stretch_size.x = segment_data.z - segment_data.x;
+ }
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
+ stretch_size.y = segment_data.w - segment_data.y;
+ }
+ }
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X_ROUND) != 0) {
+ float nx = max(1.0, round(segment_rect.size.x / stretch_size.x));
+ stretch_size.x = segment_rect.size.x / nx;
+ }
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND) != 0) {
+ float ny = max(1.0, round(segment_rect.size.y / stretch_size.y));
+ stretch_size.y = segment_rect.size.y / ny;
+ }
+ #endif
+ }
+
+ float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;
+ v_layer_and_perspective = vec2(res.layer, perspective_interpolate);
+
+ // Handle case where the UV coords are inverted (e.g. from an
+ // external image).
+ vec2 min_uv = min(uv0, uv1);
+ vec2 max_uv = max(uv0, uv1);
+
+ v_uv_sample_bounds = vec4(
+ min_uv + vec2(0.5),
+ max_uv - vec2(0.5)
+ ) / texture_size.xyxy;
+
+ vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ int color_mode = prim_user_data.x & 0xffff;
+ int blend_mode = prim_user_data.x >> 16;
+ int raster_space = prim_user_data.y;
+
+ if (color_mode == COLOR_MODE_FROM_PASS) {
+ color_mode = uMode;
+ }
+
+ // Derive the texture coordinates for this image, based on
+ // whether the source image is a local-space or screen-space
+ // image.
+ switch (raster_space) {
+ case RASTER_SCREEN: {
+ // Since the screen space UVs specify an arbitrary quad, do
+ // a bilinear interpolation to get the correct UV for this
+ // local position.
+ f = get_image_quad_uv(specific_resource_address, f);
+ break;
+ }
+ default:
+ break;
+ }
+#endif
+
+ // Offset and scale v_uv here to avoid doing it in the fragment shader.
+ vec2 repeat = local_rect.size / stretch_size;
+ v_uv = mix(uv0, uv1, f) - min_uv;
+ v_uv /= texture_size;
+ v_uv *= repeat.xy;
+ if (perspective_interpolate == 0.0) {
+ v_uv *= vi.world_pos.w;
+ }
+
+#ifdef WR_FEATURE_TEXTURE_RECT
+ v_uv_bounds = vec4(0.0, 0.0, vec2(textureSize(sColor0)));
+#else
+ v_uv_bounds = vec4(min_uv, max_uv) / texture_size.xyxy;
+#endif
+
+#ifdef WR_FEATURE_REPETITION
+ // Normalize UV to 0..1 scale only if using repetition. Otherwise, leave
+ // UVs unnormalized since we won't compute a modulus without repetition
+ // enabled.
+ v_uv /= (v_uv_bounds.zw - v_uv_bounds.xy);
+#endif
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ v_tile_repeat = repeat.xy;
+
+ float opacity = float(prim_user_data.z) / 65535.0;
+ switch (blend_mode) {
+ case BLEND_MODE_ALPHA:
+ image_data.color.a *= opacity;
+ break;
+ case BLEND_MODE_PREMUL_ALPHA:
+ default:
+ image_data.color *= opacity;
+ break;
+ }
+
+ switch (color_mode) {
+ case COLOR_MODE_ALPHA:
+ case COLOR_MODE_BITMAP:
+ v_mask_swizzle = vec2(0.0, 1.0);
+ v_color = image_data.color;
+ break;
+ case COLOR_MODE_SUBPX_BG_PASS2:
+ case COLOR_MODE_SUBPX_DUAL_SOURCE:
+ case COLOR_MODE_IMAGE:
+ v_mask_swizzle = vec2(1.0, 0.0);
+ v_color = image_data.color;
+ break;
+ case COLOR_MODE_SUBPX_CONST_COLOR:
+ case COLOR_MODE_SUBPX_BG_PASS0:
+ case COLOR_MODE_COLOR_BITMAP:
+ v_mask_swizzle = vec2(1.0, 0.0);
+ v_color = vec4(image_data.color.a);
+ break;
+ case COLOR_MODE_SUBPX_BG_PASS1:
+ v_mask_swizzle = vec2(-1.0, 1.0);
+ v_color = vec4(image_data.color.a) * image_data.background_color;
+ break;
+ default:
+ v_mask_swizzle = vec2(0.0);
+ v_color = vec4(1.0);
+ }
+
+ v_local_pos = vi.local_pos;
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+vec2 compute_repeated_uvs(float perspective_divisor) {
+ vec2 uv_size = v_uv_bounds.zw - v_uv_bounds.xy;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ // This prevents the uv on the top and left parts of the primitive that was inflated
+ // for anti-aliasing purposes from going beyound the range covered by the regular
+ // (non-inflated) primitive.
+ vec2 local_uv = max(v_uv * perspective_divisor, vec2(0.0));
+
+ // Handle horizontal and vertical repetitions.
+ vec2 repeated_uv = fract(local_uv) * uv_size + v_uv_bounds.xy;
+
+ // This takes care of the bottom and right inflated parts.
+ // We do it after the modulo because the latter wraps around the values exactly on
+ // the right and bottom edges, which we do not want.
+ if (local_uv.x >= v_tile_repeat.x) {
+ repeated_uv.x = v_uv_bounds.z;
+ }
+ if (local_uv.y >= v_tile_repeat.y) {
+ repeated_uv.y = v_uv_bounds.w;
+ }
+#else
+ vec2 repeated_uv = fract(v_uv * perspective_divisor) * uv_size + v_uv_bounds.xy;
+#endif
+
+ return repeated_uv;
+}
+
+Fragment brush_fs() {
+ float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_layer_and_perspective.y);
+
+#ifdef WR_FEATURE_REPETITION
+ vec2 repeated_uv = compute_repeated_uvs(perspective_divisor);
+#else
+ vec2 repeated_uv = v_uv * perspective_divisor + v_uv_bounds.xy;
+#endif
+
+ // Clamp the uvs to avoid sampling artifacts.
+ vec2 uv = clamp(repeated_uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);
+
+ vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, v_layer_and_perspective.x));
+
+ Fragment frag;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ #ifdef WR_FEATURE_ANTIALIASING
+ float alpha = init_transform_fs(v_local_pos);
+ #else
+ float alpha = 1.0;
+ #endif
+ texel.rgb = texel.rgb * v_mask_swizzle.x + texel.aaa * v_mask_swizzle.y;
+
+ vec4 alpha_mask = texel * alpha;
+ frag.color = v_color * alpha_mask;
+
+ #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+ frag.blend = alpha_mask * v_color.a;
+ #endif
+#else
+ frag.color = texel;
+#endif
+
+ return frag;
+}
+
+#if defined(SWGL) && (!defined(WR_FEATURE_ALPHA_PASS) || !defined(WR_FEATURE_DUAL_SOURCE_BLENDING))
+void swgl_drawSpanRGBA8() {
+ if (!swgl_isTextureRGBA8(sColor0) || !swgl_isTextureLinear(sColor0)) {
+ return;
+ }
+
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (v_mask_swizzle != vec2(1.0, 0.0)) {
+ return;
+ }
+ #endif
+
+ int layer = swgl_textureLayerOffset(sColor0, v_layer_and_perspective.x);
+
+ float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, v_layer_and_perspective.y);
+
+ #ifndef WR_FEATURE_REPETITION
+ vec2 uv = v_uv * perspective_divisor + v_uv_bounds.xy;
+
+ #ifndef WR_FEATURE_ANTIALIASING
+ if (swgl_allowTextureNearest(sColor0, uv)) {
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (v_color != vec4(1.0)) {
+ swgl_commitTextureNearestColorRGBA8(sColor0, uv, v_uv_sample_bounds, v_color, layer);
+ return;
+ }
+ #endif
+ swgl_commitTextureNearestRGBA8(sColor0, uv, v_uv_sample_bounds, layer);
+ return;
+ }
+ #endif
+
+ uv = swgl_linearQuantize(sColor0, uv);
+ vec2 min_uv = swgl_linearQuantize(sColor0, v_uv_sample_bounds.xy);
+ vec2 max_uv = swgl_linearQuantize(sColor0, v_uv_sample_bounds.zw);
+ vec2 step_uv = swgl_linearQuantizeStep(sColor0, swgl_interpStep(v_uv)) * perspective_divisor;
+ #endif
+
+ #ifdef WR_FEATURE_ALPHA_PASS
+ #ifdef WR_FEATURE_ANTIALIASING
+ {
+ #else
+ if (v_color != vec4(1.0)) {
+ #endif
+ while (swgl_SpanLength > 0) {
+ vec4 color = v_color;
+ #ifdef WR_FEATURE_ANTIALIASING
+ color *= init_transform_fs(v_local_pos);
+ v_local_pos += swgl_interpStep(v_local_pos);
+ #endif
+ #ifdef WR_FEATURE_REPETITION
+ vec2 repeated_uv = compute_repeated_uvs(perspective_divisor);
+ vec2 uv = clamp(repeated_uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);
+ swgl_commitTextureLinearColorRGBA8(sColor0, swgl_linearQuantize(sColor0, uv), color, layer);
+ v_uv += swgl_interpStep(v_uv);
+ #else
+ swgl_commitTextureLinearColorRGBA8(sColor0, clamp(uv, min_uv, max_uv), color, layer);
+ uv += step_uv;
+ #endif
+ }
+ return;
+ }
+ // No clip or color scaling required, so just fall through to a normal textured span...
+ #endif
+
+ while (swgl_SpanLength > 0) {
+ #ifdef WR_FEATURE_REPETITION
+ vec2 repeated_uv = compute_repeated_uvs(perspective_divisor);
+ vec2 uv = clamp(repeated_uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);
+ swgl_commitTextureLinearRGBA8(sColor0, swgl_linearQuantize(sColor0, uv), layer);
+ v_uv += swgl_interpStep(v_uv);
+ #else
+ swgl_commitTextureLinearRGBA8(sColor0, clamp(uv, min_uv, max_uv), layer);
+ uv += step_uv;
+ #endif
+ }
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/brush_linear_gradient.glsl b/gfx/wr/webrender/res/brush_linear_gradient.glsl
new file mode 100644
index 0000000000..5d2b92cb6c
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_linear_gradient.glsl
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 2
+
+#include shared,prim_shared,brush,gradient_shared
+
+flat varying vec2 v_start_point;
+flat varying vec2 v_scale_dir;
+
+#ifdef WR_VERTEX_SHADER
+
+struct Gradient {
+ vec4 start_end_point;
+ int extend_mode;
+ vec2 stretch_size;
+};
+
+Gradient fetch_gradient(int address) {
+ vec4 data[2] = fetch_from_gpu_cache_2(address);
+ return Gradient(
+ data[0],
+ int(data[1].x),
+ data[1].yz
+ );
+}
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 texel_rect
+) {
+ Gradient gradient = fetch_gradient(prim_address);
+
+ write_gradient_vertex(
+ vi,
+ local_rect,
+ segment_rect,
+ prim_user_data,
+ brush_flags,
+ texel_rect,
+ gradient.extend_mode,
+ gradient.stretch_size
+ );
+
+ vec2 start_point = gradient.start_end_point.xy;
+ vec2 end_point = gradient.start_end_point.zw;
+ vec2 dir = end_point - start_point;
+
+ v_start_point = start_point;
+ v_scale_dir = dir / dot(dir, dir);
+
+ // Normalize UV and offsets to 0..1 scale.
+ v_start_point /= v_repeated_size;
+ v_scale_dir *= v_repeated_size;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment brush_fs() {
+ vec2 pos = compute_gradient_pos();
+
+ float offset = dot(pos - v_start_point, v_scale_dir);
+
+ vec4 color = sample_gradient(offset);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ color *= init_transform_fs(v_local_pos);
+#endif
+
+ return Fragment(color);
+}
+#endif
diff --git a/gfx/wr/webrender/res/brush_mix_blend.glsl b/gfx/wr/webrender/res/brush_mix_blend.glsl
new file mode 100644
index 0000000000..ff86e780f4
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_mix_blend.glsl
@@ -0,0 +1,291 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 3
+#define WR_FEATURE_TEXTURE_2D
+
+#include shared,prim_shared,brush
+
+// xy: uv coorinates.
+varying vec2 v_src_uv;
+
+// xy: uv coorinates.
+varying vec2 v_backdrop_uv;
+
+flat varying int v_op;
+
+#ifdef WR_VERTEX_SHADER
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 unused
+) {
+ //Note: this is unsafe for `vi.world_pos.w <= 0.0`
+ vec2 device_pos = vi.world_pos.xy * pic_task.device_pixel_scale / max(0.0, vi.world_pos.w);
+ vec2 backdrop_texture_size = vec2(textureSize(sColor0, 0));
+ vec2 src_texture_size = vec2(textureSize(sColor1, 0));
+ v_op = prim_user_data.x;
+
+ PictureTask src_task = fetch_picture_task(prim_user_data.z);
+ vec2 src_device_pos = vi.world_pos.xy * (src_task.device_pixel_scale / max(0.0, vi.world_pos.w));
+ vec2 src_uv = src_device_pos +
+ src_task.common_data.task_rect.p0 -
+ src_task.content_origin;
+ v_src_uv = src_uv / src_texture_size;
+
+ RenderTaskCommonData backdrop_task = fetch_render_task_common_data(prim_user_data.y);
+ float src_to_backdrop_scale = pic_task.device_pixel_scale / src_task.device_pixel_scale;
+ vec2 backdrop_uv = device_pos +
+ backdrop_task.task_rect.p0 -
+ src_task.content_origin * src_to_backdrop_scale;
+ v_backdrop_uv = backdrop_uv / backdrop_texture_size;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec3 Multiply(vec3 Cb, vec3 Cs) {
+ return Cb * Cs;
+}
+
+vec3 Screen(vec3 Cb, vec3 Cs) {
+ return Cb + Cs - (Cb * Cs);
+}
+
+vec3 HardLight(vec3 Cb, vec3 Cs) {
+ vec3 m = Multiply(Cb, 2.0 * Cs);
+ vec3 s = Screen(Cb, 2.0 * Cs - 1.0);
+ vec3 edge = vec3(0.5, 0.5, 0.5);
+ return mix(m, s, step(edge, Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorDodge(float Cb, float Cs) {
+ if (Cb == 0.0)
+ return 0.0;
+ else if (Cs == 1.0)
+ return 1.0;
+ else
+ return min(1.0, Cb / (1.0 - Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorBurn(float Cb, float Cs) {
+ if (Cb == 1.0)
+ return 1.0;
+ else if (Cs == 0.0)
+ return 0.0;
+ else
+ return 1.0 - min(1.0, (1.0 - Cb) / Cs);
+}
+
+float SoftLight(float Cb, float Cs) {
+ if (Cs <= 0.5) {
+ return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb);
+ } else {
+ float D;
+
+ if (Cb <= 0.25)
+ D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb;
+ else
+ D = sqrt(Cb);
+
+ return Cb + (2.0 * Cs - 1.0) * (D - Cb);
+ }
+}
+
+vec3 Difference(vec3 Cb, vec3 Cs) {
+ return abs(Cb - Cs);
+}
+
+vec3 Exclusion(vec3 Cb, vec3 Cs) {
+ return Cb + Cs - 2.0 * Cb * Cs;
+}
+
+// These functions below are taken from the spec.
+// There's probably a much quicker way to implement
+// them in GLSL...
+float Sat(vec3 c) {
+ return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
+}
+
+float Lum(vec3 c) {
+ vec3 f = vec3(0.3, 0.59, 0.11);
+ return dot(c, f);
+}
+
+vec3 ClipColor(vec3 C) {
+ float L = Lum(C);
+ float n = min(C.r, min(C.g, C.b));
+ float x = max(C.r, max(C.g, C.b));
+
+ if (n < 0.0)
+ C = L + (((C - L) * L) / (L - n));
+
+ if (x > 1.0)
+ C = L + (((C - L) * (1.0 - L)) / (x - L));
+
+ return C;
+}
+
+vec3 SetLum(vec3 C, float l) {
+ float d = l - Lum(C);
+ return ClipColor(C + d);
+}
+
+void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) {
+ if (Cmax > Cmin) {
+ Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin));
+ Cmax = s;
+ } else {
+ Cmid = 0.0;
+ Cmax = 0.0;
+ }
+ Cmin = 0.0;
+}
+
+vec3 SetSat(vec3 C, float s) {
+ if (C.r <= C.g) {
+ if (C.g <= C.b) {
+ SetSatInner(C.r, C.g, C.b, s);
+ } else {
+ if (C.r <= C.b) {
+ SetSatInner(C.r, C.b, C.g, s);
+ } else {
+ SetSatInner(C.b, C.r, C.g, s);
+ }
+ }
+ } else {
+ if (C.r <= C.b) {
+ SetSatInner(C.g, C.r, C.b, s);
+ } else {
+ if (C.g <= C.b) {
+ SetSatInner(C.g, C.b, C.r, s);
+ } else {
+ SetSatInner(C.b, C.g, C.r, s);
+ }
+ }
+ }
+ return C;
+}
+
+vec3 Hue(vec3 Cb, vec3 Cs) {
+ return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb));
+}
+
+vec3 Saturation(vec3 Cb, vec3 Cs) {
+ return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb));
+}
+
+vec3 Color(vec3 Cb, vec3 Cs) {
+ return SetLum(Cs, Lum(Cb));
+}
+
+vec3 Luminosity(vec3 Cb, vec3 Cs) {
+ return SetLum(Cb, Lum(Cs));
+}
+
+const int MixBlendMode_Multiply = 1;
+const int MixBlendMode_Screen = 2;
+const int MixBlendMode_Overlay = 3;
+const int MixBlendMode_Darken = 4;
+const int MixBlendMode_Lighten = 5;
+const int MixBlendMode_ColorDodge = 6;
+const int MixBlendMode_ColorBurn = 7;
+const int MixBlendMode_HardLight = 8;
+const int MixBlendMode_SoftLight = 9;
+const int MixBlendMode_Difference = 10;
+const int MixBlendMode_Exclusion = 11;
+const int MixBlendMode_Hue = 12;
+const int MixBlendMode_Saturation = 13;
+const int MixBlendMode_Color = 14;
+const int MixBlendMode_Luminosity = 15;
+
+Fragment brush_fs() {
+ vec4 Cb = texture(sColor0, v_backdrop_uv);
+ vec4 Cs = texture(sColor1, v_src_uv);
+
+ // The mix-blend-mode functions assume no premultiplied alpha
+ if (Cb.a != 0.0) {
+ Cb.rgb /= Cb.a;
+ }
+
+ if (Cs.a != 0.0) {
+ Cs.rgb /= Cs.a;
+ }
+
+ // Return yellow if none of the branches match (shouldn't happen).
+ vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
+
+ switch (v_op) {
+ case MixBlendMode_Multiply:
+ result.rgb = Multiply(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Screen:
+ result.rgb = Screen(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Overlay:
+ // Overlay is inverse of Hardlight
+ result.rgb = HardLight(Cs.rgb, Cb.rgb);
+ break;
+ case MixBlendMode_Darken:
+ result.rgb = min(Cs.rgb, Cb.rgb);
+ break;
+ case MixBlendMode_Lighten:
+ result.rgb = max(Cs.rgb, Cb.rgb);
+ break;
+ case MixBlendMode_ColorDodge:
+ result.r = ColorDodge(Cb.r, Cs.r);
+ result.g = ColorDodge(Cb.g, Cs.g);
+ result.b = ColorDodge(Cb.b, Cs.b);
+ break;
+ case MixBlendMode_ColorBurn:
+ result.r = ColorBurn(Cb.r, Cs.r);
+ result.g = ColorBurn(Cb.g, Cs.g);
+ result.b = ColorBurn(Cb.b, Cs.b);
+ break;
+ case MixBlendMode_HardLight:
+ result.rgb = HardLight(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_SoftLight:
+ result.r = SoftLight(Cb.r, Cs.r);
+ result.g = SoftLight(Cb.g, Cs.g);
+ result.b = SoftLight(Cb.b, Cs.b);
+ break;
+ case MixBlendMode_Difference:
+ result.rgb = Difference(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Exclusion:
+ result.rgb = Exclusion(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Hue:
+ result.rgb = Hue(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Saturation:
+ result.rgb = Saturation(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Color:
+ result.rgb = Color(Cb.rgb, Cs.rgb);
+ break;
+ case MixBlendMode_Luminosity:
+ result.rgb = Luminosity(Cb.rgb, Cs.rgb);
+ break;
+ default: break;
+ }
+
+ result.rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
+ result.a = Cs.a;
+
+ result.rgb *= result.a;
+
+ return Fragment(result);
+}
+#endif
diff --git a/gfx/wr/webrender/res/brush_opacity.glsl b/gfx/wr/webrender/res/brush_opacity.glsl
new file mode 100644
index 0000000000..25fe1cb5aa
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_opacity.glsl
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 3
+#define WR_FEATURE_TEXTURE_2D
+
+#include shared,prim_shared,brush
+
+// Interpolated UV coordinates to sample.
+varying vec2 v_uv;
+varying vec2 v_local_pos;
+
+// Normalized bounds of the source image in the texture, adjusted to avoid
+// sampling artifacts.
+flat varying vec4 v_uv_sample_bounds;
+
+// Layer index to sample.
+// Flag to allow perspective interpolation of UV.
+flat varying float v_perspective;
+
+flat varying float v_opacity;
+
+#ifdef WR_VERTEX_SHADER
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 unused
+) {
+ ImageResource res = fetch_image_resource(prim_user_data.x);
+ vec2 uv0 = res.uv_rect.p0;
+ vec2 uv1 = res.uv_rect.p1;
+
+ vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+ vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
+ f = get_image_quad_uv(prim_user_data.x, f);
+ vec2 uv = mix(uv0, uv1, f);
+ float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;
+
+ v_uv = uv / texture_size * mix(vi.world_pos.w, 1.0, perspective_interpolate);
+ v_perspective = perspective_interpolate;
+
+ v_uv_sample_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
+
+ #ifdef WR_FEATURE_ANTIALIASING
+ v_local_pos = vi.local_pos;
+ #endif
+
+ v_opacity = float(prim_user_data.y) / 65536.0;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment brush_fs() {
+ float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective);
+ vec2 uv = v_uv * perspective_divisor;
+ // Clamp the uvs to avoid sampling artifacts.
+ uv = clamp(uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);
+
+ // No need to un-premultiply since we'll only apply a factor to the alpha.
+ vec4 color = texture(sColor0, uv);
+
+ float alpha = v_opacity;
+
+ #ifdef WR_FEATURE_ANTIALIASING
+ alpha *= init_transform_fs(v_local_pos);
+ #endif
+
+ // Pre-multiply the contribution of the opacity factor.
+ return Fragment(alpha * color);
+}
+
+#if defined(SWGL) && !defined(WR_FEATURE_DUAL_SOURCE_BLENDING)
+void swgl_drawSpanRGBA8() {
+ if (!swgl_isTextureRGBA8(sColor0)) {
+ return;
+ }
+
+ float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, v_perspective);
+ vec2 uv = v_uv * perspective_divisor;
+
+ #ifndef WR_FEATURE_ANTIALIASING
+ if (swgl_allowTextureNearest(sColor0, uv)) {
+ swgl_commitTextureNearestColorRGBA8(sColor0, uv, v_uv_sample_bounds, v_opacity, 0);
+ return;
+ }
+ #endif
+
+ uv = swgl_linearQuantize(sColor0, uv);
+ vec2 min_uv = swgl_linearQuantize(sColor0, v_uv_sample_bounds.xy);
+ vec2 max_uv = swgl_linearQuantize(sColor0, v_uv_sample_bounds.zw);
+ vec2 step_uv = swgl_linearQuantizeStep(sColor0, swgl_interpStep(v_uv)) * perspective_divisor;
+
+ while (swgl_SpanLength > 0) {
+ float alpha = v_opacity;
+ #ifdef WR_FEATURE_ANTIALIASING
+ alpha *= init_transform_fs(v_local_pos);
+ v_local_pos += swgl_interpStep(v_local_pos);
+ #endif
+ swgl_commitTextureLinearColorRGBA8(sColor0, clamp(uv, min_uv, max_uv), alpha, 0);
+ uv += step_uv;
+ }
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/brush_radial_gradient.glsl b/gfx/wr/webrender/res/brush_radial_gradient.glsl
new file mode 100644
index 0000000000..96d87115c9
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_radial_gradient.glsl
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 2
+
+#include shared,prim_shared,brush,gradient_shared
+
+flat varying vec2 v_center;
+flat varying float v_start_radius;
+flat varying float v_radius_scale;
+
+#ifdef WR_VERTEX_SHADER
+
+struct RadialGradient {
+ vec4 center_start_end_radius;
+ float ratio_xy;
+ int extend_mode;
+ vec2 stretch_size;
+};
+
+RadialGradient fetch_radial_gradient(int address) {
+ vec4 data[2] = fetch_from_gpu_cache_2(address);
+ return RadialGradient(
+ data[0],
+ data[1].x,
+ int(data[1].y),
+ data[1].zw
+ );
+}
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 texel_rect
+) {
+ RadialGradient gradient = fetch_radial_gradient(prim_address);
+
+ write_gradient_vertex(
+ vi,
+ local_rect,
+ segment_rect,
+ prim_user_data,
+ brush_flags,
+ texel_rect,
+ gradient.extend_mode,
+ gradient.stretch_size
+ );
+
+ v_center = gradient.center_start_end_radius.xy;
+ v_start_radius = gradient.center_start_end_radius.z;
+ if (gradient.center_start_end_radius.z != gradient.center_start_end_radius.w) {
+ // Store 1/rd where rd = end_radius - start_radius
+ v_radius_scale = 1.0 / (gradient.center_start_end_radius.w - gradient.center_start_end_radius.z);
+ } else {
+ // If rd = 0, we can't get its reciprocal. Instead, just use a zero scale.
+ v_radius_scale = 0.0;
+ }
+
+ // Transform all coordinates by the y scale so the
+ // fragment shader can work with circles
+ v_center.y *= gradient.ratio_xy;
+ v_repeated_size.y *= gradient.ratio_xy;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment brush_fs() {
+ vec2 pos = compute_gradient_pos();
+
+ // Rescale UV to actual repetition size. This can't be done in the vertex
+ // shader due to the use of length() below.
+ pos *= v_repeated_size;
+
+ // Solve for t in length(pd) = v_start_radius + t * rd
+ vec2 pd = pos - v_center;
+ float offset = (length(pd) - v_start_radius) * v_radius_scale;
+
+ vec4 color = sample_gradient(offset);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ color *= init_transform_fs(v_local_pos);
+#endif
+
+ return Fragment(color);
+}
+#endif
diff --git a/gfx/wr/webrender/res/brush_solid.glsl b/gfx/wr/webrender/res/brush_solid.glsl
new file mode 100644
index 0000000000..59ed5c052c
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_solid.glsl
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 1
+
+#include shared,prim_shared,brush
+
+flat varying vec4 v_color;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 v_local_pos;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+
+struct SolidBrush {
+ vec4 color;
+};
+
+SolidBrush fetch_solid_primitive(int address) {
+ vec4 data = fetch_from_gpu_cache_1(address);
+ return SolidBrush(data);
+}
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 unused
+) {
+ SolidBrush prim = fetch_solid_primitive(prim_address);
+
+ float opacity = float(prim_user_data.x) / 65535.0;
+ v_color = prim.color * opacity;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ v_local_pos = vi.local_pos;
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment brush_fs() {
+ vec4 color = v_color;
+#ifdef WR_FEATURE_ALPHA_PASS
+ color *= init_transform_fs(v_local_pos);
+#endif
+ return Fragment(color);
+}
+
+#if defined(SWGL) && (!defined(WR_FEATURE_ALPHA_PASS) || !defined(WR_FEATURE_DUAL_SOURCE_BLENDING))
+void swgl_drawSpanRGBA8() {
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (has_valid_transform_bounds()) {
+ while (swgl_SpanLength > 0) {
+ float alpha = init_transform_fs(v_local_pos);
+ swgl_commitColorRGBA8(v_color, alpha);
+ v_local_pos += swgl_interpStep(v_local_pos);
+ }
+ return;
+ }
+ // No clip or transform, so just fall through to a solid span...
+ #endif
+
+ swgl_commitSolidRGBA8(v_color);
+}
+
+void swgl_drawSpanR8() {
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (has_valid_transform_bounds()) {
+ while (swgl_SpanLength > 0) {
+ float alpha = init_transform_fs(v_local_pos);
+ swgl_commitColorR8(v_color.x, alpha);
+ v_local_pos += swgl_interpStep(v_local_pos);
+ }
+ return;
+ }
+ // No clip or transform, so just fall through to a solid span...
+ #endif
+
+ swgl_commitSolidR8(v_color.x);
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/brush_yuv_image.glsl b/gfx/wr/webrender/res/brush_yuv_image.glsl
new file mode 100644
index 0000000000..403004ff53
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_yuv_image.glsl
@@ -0,0 +1,249 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define VECS_PER_SPECIFIC_BRUSH 1
+
+#include shared,prim_shared,brush,yuv
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 vLocalPos;
+#endif
+
+flat varying vec3 vYuvLayers;
+
+varying vec2 vUv_Y;
+flat varying vec4 vUvBounds_Y;
+
+varying vec2 vUv_U;
+flat varying vec4 vUvBounds_U;
+
+varying vec2 vUv_V;
+flat varying vec4 vUvBounds_V;
+
+flat varying float vCoefficient;
+flat varying mat3 vYuvColorMatrix;
+flat varying vec3 vYuvOffsetVector;
+flat varying int vFormat;
+
+#ifdef SWGL
+flat varying int vYuvColorSpace;
+flat varying int vRescaleFactor;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+
+struct YuvPrimitive {
+ float coefficient;
+ int color_space;
+ int yuv_format;
+};
+
+YuvPrimitive fetch_yuv_primitive(int address) {
+ vec4 data = fetch_from_gpu_cache_1(address);
+ return YuvPrimitive(data.x, int(data.y), int(data.z));
+}
+
+void brush_vs(
+ VertexInfo vi,
+ int prim_address,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int specific_resource_address,
+ mat4 transform,
+ PictureTask pic_task,
+ int brush_flags,
+ vec4 unused
+) {
+ vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
+
+ YuvPrimitive prim = fetch_yuv_primitive(prim_address);
+ vCoefficient = prim.coefficient;
+
+ vYuvColorMatrix = get_yuv_color_matrix(prim.color_space);
+ vYuvOffsetVector = get_yuv_offset_vector(prim.color_space);
+ vFormat = prim.yuv_format;
+
+#ifdef SWGL
+ // swgl_commitTextureLinearYUV needs to know the color space specifier and
+ // also needs to know how many bits of scaling are required to normalize
+ // HDR textures.
+ vYuvColorSpace = prim.color_space;
+ vRescaleFactor = int(log2(prim.coefficient));
+#endif
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ vLocalPos = vi.local_pos;
+#endif
+
+ if (vFormat == YUV_FORMAT_PLANAR) {
+ ImageResource res_y = fetch_image_resource(prim_user_data.x);
+ ImageResource res_u = fetch_image_resource(prim_user_data.y);
+ ImageResource res_v = fetch_image_resource(prim_user_data.z);
+ write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE(sColor0), vUv_Y, vUvBounds_Y);
+ write_uv_rect(res_u.uv_rect.p0, res_u.uv_rect.p1, f, TEX_SIZE(sColor1), vUv_U, vUvBounds_U);
+ write_uv_rect(res_v.uv_rect.p0, res_v.uv_rect.p1, f, TEX_SIZE(sColor2), vUv_V, vUvBounds_V);
+ vYuvLayers = vec3(res_y.layer, res_u.layer, res_v.layer);
+ } else if (vFormat == YUV_FORMAT_NV12) {
+ ImageResource res_y = fetch_image_resource(prim_user_data.x);
+ ImageResource res_u = fetch_image_resource(prim_user_data.y);
+ write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE(sColor0), vUv_Y, vUvBounds_Y);
+ write_uv_rect(res_u.uv_rect.p0, res_u.uv_rect.p1, f, TEX_SIZE(sColor1), vUv_U, vUvBounds_U);
+ vYuvLayers = vec3(res_y.layer, res_u.layer, 0.0);
+ } else if (vFormat == YUV_FORMAT_INTERLEAVED) {
+ ImageResource res_y = fetch_image_resource(prim_user_data.x);
+ write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE(sColor0), vUv_Y, vUvBounds_Y);
+ vYuvLayers = vec3(res_y.layer, 0.0, 0.0);
+ }
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+Fragment brush_fs() {
+ vec4 color = sample_yuv(
+ vFormat,
+ vYuvColorMatrix,
+ vYuvOffsetVector,
+ vCoefficient,
+ vYuvLayers,
+ vUv_Y,
+ vUv_U,
+ vUv_V,
+ vUvBounds_Y,
+ vUvBounds_U,
+ vUvBounds_V
+ );
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ color *= init_transform_fs(vLocalPos);
+#endif
+
+ return Fragment(color);
+}
+
+#ifdef SWGL
+void swgl_drawSpanRGBA8() {
+ if (vFormat == YUV_FORMAT_PLANAR) {
+ if (!swgl_isTextureLinear(sColor0) || !swgl_isTextureLinear(sColor1) || !swgl_isTextureLinear(sColor2)) {
+ return;
+ }
+
+ int layer0 = swgl_textureLayerOffset(sColor0, vYuvLayers.x);
+ vec2 uv0 = swgl_linearQuantize(sColor0, vUv_Y);
+ vec2 min_uv0 = swgl_linearQuantize(sColor0, vUvBounds_Y.xy);
+ vec2 max_uv0 = swgl_linearQuantize(sColor0, vUvBounds_Y.zw);
+ vec2 step_uv0 = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUv_Y));
+
+ int layer1 = swgl_textureLayerOffset(sColor1, vYuvLayers.y);
+ vec2 uv1 = swgl_linearQuantize(sColor1, vUv_U);
+ vec2 min_uv1 = swgl_linearQuantize(sColor1, vUvBounds_U.xy);
+ vec2 max_uv1 = swgl_linearQuantize(sColor1, vUvBounds_U.zw);
+ vec2 step_uv1 = swgl_linearQuantizeStep(sColor1, swgl_interpStep(vUv_U));
+
+ int layer2 = swgl_textureLayerOffset(sColor2, vYuvLayers.z);
+ vec2 uv2 = swgl_linearQuantize(sColor2, vUv_V);
+ vec2 min_uv2 = swgl_linearQuantize(sColor2, vUvBounds_V.xy);
+ vec2 max_uv2 = swgl_linearQuantize(sColor2, vUvBounds_V.zw);
+ vec2 step_uv2 = swgl_linearQuantizeStep(sColor2, swgl_interpStep(vUv_V));
+
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (has_valid_transform_bounds()) {
+ while (swgl_SpanLength > 0) {
+ float alpha = init_transform_fs(vLocalPos);
+ vLocalPos += swgl_interpStep(vLocalPos);
+ swgl_commitTextureLinearColorYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ sColor1, clamp(uv1, min_uv1, max_uv1), layer1,
+ sColor2, clamp(uv2, min_uv2, max_uv2), layer2,
+ vYuvColorSpace, vRescaleFactor, alpha);
+ uv0 += step_uv0;
+ uv1 += step_uv1;
+ uv2 += step_uv2;
+ }
+ return;
+ }
+ #endif
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ sColor1, clamp(uv1, min_uv1, max_uv1), layer1,
+ sColor2, clamp(uv2, min_uv2, max_uv2), layer2,
+ vYuvColorSpace, vRescaleFactor);
+ uv0 += step_uv0;
+ uv1 += step_uv1;
+ uv2 += step_uv2;
+ }
+ } else if (vFormat == YUV_FORMAT_NV12) {
+ if (!swgl_isTextureLinear(sColor0) || !swgl_isTextureLinear(sColor1)) {
+ return;
+ }
+
+ int layer0 = swgl_textureLayerOffset(sColor0, vYuvLayers.x);
+ vec2 uv0 = swgl_linearQuantize(sColor0, vUv_Y);
+ vec2 min_uv0 = swgl_linearQuantize(sColor0, vUvBounds_Y.xy);
+ vec2 max_uv0 = swgl_linearQuantize(sColor0, vUvBounds_Y.zw);
+ vec2 step_uv0 = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUv_Y));
+
+ int layer1 = swgl_textureLayerOffset(sColor1, vYuvLayers.y);
+ vec2 uv1 = swgl_linearQuantize(sColor1, vUv_U);
+ vec2 min_uv1 = swgl_linearQuantize(sColor1, vUvBounds_U.xy);
+ vec2 max_uv1 = swgl_linearQuantize(sColor1, vUvBounds_U.zw);
+ vec2 step_uv1 = swgl_linearQuantizeStep(sColor1, swgl_interpStep(vUv_U));
+
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (has_valid_transform_bounds()) {
+ while (swgl_SpanLength > 0) {
+ float alpha = init_transform_fs(vLocalPos);
+ vLocalPos += swgl_interpStep(vLocalPos);
+ swgl_commitTextureLinearColorYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ sColor1, clamp(uv1, min_uv1, max_uv1), layer1,
+ vYuvColorSpace, vRescaleFactor, alpha);
+ uv0 += step_uv0;
+ uv1 += step_uv1;
+ }
+ return;
+ }
+ #endif
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ sColor1, clamp(uv1, min_uv1, max_uv1), layer1,
+ vYuvColorSpace, vRescaleFactor);
+ uv0 += step_uv0;
+ uv1 += step_uv1;
+ }
+ } else if (vFormat == YUV_FORMAT_INTERLEAVED) {
+ if (!swgl_isTextureLinear(sColor0)) {
+ return;
+ }
+
+ int layer0 = swgl_textureLayerOffset(sColor0, vYuvLayers.x);
+ vec2 uv0 = swgl_linearQuantize(sColor0, vUv_Y);
+ vec2 min_uv0 = swgl_linearQuantize(sColor0, vUvBounds_Y.xy);
+ vec2 max_uv0 = swgl_linearQuantize(sColor0, vUvBounds_Y.zw);
+ vec2 step_uv0 = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUv_Y));
+
+ #ifdef WR_FEATURE_ALPHA_PASS
+ if (has_valid_transform_bounds()) {
+ while (swgl_SpanLength > 0) {
+ float alpha = init_transform_fs(vLocalPos);
+ vLocalPos += swgl_interpStep(vLocalPos);
+ swgl_commitTextureLinearColorYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ vYuvColorSpace, vRescaleFactor, alpha);
+ uv0 += step_uv0;
+ }
+ return;
+ }
+ #endif
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ vYuvColorSpace, vRescaleFactor);
+ uv0 += step_uv0;
+ }
+ }
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/clip_shared.glsl b/gfx/wr/webrender/res/clip_shared.glsl
new file mode 100644
index 0000000000..924d6fdd1d
--- /dev/null
+++ b/gfx/wr/webrender/res/clip_shared.glsl
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include rect,render_task,gpu_cache,transform
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec4 aClipDeviceArea;
+PER_INSTANCE in vec4 aClipOrigins;
+PER_INSTANCE in float aDevicePixelScale;
+PER_INSTANCE in ivec2 aTransformIds;
+
+struct ClipMaskInstanceCommon {
+ RectWithSize sub_rect;
+ vec2 task_origin;
+ vec2 screen_origin;
+ float device_pixel_scale;
+ int clip_transform_id;
+ int prim_transform_id;
+};
+
+ClipMaskInstanceCommon fetch_clip_item_common() {
+ ClipMaskInstanceCommon cmi;
+
+ cmi.sub_rect = RectWithSize(aClipDeviceArea.xy, aClipDeviceArea.zw);
+ cmi.task_origin = aClipOrigins.xy;
+ cmi.screen_origin = aClipOrigins.zw;
+ cmi.device_pixel_scale = aDevicePixelScale;
+ cmi.clip_transform_id = aTransformIds.x;
+ cmi.prim_transform_id = aTransformIds.y;
+
+ return cmi;
+}
+
+struct ClipVertexInfo {
+ vec4 local_pos;
+ RectWithSize clipped_local_rect;
+};
+
+RectWithSize intersect_rect(RectWithSize a, RectWithSize b) {
+ vec4 p = clamp(vec4(a.p0, a.p0 + a.size), b.p0.xyxy, b.p0.xyxy + b.size.xyxy);
+ return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
+}
+
+
+// The transformed vertex function that always covers the whole clip area,
+// which is the intersection of all clip instances of a given primitive
+ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
+ Transform prim_transform,
+ Transform clip_transform,
+ RectWithSize sub_rect,
+ vec2 task_origin,
+ vec2 screen_origin,
+ float device_pixel_scale) {
+ vec2 device_pos = screen_origin + sub_rect.p0 + aPosition.xy * sub_rect.size;
+ vec2 world_pos = device_pos / device_pixel_scale;
+
+ vec4 pos = prim_transform.m * vec4(world_pos, 0.0, 1.0);
+ pos.xyz /= pos.w;
+
+ vec4 p = get_node_pos(pos.xy, clip_transform);
+ vec4 local_pos = p * pos.w;
+
+ //TODO: Interpolate in clip space, where "local_pos.w" contains
+ // the W of the homogeneous transform *from* clip space into the world.
+ // float interpolate_w = 1.0 / local_pos.w;
+ // This is problematic today, because the W<=0 hemisphere is going to be
+ // clipped, while we currently want this shader to fill out the whole rect.
+ // We can therefore simplify this when the clip construction is rewritten
+ // to only affect the areas touched by a clip.
+ vec4 vertex_pos = vec4(
+ task_origin + sub_rect.p0 + aPosition.xy * sub_rect.size,
+ 0.0,
+ 1.0
+ );
+
+ gl_Position = uTransform * vertex_pos;
+
+ init_transform_vs(vec4(local_clip_rect.p0, local_clip_rect.p0 + local_clip_rect.size));
+
+ ClipVertexInfo vi = ClipVertexInfo(local_pos, local_clip_rect);
+ return vi;
+}
+
+#endif //WR_VERTEX_SHADER
diff --git a/gfx/wr/webrender/res/composite.glsl b/gfx/wr/webrender/res/composite.glsl
new file mode 100644
index 0000000000..8f612e2a92
--- /dev/null
+++ b/gfx/wr/webrender/res/composite.glsl
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Composite a picture cache tile into the framebuffer.
+
+#include shared,yuv
+
+#ifdef WR_FEATURE_YUV
+flat varying mat3 vYuvColorMatrix;
+flat varying vec3 vYuvOffsetVector;
+flat varying float vYuvCoefficient;
+flat varying int vYuvFormat;
+flat varying vec3 vYuvLayers;
+#ifdef SWGL
+flat varying int vYuvColorSpace;
+flat varying int vRescaleFactor;
+#endif
+varying vec2 vUV_y;
+varying vec2 vUV_u;
+varying vec2 vUV_v;
+flat varying vec4 vUVBounds_y;
+flat varying vec4 vUVBounds_u;
+flat varying vec4 vUVBounds_v;
+#else
+flat varying vec4 vColor;
+flat varying float vLayer;
+varying vec2 vUv;
+flat varying vec4 vUVBounds;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+// CPU side data is in CompositeInstance (gpu_types.rs) and is
+// converted to GPU data using desc::COMPOSITE (renderer.rs) by
+// filling vaos.composite_vao with VertexArrayKind::Composite.
+PER_INSTANCE in vec4 aDeviceRect;
+PER_INSTANCE in vec4 aDeviceClipRect;
+PER_INSTANCE in vec4 aColor;
+PER_INSTANCE in vec4 aParams;
+PER_INSTANCE in vec3 aTextureLayers;
+
+#ifdef WR_FEATURE_YUV
+// YUV treats these as a UV clip rect (clamp)
+PER_INSTANCE in vec4 aUvRect0;
+PER_INSTANCE in vec4 aUvRect1;
+PER_INSTANCE in vec4 aUvRect2;
+#else
+PER_INSTANCE in vec4 aUvRect0;
+#endif
+
+void main(void) {
+ // Get world position
+ vec2 world_pos = aDeviceRect.xy + aPosition.xy * aDeviceRect.zw;
+
+ // Clip the position to the world space clip rect
+ vec2 clipped_world_pos = clamp(world_pos, aDeviceClipRect.xy, aDeviceClipRect.xy + aDeviceClipRect.zw);
+
+ // Derive the normalized UV from the clipped vertex position
+ vec2 uv = (clipped_world_pos - aDeviceRect.xy) / aDeviceRect.zw;
+
+#ifdef WR_FEATURE_YUV
+ int yuv_color_space = int(aParams.y);
+ int yuv_format = int(aParams.z);
+ float yuv_coefficient = aParams.w;
+
+ vYuvColorMatrix = get_yuv_color_matrix(yuv_color_space);
+ vYuvOffsetVector = get_yuv_offset_vector(yuv_color_space);
+ vYuvCoefficient = yuv_coefficient;
+ vYuvFormat = yuv_format;
+ vYuvLayers = aTextureLayers.xyz;
+
+#ifdef SWGL
+ // swgl_commitTextureLinearYUV needs to know the color space specifier and
+ // also needs to know how many bits of scaling are required to normalize
+ // HDR textures.
+ vYuvColorSpace = yuv_color_space;
+ vRescaleFactor = int(log2(yuv_coefficient));
+#endif
+
+ write_uv_rect(
+ aUvRect0.xy,
+ aUvRect0.zw,
+ uv,
+ TEX_SIZE(sColor0),
+ vUV_y,
+ vUVBounds_y
+ );
+ write_uv_rect(
+ aUvRect1.xy,
+ aUvRect1.zw,
+ uv,
+ TEX_SIZE(sColor1),
+ vUV_u,
+ vUVBounds_u
+ );
+ write_uv_rect(
+ aUvRect2.xy,
+ aUvRect2.zw,
+ uv,
+ TEX_SIZE(sColor2),
+ vUV_v,
+ vUVBounds_v
+ );
+#else
+ vUv = mix(aUvRect0.xy, aUvRect0.zw, uv);
+ // flip_y might have the UV rect "upside down", make sure
+ // clamp works correctly:
+ vUVBounds = vec4(aUvRect0.x, min(aUvRect0.y, aUvRect0.w),
+ aUvRect0.z, max(aUvRect0.y, aUvRect0.w));
+ int rescale_uv = int(aParams.y);
+ if (rescale_uv == 1)
+ {
+ // using an atlas, so UVs are in pixels, and need to be
+ // normalized and clamped.
+ vec2 texture_size = TEX_SIZE(sColor0);
+ vUVBounds += vec4(0.5, 0.5, -0.5, -0.5);
+ #ifndef WR_FEATURE_TEXTURE_RECT
+ vUv /= texture_size;
+ vUVBounds /= texture_size.xyxy;
+ #endif
+ }
+ // Pass through color and texture array layer
+ vColor = aColor;
+ vLayer = aTextureLayers.x;
+#endif
+
+ gl_Position = uTransform * vec4(clipped_world_pos, aParams.x /* z_id */, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+#ifdef WR_FEATURE_YUV
+ vec4 color = sample_yuv(
+ vYuvFormat,
+ vYuvColorMatrix,
+ vYuvOffsetVector,
+ vYuvCoefficient,
+ vYuvLayers,
+ vUV_y,
+ vUV_u,
+ vUV_v,
+ vUVBounds_y,
+ vUVBounds_u,
+ vUVBounds_v
+ );
+#else
+ // The color is just the texture sample modulated by a supplied color
+ vec2 uv = clamp(vUv.xy, vUVBounds.xy, vUVBounds.zw);
+# if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_2D) || defined(WR_FEATURE_TEXTURE_RECT)
+ vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer));
+# else
+ vec4 texel = textureLod(sColor0, vec3(uv, vLayer), 0.0);
+# endif
+ vec4 color = vColor * texel;
+#endif
+ write_output(color);
+}
+
+#ifdef SWGL
+void swgl_drawSpanRGBA8() {
+#ifdef WR_FEATURE_YUV
+ if (vYuvFormat == YUV_FORMAT_PLANAR) {
+ if (!swgl_isTextureLinear(sColor0) || !swgl_isTextureLinear(sColor1) || !swgl_isTextureLinear(sColor2)) {
+ return;
+ }
+
+ int layer0 = swgl_textureLayerOffset(sColor0, vYuvLayers.x);
+ vec2 uv0 = swgl_linearQuantize(sColor0, vUV_y);
+ vec2 min_uv0 = swgl_linearQuantize(sColor0, vUVBounds_y.xy);
+ vec2 max_uv0 = swgl_linearQuantize(sColor0, vUVBounds_y.zw);
+ vec2 step_uv0 = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUV_y));
+
+ int layer1 = swgl_textureLayerOffset(sColor1, vYuvLayers.y);
+ vec2 uv1 = swgl_linearQuantize(sColor1, vUV_u);
+ vec2 min_uv1 = swgl_linearQuantize(sColor1, vUVBounds_u.xy);
+ vec2 max_uv1 = swgl_linearQuantize(sColor1, vUVBounds_u.zw);
+ vec2 step_uv1 = swgl_linearQuantizeStep(sColor1, swgl_interpStep(vUV_u));
+
+ int layer2 = swgl_textureLayerOffset(sColor2, vYuvLayers.z);
+ vec2 uv2 = swgl_linearQuantize(sColor2, vUV_v);
+ vec2 min_uv2 = swgl_linearQuantize(sColor2, vUVBounds_v.xy);
+ vec2 max_uv2 = swgl_linearQuantize(sColor2, vUVBounds_v.zw);
+ vec2 step_uv2 = swgl_linearQuantizeStep(sColor2, swgl_interpStep(vUV_v));
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ sColor1, clamp(uv1, min_uv1, max_uv1), layer1,
+ sColor2, clamp(uv2, min_uv2, max_uv2), layer2,
+ vYuvColorSpace, vRescaleFactor);
+ uv0 += step_uv0;
+ uv1 += step_uv1;
+ uv2 += step_uv2;
+ }
+ } else if (vYuvFormat == YUV_FORMAT_NV12) {
+ if (!swgl_isTextureLinear(sColor0) || !swgl_isTextureLinear(sColor1)) {
+ return;
+ }
+
+ int layer0 = swgl_textureLayerOffset(sColor0, vYuvLayers.x);
+ vec2 uv0 = swgl_linearQuantize(sColor0, vUV_y);
+ vec2 min_uv0 = swgl_linearQuantize(sColor0, vUVBounds_y.xy);
+ vec2 max_uv0 = swgl_linearQuantize(sColor0, vUVBounds_y.zw);
+ vec2 step_uv0 = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUV_y));
+
+ int layer1 = swgl_textureLayerOffset(sColor1, vYuvLayers.y);
+ vec2 uv1 = swgl_linearQuantize(sColor1, vUV_u);
+ vec2 min_uv1 = swgl_linearQuantize(sColor1, vUVBounds_u.xy);
+ vec2 max_uv1 = swgl_linearQuantize(sColor1, vUVBounds_u.zw);
+ vec2 step_uv1 = swgl_linearQuantizeStep(sColor1, swgl_interpStep(vUV_u));
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ sColor1, clamp(uv1, min_uv1, max_uv1), layer1,
+ vYuvColorSpace, vRescaleFactor);
+ uv0 += step_uv0;
+ uv1 += step_uv1;
+ }
+ } else if (vYuvFormat == YUV_FORMAT_INTERLEAVED) {
+ if (!swgl_isTextureLinear(sColor0) || !swgl_isTextureLinear(sColor1)) {
+ return;
+ }
+
+ int layer0 = swgl_textureLayerOffset(sColor0, vYuvLayers.x);
+ vec2 uv0 = swgl_linearQuantize(sColor0, vUV_y);
+ vec2 min_uv0 = swgl_linearQuantize(sColor0, vUVBounds_y.xy);
+ vec2 max_uv0 = swgl_linearQuantize(sColor0, vUVBounds_y.zw);
+ vec2 step_uv0 = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUV_y));
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearYUV(sColor0, clamp(uv0, min_uv0, max_uv0), layer0,
+ vYuvColorSpace, vRescaleFactor);
+ uv0 += step_uv0;
+ }
+ }
+#else
+ if (!swgl_isTextureRGBA8(sColor0) || !swgl_isTextureLinear(sColor0)) {
+ return;
+ }
+
+ int layer = swgl_textureLayerOffset(sColor0, vLayer);
+
+ if (swgl_allowTextureNearest(sColor0, vUv)) {
+ if (vColor != vec4(1.0)) {
+ swgl_commitTextureNearestColorRGBA8(sColor0, vUv, vUVBounds, vColor, layer);
+ } else {
+ swgl_commitTextureNearestRGBA8(sColor0, vUv, vUVBounds, layer);
+ }
+ return;
+ }
+
+ vec2 uv = swgl_linearQuantize(sColor0, vUv);
+ vec2 min_uv = swgl_linearQuantize(sColor0, vUVBounds.xy);
+ vec2 max_uv = swgl_linearQuantize(sColor0, vUVBounds.zw);
+ vec2 step_uv = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUv));
+
+ if (vColor != vec4(1.0)) {
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearColorRGBA8(sColor0, clamp(uv, min_uv, max_uv), vColor, layer);
+ uv += step_uv;
+ }
+ } else {
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearRGBA8(sColor0, clamp(uv, min_uv, max_uv), layer);
+ uv += step_uv;
+ }
+ }
+#endif
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/cs_blur.glsl b/gfx/wr/webrender/res/cs_blur.glsl
new file mode 100644
index 0000000000..3917e6d154
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_blur.glsl
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define WR_FEATURE_TEXTURE_2D
+
+#include shared,prim_shared
+
+varying vec2 vUv;
+flat varying vec4 vUvRect;
+flat varying vec2 vOffsetScale;
+// The number of pixels on each end that we apply the blur filter over.
+flat varying int vSupport;
+flat varying vec2 vGaussCoefficients;
+
+#ifdef WR_VERTEX_SHADER
+// Applies a separable gaussian blur in one direction, as specified
+// by the dir field in the blur command.
+
+#define DIR_HORIZONTAL 0
+#define DIR_VERTICAL 1
+
+PER_INSTANCE in int aBlurRenderTaskAddress;
+PER_INSTANCE in int aBlurSourceTaskAddress;
+PER_INSTANCE in int aBlurDirection;
+
+struct BlurTask {
+ RenderTaskCommonData common_data;
+ float blur_radius;
+ vec2 blur_region;
+};
+
+BlurTask fetch_blur_task(int address) {
+ RenderTaskData task_data = fetch_render_task_data(address);
+
+ BlurTask task = BlurTask(
+ task_data.common_data,
+ task_data.user_data.x,
+ task_data.user_data.yz
+ );
+
+ return task;
+}
+
+void calculate_gauss_coefficients(float sigma) {
+ // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
+ vGaussCoefficients = vec2(1.0 / (sqrt(2.0 * 3.14159265) * sigma),
+ exp(-0.5 / (sigma * sigma)));
+
+ // Pre-calculate the coefficient total in the vertex shader so that
+ // we can avoid having to do it per-fragment and also avoid division
+ // by zero in the degenerate case.
+ vec3 gauss_coefficient = vec3(vGaussCoefficients,
+ vGaussCoefficients.y * vGaussCoefficients.y);
+ float gauss_coefficient_total = gauss_coefficient.x;
+ for (int i = 1; i <= vSupport; i += 2) {
+ gauss_coefficient.xy *= gauss_coefficient.yz;
+ float gauss_coefficient_subtotal = gauss_coefficient.x;
+ gauss_coefficient.xy *= gauss_coefficient.yz;
+ gauss_coefficient_subtotal += gauss_coefficient.x;
+ gauss_coefficient_total += 2.0 * gauss_coefficient_subtotal;
+ }
+
+ // Scale initial coefficient by total to avoid passing the total separately
+ // to the fragment shader.
+ vGaussCoefficients.x /= gauss_coefficient_total;
+}
+
+void main(void) {
+ BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
+ RenderTaskCommonData src_task = fetch_render_task_common_data(aBlurSourceTaskAddress);
+
+ RectWithSize src_rect = src_task.task_rect;
+ RectWithSize target_rect = blur_task.common_data.task_rect;
+
+ vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+
+ // Ensure that the support is an even number of pixels to simplify the
+ // fragment shader logic.
+ //
+ // TODO(pcwalton): Actually make use of this fact and use the texture
+ // hardware for linear filtering.
+ vSupport = int(ceil(1.5 * blur_task.blur_radius)) * 2;
+
+ if (vSupport > 0) {
+ calculate_gauss_coefficients(blur_task.blur_radius);
+ } else {
+ // The gauss function gets NaNs when blur radius is zero.
+ vGaussCoefficients = vec2(1.0, 1.0);
+ }
+
+ switch (aBlurDirection) {
+ case DIR_HORIZONTAL:
+ vOffsetScale = vec2(1.0 / texture_size.x, 0.0);
+ break;
+ case DIR_VERTICAL:
+ vOffsetScale = vec2(0.0, 1.0 / texture_size.y);
+ break;
+ default:
+ vOffsetScale = vec2(0.0);
+ }
+
+ vUvRect = vec4(src_rect.p0 + vec2(0.5),
+ src_rect.p0 + blur_task.blur_region - vec2(0.5));
+ vUvRect /= texture_size.xyxy;
+
+ vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy;
+
+ vec2 uv0 = src_rect.p0 / texture_size;
+ vec2 uv1 = (src_rect.p0 + src_rect.size) / texture_size;
+ vUv = mix(uv0, uv1, aPosition.xy);
+
+ gl_Position = uTransform * vec4(pos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#if defined WR_FEATURE_COLOR_TARGET
+#define SAMPLE_TYPE vec4
+#define SAMPLE_TEXTURE(uv) texture(sColor0, uv)
+#else
+#define SAMPLE_TYPE float
+#define SAMPLE_TEXTURE(uv) texture(sColor0, uv).r
+#endif
+
+// TODO(gw): Write a fast path blur that handles smaller blur radii
+// with a offset / weight uniform table and a constant
+// loop iteration count!
+
+void main(void) {
+ SAMPLE_TYPE original_color = SAMPLE_TEXTURE(vUv);
+
+ // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
+ vec3 gauss_coefficient = vec3(vGaussCoefficients,
+ vGaussCoefficients.y * vGaussCoefficients.y);
+
+ SAMPLE_TYPE avg_color = original_color * gauss_coefficient.x;
+
+ // Evaluate two adjacent texels at a time. We can do this because, if c0
+ // and c1 are colors of adjacent texels and k0 and k1 are arbitrary
+ // factors, this formula:
+ //
+ // k0 * c0 + k1 * c1 (Equation 1)
+ //
+ // is equivalent to:
+ //
+ // k1
+ // (k0 + k1) * lerp(c0, c1, -------)
+ // k0 + k1
+ //
+ // A texture lookup of adjacent texels evaluates this formula:
+ //
+ // lerp(c0, c1, t)
+ //
+ // for some t. So we can let `t = k1/(k0 + k1)` and effectively evaluate
+ // Equation 1 with a single texture lookup.
+
+ for (int i = 1; i <= vSupport; i += 2) {
+ gauss_coefficient.xy *= gauss_coefficient.yz;
+
+ float gauss_coefficient_subtotal = gauss_coefficient.x;
+ gauss_coefficient.xy *= gauss_coefficient.yz;
+ gauss_coefficient_subtotal += gauss_coefficient.x;
+ float gauss_ratio = gauss_coefficient.x / gauss_coefficient_subtotal;
+
+ vec2 offset = vOffsetScale * (float(i) + gauss_ratio);
+
+ vec2 st0 = max(vUv - offset, vUvRect.xy);
+ vec2 st1 = min(vUv + offset, vUvRect.zw);
+ avg_color += (SAMPLE_TEXTURE(st0) + SAMPLE_TEXTURE(st1)) *
+ gauss_coefficient_subtotal;
+ }
+
+ oFragColor = vec4(avg_color);
+}
+
+#ifdef SWGL
+ #ifdef WR_FEATURE_COLOR_TARGET
+void swgl_drawSpanRGBA8() {
+ if (!swgl_isTextureRGBA8(sColor0)) {
+ return;
+ }
+
+ swgl_commitGaussianBlurRGBA8(sColor0, vUv, vUvRect, vOffsetScale.x != 0.0,
+ vSupport, vGaussCoefficients, 0);
+}
+ #else
+void swgl_drawSpanR8() {
+ if (!swgl_isTextureR8(sColor0)) {
+ return;
+ }
+
+ swgl_commitGaussianBlurR8(sColor0, vUv, vUvRect, vOffsetScale.x != 0.0,
+ vSupport, vGaussCoefficients, 0);
+}
+ #endif
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/cs_border_segment.glsl b/gfx/wr/webrender/res/cs_border_segment.glsl
new file mode 100644
index 0000000000..68553074fa
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_border_segment.glsl
@@ -0,0 +1,450 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,ellipse
+
+// For edges, the colors are the same. For corners, these
+// are the colors of each edge making up the corner.
+flat varying vec4 vColor00;
+flat varying vec4 vColor01;
+flat varying vec4 vColor10;
+flat varying vec4 vColor11;
+
+// A point + tangent defining the line where the edge
+// transition occurs. Used for corners only.
+flat varying vec4 vColorLine;
+
+// x = segment, y = styles, z = edge axes, w = clip mode
+// Since by default in GLES the vertex shader uses highp
+// and the fragment shader uses mediump, we explicitely
+// use mediump precision so we align with the default
+// mediump precision in the fragment shader.
+flat varying mediump ivec4 vConfig;
+
+// xy = Local space position of the clip center.
+// zw = Scale the rect origin by this to get the outer
+// corner from the segment rectangle.
+flat varying vec4 vClipCenter_Sign;
+
+// An outer and inner elliptical radii for border
+// corner clipping.
+flat varying vec4 vClipRadii;
+
+// Reference point for determine edge clip lines.
+flat varying vec4 vEdgeReference;
+
+// Stores widths/2 and widths/3 to save doing this in FS.
+flat varying vec4 vPartialWidths;
+
+// Clipping parameters for dot or dash.
+flat varying vec4 vClipParams1;
+flat varying vec4 vClipParams2;
+
+// Local space position
+varying vec2 vPos;
+
+#define SEGMENT_TOP_LEFT 0
+#define SEGMENT_TOP_RIGHT 1
+#define SEGMENT_BOTTOM_RIGHT 2
+#define SEGMENT_BOTTOM_LEFT 3
+#define SEGMENT_LEFT 4
+#define SEGMENT_TOP 5
+#define SEGMENT_RIGHT 6
+#define SEGMENT_BOTTOM 7
+
+// Border styles as defined in webrender_api/types.rs
+#define BORDER_STYLE_NONE 0
+#define BORDER_STYLE_SOLID 1
+#define BORDER_STYLE_DOUBLE 2
+#define BORDER_STYLE_DOTTED 3
+#define BORDER_STYLE_DASHED 4
+#define BORDER_STYLE_HIDDEN 5
+#define BORDER_STYLE_GROOVE 6
+#define BORDER_STYLE_RIDGE 7
+#define BORDER_STYLE_INSET 8
+#define BORDER_STYLE_OUTSET 9
+
+#define CLIP_NONE 0
+#define CLIP_DASH_CORNER 1
+#define CLIP_DASH_EDGE 2
+#define CLIP_DOT 3
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec2 aTaskOrigin;
+PER_INSTANCE in vec4 aRect;
+PER_INSTANCE in vec4 aColor0;
+PER_INSTANCE in vec4 aColor1;
+PER_INSTANCE in int aFlags;
+PER_INSTANCE in vec2 aWidths;
+PER_INSTANCE in vec2 aRadii;
+PER_INSTANCE in vec4 aClipParams1;
+PER_INSTANCE in vec4 aClipParams2;
+
+vec2 get_outer_corner_scale(int segment) {
+ vec2 p;
+
+ switch (segment) {
+ case SEGMENT_TOP_LEFT:
+ p = vec2(0.0, 0.0);
+ break;
+ case SEGMENT_TOP_RIGHT:
+ p = vec2(1.0, 0.0);
+ break;
+ case SEGMENT_BOTTOM_RIGHT:
+ p = vec2(1.0, 1.0);
+ break;
+ case SEGMENT_BOTTOM_LEFT:
+ p = vec2(0.0, 1.0);
+ break;
+ default:
+ // The result is only used for non-default segment cases
+ p = vec2(0.0);
+ break;
+ }
+
+ return p;
+}
+
+// NOTE(emilio): If you change this algorithm, do the same change
+// in border.rs
+vec4 mod_color(vec4 color, bool is_black, bool lighter) {
+ const float light_black = 0.7;
+ const float dark_black = 0.3;
+
+ const float dark_scale = 0.66666666;
+ const float light_scale = 1.0;
+
+ if (is_black) {
+ if (lighter) {
+ return vec4(vec3(light_black), color.a);
+ }
+ return vec4(vec3(dark_black), color.a);
+ }
+
+ if (lighter) {
+ return vec4(color.rgb * light_scale, color.a);
+ }
+ return vec4(color.rgb * dark_scale, color.a);
+}
+
+vec4[2] get_colors_for_side(vec4 color, int style) {
+ vec4 result[2];
+
+ bool is_black = color.rgb == vec3(0.0, 0.0, 0.0);
+
+ switch (style) {
+ case BORDER_STYLE_GROOVE:
+ result[0] = mod_color(color, is_black, true);
+ result[1] = mod_color(color, is_black, false);
+ break;
+ case BORDER_STYLE_RIDGE:
+ result[0] = mod_color(color, is_black, false);
+ result[1] = mod_color(color, is_black, true);
+ break;
+ default:
+ result[0] = color;
+ result[1] = color;
+ break;
+ }
+
+ return result;
+}
+
+void main(void) {
+ int segment = aFlags & 0xff;
+ int style0 = (aFlags >> 8) & 0xff;
+ int style1 = (aFlags >> 16) & 0xff;
+ int clip_mode = (aFlags >> 24) & 0x0f;
+
+ vec2 outer_scale = get_outer_corner_scale(segment);
+ vec2 outer = outer_scale * aRect.zw;
+ vec2 clip_sign = 1.0 - 2.0 * outer_scale;
+
+ // Set some flags used by the FS to determine the
+ // orientation of the two edges in this corner.
+ ivec2 edge_axis = ivec2(0, 0);
+ // Derive the positions for the edge clips, which must be handled
+ // differently between corners and edges.
+ vec2 edge_reference = vec2(0.0);
+ switch (segment) {
+ case SEGMENT_TOP_LEFT:
+ edge_axis = ivec2(0, 1);
+ edge_reference = outer;
+ break;
+ case SEGMENT_TOP_RIGHT:
+ edge_axis = ivec2(1, 0);
+ edge_reference = vec2(outer.x - aWidths.x, outer.y);
+ break;
+ case SEGMENT_BOTTOM_RIGHT:
+ edge_axis = ivec2(0, 1);
+ edge_reference = outer - aWidths;
+ break;
+ case SEGMENT_BOTTOM_LEFT:
+ edge_axis = ivec2(1, 0);
+ edge_reference = vec2(outer.x, outer.y - aWidths.y);
+ break;
+ case SEGMENT_TOP:
+ case SEGMENT_BOTTOM:
+ edge_axis = ivec2(1, 1);
+ break;
+ case SEGMENT_LEFT:
+ case SEGMENT_RIGHT:
+ default:
+ break;
+ }
+
+ vConfig = ivec4(
+ segment,
+ style0 | (style1 << 8),
+ edge_axis.x | (edge_axis.y << 8),
+ clip_mode
+ );
+ vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
+ vPos = aRect.zw * aPosition.xy;
+
+ vec4[2] color0 = get_colors_for_side(aColor0, style0);
+ vColor00 = color0[0];
+ vColor01 = color0[1];
+ vec4[2] color1 = get_colors_for_side(aColor1, style1);
+ vColor10 = color1[0];
+ vColor11 = color1[1];
+ vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
+ vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
+ vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+ vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
+ vClipParams1 = aClipParams1;
+ vClipParams2 = aClipParams2;
+
+ // For the case of dot and dash clips, optimize the number of pixels that
+ // are hit to just include the dot itself.
+ if (clip_mode == CLIP_DOT) {
+ float radius = aClipParams1.z;
+
+ // Expand by a small amount to allow room for AA around
+ // the dot if it's big enough.
+ if (radius > 0.5)
+ radius += 2.0;
+
+ vPos = vClipParams1.xy + radius * (2.0 * aPosition.xy - 1.0);
+ vPos = clamp(vPos, vec2(0.0), aRect.zw);
+ } else if (clip_mode == CLIP_DASH_CORNER) {
+ vec2 center = (aClipParams1.xy + aClipParams2.xy) * 0.5;
+ // This is a gross approximation which works out because dashes don't have
+ // a strong curvature and we will overshoot by inflating the geometry by
+ // this amount on each side (sqrt(2) * length(dash) would be enough and we
+ // compute 2 * approx_length(dash)).
+ float dash_length = length(aClipParams1.xy - aClipParams2.xy);
+ float width = max(aWidths.x, aWidths.y);
+ // expand by a small amout for AA just like we do for dots.
+ vec2 r = vec2(max(dash_length, width)) + 2.0;
+ vPos = clamp(vPos, center - r, center + r);
+ }
+
+ gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+vec4 evaluate_color_for_style_in_corner(
+ vec2 clip_relative_pos,
+ int style,
+ vec4 color0,
+ vec4 color1,
+ vec4 clip_radii,
+ float mix_factor,
+ int segment,
+ float aa_range
+) {
+ switch (style) {
+ case BORDER_STYLE_DOUBLE: {
+ // Get the distances from 0.33 of the radii, and
+ // also 0.67 of the radii. Use these to form a
+ // SDF subtraction which will clip out the inside
+ // third of the rounded edge.
+ float d_radii_a = distance_to_ellipse(
+ clip_relative_pos,
+ clip_radii.xy - vPartialWidths.xy
+ );
+ float d_radii_b = distance_to_ellipse(
+ clip_relative_pos,
+ clip_radii.xy - 2.0 * vPartialWidths.xy
+ );
+ float d = min(-d_radii_a, d_radii_b);
+ color0 *= distance_aa(aa_range, d);
+ break;
+ }
+ case BORDER_STYLE_GROOVE:
+ case BORDER_STYLE_RIDGE: {
+ float d = distance_to_ellipse(
+ clip_relative_pos,
+ clip_radii.xy - vPartialWidths.zw
+ );
+ float alpha = distance_aa(aa_range, d);
+ float swizzled_factor;
+ switch (segment) {
+ case SEGMENT_TOP_LEFT: swizzled_factor = 0.0; break;
+ case SEGMENT_TOP_RIGHT: swizzled_factor = mix_factor; break;
+ case SEGMENT_BOTTOM_RIGHT: swizzled_factor = 1.0; break;
+ case SEGMENT_BOTTOM_LEFT: swizzled_factor = 1.0 - mix_factor; break;
+ default: swizzled_factor = 0.0; break;
+ };
+ vec4 c0 = mix(color1, color0, swizzled_factor);
+ vec4 c1 = mix(color0, color1, swizzled_factor);
+ color0 = mix(c0, c1, alpha);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return color0;
+}
+
+vec4 evaluate_color_for_style_in_edge(
+ vec2 pos_vec,
+ int style,
+ vec4 color0,
+ vec4 color1,
+ float aa_range,
+ int edge_axis_id
+) {
+ vec2 edge_axis = edge_axis_id != 0 ? vec2(0.0, 1.0) : vec2(1.0, 0.0);
+ float pos = dot(pos_vec, edge_axis);
+ switch (style) {
+ case BORDER_STYLE_DOUBLE: {
+ float d = -1.0;
+ float partial_width = dot(vPartialWidths.xy, edge_axis);
+ if (partial_width >= 1.0) {
+ vec2 ref = vec2(
+ dot(vEdgeReference.xy, edge_axis) + partial_width,
+ dot(vEdgeReference.zw, edge_axis) - partial_width
+ );
+ d = min(pos - ref.x, ref.y - pos);
+ }
+ color0 *= distance_aa(aa_range, d);
+ break;
+ }
+ case BORDER_STYLE_GROOVE:
+ case BORDER_STYLE_RIDGE: {
+ float ref = dot(vEdgeReference.xy + vPartialWidths.zw, edge_axis);
+ float d = pos - ref;
+ float alpha = distance_aa(aa_range, d);
+ color0 = mix(color0, color1, alpha);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return color0;
+}
+
+void main(void) {
+ float aa_range = compute_aa_range(vPos);
+ vec4 color0, color1;
+
+ int segment = vConfig.x;
+ ivec2 style = ivec2(vConfig.y & 0xff, vConfig.y >> 8);
+ ivec2 edge_axis = ivec2(vConfig.z & 0xff, vConfig.z >> 8);
+ int clip_mode = vConfig.w;
+
+ float mix_factor = 0.0;
+ if (edge_axis.x != edge_axis.y) {
+ float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
+ mix_factor = distance_aa(aa_range, -d_line);
+ }
+
+ // Check if inside corner clip-region
+ vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
+ bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+ float d = -1.0;
+
+ switch (clip_mode) {
+ case CLIP_DOT: {
+ // Set clip distance based or dot position and radius.
+ d = distance(vClipParams1.xy, vPos) - vClipParams1.z;
+ break;
+ }
+ case CLIP_DASH_EDGE: {
+ bool is_vertical = vClipParams1.x == 0.;
+ float half_dash = is_vertical ? vClipParams1.y : vClipParams1.x;
+ // We want to draw something like:
+ // +---+---+---+---+
+ // |xxx| | |xxx|
+ // +---+---+---+---+
+ float pos = is_vertical ? vPos.y : vPos.x;
+ bool in_dash = pos < half_dash || pos > 3.0 * half_dash;
+ if (!in_dash) {
+ d = 1.;
+ }
+ break;
+ }
+ case CLIP_DASH_CORNER: {
+ // Get SDF for the two line/tangent clip lines,
+ // do SDF subtract to get clip distance.
+ float d0 = distance_to_line(vClipParams1.xy,
+ vClipParams1.zw,
+ vPos);
+ float d1 = distance_to_line(vClipParams2.xy,
+ vClipParams2.zw,
+ vPos);
+ d = max(d0, -d1);
+ break;
+ }
+ case CLIP_NONE:
+ default:
+ break;
+ }
+
+ if (in_clip_region) {
+ float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy);
+ float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw);
+ float d_radii = max(d_radii_a, -d_radii_b);
+ d = max(d, d_radii);
+
+ color0 = evaluate_color_for_style_in_corner(
+ clip_relative_pos,
+ style.x,
+ vColor00,
+ vColor01,
+ vClipRadii,
+ mix_factor,
+ segment,
+ aa_range
+ );
+ color1 = evaluate_color_for_style_in_corner(
+ clip_relative_pos,
+ style.y,
+ vColor10,
+ vColor11,
+ vClipRadii,
+ mix_factor,
+ segment,
+ aa_range
+ );
+ } else {
+ color0 = evaluate_color_for_style_in_edge(
+ vPos,
+ style.x,
+ vColor00,
+ vColor01,
+ aa_range,
+ edge_axis.x
+ );
+ color1 = evaluate_color_for_style_in_edge(
+ vPos,
+ style.y,
+ vColor10,
+ vColor11,
+ aa_range,
+ edge_axis.y
+ );
+ }
+
+ float alpha = distance_aa(aa_range, d);
+ vec4 color = mix(color0, color1, mix_factor);
+ oFragColor = color * alpha;
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_border_solid.glsl b/gfx/wr/webrender/res/cs_border_solid.glsl
new file mode 100644
index 0000000000..409ffb1b2e
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_border_solid.glsl
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,ellipse
+
+#define DONT_MIX 0
+#define MIX_AA 1
+#define MIX_NO_AA 2
+
+// For edges, the colors are the same. For corners, these
+// are the colors of each edge making up the corner.
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+
+// A point + tangent defining the line where the edge
+// transition occurs. Used for corners only.
+flat varying vec4 vColorLine;
+
+// A boolean indicating that we should be mixing between edge colors.
+flat varying int vMixColors;
+
+// xy = Local space position of the clip center.
+// zw = Scale the rect origin by this to get the outer
+// corner from the segment rectangle.
+flat varying vec4 vClipCenter_Sign;
+
+// An outer and inner elliptical radii for border
+// corner clipping.
+flat varying vec4 vClipRadii;
+
+// Position, scale, and radii of horizontally and vertically adjacent corner clips.
+flat varying vec4 vHorizontalClipCenter_Sign;
+flat varying vec2 vHorizontalClipRadii;
+flat varying vec4 vVerticalClipCenter_Sign;
+flat varying vec2 vVerticalClipRadii;
+
+// Local space position
+varying vec2 vPos;
+
+#define SEGMENT_TOP_LEFT 0
+#define SEGMENT_TOP_RIGHT 1
+#define SEGMENT_BOTTOM_RIGHT 2
+#define SEGMENT_BOTTOM_LEFT 3
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec2 aTaskOrigin;
+PER_INSTANCE in vec4 aRect;
+PER_INSTANCE in vec4 aColor0;
+PER_INSTANCE in vec4 aColor1;
+PER_INSTANCE in int aFlags;
+PER_INSTANCE in vec2 aWidths;
+PER_INSTANCE in vec2 aRadii;
+PER_INSTANCE in vec4 aClipParams1;
+PER_INSTANCE in vec4 aClipParams2;
+
+vec2 get_outer_corner_scale(int segment) {
+ vec2 p;
+
+ switch (segment) {
+ case SEGMENT_TOP_LEFT:
+ p = vec2(0.0, 0.0);
+ break;
+ case SEGMENT_TOP_RIGHT:
+ p = vec2(1.0, 0.0);
+ break;
+ case SEGMENT_BOTTOM_RIGHT:
+ p = vec2(1.0, 1.0);
+ break;
+ case SEGMENT_BOTTOM_LEFT:
+ p = vec2(0.0, 1.0);
+ break;
+ default:
+ // The result is only used for non-default segment cases
+ p = vec2(0.0);
+ break;
+ }
+
+ return p;
+}
+
+void main(void) {
+ int segment = aFlags & 0xff;
+ bool do_aa = ((aFlags >> 24) & 0xf0) != 0;
+
+ vec2 outer_scale = get_outer_corner_scale(segment);
+ vec2 outer = outer_scale * aRect.zw;
+ vec2 clip_sign = 1.0 - 2.0 * outer_scale;
+
+ int mix_colors;
+ switch (segment) {
+ case SEGMENT_TOP_LEFT:
+ case SEGMENT_TOP_RIGHT:
+ case SEGMENT_BOTTOM_RIGHT:
+ case SEGMENT_BOTTOM_LEFT: {
+ mix_colors = do_aa ? MIX_AA : MIX_NO_AA;
+ break;
+ }
+ default:
+ mix_colors = DONT_MIX;
+ break;
+ }
+
+ vMixColors = mix_colors;
+ vPos = aRect.zw * aPosition.xy;
+
+ vColor0 = aColor0;
+ vColor1 = aColor1;
+ vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
+ vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
+ vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
+
+ vec2 horizontal_clip_sign = vec2(-clip_sign.x, clip_sign.y);
+ vHorizontalClipCenter_Sign = vec4(aClipParams1.xy +
+ horizontal_clip_sign * aClipParams1.zw,
+ horizontal_clip_sign);
+ vHorizontalClipRadii = aClipParams1.zw;
+
+ vec2 vertical_clip_sign = vec2(clip_sign.x, -clip_sign.y);
+ vVerticalClipCenter_Sign = vec4(aClipParams2.xy +
+ vertical_clip_sign * aClipParams2.zw,
+ vertical_clip_sign);
+ vVerticalClipRadii = aClipParams2.zw;
+
+ gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ float aa_range = compute_aa_range(vPos);
+ bool do_aa = vMixColors != MIX_NO_AA;
+
+ float mix_factor = 0.0;
+ if (vMixColors != DONT_MIX) {
+ float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
+ if (do_aa) {
+ mix_factor = distance_aa(aa_range, -d_line);
+ } else {
+ mix_factor = d_line + EPSILON >= 0. ? 1.0 : 0.0;
+ }
+ }
+
+ // Check if inside main corner clip-region
+ vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
+ bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+
+ float d = -1.0;
+ if (in_clip_region) {
+ float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy);
+ float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw);
+ d = max(d_radii_a, -d_radii_b);
+ }
+
+ // And again for horizontally-adjacent corner
+ clip_relative_pos = vPos - vHorizontalClipCenter_Sign.xy;
+ in_clip_region = all(lessThan(vHorizontalClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+ if (in_clip_region) {
+ float d_radii = distance_to_ellipse(clip_relative_pos, vHorizontalClipRadii.xy);
+ d = max(d_radii, d);
+ }
+
+ // And finally for vertically-adjacent corner
+ clip_relative_pos = vPos - vVerticalClipCenter_Sign.xy;
+ in_clip_region = all(lessThan(vVerticalClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+ if (in_clip_region) {
+ float d_radii = distance_to_ellipse(clip_relative_pos, vVerticalClipRadii.xy);
+ d = max(d_radii, d);
+ }
+
+ float alpha = do_aa ? distance_aa(aa_range, d) : 1.0;
+ vec4 color = mix(vColor0, vColor1, mix_factor);
+ oFragColor = color * alpha;
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_clip_box_shadow.glsl b/gfx/wr/webrender/res/cs_clip_box_shadow.glsl
new file mode 100644
index 0000000000..cc8252fdb9
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_clip_box_shadow.glsl
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,clip_shared
+
+varying vec4 vLocalPos;
+varying vec2 vUv;
+flat varying vec4 vUvBounds;
+flat varying float vLayer;
+flat varying vec4 vEdge;
+flat varying vec4 vUvBounds_NoClamp;
+flat varying float vClipMode;
+
+#define MODE_STRETCH 0
+#define MODE_SIMPLE 1
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in ivec2 aClipDataResourceAddress;
+PER_INSTANCE in vec2 aClipSrcRectSize;
+PER_INSTANCE in int aClipMode;
+PER_INSTANCE in ivec2 aStretchMode;
+PER_INSTANCE in vec4 aClipDestRect;
+
+struct ClipMaskInstanceBoxShadow {
+ ClipMaskInstanceCommon shared;
+ ivec2 resource_address;
+};
+
+ClipMaskInstanceBoxShadow fetch_clip_item() {
+ ClipMaskInstanceBoxShadow cmi;
+
+ cmi.shared = fetch_clip_item_common();
+ cmi.resource_address = aClipDataResourceAddress;
+
+ return cmi;
+}
+
+struct BoxShadowData {
+ vec2 src_rect_size;
+ int clip_mode;
+ int stretch_mode_x;
+ int stretch_mode_y;
+ RectWithSize dest_rect;
+};
+
+BoxShadowData fetch_data() {
+ BoxShadowData bs_data = BoxShadowData(
+ aClipSrcRectSize,
+ aClipMode,
+ aStretchMode.x,
+ aStretchMode.y,
+ RectWithSize(aClipDestRect.xy, aClipDestRect.zw)
+ );
+ return bs_data;
+}
+
+void main(void) {
+ ClipMaskInstanceBoxShadow cmi = fetch_clip_item();
+ Transform clip_transform = fetch_transform(cmi.shared.clip_transform_id);
+ Transform prim_transform = fetch_transform(cmi.shared.prim_transform_id);
+ BoxShadowData bs_data = fetch_data();
+ ImageResource res = fetch_image_resource_direct(cmi.resource_address);
+
+ RectWithSize dest_rect = bs_data.dest_rect;
+
+ ClipVertexInfo vi = write_clip_tile_vertex(
+ dest_rect,
+ prim_transform,
+ clip_transform,
+ cmi.shared.sub_rect,
+ cmi.shared.task_origin,
+ cmi.shared.screen_origin,
+ cmi.shared.device_pixel_scale
+ );
+ vLayer = res.layer;
+ vClipMode = float(bs_data.clip_mode);
+
+ vec2 texture_size = vec2(textureSize(sColor0, 0));
+ vec2 local_pos = vi.local_pos.xy / vi.local_pos.w;
+ vLocalPos = vi.local_pos;
+
+ switch (bs_data.stretch_mode_x) {
+ case MODE_STRETCH: {
+ vEdge.x = 0.5;
+ vEdge.z = (dest_rect.size.x / bs_data.src_rect_size.x) - 0.5;
+ vUv.x = (local_pos.x - dest_rect.p0.x) / bs_data.src_rect_size.x;
+ break;
+ }
+ case MODE_SIMPLE:
+ default: {
+ vEdge.xz = vec2(1.0);
+ vUv.x = (local_pos.x - dest_rect.p0.x) / dest_rect.size.x;
+ break;
+ }
+ }
+
+ switch (bs_data.stretch_mode_y) {
+ case MODE_STRETCH: {
+ vEdge.y = 0.5;
+ vEdge.w = (dest_rect.size.y / bs_data.src_rect_size.y) - 0.5;
+ vUv.y = (local_pos.y - dest_rect.p0.y) / bs_data.src_rect_size.y;
+ break;
+ }
+ case MODE_SIMPLE:
+ default: {
+ vEdge.yw = vec2(1.0);
+ vUv.y = (local_pos.y - dest_rect.p0.y) / dest_rect.size.y;
+ break;
+ }
+ }
+
+ vUv *= vi.local_pos.w;
+ vec2 uv0 = res.uv_rect.p0;
+ vec2 uv1 = res.uv_rect.p1;
+ vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
+ vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ vec2 uv_linear = vUv / vLocalPos.w;
+ vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
+ uv += max(vec2(0.0), uv_linear - vEdge.zw);
+ uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
+ uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
+
+ float in_shadow_rect = init_transform_rough_fs(vLocalPos.xy / vLocalPos.w);
+
+ float texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer)).r;
+
+ float alpha = mix(texel, 1.0 - texel, vClipMode);
+ float result = vLocalPos.w > 0.0 ? mix(vClipMode, alpha, in_shadow_rect) : 0.0;
+
+ oFragColor = vec4(result);
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_clip_image.glsl b/gfx/wr/webrender/res/cs_clip_image.glsl
new file mode 100644
index 0000000000..248af6dbdd
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_clip_image.glsl
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,clip_shared
+
+varying vec4 vLocalPos;
+varying vec2 vClipMaskImageUv;
+
+flat varying vec4 vClipMaskUvRect;
+flat varying vec4 vClipMaskUvInnerRect;
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec4 aClipTileRect;
+PER_INSTANCE in ivec2 aClipDataResourceAddress;
+PER_INSTANCE in vec4 aClipLocalRect;
+
+struct ClipMaskInstanceImage {
+ ClipMaskInstanceCommon shared;
+ RectWithSize tile_rect;
+ ivec2 resource_address;
+ RectWithSize local_rect;
+};
+
+ClipMaskInstanceImage fetch_clip_item() {
+ ClipMaskInstanceImage cmi;
+
+ cmi.shared = fetch_clip_item_common();
+
+ cmi.tile_rect = RectWithSize(aClipTileRect.xy, aClipTileRect.zw);
+ cmi.resource_address = aClipDataResourceAddress;
+ cmi.local_rect = RectWithSize(aClipLocalRect.xy, aClipLocalRect.zw);
+
+ return cmi;
+}
+
+void main(void) {
+ ClipMaskInstanceImage cmi = fetch_clip_item();
+ Transform clip_transform = fetch_transform(cmi.shared.clip_transform_id);
+ Transform prim_transform = fetch_transform(cmi.shared.prim_transform_id);
+ ImageResource res = fetch_image_resource_direct(cmi.resource_address);
+
+ ClipVertexInfo vi = write_clip_tile_vertex(
+ cmi.local_rect,
+ prim_transform,
+ clip_transform,
+ cmi.shared.sub_rect,
+ cmi.shared.task_origin,
+ cmi.shared.screen_origin,
+ cmi.shared.device_pixel_scale
+ );
+ vLocalPos = vi.local_pos;
+ vClipMaskImageUv = (vi.local_pos.xy - cmi.tile_rect.p0 * vi.local_pos.w) / cmi.tile_rect.size;
+
+ vec2 texture_size = vec2(textureSize(sColor0, 0));
+ vClipMaskUvRect = vec4(res.uv_rect.p0, res.uv_rect.p1 - res.uv_rect.p0) / texture_size.xyxy;
+ // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
+ vec4 inner_rect = vec4(res.uv_rect.p0, res.uv_rect.p1);
+ vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ vec2 local_pos = vLocalPos.xy / vLocalPos.w;
+ float alpha = vLocalPos.w > 0.0 ? init_transform_fs(local_pos) : 0.0;
+
+ // TODO: Handle repeating masks?
+ vec2 clamped_mask_uv = clamp(vClipMaskImageUv, vec2(0.0, 0.0), vLocalPos.ww);
+
+ // Ensure we don't draw outside of our tile.
+ // FIXME(emilio): Can we do this earlier?
+ if (clamped_mask_uv != vClipMaskImageUv)
+ discard;
+
+ vec2 source_uv = clamp(
+ clamped_mask_uv / vLocalPos.w * vClipMaskUvRect.zw + vClipMaskUvRect.xy,
+ vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw);
+ float clip_alpha = texture(sColor0, source_uv).r; //careful: texture has type A8
+ oFragColor = vec4(alpha * clip_alpha, 1.0, 1.0, 1.0);
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_clip_rectangle.glsl b/gfx/wr/webrender/res/cs_clip_rectangle.glsl
new file mode 100644
index 0000000000..de982c80e0
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_clip_rectangle.glsl
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,clip_shared,ellipse
+
+varying vec4 vLocalPos;
+#ifdef WR_FEATURE_FAST_PATH
+flat varying vec3 vClipParams; // xy = box size, z = radius
+#else
+flat varying vec4 vClipCenter_Radius_TL;
+flat varying vec4 vClipCenter_Radius_TR;
+flat varying vec4 vClipCenter_Radius_BL;
+flat varying vec4 vClipCenter_Radius_BR;
+#endif
+
+flat varying float vClipMode;
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec2 aClipLocalPos;
+PER_INSTANCE in vec4 aClipLocalRect;
+PER_INSTANCE in float aClipMode;
+PER_INSTANCE in vec4 aClipRect_TL;
+PER_INSTANCE in vec4 aClipRadii_TL;
+PER_INSTANCE in vec4 aClipRect_TR;
+PER_INSTANCE in vec4 aClipRadii_TR;
+PER_INSTANCE in vec4 aClipRect_BL;
+PER_INSTANCE in vec4 aClipRadii_BL;
+PER_INSTANCE in vec4 aClipRect_BR;
+PER_INSTANCE in vec4 aClipRadii_BR;
+
+struct ClipMaskInstanceRect {
+ ClipMaskInstanceCommon shared;
+ vec2 local_pos;
+};
+
+ClipMaskInstanceRect fetch_clip_item() {
+ ClipMaskInstanceRect cmi;
+
+ cmi.shared = fetch_clip_item_common();
+ cmi.local_pos = aClipLocalPos;
+
+ return cmi;
+}
+
+struct ClipRect {
+ RectWithSize rect;
+ float mode;
+};
+
+struct ClipCorner {
+ RectWithSize rect;
+ vec4 outer_inner_radius;
+};
+
+struct ClipData {
+ ClipRect rect;
+ ClipCorner top_left;
+ ClipCorner top_right;
+ ClipCorner bottom_left;
+ ClipCorner bottom_right;
+};
+
+ClipData fetch_clip() {
+ ClipData clip;
+
+ clip.rect = ClipRect(RectWithSize(aClipLocalRect.xy, aClipLocalRect.zw), aClipMode);
+ clip.top_left = ClipCorner(RectWithSize(aClipRect_TL.xy, aClipRect_TL.zw), aClipRadii_TL);
+ clip.top_right = ClipCorner(RectWithSize(aClipRect_TR.xy, aClipRect_TR.zw), aClipRadii_TR);
+ clip.bottom_left = ClipCorner(RectWithSize(aClipRect_BL.xy, aClipRect_BL.zw), aClipRadii_BL);
+ clip.bottom_right = ClipCorner(RectWithSize(aClipRect_BR.xy, aClipRect_BR.zw), aClipRadii_BR);
+
+ return clip;
+}
+
+void main(void) {
+ ClipMaskInstanceRect cmi = fetch_clip_item();
+ Transform clip_transform = fetch_transform(cmi.shared.clip_transform_id);
+ Transform prim_transform = fetch_transform(cmi.shared.prim_transform_id);
+ ClipData clip = fetch_clip();
+
+ RectWithSize local_rect = clip.rect.rect;
+ local_rect.p0 = cmi.local_pos;
+
+ ClipVertexInfo vi = write_clip_tile_vertex(
+ local_rect,
+ prim_transform,
+ clip_transform,
+ cmi.shared.sub_rect,
+ cmi.shared.task_origin,
+ cmi.shared.screen_origin,
+ cmi.shared.device_pixel_scale
+ );
+
+ vClipMode = clip.rect.mode;
+ vLocalPos = vi.local_pos;
+
+#ifdef WR_FEATURE_FAST_PATH
+ // If the radii are all uniform, we can use a much simpler 2d
+ // signed distance function to get a rounded rect clip.
+ vec2 half_size = 0.5 * local_rect.size;
+ float radius = clip.top_left.outer_inner_radius.x;
+ vLocalPos.xy -= (half_size + cmi.local_pos) * vi.local_pos.w;
+ vClipParams = vec3(half_size - vec2(radius), radius);
+#else
+ RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
+
+ vec2 r_tl = clip.top_left.outer_inner_radius.xy;
+ vec2 r_tr = clip.top_right.outer_inner_radius.xy;
+ vec2 r_br = clip.bottom_right.outer_inner_radius.xy;
+ vec2 r_bl = clip.bottom_left.outer_inner_radius.xy;
+
+ vClipCenter_Radius_TL = vec4(clip_rect.p0 + r_tl,
+ inverse_radii_squared(r_tl));
+
+ vClipCenter_Radius_TR = vec4(clip_rect.p1.x - r_tr.x,
+ clip_rect.p0.y + r_tr.y,
+ inverse_radii_squared(r_tr));
+
+ vClipCenter_Radius_BR = vec4(clip_rect.p1 - r_br,
+ inverse_radii_squared(r_br));
+
+ vClipCenter_Radius_BL = vec4(clip_rect.p0.x + r_bl.x,
+ clip_rect.p1.y - r_bl.y,
+ inverse_radii_squared(r_bl));
+#endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#ifdef WR_FEATURE_FAST_PATH
+// See http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
+float sd_box(in vec2 pos, in vec2 box_size) {
+ vec2 d = abs(pos) - box_size;
+ return length(max(d, vec2(0.0))) + min(max(d.x,d.y), 0.0);
+}
+
+float sd_rounded_box(in vec2 pos, in vec2 box_size, in float radius) {
+ return sd_box(pos, box_size) - radius;
+}
+#endif
+
+void main(void) {
+ vec2 local_pos = vLocalPos.xy / vLocalPos.w;
+ float aa_range = compute_aa_range(local_pos);
+
+#ifdef WR_FEATURE_FAST_PATH
+ float d = sd_rounded_box(local_pos, vClipParams.xy, vClipParams.z);
+ float f = distance_aa(aa_range, d);
+ float final_alpha = mix(f, 1.0 - f, vClipMode);
+#else
+ float alpha = init_transform_fs(local_pos);
+
+ float clip_alpha = rounded_rect(local_pos,
+ vClipCenter_Radius_TL,
+ vClipCenter_Radius_TR,
+ vClipCenter_Radius_BR,
+ vClipCenter_Radius_BL,
+ aa_range);
+
+ float combined_alpha = alpha * clip_alpha;
+
+ // Select alpha or inverse alpha depending on clip in/out.
+ float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
+#endif
+
+ float final_final_alpha = vLocalPos.w > 0.0 ? final_alpha : 0.0;
+ oFragColor = vec4(final_final_alpha, 0.0, 0.0, 1.0);
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_gradient.glsl b/gfx/wr/webrender/res/cs_gradient.glsl
new file mode 100644
index 0000000000..6e6b5c4ad0
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_gradient.glsl
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+varying float vPos;
+flat varying vec4 vStops;
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+flat varying vec4 vColor2;
+flat varying vec4 vColor3;
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec4 aTaskRect;
+PER_INSTANCE in float aAxisSelect;
+PER_INSTANCE in vec4 aStops;
+PER_INSTANCE in vec4 aColor0;
+PER_INSTANCE in vec4 aColor1;
+PER_INSTANCE in vec4 aColor2;
+PER_INSTANCE in vec4 aColor3;
+PER_INSTANCE in vec2 aStartStop;
+
+void main(void) {
+ vPos = mix(aStartStop.x, aStartStop.y, mix(aPosition.x, aPosition.y, aAxisSelect));
+
+ vStops = aStops;
+ vColor0 = aColor0;
+ vColor1 = aColor1;
+ vColor2 = aColor2;
+ vColor3 = aColor3;
+
+ gl_Position = uTransform * vec4(aTaskRect.xy + aTaskRect.zw * aPosition.xy, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+float linear_step(float edge0, float edge1, float x) {
+ if (edge0 >= edge1) {
+ return 0.0;
+ }
+
+ return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
+}
+
+void main(void) {
+ vec4 color = vColor0;
+
+ color = mix(color, vColor1, linear_step(vStops.x, vStops.y, vPos));
+ color = mix(color, vColor2, linear_step(vStops.y, vStops.z, vPos));
+ color = mix(color, vColor3, linear_step(vStops.z, vStops.w, vPos));
+
+ oFragColor = color;
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_line_decoration.glsl b/gfx/wr/webrender/res/cs_line_decoration.glsl
new file mode 100644
index 0000000000..90d0fa5550
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_line_decoration.glsl
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+#define LINE_STYLE_SOLID 0
+#define LINE_STYLE_DOTTED 1
+#define LINE_STYLE_DASHED 2
+#define LINE_STYLE_WAVY 3
+
+// Fragment position in the coordinate system used for positioning decorations.
+// To keep the code independent of whether the line is horizontal or vertical,
+// vLocalPos.x is always parallel, and .y always perpendicular, to the line
+// being decorated.
+varying vec2 vLocalPos;
+
+flat varying int vStyle;
+flat varying vec4 vParams;
+
+#ifdef WR_VERTEX_SHADER
+
+// The size of the mask tile we're rendering, in pixels.
+PER_INSTANCE in vec4 aTaskRect;
+
+// The size of the mask tile. aLocalSize.x is always horizontal and .y vertical,
+// regardless of the line's orientation. The size is chosen by
+// prim_store::line_dec::get_line_decoration_sizes.
+PER_INSTANCE in vec2 aLocalSize;
+
+// A LINE_STYLE_* value, indicating what sort of line to draw.
+PER_INSTANCE in int aStyle;
+
+// 0.0 for a horizontal line, 1.0 for a vertical line.
+PER_INSTANCE in float aAxisSelect;
+
+// The thickness of the wavy line itself, not the amplitude of the waves (i.e.,
+// the thickness of the final decorated line).
+PER_INSTANCE in float aWavyLineThickness;
+
+void main(void) {
+ vec2 size = mix(aLocalSize, aLocalSize.yx, aAxisSelect);
+ vStyle = aStyle;
+
+ switch (vStyle) {
+ case LINE_STYLE_SOLID: {
+ break;
+ }
+ case LINE_STYLE_DASHED: {
+ vParams = vec4(size.x, // period
+ 0.5 * size.x, // dash length
+ 0.0,
+ 0.0);
+ break;
+ }
+ case LINE_STYLE_DOTTED: {
+ float diameter = size.y;
+ float period = diameter * 2.0;
+ float center_line = 0.5 * size.y;
+ vParams = vec4(period,
+ diameter / 2.0, // radius
+ center_line,
+ 0.0);
+ break;
+ }
+ case LINE_STYLE_WAVY: {
+ // This logic copied from gecko to get the same results
+ float line_thickness = max(aWavyLineThickness, 1.0);
+ // Difference in height between peaks and troughs
+ // (and since slopes are 45 degrees, the length of each slope)
+ float slope_length = size.y - line_thickness;
+ // Length of flat runs
+ float flat_length = max((line_thickness - 1.0) * 2.0, 1.0);
+
+ vParams = vec4(line_thickness / 2.0,
+ slope_length,
+ flat_length,
+ size.y);
+ break;
+ }
+ default:
+ vParams = vec4(0.0);
+ }
+
+ vLocalPos = mix(aPosition.xy, aPosition.yx, aAxisSelect) * size;
+
+ gl_Position = uTransform * vec4(aTaskRect.xy + aTaskRect.zw * aPosition.xy, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#define MAGIC_WAVY_LINE_AA_SNAP 0.5
+
+void main(void) {
+ // Find the appropriate distance to apply the step over.
+ vec2 pos = vLocalPos;
+ float aa_range = compute_aa_range(pos);
+ float alpha = 1.0;
+
+ switch (vStyle) {
+ case LINE_STYLE_SOLID: {
+ break;
+ }
+ case LINE_STYLE_DASHED: {
+ // Calculate dash alpha (on/off) based on dash length
+ alpha = step(floor(pos.x + 0.5), vParams.y);
+ break;
+ }
+ case LINE_STYLE_DOTTED: {
+ // Get the dot alpha
+ vec2 dot_relative_pos = pos - vParams.yz;
+ float dot_distance = length(dot_relative_pos) - vParams.y;
+ alpha = distance_aa(aa_range, dot_distance);
+ break;
+ }
+ case LINE_STYLE_WAVY: {
+ float half_line_thickness = vParams.x;
+ float slope_length = vParams.y;
+ float flat_length = vParams.z;
+ float vertical_bounds = vParams.w;
+ // Our pattern is just two slopes and two flats
+ float half_period = slope_length + flat_length;
+
+ float mid_height = vertical_bounds / 2.0;
+ float peak_offset = mid_height - half_line_thickness;
+ // Flip the wave every half period
+ float flip = -2.0 * (step(mod(pos.x, 2.0 * half_period), half_period) - 0.5);
+ // float flip = -1.0;
+ peak_offset *= flip;
+ float peak_height = mid_height + peak_offset;
+
+ // Convert pos to a local position within one half period
+ pos.x = mod(pos.x, half_period);
+
+ // Compute signed distance to the 3 lines that make up an arc
+ float dist1 = distance_to_line(vec2(0.0, peak_height),
+ vec2(1.0, -flip),
+ pos);
+ float dist2 = distance_to_line(vec2(0.0, peak_height),
+ vec2(0, -flip),
+ pos);
+ float dist3 = distance_to_line(vec2(flat_length, peak_height),
+ vec2(-1.0, -flip),
+ pos);
+ float dist = abs(max(max(dist1, dist2), dist3));
+
+ // Apply AA based on the thickness of the wave
+ alpha = distance_aa(aa_range, dist - half_line_thickness);
+
+ // Disable AA for thin lines
+ if (half_line_thickness <= 1.0) {
+ alpha = 1.0 - step(alpha, MAGIC_WAVY_LINE_AA_SNAP);
+ }
+
+ break;
+ }
+ default: break;
+ }
+
+ oFragColor = vec4(alpha);
+}
+#endif
diff --git a/gfx/wr/webrender/res/cs_scale.glsl b/gfx/wr/webrender/res/cs_scale.glsl
new file mode 100644
index 0000000000..b046a2503d
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_scale.glsl
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,prim_shared
+
+varying vec2 vUv;
+flat varying float vUvLayer;
+flat varying vec4 vUvRect;
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec4 aScaleTargetRect;
+PER_INSTANCE in ivec4 aScaleSourceRect;
+PER_INSTANCE in int aScaleSourceLayer;
+
+void main(void) {
+ RectWithSize src_rect = RectWithSize(vec2(aScaleSourceRect.xy), vec2(aScaleSourceRect.zw));
+
+ // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
+ // non-normalized texture coordinates.
+#ifdef WR_FEATURE_TEXTURE_RECT
+ vec2 texture_size = vec2(1, 1);
+#else
+ vec2 texture_size = vec2(textureSize(sColor0, 0));
+#endif
+
+ vUvLayer = float(aScaleSourceLayer);
+
+ vUvRect = vec4(src_rect.p0 + vec2(0.5),
+ src_rect.p0 + src_rect.size - vec2(0.5)) / texture_size.xyxy;
+
+ vec2 pos = aScaleTargetRect.xy + aScaleTargetRect.zw * aPosition.xy;
+ vUv = (src_rect.p0 + src_rect.size * aPosition.xy) / texture_size;
+
+ gl_Position = uTransform * vec4(pos, 0.0, 1.0);
+}
+
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+void main(void) {
+ vec2 st = clamp(vUv, vUvRect.xy, vUvRect.zw);
+ oFragColor = TEX_SAMPLE(sColor0, vec3(st, vUvLayer));
+}
+
+#ifdef SWGL
+void swgl_drawSpanRGBA8() {
+ if (!swgl_isTextureRGBA8(sColor0)) {
+ return;
+ }
+
+ int layer = swgl_textureLayerOffset(sColor0, vUvLayer);
+ vec2 uv = swgl_linearQuantize(sColor0, vUv);
+ vec2 min_uv = swgl_linearQuantize(sColor0, vUvRect.xy);
+ vec2 max_uv = swgl_linearQuantize(sColor0, vUvRect.zw);
+ vec2 step_uv = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUv));
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearRGBA8(sColor0, clamp(uv, min_uv, max_uv), layer);
+ uv += step_uv;
+ }
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/cs_svg_filter.glsl b/gfx/wr/webrender/res/cs_svg_filter.glsl
new file mode 100644
index 0000000000..64d7d307fa
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_svg_filter.glsl
@@ -0,0 +1,590 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define WR_FEATURE_TEXTURE_2D
+
+#include shared,prim_shared
+
+varying vec2 vInput1Uv;
+varying vec2 vInput2Uv;
+flat varying vec4 vInput1UvRect;
+flat varying vec4 vInput2UvRect;
+flat varying int vFilterInputCount;
+flat varying int vFilterKind;
+flat varying ivec4 vData;
+flat varying vec4 vFilterData0;
+flat varying vec4 vFilterData1;
+flat varying float vFloat0;
+flat varying mat4 vColorMat;
+flat varying int vFuncs[4];
+
+#define FILTER_BLEND 0
+#define FILTER_FLOOD 1
+#define FILTER_LINEAR_TO_SRGB 2
+#define FILTER_SRGB_TO_LINEAR 3
+#define FILTER_OPACITY 4
+#define FILTER_COLOR_MATRIX 5
+#define FILTER_DROP_SHADOW 6
+#define FILTER_OFFSET 7
+#define FILTER_COMPONENT_TRANSFER 8
+#define FILTER_IDENTITY 9
+#define FILTER_COMPOSITE 10
+
+#define COMPOSITE_OVER 0
+#define COMPOSITE_IN 1
+#define COMPOSITE_OUT 2
+#define COMPOSITE_ATOP 3
+#define COMPOSITE_XOR 4
+#define COMPOSITE_LIGHTER 5
+#define COMPOSITE_ARITHMETIC 6
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in int aFilterRenderTaskAddress;
+PER_INSTANCE in int aFilterInput1TaskAddress;
+PER_INSTANCE in int aFilterInput2TaskAddress;
+PER_INSTANCE in int aFilterKind;
+PER_INSTANCE in int aFilterInputCount;
+PER_INSTANCE in int aFilterGenericInt;
+PER_INSTANCE in ivec2 aFilterExtraDataAddress;
+
+struct FilterTask {
+ RenderTaskCommonData common_data;
+ vec3 user_data;
+};
+
+FilterTask fetch_filter_task(int address) {
+ RenderTaskData task_data = fetch_render_task_data(address);
+
+ FilterTask task = FilterTask(
+ task_data.common_data,
+ task_data.user_data.xyz
+ );
+
+ return task;
+}
+
+vec4 compute_uv_rect(RenderTaskCommonData task, vec2 texture_size) {
+ RectWithSize task_rect = task.task_rect;
+
+ vec4 uvRect = vec4(task_rect.p0 + vec2(0.5),
+ task_rect.p0 + task_rect.size - vec2(0.5));
+ uvRect /= texture_size.xyxy;
+ return uvRect;
+}
+
+vec2 compute_uv(RenderTaskCommonData task, vec2 texture_size) {
+ RectWithSize task_rect = task.task_rect;
+
+ vec2 uv0 = task_rect.p0 / texture_size;
+ vec2 uv1 = floor(task_rect.p0 + task_rect.size) / texture_size;
+ return mix(uv0, uv1, aPosition.xy);
+}
+
+void main(void) {
+ FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress);
+ RectWithSize target_rect = filter_task.common_data.task_rect;
+
+ vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy;
+
+ RenderTaskCommonData input_1_task;
+ if (aFilterInputCount > 0) {
+ vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+ input_1_task = fetch_render_task_common_data(aFilterInput1TaskAddress);
+ vInput1UvRect = compute_uv_rect(input_1_task, texture_size);
+ vInput1Uv = compute_uv(input_1_task, texture_size);
+ }
+
+ RenderTaskCommonData input_2_task;
+ if (aFilterInputCount > 1) {
+ vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
+ input_2_task = fetch_render_task_common_data(aFilterInput2TaskAddress);
+ vInput2UvRect = compute_uv_rect(input_2_task, texture_size);
+ vInput2Uv = compute_uv(input_2_task, texture_size);
+ }
+
+ vFilterInputCount = aFilterInputCount;
+ vFilterKind = aFilterKind;
+
+ // This assignment is only used for component transfer filters but this
+ // assignment has to be done here and not in the component transfer case
+ // below because it doesn't get executed on Windows because of a suspected
+ // miscompile of this shader on Windows. See
+ // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows
+ // default: just to satisfy angle_shader_validation.rs which needs one
+ // default: for every switch, even in comments.
+ vFuncs[0] = (aFilterGenericInt >> 12) & 0xf; // R
+ vFuncs[1] = (aFilterGenericInt >> 8) & 0xf; // G
+ vFuncs[2] = (aFilterGenericInt >> 4) & 0xf; // B
+ vFuncs[3] = (aFilterGenericInt) & 0xf; // A
+
+ switch (aFilterKind) {
+ case FILTER_BLEND:
+ vData = ivec4(aFilterGenericInt, 0, 0, 0);
+ break;
+ case FILTER_FLOOD:
+ vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+ break;
+ case FILTER_OPACITY:
+ vFloat0 = filter_task.user_data.x;
+ break;
+ case FILTER_COLOR_MATRIX:
+ vec4 mat_data[4] = fetch_from_gpu_cache_4_direct(aFilterExtraDataAddress);
+ vColorMat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]);
+ vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress + ivec2(4, 0));
+ break;
+ case FILTER_DROP_SHADOW:
+ vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+ break;
+ case FILTER_OFFSET:
+ vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+ vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0));
+
+ RectWithSize task_rect = input_1_task.task_rect;
+ vec4 clipRect = vec4(task_rect.p0, task_rect.p0 + task_rect.size);
+ clipRect /= texture_size.xyxy;
+ vFilterData1 = clipRect;
+ break;
+ case FILTER_COMPONENT_TRANSFER:
+ vData = ivec4(aFilterExtraDataAddress, 0, 0);
+ break;
+ case FILTER_COMPOSITE:
+ vData = ivec4(aFilterGenericInt, 0, 0, 0);
+ if (aFilterGenericInt == COMPOSITE_ARITHMETIC) {
+ vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+ }
+ break;
+ default:
+ break;
+ }
+
+ gl_Position = uTransform * vec4(pos, 0.0, 1.0);
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#define COMPONENT_TRANSFER_IDENTITY 0
+#define COMPONENT_TRANSFER_TABLE 1
+#define COMPONENT_TRANSFER_DISCRETE 2
+#define COMPONENT_TRANSFER_LINEAR 3
+#define COMPONENT_TRANSFER_GAMMA 4
+
+vec3 Multiply(vec3 Cb, vec3 Cs) {
+ return Cb * Cs;
+}
+
+vec3 Screen(vec3 Cb, vec3 Cs) {
+ return Cb + Cs - (Cb * Cs);
+}
+
+vec3 HardLight(vec3 Cb, vec3 Cs) {
+ vec3 m = Multiply(Cb, 2.0 * Cs);
+ vec3 s = Screen(Cb, 2.0 * Cs - 1.0);
+ vec3 edge = vec3(0.5, 0.5, 0.5);
+ return mix(m, s, step(edge, Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorDodge(float Cb, float Cs) {
+ if (Cb == 0.0)
+ return 0.0;
+ else if (Cs == 1.0)
+ return 1.0;
+ else
+ return min(1.0, Cb / (1.0 - Cs));
+}
+
+// TODO: Worth doing with mix/step? Check GLSL output.
+float ColorBurn(float Cb, float Cs) {
+ if (Cb == 1.0)
+ return 1.0;
+ else if (Cs == 0.0)
+ return 0.0;
+ else
+ return 1.0 - min(1.0, (1.0 - Cb) / Cs);
+}
+
+float SoftLight(float Cb, float Cs) {
+ if (Cs <= 0.5) {
+ return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb);
+ } else {
+ float D;
+
+ if (Cb <= 0.25)
+ D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb;
+ else
+ D = sqrt(Cb);
+
+ return Cb + (2.0 * Cs - 1.0) * (D - Cb);
+ }
+}
+
+vec3 Difference(vec3 Cb, vec3 Cs) {
+ return abs(Cb - Cs);
+}
+
+vec3 Exclusion(vec3 Cb, vec3 Cs) {
+ return Cb + Cs - 2.0 * Cb * Cs;
+}
+
+// These functions below are taken from the spec.
+// There's probably a much quicker way to implement
+// them in GLSL...
+float Sat(vec3 c) {
+ return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
+}
+
+float Lum(vec3 c) {
+ vec3 f = vec3(0.3, 0.59, 0.11);
+ return dot(c, f);
+}
+
+vec3 ClipColor(vec3 C) {
+ float L = Lum(C);
+ float n = min(C.r, min(C.g, C.b));
+ float x = max(C.r, max(C.g, C.b));
+
+ if (n < 0.0)
+ C = L + (((C - L) * L) / (L - n));
+
+ if (x > 1.0)
+ C = L + (((C - L) * (1.0 - L)) / (x - L));
+
+ return C;
+}
+
+vec3 SetLum(vec3 C, float l) {
+ float d = l - Lum(C);
+ return ClipColor(C + d);
+}
+
+void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) {
+ if (Cmax > Cmin) {
+ Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin));
+ Cmax = s;
+ } else {
+ Cmid = 0.0;
+ Cmax = 0.0;
+ }
+ Cmin = 0.0;
+}
+
+vec3 SetSat(vec3 C, float s) {
+ if (C.r <= C.g) {
+ if (C.g <= C.b) {
+ SetSatInner(C.r, C.g, C.b, s);
+ } else {
+ if (C.r <= C.b) {
+ SetSatInner(C.r, C.b, C.g, s);
+ } else {
+ SetSatInner(C.b, C.r, C.g, s);
+ }
+ }
+ } else {
+ if (C.r <= C.b) {
+ SetSatInner(C.g, C.r, C.b, s);
+ } else {
+ if (C.g <= C.b) {
+ SetSatInner(C.g, C.b, C.r, s);
+ } else {
+ SetSatInner(C.b, C.g, C.r, s);
+ }
+ }
+ }
+ return C;
+}
+
+vec3 Hue(vec3 Cb, vec3 Cs) {
+ return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb));
+}
+
+vec3 Saturation(vec3 Cb, vec3 Cs) {
+ return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb));
+}
+
+vec3 Color(vec3 Cb, vec3 Cs) {
+ return SetLum(Cs, Lum(Cb));
+}
+
+vec3 Luminosity(vec3 Cb, vec3 Cs) {
+ return SetLum(Cb, Lum(Cs));
+}
+
+const int BlendMode_Normal = 0;
+const int BlendMode_Multiply = 1;
+const int BlendMode_Screen = 2;
+const int BlendMode_Overlay = 3;
+const int BlendMode_Darken = 4;
+const int BlendMode_Lighten = 5;
+const int BlendMode_ColorDodge = 6;
+const int BlendMode_ColorBurn = 7;
+const int BlendMode_HardLight = 8;
+const int BlendMode_SoftLight = 9;
+const int BlendMode_Difference = 10;
+const int BlendMode_Exclusion = 11;
+const int BlendMode_Hue = 12;
+const int BlendMode_Saturation = 13;
+const int BlendMode_Color = 14;
+const int BlendMode_Luminosity = 15;
+
+vec4 blend(vec4 Cs, vec4 Cb, int mode) {
+ vec4 result = vec4(1.0, 0.0, 0.0, 1.0);
+
+ switch (mode) {
+ case BlendMode_Normal:
+ result.rgb = Cs.rgb;
+ break;
+ case BlendMode_Multiply:
+ result.rgb = Multiply(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Screen:
+ result.rgb = Screen(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Overlay:
+ // Overlay is inverse of Hardlight
+ result.rgb = HardLight(Cs.rgb, Cb.rgb);
+ break;
+ case BlendMode_Darken:
+ result.rgb = min(Cs.rgb, Cb.rgb);
+ break;
+ case BlendMode_Lighten:
+ result.rgb = max(Cs.rgb, Cb.rgb);
+ break;
+ case BlendMode_ColorDodge:
+ result.r = ColorDodge(Cb.r, Cs.r);
+ result.g = ColorDodge(Cb.g, Cs.g);
+ result.b = ColorDodge(Cb.b, Cs.b);
+ break;
+ case BlendMode_ColorBurn:
+ result.r = ColorBurn(Cb.r, Cs.r);
+ result.g = ColorBurn(Cb.g, Cs.g);
+ result.b = ColorBurn(Cb.b, Cs.b);
+ break;
+ case BlendMode_HardLight:
+ result.rgb = HardLight(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_SoftLight:
+ result.r = SoftLight(Cb.r, Cs.r);
+ result.g = SoftLight(Cb.g, Cs.g);
+ result.b = SoftLight(Cb.b, Cs.b);
+ break;
+ case BlendMode_Difference:
+ result.rgb = Difference(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Exclusion:
+ result.rgb = Exclusion(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Hue:
+ result.rgb = Hue(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Saturation:
+ result.rgb = Saturation(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Color:
+ result.rgb = Color(Cb.rgb, Cs.rgb);
+ break;
+ case BlendMode_Luminosity:
+ result.rgb = Luminosity(Cb.rgb, Cs.rgb);
+ break;
+ default: break;
+ }
+ vec3 rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
+ result = mix(vec4(Cb.rgb * Cb.a, Cb.a), vec4(rgb, 1.0), Cs.a);
+ return result;
+}
+
+// Based on the Gecko's implementation in
+// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24
+// These could be made faster by sampling a lookup table stored in a float texture
+// with linear interpolation.
+
+vec3 SrgbToLinear(vec3 color) {
+ vec3 c1 = color / 12.92;
+ vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4));
+ return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2);
+}
+
+vec3 LinearToSrgb(vec3 color) {
+ vec3 c1 = color * 12.92;
+ vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
+ return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
+}
+
+// This function has to be factored out due to the following issue:
+// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones
+// (and now the words "default: default:" so angle_shader_validation.rs passes)
+vec4 ComponentTransfer(vec4 colora) {
+ // We push a different amount of data to the gpu cache depending on the
+ // function type.
+ // Identity => 0 blocks
+ // Table/Discrete => 64 blocks (256 values)
+ // Linear => 1 block (2 values)
+ // Gamma => 1 block (3 values)
+ // We loop through the color components and increment the offset (for the
+ // next color component) into the gpu cache based on how many blocks that
+ // function type put into the gpu cache.
+ // Table/Discrete use a 256 entry look up table.
+ // Linear/Gamma are a simple calculation.
+ int offset = 0;
+ vec4 texel;
+ int k;
+
+ for (int i = 0; i < 4; i++) {
+ switch (vFuncs[i]) {
+ case COMPONENT_TRANSFER_IDENTITY:
+ break;
+ case COMPONENT_TRANSFER_TABLE:
+ case COMPONENT_TRANSFER_DISCRETE:
+ // fetch value from lookup table
+ k = int(floor(colora[i]*255.0));
+ texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset + k/4, 0));
+ colora[i] = clamp(texel[k % 4], 0.0, 1.0);
+ // offset plus 256/4 blocks
+ offset = offset + 64;
+ break;
+ case COMPONENT_TRANSFER_LINEAR:
+ // fetch the two values for use in the linear equation
+ texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0));
+ colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
+ // offset plus 1 block
+ offset = offset + 1;
+ break;
+ case COMPONENT_TRANSFER_GAMMA:
+ // fetch the three values for use in the gamma equation
+ texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0));
+ colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
+ // offset plus 1 block
+ offset = offset + 1;
+ break;
+ default:
+ // shouldn't happen
+ break;
+ }
+ }
+ return colora;
+}
+
+// Composite Filter
+
+vec4 composite(vec4 Cs, vec4 Cb, int mode) {
+ vec4 Cr = vec4(0.0, 1.0, 0.0, 1.0);
+ switch (mode) {
+ case COMPOSITE_OVER:
+ Cr.rgb = Cs.a * Cs.rgb + Cb.a * Cb.rgb * (1.0 - Cs.a);
+ Cr.a = Cs.a + Cb.a * (1.0 - Cs.a);
+ break;
+ case COMPOSITE_IN:
+ Cr.rgb = Cs.a * Cs.rgb * Cb.a;
+ Cr.a = Cs.a * Cb.a;
+ break;
+ case COMPOSITE_OUT:
+ Cr.rgb = Cs.a * Cs.rgb * (1.0 - Cb.a);
+ Cr.a = Cs.a * (1.0 - Cb.a);
+ break;
+ case COMPOSITE_ATOP:
+ Cr.rgb = Cs.a * Cs.rgb * Cb.a + Cb.a * Cb.rgb * (1.0 - Cs.a);
+ Cr.a = Cs.a * Cb.a + Cb.a * (1.0 - Cs.a);
+ break;
+ case COMPOSITE_XOR:
+ Cr.rgb = Cs.a * Cs.rgb * (1.0 - Cb.a) + Cb.a * Cb.rgb * (1.0 - Cs.a);
+ Cr.a = Cs.a * (1.0 - Cb.a) + Cb.a * (1.0 - Cs.a);
+ break;
+ case COMPOSITE_LIGHTER:
+ Cr.rgb = Cs.a * Cs.rgb + Cb.a * Cb.rgb;
+ Cr.a = Cs.a + Cb.a;
+ Cr = clamp(Cr, vec4(0.0), vec4(1.0));
+ break;
+ case COMPOSITE_ARITHMETIC:
+ Cr = vec4(vFilterData0.x) * Cs * Cb + vec4(vFilterData0.y) * Cs + vec4(vFilterData0.z) * Cb + vec4(vFilterData0.w);
+ Cr = clamp(Cr, vec4(0.0), vec4(1.0));
+ break;
+ default:
+ break;
+ }
+ return Cr;
+}
+
+vec4 sampleInUvRect(sampler2D sampler, vec2 uv, vec4 uvRect) {
+ vec2 clamped = clamp(uv.xy, uvRect.xy, uvRect.zw);
+ return texture(sampler, clamped);
+}
+
+void main(void) {
+ vec4 Ca = vec4(0.0, 0.0, 0.0, 0.0);
+ vec4 Cb = vec4(0.0, 0.0, 0.0, 0.0);
+ if (vFilterInputCount > 0) {
+ Ca = sampleInUvRect(sColor0, vInput1Uv, vInput1UvRect);
+ if (Ca.a != 0.0) {
+ Ca.rgb /= Ca.a;
+ }
+ }
+ if (vFilterInputCount > 1) {
+ Cb = sampleInUvRect(sColor1, vInput2Uv, vInput2UvRect);
+ if (Cb.a != 0.0) {
+ Cb.rgb /= Cb.a;
+ }
+ }
+
+ vec4 result = vec4(1.0, 0.0, 0.0, 1.0);
+
+ bool needsPremul = true;
+
+ switch (vFilterKind) {
+ case FILTER_BLEND:
+ result = blend(Ca, Cb, vData.x);
+ needsPremul = false;
+ break;
+ case FILTER_FLOOD:
+ result = vFilterData0;
+ needsPremul = false;
+ break;
+ case FILTER_LINEAR_TO_SRGB:
+ result.rgb = LinearToSrgb(Ca.rgb);
+ result.a = Ca.a;
+ break;
+ case FILTER_SRGB_TO_LINEAR:
+ result.rgb = SrgbToLinear(Ca.rgb);
+ result.a = Ca.a;
+ break;
+ case FILTER_OPACITY:
+ result.rgb = Ca.rgb;
+ result.a = Ca.a * vFloat0;
+ break;
+ case FILTER_COLOR_MATRIX:
+ result = vColorMat * Ca + vFilterData0;
+ result = clamp(result, vec4(0.0), vec4(1.0));
+ break;
+ case FILTER_DROP_SHADOW:
+ vec4 shadow = vec4(vFilterData0.rgb, Cb.a * vFilterData0.a);
+ // Normal blend + source-over coposite
+ result = blend(Ca, shadow, BlendMode_Normal);
+ needsPremul = false;
+ break;
+ case FILTER_OFFSET:
+ vec2 offsetUv = vInput1Uv + vFilterData0.xy;
+ result = sampleInUvRect(sColor0, offsetUv, vInput1UvRect);
+ result *= point_inside_rect(offsetUv, vFilterData1.xy, vFilterData1.zw);
+ needsPremul = false;
+ break;
+ case FILTER_COMPONENT_TRANSFER:
+ result = ComponentTransfer(Ca);
+ break;
+ case FILTER_IDENTITY:
+ result = Ca;
+ break;
+ case FILTER_COMPOSITE:
+ result = composite(Ca, Cb, vData.x);
+ needsPremul = false;
+ default:
+ break;
+ }
+
+ if (needsPremul) {
+ result.rgb *= result.a;
+ }
+
+ oFragColor = result;
+}
+#endif
diff --git a/gfx/wr/webrender/res/debug_color.glsl b/gfx/wr/webrender/res/debug_color.glsl
new file mode 100644
index 0000000000..b5a636e535
--- /dev/null
+++ b/gfx/wr/webrender/res/debug_color.glsl
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,shared_other
+
+varying vec4 vColor;
+
+#ifdef WR_VERTEX_SHADER
+in vec4 aColor;
+
+void main(void) {
+ vColor = vec4(aColor.rgb * aColor.a, aColor.a);
+ vec4 pos = vec4(aPosition, 0.0, 1.0);
+ pos.xy = floor(pos.xy + 0.5);
+ gl_Position = uTransform * pos;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ oFragColor = vColor;
+}
+#endif
diff --git a/gfx/wr/webrender/res/debug_font.glsl b/gfx/wr/webrender/res/debug_font.glsl
new file mode 100644
index 0000000000..475a97dfce
--- /dev/null
+++ b/gfx/wr/webrender/res/debug_font.glsl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define WR_FEATURE_TEXTURE_2D
+
+#include shared,shared_other
+
+varying vec2 vColorTexCoord;
+varying vec4 vColor;
+
+#ifdef WR_VERTEX_SHADER
+in vec4 aColor;
+in vec2 aColorTexCoord;
+
+void main(void) {
+ vColor = aColor;
+ vColorTexCoord = aColorTexCoord;
+ vec4 pos = vec4(aPosition, 0.0, 1.0);
+ pos.xy = floor(pos.xy + 0.5);
+ gl_Position = uTransform * pos;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ float alpha = texture(sColor0, vColorTexCoord).r;
+ oFragColor = vColor * alpha;
+}
+#endif
diff --git a/gfx/wr/webrender/res/ellipse.glsl b/gfx/wr/webrender/res/ellipse.glsl
new file mode 100644
index 0000000000..04dc890b51
--- /dev/null
+++ b/gfx/wr/webrender/res/ellipse.glsl
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Preprocess the radii for computing the distance approximation. This should
+// be used in the vertex shader if possible to avoid doing expensive division
+// in the fragment shader. When dealing with a point (zero radii), approximate
+// it as an ellipse with very small radii so that we don't need to branch.
+vec2 inverse_radii_squared(vec2 radii) {
+ return any(lessThanEqual(radii, vec2(0.0)))
+ ? vec2(1.0 / (1.0e-3 * 1.0e-3))
+ : 1.0 / (radii * radii);
+}
+
+#ifdef WR_FRAGMENT_SHADER
+
+// One iteration of Newton's method on the 2D equation of an ellipse:
+//
+// E(x, y) = x^2/a^2 + y^2/b^2 - 1
+//
+// The Jacobian of this equation is:
+//
+// J(E(x, y)) = [ 2*x/a^2 2*y/b^2 ]
+//
+// We approximate the distance with:
+//
+// E(x, y) / ||J(E(x, y))||
+//
+// See G. Taubin, "Distance Approximations for Rasterizing Implicit
+// Curves", section 3.
+float distance_to_ellipse_approx(vec2 p, vec2 inv_radii_sq) {
+ float g = dot(p * p, inv_radii_sq) - 1.0;
+ vec2 dG = 2.0 * p * inv_radii_sq;
+ return g * inversesqrt(dot(dG, dG));
+}
+
+// Slower but more accurate version that uses the exact distance when dealing
+// with a 0-radius point distance and otherwise uses the faster approximation
+// when dealing with non-zero radii.
+float distance_to_ellipse(vec2 p, vec2 radii) {
+ return any(lessThanEqual(radii, vec2(0.0)))
+ ? length(p)
+ : distance_to_ellipse_approx(p, 1.0 / (radii * radii));
+}
+
+float clip_against_ellipse_if_needed(
+ vec2 pos,
+ vec4 ellipse_center_radius,
+ vec2 sign_modifier
+) {
+ vec2 p = pos - ellipse_center_radius.xy;
+ // If we're not within the distance bounds of both of the radii (in the
+ // corner), then return a large magnitude negative value to cause us to
+ // clamp off the anti-aliasing.
+ return all(lessThan(sign_modifier * p, vec2(0.0)))
+ ? distance_to_ellipse_approx(p, ellipse_center_radius.zw)
+ : -1.0e6;
+}
+
+float rounded_rect(vec2 pos,
+ vec4 clip_center_radius_tl,
+ vec4 clip_center_radius_tr,
+ vec4 clip_center_radius_br,
+ vec4 clip_center_radius_bl,
+ float aa_range) {
+ // Clip against each ellipse. If the fragment is in a corner, one of the
+ // clip_against_ellipse_if_needed calls below will update it. If outside
+ // any ellipse, the clip routine will return a negative value so that max()
+ // will choose the greatest amount of applicable anti-aliasing.
+ float current_distance =
+ max(max(clip_against_ellipse_if_needed(pos, clip_center_radius_tl, vec2(1.0)),
+ clip_against_ellipse_if_needed(pos, clip_center_radius_tr, vec2(-1.0, 1.0))),
+ max(clip_against_ellipse_if_needed(pos, clip_center_radius_br, vec2(-1.0)),
+ clip_against_ellipse_if_needed(pos, clip_center_radius_bl, vec2(1.0, -1.0))));
+
+ // Apply AA
+ // See comment in ps_border_corner about the choice of constants.
+
+ return distance_aa(aa_range, current_distance);
+}
+#endif
diff --git a/gfx/wr/webrender/res/gpu_cache.glsl b/gfx/wr/webrender/res/gpu_cache.glsl
new file mode 100644
index 0000000000..e3ff0f4397
--- /dev/null
+++ b/gfx/wr/webrender/res/gpu_cache.glsl
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sGpuCache;
+
+#define VECS_PER_IMAGE_RESOURCE 2
+
+// TODO(gw): This is here temporarily while we have
+// both GPU store and cache. When the GPU
+// store code is removed, we can change the
+// PrimitiveInstance instance structure to
+// use 2x unsigned shorts as vertex attributes
+// instead of an int, and encode the UV directly
+// in the vertices.
+ivec2 get_gpu_cache_uv(HIGHP_FS_ADDRESS int address) {
+ return ivec2(uint(address) % WR_MAX_VERTEX_TEXTURE_WIDTH,
+ uint(address) / WR_MAX_VERTEX_TEXTURE_WIDTH);
+}
+
+vec4[2] fetch_from_gpu_cache_2_direct(ivec2 address) {
+ return vec4[2](
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0))
+ );
+}
+
+vec4[2] fetch_from_gpu_cache_2(HIGHP_FS_ADDRESS int address) {
+ ivec2 uv = get_gpu_cache_uv(address);
+ return vec4[2](
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0))
+ );
+}
+
+vec4 fetch_from_gpu_cache_1_direct(ivec2 address) {
+ return texelFetch(sGpuCache, address, 0);
+}
+
+vec4 fetch_from_gpu_cache_1(HIGHP_FS_ADDRESS int address) {
+ ivec2 uv = get_gpu_cache_uv(address);
+ return texelFetch(sGpuCache, uv, 0);
+}
+
+#ifdef WR_VERTEX_SHADER
+
+vec4[8] fetch_from_gpu_cache_8(int address) {
+ ivec2 uv = get_gpu_cache_uv(address);
+ return vec4[8](
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(4, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(5, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(6, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(7, 0))
+ );
+}
+
+vec4[3] fetch_from_gpu_cache_3(int address) {
+ ivec2 uv = get_gpu_cache_uv(address);
+ return vec4[3](
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0))
+ );
+}
+
+vec4[3] fetch_from_gpu_cache_3_direct(ivec2 address) {
+ return vec4[3](
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)),
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(2, 0))
+ );
+}
+
+vec4[4] fetch_from_gpu_cache_4_direct(ivec2 address) {
+ return vec4[4](
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)),
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(2, 0)),
+ TEXEL_FETCH(sGpuCache, address, 0, ivec2(3, 0))
+ );
+}
+
+vec4[4] fetch_from_gpu_cache_4(int address) {
+ ivec2 uv = get_gpu_cache_uv(address);
+ return vec4[4](
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)),
+ TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0))
+ );
+}
+
+//TODO: image resource is too specific for this module
+
+struct ImageResource {
+ RectWithEndpoint uv_rect;
+ float layer;
+ vec3 user_data;
+};
+
+ImageResource fetch_image_resource(int address) {
+ //Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
+ vec4 data[2] = fetch_from_gpu_cache_2(address);
+ RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
+ return ImageResource(uv_rect, data[1].x, data[1].yzw);
+}
+
+ImageResource fetch_image_resource_direct(ivec2 address) {
+ vec4 data[2] = fetch_from_gpu_cache_2_direct(address);
+ RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
+ return ImageResource(uv_rect, data[1].x, data[1].yzw);
+}
+
+// Fetch optional extra data for a texture cache resource. This can contain
+// a polygon defining a UV rect within the texture cache resource.
+// Note: the polygon coordinates are in homogeneous space.
+struct ImageResourceExtra {
+ vec4 st_tl;
+ vec4 st_tr;
+ vec4 st_bl;
+ vec4 st_br;
+};
+
+ImageResourceExtra fetch_image_resource_extra(int address) {
+ vec4 data[4] = fetch_from_gpu_cache_4(address + VECS_PER_IMAGE_RESOURCE);
+ return ImageResourceExtra(
+ data[0],
+ data[1],
+ data[2],
+ data[3]
+ );
+}
+
+#endif //WR_VERTEX_SHADER
diff --git a/gfx/wr/webrender/res/gpu_cache_update.glsl b/gfx/wr/webrender/res/gpu_cache_update.glsl
new file mode 100644
index 0000000000..90a8534246
--- /dev/null
+++ b/gfx/wr/webrender/res/gpu_cache_update.glsl
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include base
+
+varying vec4 vData;
+
+#ifdef WR_VERTEX_SHADER
+in vec4 aValue;
+in vec2 aPosition;
+
+void main() {
+ vData = aValue;
+ gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);
+ gl_PointSize = 1.0;
+}
+
+#endif //WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+out vec4 oValue;
+
+void main() {
+ oValue = vData;
+}
+#endif //WR_FRAGMENT_SHADER
diff --git a/gfx/wr/webrender/res/gradient_shared.glsl b/gfx/wr/webrender/res/gradient_shared.glsl
new file mode 100644
index 0000000000..38ad11aa12
--- /dev/null
+++ b/gfx/wr/webrender/res/gradient_shared.glsl
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+flat varying HIGHP_FS_ADDRESS int v_gradient_address;
+
+// Size of the gradient pattern's rectangle, used to compute horizontal and vertical
+// repetitions. Not to be confused with another kind of repetition of the pattern
+// which happens along the gradient stops.
+flat varying vec2 v_repeated_size;
+// Repetition along the gradient stops.
+flat varying float v_gradient_repeat;
+
+varying vec2 v_pos;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 v_local_pos;
+flat varying vec2 v_tile_repeat;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+void write_gradient_vertex(
+ VertexInfo vi,
+ RectWithSize local_rect,
+ RectWithSize segment_rect,
+ ivec4 prim_user_data,
+ int brush_flags,
+ vec4 texel_rect,
+ int extend_mode,
+ vec2 stretch_size
+) {
+ if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
+ v_pos = (vi.local_pos - segment_rect.p0) / segment_rect.size;
+ v_pos = v_pos * (texel_rect.zw - texel_rect.xy) + texel_rect.xy;
+ v_pos = v_pos * local_rect.size;
+ } else {
+ v_pos = vi.local_pos - local_rect.p0;
+ }
+
+ vec2 tile_repeat = local_rect.size / stretch_size;
+ v_repeated_size = stretch_size;
+
+ // Normalize UV to 0..1 scale.
+ v_pos /= v_repeated_size;
+
+ v_gradient_address = prim_user_data.x;
+
+ // Whether to repeat the gradient along the line instead of clamping.
+ v_gradient_repeat = float(extend_mode != EXTEND_MODE_CLAMP);
+
+#ifdef WR_FEATURE_ALPHA_PASS
+ v_tile_repeat = tile_repeat;
+ v_local_pos = vi.local_pos;
+#endif
+}
+#endif //WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+vec2 compute_gradient_pos() {
+#ifdef WR_FEATURE_ALPHA_PASS
+ // Handle top and left inflated edges (see brush_image).
+ vec2 local_pos = max(v_pos, vec2(0.0));
+
+ // Apply potential horizontal and vertical repetitions.
+ vec2 pos = fract(local_pos);
+
+ // Handle bottom and right inflated edges (see brush_image).
+ if (local_pos.x >= v_tile_repeat.x) {
+ pos.x = 1.0;
+ }
+ if (local_pos.y >= v_tile_repeat.y) {
+ pos.y = 1.0;
+ }
+ return pos;
+#else
+ // Apply potential horizontal and vertical repetitions.
+ return fract(v_pos);
+#endif
+}
+
+#ifdef WR_FEATURE_DITHERING
+vec4 dither(vec4 color) {
+ const int matrix_mask = 7;
+
+ ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
+ float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
+ float noise = (noise_normalized - 0.5) / 256.0; // scale down to the unit length
+
+ return color + vec4(noise, noise, noise, 0);
+}
+#else
+vec4 dither(vec4 color) {
+ return color;
+}
+#endif //WR_FEATURE_DITHERING
+
+vec4 sample_gradient(float offset) {
+ // Modulo the offset if the gradient repeats.
+ float x = offset - floor(offset) * v_gradient_repeat;
+
+ // Calculate the color entry index to use for this offset:
+ // offsets < 0 use the first color entry, 0
+ // offsets from [0, 1) use the color entries in the range of [1, N-1)
+ // offsets >= 1 use the last color entry, N-1
+ // so transform the range [0, 1) -> [1, N-1)
+
+ // TODO(gw): In the future we might consider making the size of the
+ // LUT vary based on number / distribution of stops in the gradient.
+ // Ensure we don't fetch outside the valid range of the LUT.
+ const float GRADIENT_ENTRIES = 128.0;
+ x = clamp(1.0 + x * GRADIENT_ENTRIES, 0.0, 1.0 + GRADIENT_ENTRIES);
+
+ // Calculate the texel to index into the gradient color entries:
+ // floor(x) is the gradient color entry index
+ // fract(x) is the linear filtering factor between start and end
+ float entry_index = floor(x);
+ float entry_fract = x - entry_index;
+
+ // Fetch the start and end color. There is a [start, end] color per entry.
+ vec4 texels[2] = fetch_from_gpu_cache_2(v_gradient_address + 2 * int(entry_index));
+
+ // Finally interpolate and apply dithering
+ return dither(mix(texels[0], texels[1], entry_fract));
+}
+
+#endif //WR_FRAGMENT_SHADER
+
diff --git a/gfx/wr/webrender/res/pf_vector_cover.glsl b/gfx/wr/webrender/res/pf_vector_cover.glsl
new file mode 100644
index 0000000000..1b2eeabb7d
--- /dev/null
+++ b/gfx/wr/webrender/res/pf_vector_cover.glsl
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in ivec4 aTargetRect;
+PER_INSTANCE in ivec2 aStencilOrigin;
+PER_INSTANCE in int aSubpixel;
+PER_INSTANCE in int aPad;
+
+out vec2 vStencilUV;
+flat out int vSubpixel;
+
+void main(void) {
+ vec4 targetRect = vec4(aTargetRect);
+ vec2 stencilOrigin = vec2(aStencilOrigin);
+
+ vec2 targetOffset = mix(vec2(0.0), targetRect.zw, aPosition.xy);
+ vec2 targetPosition = targetRect.xy + targetOffset;
+ vec2 stencilOffset = targetOffset * vec2(aSubpixel == 0 ? 1.0 : 3.0, 1.0);
+ vec2 stencilPosition = stencilOrigin + stencilOffset;
+
+ gl_Position = uTransform * vec4(targetPosition, aPosition.z, 1.0);
+ vStencilUV = stencilPosition;
+ vSubpixel = aSubpixel;
+}
+
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+#define LCD_FILTER_FACTOR_0 (86.0 / 255.0)
+#define LCD_FILTER_FACTOR_1 (77.0 / 255.0)
+#define LCD_FILTER_FACTOR_2 (8.0 / 255.0)
+
+in vec2 vStencilUV;
+flat in int vSubpixel;
+
+/// Applies a slight horizontal blur to reduce color fringing on LCD screens
+/// when performing subpixel AA.
+///
+/// The algorithm should be identical to that of FreeType:
+/// https://www.freetype.org/freetype2/docs/reference/ft2-lcd_filtering.html
+float lcdFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float shadeR2) {
+ return LCD_FILTER_FACTOR_2 * shadeL2 +
+ LCD_FILTER_FACTOR_1 * shadeL1 +
+ LCD_FILTER_FACTOR_0 * shade0 +
+ LCD_FILTER_FACTOR_1 * shadeR1 +
+ LCD_FILTER_FACTOR_2 * shadeR2;
+}
+
+void main(void) {
+ ivec2 stencilUV = ivec2(vStencilUV);
+ float shade0 = abs(TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(0, 0)).r);
+
+ if (vSubpixel == 0) {
+ oFragColor = vec4(shade0);
+ return;
+ }
+
+ vec3 shadeL = abs(vec3(TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(-1, 0)).r,
+ TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(-2, 0)).r,
+ TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(-3, 0)).r));
+ vec3 shadeR = abs(vec3(TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(1, 0)).r,
+ TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(2, 0)).r,
+ TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(3, 0)).r));
+
+ oFragColor = vec4(lcdFilter(shadeL.z, shadeL.y, shadeL.x, shade0, shadeR.x),
+ lcdFilter(shadeL.y, shadeL.x, shade0, shadeR.x, shadeR.y),
+ lcdFilter(shadeL.x, shade0, shadeR.x, shadeR.y, shadeR.z),
+ 1.0);
+}
+
+#endif
diff --git a/gfx/wr/webrender/res/pf_vector_stencil.glsl b/gfx/wr/webrender/res/pf_vector_stencil.glsl
new file mode 100644
index 0000000000..2029768fcb
--- /dev/null
+++ b/gfx/wr/webrender/res/pf_vector_stencil.glsl
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+#ifdef WR_VERTEX_SHADER
+
+PER_INSTANCE in vec2 aFromPosition;
+PER_INSTANCE in vec2 aCtrlPosition;
+PER_INSTANCE in vec2 aToPosition;
+PER_INSTANCE in vec2 aFromNormal;
+PER_INSTANCE in vec2 aCtrlNormal;
+PER_INSTANCE in vec2 aToNormal;
+PER_INSTANCE in int aPathID;
+PER_INSTANCE in int aPad;
+
+out vec2 vFrom;
+out vec2 vCtrl;
+out vec2 vTo;
+
+void main(void) {
+ // Unpack.
+ int pathID = int(aPathID);
+
+ ivec2 pathAddress = ivec2(0.0, aPathID);
+ mat2 transformLinear = mat2(TEXEL_FETCH(sColor1, pathAddress, 0, ivec2(0, 0)));
+ vec2 transformTranslation = TEXEL_FETCH(sColor1, pathAddress, 0, ivec2(1, 0)).xy;
+
+ vec4 miscInfo = TEXEL_FETCH(sColor1, pathAddress, 0, ivec2(2, 0));
+ float rectHeight = miscInfo.y;
+ vec2 emboldenAmount = miscInfo.zw * 0.5;
+
+ // TODO(pcwalton): Hint positions.
+ vec2 from = aFromPosition;
+ vec2 ctrl = aCtrlPosition;
+ vec2 to = aToPosition;
+
+ // Embolden as necessary.
+ from -= aFromNormal * emboldenAmount;
+ ctrl -= aCtrlNormal * emboldenAmount;
+ to -= aToNormal * emboldenAmount;
+
+ // Perform the transform.
+ from = transformLinear * from + transformTranslation;
+ ctrl = transformLinear * ctrl + transformTranslation;
+ to = transformLinear * to + transformTranslation;
+
+ // Choose correct quadrant for rotation.
+ vec2 corner = vec2(0.0, rectHeight) + transformTranslation;
+
+ // Compute edge vectors. De Casteljau subdivide if necessary.
+ // TODO(pcwalton): Actually do the two-pass rendering.
+
+ // Compute position and dilate. If too thin, discard to avoid artefacts.
+ vec2 position;
+ if (abs(from.x - to.x) < 0.0001)
+ position.x = 0.0;
+ else if (aPosition.x < 0.5)
+ position.x = floor(min(min(from.x, to.x), ctrl.x));
+ else
+ position.x = ceil(max(max(from.x, to.x), ctrl.x));
+ if (aPosition.y < 0.5)
+ position.y = floor(min(min(from.y, to.y), ctrl.y));
+ else
+ position.y = corner.y;
+
+ // Compute final position and depth.
+ vec4 clipPosition = uTransform * vec4(position, aPosition.z, 1.0);
+
+ // Finish up.
+ gl_Position = clipPosition;
+ vFrom = from - position;
+ vCtrl = ctrl - position;
+ vTo = to - position;
+}
+
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+uniform sampler2D uAreaLUT;
+
+in vec2 vFrom;
+in vec2 vCtrl;
+in vec2 vTo;
+
+void main(void) {
+ // Unpack.
+ vec2 from = vFrom, ctrl = vCtrl, to = vTo;
+
+ // Determine winding, and sort into a consistent order so we only need to find one root below.
+ bool winding = from.x < to.x;
+ vec2 left = winding ? from : to, right = winding ? to : from;
+ vec2 v0 = ctrl - left, v1 = right - ctrl;
+
+ // Shoot a vertical ray toward the curve.
+ vec2 window = clamp(vec2(from.x, to.x), -0.5, 0.5);
+ float offset = mix(window.x, window.y, 0.5) - left.x;
+ float t = offset / (v0.x + sqrt(v1.x * offset - v0.x * (offset - v0.x)));
+
+ // Compute position and derivative to form a line approximation.
+ float y = mix(mix(left.y, ctrl.y, t), mix(ctrl.y, right.y, t), t);
+ float d = mix(v0.y, v1.y, t) / mix(v0.x, v1.x, t);
+
+ // Look up area under that line, and scale horizontally to the window size.
+ float dX = window.x - window.y;
+ oFragColor = vec4(texture(sColor0, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX);
+}
+
+#endif
diff --git a/gfx/wr/webrender/res/prim_shared.glsl b/gfx/wr/webrender/res/prim_shared.glsl
new file mode 100644
index 0000000000..4afd147f4e
--- /dev/null
+++ b/gfx/wr/webrender/res/prim_shared.glsl
@@ -0,0 +1,284 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include rect,render_task,gpu_cache,transform
+
+#define EXTEND_MODE_CLAMP 0
+#define EXTEND_MODE_REPEAT 1
+
+#define SUBPX_DIR_NONE 0
+#define SUBPX_DIR_HORIZONTAL 1
+#define SUBPX_DIR_VERTICAL 2
+#define SUBPX_DIR_MIXED 3
+
+#define RASTER_LOCAL 0
+#define RASTER_SCREEN 1
+
+uniform sampler2D sClipMask;
+
+vec2 clamp_rect(vec2 pt, RectWithSize rect) {
+ return clamp(pt, rect.p0, rect.p0 + rect.size);
+}
+
+#ifndef SWGL
+// TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
+flat varying vec4 vClipMaskUvBounds;
+// XY and W are homogeneous coordinates, Z is the layer index
+varying vec4 vClipMaskUv;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+
+#define COLOR_MODE_FROM_PASS 0
+#define COLOR_MODE_ALPHA 1
+#define COLOR_MODE_SUBPX_CONST_COLOR 2
+#define COLOR_MODE_SUBPX_BG_PASS0 3
+#define COLOR_MODE_SUBPX_BG_PASS1 4
+#define COLOR_MODE_SUBPX_BG_PASS2 5
+#define COLOR_MODE_SUBPX_DUAL_SOURCE 6
+#define COLOR_MODE_BITMAP 7
+#define COLOR_MODE_COLOR_BITMAP 8
+#define COLOR_MODE_IMAGE 9
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF;
+uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI;
+
+// Instanced attributes
+PER_INSTANCE in ivec4 aData;
+
+#define VECS_PER_PRIM_HEADER_F 2U
+#define VECS_PER_PRIM_HEADER_I 2U
+
+struct Instance
+{
+ int prim_header_address;
+ int picture_task_address;
+ int clip_address;
+ int segment_index;
+ int flags;
+ int resource_address;
+ int brush_kind;
+};
+
+Instance decode_instance_attributes() {
+ Instance instance;
+
+ instance.prim_header_address = aData.x;
+ instance.picture_task_address = aData.y >> 16;
+ instance.clip_address = aData.y & 0xffff;
+ instance.segment_index = aData.z & 0xffff;
+ instance.flags = aData.z >> 16;
+ instance.resource_address = aData.w & 0xffffff;
+ instance.brush_kind = aData.w >> 24;
+
+ return instance;
+}
+
+struct PrimitiveHeader {
+ RectWithSize local_rect;
+ RectWithSize local_clip_rect;
+ float z;
+ int specific_prim_address;
+ int transform_id;
+ ivec4 user_data;
+};
+
+PrimitiveHeader fetch_prim_header(int index) {
+ PrimitiveHeader ph;
+
+ ivec2 uv_f = get_fetch_uv(index, VECS_PER_PRIM_HEADER_F);
+ vec4 local_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(0, 0));
+ vec4 local_clip_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(1, 0));
+ ph.local_rect = RectWithSize(local_rect.xy, local_rect.zw);
+ ph.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
+
+ ivec2 uv_i = get_fetch_uv(index, VECS_PER_PRIM_HEADER_I);
+ ivec4 data0 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(0, 0));
+ ivec4 data1 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(1, 0));
+ ph.z = float(data0.x);
+ ph.specific_prim_address = data0.y;
+ ph.transform_id = data0.z;
+ ph.user_data = data1;
+
+ return ph;
+}
+
+struct VertexInfo {
+ vec2 local_pos;
+ vec4 world_pos;
+};
+
+VertexInfo write_vertex(vec2 local_pos,
+ RectWithSize local_clip_rect,
+ float z,
+ Transform transform,
+ PictureTask task) {
+ // Clamp to the two local clip rects.
+ vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect);
+
+ // Transform the current vertex to world space.
+ vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
+
+ // Convert the world positions to device pixel space.
+ vec2 device_pos = world_pos.xy * task.device_pixel_scale;
+
+ // Apply offsets for the render task to get correct screen location.
+ vec2 final_offset = -task.content_origin + task.common_data.task_rect.p0;
+
+ gl_Position = uTransform * vec4(device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w);
+
+ VertexInfo vi = VertexInfo(
+ clamped_local_pos,
+ world_pos
+ );
+
+ return vi;
+}
+
+float cross2(vec2 v0, vec2 v1) {
+ return v0.x * v1.y - v0.y * v1.x;
+}
+
+// Return intersection of line (p0,p1) and line (p2,p3)
+vec2 intersect_lines(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
+ vec2 d0 = p0 - p1;
+ vec2 d1 = p2 - p3;
+
+ float s0 = cross2(p0, p1);
+ float s1 = cross2(p2, p3);
+
+ float d = cross2(d0, d1);
+ float nx = s0 * d1.x - d0.x * s1;
+ float ny = s0 * d1.y - d0.y * s1;
+
+ return vec2(nx / d, ny / d);
+}
+
+VertexInfo write_transform_vertex(RectWithSize local_segment_rect,
+ RectWithSize local_prim_rect,
+ RectWithSize local_clip_rect,
+ vec4 clip_edge_mask,
+ float z,
+ Transform transform,
+ PictureTask task) {
+ // Calculate a clip rect from local_rect + local clip
+ RectWithEndpoint clip_rect = to_rect_with_endpoint(local_clip_rect);
+ RectWithEndpoint segment_rect = to_rect_with_endpoint(local_segment_rect);
+ segment_rect.p0 = clamp(segment_rect.p0, clip_rect.p0, clip_rect.p1);
+ segment_rect.p1 = clamp(segment_rect.p1, clip_rect.p0, clip_rect.p1);
+
+ // Calculate a clip rect from local_rect + local clip
+ RectWithEndpoint prim_rect = to_rect_with_endpoint(local_prim_rect);
+ prim_rect.p0 = clamp(prim_rect.p0, clip_rect.p0, clip_rect.p1);
+ prim_rect.p1 = clamp(prim_rect.p1, clip_rect.p0, clip_rect.p1);
+
+ // As this is a transform shader, extrude by 2 (local space) pixels
+ // in each direction. This gives enough space around the edge to
+ // apply distance anti-aliasing. Technically, it:
+ // (a) slightly over-estimates the number of required pixels in the simple case.
+ // (b) might not provide enough edge in edge case perspective projections.
+ // However, it's fast and simple. If / when we ever run into issues, we
+ // can do some math on the projection matrix to work out a variable
+ // amount to extrude.
+
+ // Only extrude along edges where we are going to apply AA.
+ float extrude_amount = 2.0;
+ vec4 extrude_distance = vec4(extrude_amount) * clip_edge_mask;
+ local_segment_rect.p0 -= extrude_distance.xy;
+ local_segment_rect.size += extrude_distance.xy + extrude_distance.zw;
+
+ // Select the corner of the local rect that we are processing.
+ vec2 local_pos = local_segment_rect.p0 + local_segment_rect.size * aPosition.xy;
+
+ // Convert the world positions to device pixel space.
+ vec2 task_offset = task.common_data.task_rect.p0 - task.content_origin;
+
+ // Transform the current vertex to the world cpace.
+ vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
+ vec4 final_pos = vec4(
+ world_pos.xy * task.device_pixel_scale + task_offset * world_pos.w,
+ z * world_pos.w,
+ world_pos.w
+ );
+
+ gl_Position = uTransform * final_pos;
+
+ init_transform_vs(mix(
+ vec4(prim_rect.p0, prim_rect.p1),
+ vec4(segment_rect.p0, segment_rect.p1),
+ clip_edge_mask
+ ));
+
+ VertexInfo vi = VertexInfo(
+ local_pos,
+ world_pos
+ );
+
+ return vi;
+}
+
+void write_clip(vec4 world_pos, ClipArea area, PictureTask task) {
+#ifdef SWGL
+ swgl_clipMask(
+ sClipMask,
+ (task.common_data.task_rect.p0 - task.content_origin) - (area.common_data.task_rect.p0 - area.screen_origin),
+ area.common_data.task_rect.p0,
+ area.common_data.task_rect.size
+ );
+#else
+ vec2 uv = world_pos.xy * area.device_pixel_scale +
+ world_pos.w * (area.common_data.task_rect.p0 - area.screen_origin);
+ vClipMaskUvBounds = vec4(
+ area.common_data.task_rect.p0,
+ area.common_data.task_rect.p0 + area.common_data.task_rect.size
+ );
+ vClipMaskUv = vec4(uv, area.common_data.texture_layer_index, world_pos.w);
+#endif
+}
+
+// Read the exta image data containing the homogeneous screen space coordinates
+// of the corners, interpolate between them, and return real screen space UV.
+vec2 get_image_quad_uv(int address, vec2 f) {
+ ImageResourceExtra extra_data = fetch_image_resource_extra(address);
+ vec4 x = mix(extra_data.st_tl, extra_data.st_tr, f.x);
+ vec4 y = mix(extra_data.st_bl, extra_data.st_br, f.x);
+ vec4 z = mix(x, y, f.y);
+ return z.xy / z.w;
+}
+#endif //WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+
+struct Fragment {
+ vec4 color;
+#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+ vec4 blend;
+#endif
+};
+
+float do_clip() {
+#ifdef SWGL
+ // SWGL relies on builtin clip-mask support to do this more efficiently,
+ // so no clipping is required here.
+ return 1.0;
+#else
+ // check for the dummy bounds, which are given to the opaque objects
+ if (vClipMaskUvBounds.xy == vClipMaskUvBounds.zw) {
+ return 1.0;
+ }
+ // anything outside of the mask is considered transparent
+ //Note: we assume gl_FragCoord.w == interpolated(1 / vClipMaskUv.w)
+ vec2 mask_uv = vClipMaskUv.xy * gl_FragCoord.w;
+ bvec2 left = lessThanEqual(vClipMaskUvBounds.xy, mask_uv); // inclusive
+ bvec2 right = greaterThan(vClipMaskUvBounds.zw, mask_uv); // non-inclusive
+ // bail out if the pixel is outside the valid bounds
+ if (!all(bvec4(left, right))) {
+ return 0.0;
+ }
+ // finally, the slow path - fetch the mask value from an image
+ return texelFetch(sClipMask, ivec2(mask_uv), 0).r;
+#endif
+}
+
+#endif //WR_FRAGMENT_SHADER
diff --git a/gfx/wr/webrender/res/ps_clear.glsl b/gfx/wr/webrender/res/ps_clear.glsl
new file mode 100644
index 0000000000..5ff1c206f7
--- /dev/null
+++ b/gfx/wr/webrender/res/ps_clear.glsl
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+varying vec4 vColor;
+
+#ifdef WR_VERTEX_SHADER
+PER_INSTANCE in vec4 aRect;
+PER_INSTANCE in vec4 aColor;
+
+void main(void) {
+ vec2 pos = aRect.xy + aPosition.xy * aRect.zw;
+ gl_Position = uTransform * vec4(pos, 0.0, 1.0);
+ gl_Position.z = gl_Position.w; // force depth clear to 1.0
+ vColor = aColor;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ oFragColor = vColor;
+}
+#endif
diff --git a/gfx/wr/webrender/res/ps_split_composite.glsl b/gfx/wr/webrender/res/ps_split_composite.glsl
new file mode 100644
index 0000000000..5ff877820f
--- /dev/null
+++ b/gfx/wr/webrender/res/ps_split_composite.glsl
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define WR_FEATURE_TEXTURE_2D
+
+#include shared,prim_shared
+
+// interpolated UV coordinates to sample.
+varying vec2 vUv;
+// flag to allow perspective interpolation of UV.
+flat varying float vPerspective;
+flat varying vec4 vUvSampleBounds;
+
+#ifdef WR_VERTEX_SHADER
+struct SplitGeometry {
+ vec2 local[4];
+};
+
+SplitGeometry fetch_split_geometry(int address) {
+ ivec2 uv = get_gpu_cache_uv(address);
+
+ vec4 data0 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0));
+ vec4 data1 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0));
+
+ SplitGeometry geo;
+ geo.local = vec2[4](
+ data0.xy,
+ data0.zw,
+ data1.xy,
+ data1.zw
+ );
+
+ return geo;
+}
+
+vec2 bilerp(vec2 a, vec2 b, vec2 c, vec2 d, float s, float t) {
+ vec2 x = mix(a, b, t);
+ vec2 y = mix(c, d, t);
+ return mix(x, y, s);
+}
+
+struct SplitCompositeInstance {
+ int prim_header_index;
+ int polygons_address;
+ float z;
+ int render_task_index;
+};
+
+SplitCompositeInstance fetch_composite_instance() {
+ SplitCompositeInstance ci;
+
+ ci.prim_header_index = aData.x;
+ ci.polygons_address = aData.y;
+ ci.z = float(aData.z);
+ ci.render_task_index = aData.w;
+
+ return ci;
+}
+
+void main(void) {
+ SplitCompositeInstance ci = fetch_composite_instance();
+ SplitGeometry geometry = fetch_split_geometry(ci.polygons_address);
+ PrimitiveHeader ph = fetch_prim_header(ci.prim_header_index);
+ PictureTask dest_task = fetch_picture_task(ci.render_task_index);
+ Transform transform = fetch_transform(ph.transform_id);
+ ImageResource res = fetch_image_resource(ph.user_data.x);
+ ClipArea clip_area = fetch_clip_area(ph.user_data.w);
+
+ vec2 dest_origin = dest_task.common_data.task_rect.p0 -
+ dest_task.content_origin;
+
+ vec2 local_pos = bilerp(geometry.local[0], geometry.local[1],
+ geometry.local[3], geometry.local[2],
+ aPosition.y, aPosition.x);
+ vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0);
+
+ vec4 final_pos = vec4(
+ dest_origin * world_pos.w + world_pos.xy * dest_task.device_pixel_scale,
+ world_pos.w * ci.z,
+ world_pos.w
+ );
+
+ write_clip(
+ world_pos,
+ clip_area,
+ dest_task
+ );
+
+ gl_Position = uTransform * final_pos;
+
+ vec2 texture_size = vec2(textureSize(sColor0, 0));
+ vec2 uv0 = res.uv_rect.p0;
+ vec2 uv1 = res.uv_rect.p1;
+
+ vec2 min_uv = min(uv0, uv1);
+ vec2 max_uv = max(uv0, uv1);
+
+ vUvSampleBounds = vec4(
+ min_uv + vec2(0.5),
+ max_uv - vec2(0.5)
+ ) / texture_size.xyxy;
+
+ vec2 f = (local_pos - ph.local_rect.p0) / ph.local_rect.size;
+ f = get_image_quad_uv(ph.user_data.x, f);
+ vec2 uv = mix(uv0, uv1, f);
+ float perspective_interpolate = float(ph.user_data.y);
+
+ vUv = uv / texture_size * mix(gl_Position.w, 1.0, perspective_interpolate);
+ vPerspective = perspective_interpolate;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+void main(void) {
+ float alpha = do_clip();
+ float perspective_divisor = mix(gl_FragCoord.w, 1.0, vPerspective);
+ vec2 uv = clamp(vUv * perspective_divisor, vUvSampleBounds.xy, vUvSampleBounds.zw);
+ write_output(alpha * texture(sColor0, uv));
+}
+
+#ifdef SWGL
+void swgl_drawSpanRGBA8() {
+ if (!swgl_isTextureRGBA8(sColor0) || !swgl_isTextureLinear(sColor0)) {
+ return;
+ }
+
+ float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, vPerspective);
+
+ vec2 uv = vUv * perspective_divisor;
+
+ if (swgl_allowTextureNearest(sColor0, uv)) {
+ swgl_commitTextureNearestRGBA8(sColor0, uv, vUvSampleBounds, 0);
+ return;
+ }
+
+ uv = swgl_linearQuantize(sColor0, uv);
+ vec2 min_uv = swgl_linearQuantize(sColor0, vUvSampleBounds.xy);
+ vec2 max_uv = swgl_linearQuantize(sColor0, vUvSampleBounds.zw);
+ vec2 step_uv = swgl_linearQuantizeStep(sColor0, swgl_interpStep(vUv)) * perspective_divisor;
+
+ while (swgl_SpanLength > 0) {
+ swgl_commitTextureLinearRGBA8(sColor0, clamp(uv, min_uv, max_uv), 0);
+ uv += step_uv;
+ }
+}
+#endif
+
+#endif
diff --git a/gfx/wr/webrender/res/ps_text_run.glsl b/gfx/wr/webrender/res/ps_text_run.glsl
new file mode 100644
index 0000000000..48ee50c391
--- /dev/null
+++ b/gfx/wr/webrender/res/ps_text_run.glsl
@@ -0,0 +1,302 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared,prim_shared
+
+flat varying vec4 v_color;
+flat varying vec2 v_mask_swizzle;
+// Normalized bounds of the source image in the texture.
+flat varying vec4 v_uv_bounds;
+
+// Interpolated UV coordinates to sample.
+varying vec2 v_uv;
+
+
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+varying vec4 v_uv_clip;
+#endif
+
+#ifdef WR_VERTEX_SHADER
+
+#define VECS_PER_TEXT_RUN 2
+#define GLYPHS_PER_GPU_BLOCK 2U
+
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+RectWithSize transform_rect(RectWithSize rect, mat2 transform) {
+ vec2 center = transform * (rect.p0 + rect.size * 0.5);
+ vec2 radius = mat2(abs(transform[0]), abs(transform[1])) * (rect.size * 0.5);
+ return RectWithSize(center - radius, radius * 2.0);
+}
+
+bool rect_inside_rect(RectWithSize little, RectWithSize big) {
+ return all(lessThanEqual(vec4(big.p0, little.p0 + little.size),
+ vec4(little.p0, big.p0 + big.size)));
+}
+#endif //WR_FEATURE_GLYPH_TRANSFORM
+
+struct Glyph {
+ vec2 offset;
+};
+
+Glyph fetch_glyph(int specific_prim_address,
+ int glyph_index) {
+ // Two glyphs are packed in each texel in the GPU cache.
+ int glyph_address = specific_prim_address +
+ VECS_PER_TEXT_RUN +
+ int(uint(glyph_index) / GLYPHS_PER_GPU_BLOCK);
+ vec4 data = fetch_from_gpu_cache_1(glyph_address);
+ // Select XY or ZW based on glyph index.
+ // We use "!= 0" instead of "== 1" here in order to work around a driver
+ // bug with equality comparisons on integers.
+ vec2 glyph = mix(data.xy, data.zw,
+ bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK != 0U));
+
+ return Glyph(glyph);
+}
+
+struct GlyphResource {
+ vec4 uv_rect;
+ float layer;
+ vec2 offset;
+ float scale;
+};
+
+GlyphResource fetch_glyph_resource(int address) {
+ vec4 data[2] = fetch_from_gpu_cache_2(address);
+ return GlyphResource(data[0], data[1].x, data[1].yz, data[1].w);
+}
+
+struct TextRun {
+ vec4 color;
+ vec4 bg_color;
+};
+
+TextRun fetch_text_run(int address) {
+ vec4 data[2] = fetch_from_gpu_cache_2(address);
+ return TextRun(data[0], data[1]);
+}
+
+vec2 get_snap_bias(int subpx_dir) {
+ // In subpixel mode, the subpixel offset has already been
+ // accounted for while rasterizing the glyph. However, we
+ // must still round with a subpixel bias rather than rounding
+ // to the nearest whole pixel, depending on subpixel direciton.
+ switch (subpx_dir) {
+ case SUBPX_DIR_NONE:
+ default:
+ return vec2(0.5);
+ case SUBPX_DIR_HORIZONTAL:
+ // Glyphs positioned [-0.125, 0.125] get a
+ // subpx position of zero. So include that
+ // offset in the glyph position to ensure
+ // we round to the correct whole position.
+ return vec2(0.125, 0.5);
+ case SUBPX_DIR_VERTICAL:
+ return vec2(0.5, 0.125);
+ case SUBPX_DIR_MIXED:
+ return vec2(0.125);
+ }
+}
+
+void main() {
+ Instance instance = decode_instance_attributes();
+ PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address);
+ Transform transform = fetch_transform(ph.transform_id);
+ ClipArea clip_area = fetch_clip_area(instance.clip_address);
+ PictureTask task = fetch_picture_task(instance.picture_task_address);
+
+ int glyph_index = instance.segment_index;
+ int subpx_dir = (instance.flags >> 8) & 0xff;
+ int color_mode = instance.flags & 0xff;
+
+ // Note that the reference frame relative offset is stored in the prim local
+ // rect size during batching, instead of the actual size of the primitive.
+ TextRun text = fetch_text_run(ph.specific_prim_address);
+ vec2 text_offset = ph.local_rect.size;
+
+ if (color_mode == COLOR_MODE_FROM_PASS) {
+ color_mode = uMode;
+ }
+
+ // Note that the unsnapped reference frame relative offset has already
+ // been subtracted from the prim local rect origin during batching.
+ // It was done this way to avoid pushing both the snapped and the
+ // unsnapped offsets to the shader.
+ Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
+ glyph.offset += ph.local_rect.p0;
+
+ GlyphResource res = fetch_glyph_resource(instance.resource_address);
+
+ vec2 snap_bias = get_snap_bias(subpx_dir);
+
+ // Glyph space refers to the pixel space used by glyph rasterization during frame
+ // building. If a non-identity transform was used, WR_FEATURE_GLYPH_TRANSFORM will
+ // be set. Otherwise, regardless of whether the raster space is LOCAL or SCREEN,
+ // we ignored the transform during glyph rasterization, and need to snap just using
+ // the device pixel scale and the raster scale.
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+ // Transform from local space to glyph space.
+ mat2 glyph_transform = mat2(transform.m) * task.device_pixel_scale;
+ vec2 glyph_translation = transform.m[3].xy * task.device_pixel_scale;
+
+ // Transform from glyph space back to local space.
+ mat2 glyph_transform_inv = inverse(glyph_transform);
+
+ // Glyph raster pixels include the impact of the transform. This path can only be
+ // entered for 3d transforms that can be coerced into a 2d transform; they have no
+ // perspective, and have a 2d inverse. This is a looser condition than axis aligned
+ // transforms because it also allows 2d rotations.
+ vec2 raster_glyph_offset = floor(glyph_transform * glyph.offset + snap_bias);
+
+ // We want to eliminate any subpixel translation in device space to ensure glyph
+ // snapping is stable for equivalent glyph subpixel positions. Note that we must take
+ // into account the translation from the transform for snapping purposes.
+ vec2 raster_text_offset = floor(glyph_transform * text_offset + glyph_translation + 0.5) - glyph_translation;
+
+ // Compute the glyph rect in glyph space.
+ RectWithSize glyph_rect = RectWithSize(res.offset + raster_glyph_offset + raster_text_offset,
+ res.uv_rect.zw - res.uv_rect.xy);
+
+ // The glyph rect is in glyph space, so transform it back to local space.
+ RectWithSize local_rect = transform_rect(glyph_rect, glyph_transform_inv);
+
+ // Select the corner of the glyph's local space rect that we are processing.
+ vec2 local_pos = local_rect.p0 + local_rect.size * aPosition.xy;
+
+ // If the glyph's local rect would fit inside the local clip rect, then select a corner from
+ // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
+ // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
+ if (rect_inside_rect(local_rect, ph.local_clip_rect)) {
+ local_pos = glyph_transform_inv * (glyph_rect.p0 + glyph_rect.size * aPosition.xy);
+ }
+#else
+ float raster_scale = float(ph.user_data.x) / 65535.0;
+
+ // Scale in which the glyph is snapped when rasterized.
+ float glyph_raster_scale = raster_scale * task.device_pixel_scale;
+
+ // Scale from glyph space to local space.
+ float glyph_scale_inv = res.scale / glyph_raster_scale;
+
+ // Glyph raster pixels do not include the impact of the transform. Instead it was
+ // replaced with an identity transform during glyph rasterization. As such only the
+ // impact of the raster scale (if in local space) and the device pixel scale (for both
+ // local and screen space) are included.
+ //
+ // This implies one or more of the following conditions:
+ // - The transform is an identity. In that case, setting WR_FEATURE_GLYPH_TRANSFORM
+ // should have the same output result as not. We just distingush which path to use
+ // based on the transform used during glyph rasterization. (Screen space).
+ // - The transform contains an animation. We will imply local raster space in such
+ // cases to avoid constantly rerasterizing the glyphs.
+ // - The transform has perspective or does not have a 2d inverse (Screen or local space).
+ // - The transform's scale will result in result in very large rasterized glyphs and
+ // we clamped the size. This will imply local raster space.
+ vec2 raster_glyph_offset = floor(glyph.offset * glyph_raster_scale + snap_bias) / res.scale;
+
+ // Compute the glyph rect in local space.
+ //
+ // The transform may be animated, so we don't want to do any snapping here for the
+ // text offset to avoid glyphs wiggling. The text offset should have been snapped
+ // already for axis aligned transforms excluding any animations during frame building.
+ RectWithSize glyph_rect = RectWithSize(glyph_scale_inv * (res.offset + raster_glyph_offset) + text_offset,
+ glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy));
+
+ // Select the corner of the glyph rect that we are processing.
+ vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy;
+#endif
+
+ VertexInfo vi = write_vertex(
+ local_pos,
+ ph.local_clip_rect,
+ ph.z,
+ transform,
+ task
+ );
+
+#ifdef WR_FEATURE_GLYPH_TRANSFORM
+ vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
+ v_uv_clip = vec4(f, 1.0 - f);
+#else
+ vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size;
+#endif
+
+ write_clip(vi.world_pos, clip_area, task);
+
+ switch (color_mode) {
+ case COLOR_MODE_ALPHA:
+ case COLOR_MODE_BITMAP:
+ v_mask_swizzle = vec2(0.0, 1.0);
+ v_color = text.color;
+ break;
+ case COLOR_MODE_SUBPX_BG_PASS2:
+ case COLOR_MODE_SUBPX_DUAL_SOURCE:
+ v_mask_swizzle = vec2(1.0, 0.0);
+ v_color = text.color;
+ break;
+ case COLOR_MODE_SUBPX_CONST_COLOR:
+ case COLOR_MODE_SUBPX_BG_PASS0:
+ case COLOR_MODE_COLOR_BITMAP:
+ v_mask_swizzle = vec2(1.0, 0.0);
+ v_color = vec4(text.color.a);
+ break;
+ case COLOR_MODE_SUBPX_BG_PASS1:
+ v_mask_swizzle = vec2(-1.0, 1.0);
+ v_color = vec4(text.color.a) * text.bg_color;
+ break;
+ default:
+ v_mask_swizzle = vec2(0.0);
+ v_color = vec4(1.0);
+ }
+
+ vec2 texture_size = vec2(textureSize(sColor0, 0));
+ vec2 st0 = res.uv_rect.xy / texture_size;
+ vec2 st1 = res.uv_rect.zw / texture_size;
+
+ v_uv = mix(st0, st1, f);
+ v_uv_bounds = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
+}
+
+#endif // WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+
+Fragment text_fs(void) {
+ Fragment frag;
+
+ vec2 tc = clamp(v_uv, v_uv_bounds.xy, v_uv_bounds.zw);
+ vec4 mask = texture(sColor0, tc);
+ mask.rgb = mask.rgb * v_mask_swizzle.x + mask.aaa * v_mask_swizzle.y;
+
+ #ifdef WR_FEATURE_GLYPH_TRANSFORM
+ mask *= float(all(greaterThanEqual(v_uv_clip, vec4(0.0))));
+ #endif
+
+ frag.color = v_color * mask;
+
+ #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+ frag.blend = v_color.a * mask;
+ #endif
+
+ return frag;
+}
+
+
+void main() {
+ Fragment frag = text_fs();
+
+ float clip_mask = do_clip();
+ frag.color *= clip_mask;
+
+ #if defined(WR_FEATURE_DEBUG_OVERDRAW)
+ oFragColor = WR_DEBUG_OVERDRAW_COLOR;
+ #elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING)
+ oFragColor = frag.color;
+ oFragBlend = frag.blend * clip_mask;
+ #else
+ write_output(frag.color);
+ #endif
+}
+
+#endif // WR_FRAGMENT_SHADER
diff --git a/gfx/wr/webrender/res/rect.glsl b/gfx/wr/webrender/res/rect.glsl
new file mode 100644
index 0000000000..093aea0280
--- /dev/null
+++ b/gfx/wr/webrender/res/rect.glsl
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+struct RectWithSize {
+ vec2 p0;
+ vec2 size;
+};
+
+struct RectWithEndpoint {
+ vec2 p0;
+ vec2 p1;
+};
+
+RectWithEndpoint to_rect_with_endpoint(RectWithSize rect) {
+ RectWithEndpoint result;
+ result.p0 = rect.p0;
+ result.p1 = rect.p0 + rect.size;
+
+ return result;
+}
+
+RectWithSize to_rect_with_size(RectWithEndpoint rect) {
+ RectWithSize result;
+ result.p0 = rect.p0;
+ result.size = rect.p1 - rect.p0;
+
+ return result;
+}
+
+RectWithSize intersect_rects(RectWithSize a, RectWithSize b) {
+ RectWithSize result;
+ result.p0 = max(a.p0, b.p0);
+ result.size = min(a.p0 + a.size, b.p0 + b.size) - result.p0;
+
+ return result;
+}
+
+float point_inside_rect(vec2 p, vec2 p0, vec2 p1) {
+ vec2 s = step(p0, p) - step(p1, p);
+ return s.x * s.y;
+}
diff --git a/gfx/wr/webrender/res/render_task.glsl b/gfx/wr/webrender/res/render_task.glsl
new file mode 100644
index 0000000000..54582cd9ad
--- /dev/null
+++ b/gfx/wr/webrender/res/render_task.glsl
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifdef WR_VERTEX_SHADER
+#define VECS_PER_RENDER_TASK 2U
+
+uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
+
+struct RenderTaskCommonData {
+ RectWithSize task_rect;
+ float texture_layer_index;
+};
+
+struct RenderTaskData {
+ RenderTaskCommonData common_data;
+ vec3 user_data;
+};
+
+RenderTaskData fetch_render_task_data(int index) {
+ ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
+
+ vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
+ vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
+
+ RectWithSize task_rect = RectWithSize(
+ texel0.xy,
+ texel0.zw
+ );
+
+ RenderTaskCommonData common_data = RenderTaskCommonData(
+ task_rect,
+ texel1.x
+ );
+
+ RenderTaskData data = RenderTaskData(
+ common_data,
+ texel1.yzw
+ );
+
+ return data;
+}
+
+RenderTaskCommonData fetch_render_task_common_data(int index) {
+ ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
+
+ vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
+ vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
+
+ RectWithSize task_rect = RectWithSize(
+ texel0.xy,
+ texel0.zw
+ );
+
+ RenderTaskCommonData data = RenderTaskCommonData(
+ task_rect,
+ texel1.x
+ );
+
+ return data;
+}
+
+#define PIC_TYPE_IMAGE 1
+#define PIC_TYPE_TEXT_SHADOW 2
+
+/*
+ The dynamic picture that this brush exists on. Right now, it
+ contains minimal information. In the future, it will describe
+ the transform mode of primitives on this picture, among other things.
+ */
+struct PictureTask {
+ RenderTaskCommonData common_data;
+ float device_pixel_scale;
+ vec2 content_origin;
+};
+
+PictureTask fetch_picture_task(int address) {
+ RenderTaskData task_data = fetch_render_task_data(address);
+
+ PictureTask task = PictureTask(
+ task_data.common_data,
+ task_data.user_data.x,
+ task_data.user_data.yz
+ );
+
+ return task;
+}
+
+#define CLIP_TASK_EMPTY 0x7FFF
+
+struct ClipArea {
+ RenderTaskCommonData common_data;
+ float device_pixel_scale;
+ vec2 screen_origin;
+};
+
+ClipArea fetch_clip_area(int index) {
+ ClipArea area;
+
+ if (index >= CLIP_TASK_EMPTY) {
+ RectWithSize rect = RectWithSize(vec2(0.0), vec2(0.0));
+
+ area.common_data = RenderTaskCommonData(rect, 0.0);
+ area.device_pixel_scale = 0.0;
+ area.screen_origin = vec2(0.0);
+ } else {
+ RenderTaskData task_data = fetch_render_task_data(index);
+
+ area.common_data = task_data.common_data;
+ area.device_pixel_scale = task_data.user_data.x;
+ area.screen_origin = task_data.user_data.yz;
+ }
+
+ return area;
+}
+
+#endif //WR_VERTEX_SHADER
diff --git a/gfx/wr/webrender/res/shared.glsl b/gfx/wr/webrender/res/shared.glsl
new file mode 100644
index 0000000000..b784d528e6
--- /dev/null
+++ b/gfx/wr/webrender/res/shared.glsl
@@ -0,0 +1,192 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef WR_FEATURE_TEXTURE_EXTERNAL
+// Please check https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external_essl3.txt
+// for this extension.
+#extension GL_OES_EGL_image_external_essl3 : require
+#endif
+
+#ifdef WR_FEATURE_ADVANCED_BLEND
+#extension GL_KHR_blend_equation_advanced : require
+#endif
+
+#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+#ifdef GL_ES
+#extension GL_EXT_blend_func_extended : require
+#else
+#extension GL_ARB_explicit_attrib_location : require
+#endif
+#endif
+
+#include base
+
+#if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT) || defined(WR_FEATURE_TEXTURE_2D)
+#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy)
+#else
+#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord)
+#endif
+
+//======================================================================================
+// Vertex shader attributes and uniforms
+//======================================================================================
+#ifdef WR_VERTEX_SHADER
+ // A generic uniform that shaders can optionally use to configure
+ // an operation mode for this batch.
+ uniform int uMode;
+
+ // Uniform inputs
+ uniform mat4 uTransform; // Orthographic projection
+
+ // Attribute inputs
+ in vec2 aPosition;
+
+ // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
+ // TODO: convert back to a function once the driver issues are resolved, if ever.
+ // https://github.com/servo/webrender/pull/623
+ // https://github.com/servo/servo/issues/13953
+ // Do the division with unsigned ints because that's more efficient with D3D
+ #define get_fetch_uv(i, vpi) ivec2(int(vpi * (uint(i) % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
+#endif
+
+//======================================================================================
+// Fragment shader attributes and uniforms
+//======================================================================================
+#ifdef WR_FRAGMENT_SHADER
+ // Uniform inputs
+
+ // Fragment shader outputs
+ #ifdef WR_FEATURE_ADVANCED_BLEND
+ layout(blend_support_all_equations) out;
+ #endif
+
+ #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+ layout(location = 0, index = 0) out vec4 oFragColor;
+ layout(location = 0, index = 1) out vec4 oFragBlend;
+ #else
+ out vec4 oFragColor;
+ #endif
+
+ // Write an output color in normal shaders.
+ void write_output(vec4 color) {
+ oFragColor = color;
+ }
+
+ #define EPSILON 0.0001
+
+ // "Show Overdraw" color. Premultiplied.
+ #define WR_DEBUG_OVERDRAW_COLOR vec4(0.110, 0.077, 0.027, 0.125)
+
+ float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
+ vec2 dir_to_p0 = p0 - p;
+ return dot(normalize(perp_dir), dir_to_p0);
+ }
+
+ /// Find the appropriate half range to apply the AA approximation over.
+ /// This range represents a coefficient to go from one CSS pixel to half a device pixel.
+ float compute_aa_range(vec2 position) {
+ // The constant factor is chosen to compensate for the fact that length(fw) is equal
+ // to sqrt(2) times the device pixel ratio in the typical case. 1/sqrt(2) = 0.7071.
+ //
+ // This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of
+ // the shape has no anti-aliasing applied to it (since pixels are sampled at their center,
+ // such a pixel (axis aligned) is fully inside the border). We need this so that antialiased
+ // curves properly connect with non-antialiased vertical or horizontal lines, among other things.
+ //
+ // Lines over a half-pixel away from the pixel center *can* intersect with the pixel square;
+ // indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing
+ // a nonzero area for such pixels causes noticeable artifacts at the junction between an anti-
+ // aliased corner and a straight edge.
+ //
+ // We may want to adjust this constant in specific scenarios (for example keep the principled
+ // value for straight edges where we want pixel-perfect equivalence with non antialiased lines
+ // when axis aligned, while selecting a larger and smoother aa range on curves).
+ #ifdef SWGL
+ // SWGL uses an approximation for fwidth() such that it returns equal x and y.
+ // Thus, 1/sqrt(2) * length((x,y)) = 1/sqrt(2) * sqrt(x*x + x*x) = x.
+ return fwidth(position).x;
+ #else
+ return 0.7071 * length(fwidth(position));
+ #endif
+ }
+
+ /// Return the blending coefficient for distance antialiasing.
+ ///
+ /// 0.0 means inside the shape, 1.0 means outside.
+ ///
+ /// This cubic polynomial approximates the area of a 1x1 pixel square under a
+ /// line, given the signed Euclidean distance from the center of the square to
+ /// that line. Calculating the *exact* area would require taking into account
+ /// not only this distance but also the angle of the line. However, in
+ /// practice, this complexity is not required, as the area is roughly the same
+ /// regardless of the angle.
+ ///
+ /// The coefficients of this polynomial were determined through least-squares
+ /// regression and are accurate to within 2.16% of the total area of the pixel
+ /// square 95% of the time, with a maximum error of 3.53%.
+ ///
+ /// See the comments in `compute_aa_range()` for more information on the
+ /// cutoff values of -0.5 and 0.5.
+ float distance_aa(float aa_range, float signed_distance) {
+ float dist = signed_distance / aa_range;
+ bool inside = dist <= -0.5 + EPSILON;
+ bool outside = dist >= 0.5 - EPSILON;
+ return inside || outside
+ ? float(inside)
+ : 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
+ }
+
+ /// Component-wise selection.
+ ///
+ /// The idea of using this is to ensure both potential branches are executed before
+ /// selecting the result, to avoid observable timing differences based on the condition.
+ ///
+ /// Example usage: color = if_then_else(LessThanEqual(color, vec3(0.5)), vec3(0.0), vec3(1.0));
+ ///
+ /// The above example sets each component to 0.0 or 1.0 independently depending on whether
+ /// their values are below or above 0.5.
+ ///
+ /// This is written as a macro in order to work with vectors of any dimension.
+ ///
+ /// Note: Some older android devices don't support mix with bvec. If we ever run into them
+ /// the only option we have is to polyfill it with a branch per component.
+ #define if_then_else(cond, then_branch, else_branch) mix(else_branch, then_branch, cond)
+#endif
+
+//======================================================================================
+// Shared shader uniforms
+//======================================================================================
+#ifdef WR_FEATURE_TEXTURE_2D
+uniform sampler2D sColor0;
+uniform sampler2D sColor1;
+uniform sampler2D sColor2;
+#elif defined WR_FEATURE_TEXTURE_RECT
+uniform sampler2DRect sColor0;
+uniform sampler2DRect sColor1;
+uniform sampler2DRect sColor2;
+#elif defined WR_FEATURE_TEXTURE_EXTERNAL
+uniform samplerExternalOES sColor0;
+uniform samplerExternalOES sColor1;
+uniform samplerExternalOES sColor2;
+#elif defined WR_FEATURE_TEXTURE_2D_ARRAY
+uniform sampler2DArray sColor0;
+uniform sampler2DArray sColor1;
+uniform sampler2DArray sColor2;
+#endif
+
+#ifdef WR_FEATURE_DITHERING
+uniform sampler2D sDither;
+#endif
+
+//======================================================================================
+// Interpolator definitions
+//======================================================================================
+
+//======================================================================================
+// VS only types and UBOs
+//======================================================================================
+
+//======================================================================================
+// VS only functions
+//======================================================================================
diff --git a/gfx/wr/webrender/res/shared_other.glsl b/gfx/wr/webrender/res/shared_other.glsl
new file mode 100644
index 0000000000..03cad173cd
--- /dev/null
+++ b/gfx/wr/webrender/res/shared_other.glsl
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//======================================================================================
+// Vertex shader attributes and uniforms
+//======================================================================================
+#ifdef WR_VERTEX_SHADER
+#endif
+
+//======================================================================================
+// Fragment shader attributes and uniforms
+//======================================================================================
+#ifdef WR_FRAGMENT_SHADER
+#endif
+
+//======================================================================================
+// Interpolator definitions
+//======================================================================================
+
+//======================================================================================
+// VS only types and UBOs
+//======================================================================================
+
+//======================================================================================
+// VS only functions
+//======================================================================================
+
+//======================================================================================
+// FS only functions
+//======================================================================================
+#ifdef WR_FRAGMENT_SHADER
+#endif
diff --git a/gfx/wr/webrender/res/transform.glsl b/gfx/wr/webrender/res/transform.glsl
new file mode 100644
index 0000000000..226f517e88
--- /dev/null
+++ b/gfx/wr/webrender/res/transform.glsl
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+flat varying vec4 vTransformBounds;
+
+#ifdef WR_VERTEX_SHADER
+
+#define VECS_PER_TRANSFORM 8U
+uniform HIGHP_SAMPLER_FLOAT sampler2D sTransformPalette;
+
+void init_transform_vs(vec4 local_bounds) {
+ vTransformBounds = local_bounds;
+}
+
+struct Transform {
+ mat4 m;
+ mat4 inv_m;
+ bool is_axis_aligned;
+};
+
+Transform fetch_transform(int id) {
+ Transform transform;
+
+ transform.is_axis_aligned = (id >> 24) == 0;
+ int index = id & 0x00ffffff;
+
+ // Create a UV base coord for each 8 texels.
+ // This is required because trying to use an offset
+ // of more than 8 texels doesn't work on some versions
+ // of macOS.
+ ivec2 uv = get_fetch_uv(index, VECS_PER_TRANSFORM);
+ ivec2 uv0 = ivec2(uv.x + 0, uv.y);
+
+ transform.m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(0, 0));
+ transform.m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(1, 0));
+ transform.m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(2, 0));
+ transform.m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(3, 0));
+
+ transform.inv_m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(4, 0));
+ transform.inv_m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(5, 0));
+ transform.inv_m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(6, 0));
+ transform.inv_m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(7, 0));
+
+ return transform;
+}
+
+// Return the intersection of the plane (set up by "normal" and "point")
+// with the ray (set up by "ray_origin" and "ray_dir"),
+// writing the resulting scaler into "t".
+bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
+{
+ float denom = dot(normal, ray_dir);
+ if (abs(denom) > 1e-6) {
+ vec3 d = pt - ray_origin;
+ t = dot(d, normal) / denom;
+ return t >= 0.0;
+ }
+
+ return false;
+}
+
+// Apply the inverse transform "inv_transform"
+// to the reference point "ref" in CSS space,
+// producing a local point on a Transform plane,
+// set by a base point "a" and a normal "n".
+vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) {
+ vec3 p = vec3(ref, -10000.0);
+ vec3 d = vec3(0, 0, 1.0);
+
+ float t = 0.0;
+ // get an intersection of the Transform plane with Z axis vector,
+ // originated from the "ref" point
+ ray_plane(n, a, p, d, t);
+ float z = p.z + d.z * t; // Z of the visible point on the Transform
+
+ vec4 r = inv_transform * vec4(ref, z, 1.0);
+ return r;
+}
+
+// Given a CSS space position, transform it back into the Transform space.
+vec4 get_node_pos(vec2 pos, Transform transform) {
+ // get a point on the scroll node plane
+ vec4 ah = transform.m * vec4(0.0, 0.0, 0.0, 1.0);
+ vec3 a = ah.xyz / ah.w;
+
+ // get the normal to the scroll node plane
+ vec3 n = transpose(mat3(transform.inv_m)) * vec3(0.0, 0.0, 1.0);
+ return untransform(pos, n, a, transform.inv_m);
+}
+
+#endif //WR_VERTEX_SHADER
+
+#ifdef WR_FRAGMENT_SHADER
+
+// Assume transform bounds are set to a large scale to signal they are invalid.
+bool has_valid_transform_bounds() {
+ return vTransformBounds.w < 1.0e15;
+}
+
+float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
+ vec2 d = max(p0 - pos, pos - p1);
+ return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
+}
+
+float init_transform_fs(vec2 local_pos) {
+ // Get signed distance from local rect bounds.
+ float d = signed_distance_rect(
+ local_pos,
+ vTransformBounds.xy,
+ vTransformBounds.zw
+ );
+
+ // Find the appropriate distance to apply the AA smoothstep over.
+ float aa_range = compute_aa_range(local_pos);
+
+ // Only apply AA to fragments outside the signed distance field.
+ return distance_aa(aa_range, d);
+}
+
+float init_transform_rough_fs(vec2 local_pos) {
+ return point_inside_rect(
+ local_pos,
+ vTransformBounds.xy,
+ vTransformBounds.zw
+ );
+}
+
+#endif //WR_FRAGMENT_SHADER
diff --git a/gfx/wr/webrender/res/yuv.glsl b/gfx/wr/webrender/res/yuv.glsl
new file mode 100644
index 0000000000..bfcf656b16
--- /dev/null
+++ b/gfx/wr/webrender/res/yuv.glsl
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include shared
+
+#define YUV_FORMAT_NV12 0
+#define YUV_FORMAT_PLANAR 1
+#define YUV_FORMAT_INTERLEAVED 2
+
+#ifdef WR_VERTEX_SHADER
+
+#ifdef WR_FEATURE_TEXTURE_RECT
+ #define TEX_SIZE(sampler) vec2(1.0)
+#else
+ #define TEX_SIZE(sampler) vec2(textureSize(sampler, 0).xy)
+#endif
+
+#define YUV_COLOR_SPACE_REC601 0
+#define YUV_COLOR_SPACE_REC709 1
+#define YUV_COLOR_SPACE_REC2020 2
+#define YUV_COLOR_SPACE_IDENTITY 3
+
+// The constants added to the Y, U and V components are applied in the fragment shader.
+
+// From Rec601:
+// [R] [1.1643835616438356, 0.0, 1.5960267857142858 ] [Y - 16]
+// [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708 ] x [U - 128]
+// [B] [1.1643835616438356, 2.017232142857143, 8.862867620416422e-17] [V - 128]
+//
+// For the range [0,1] instead of [0,255].
+//
+// The matrix is stored in column-major.
+const mat3 YuvColorMatrixRec601 = mat3(
+ 1.16438, 1.16438, 1.16438,
+ 0.0, -0.39176, 2.01723,
+ 1.59603, -0.81297, 0.0
+);
+
+// From Rec709:
+// [R] [1.1643835616438356, 0.0, 1.7927410714285714] [Y - 16]
+// [G] = [1.1643835616438358, -0.21324861427372963, -0.532909328559444 ] x [U - 128]
+// [B] [1.1643835616438356, 2.1124017857142854, 0.0 ] [V - 128]
+//
+// For the range [0,1] instead of [0,255]:
+//
+// The matrix is stored in column-major.
+const mat3 YuvColorMatrixRec709 = mat3(
+ 1.16438, 1.16438, 1.16438,
+ 0.0 , -0.21325, 2.11240,
+ 1.79274, -0.53291, 0.0
+);
+
+// From Re2020:
+// [R] [1.16438356164384, 0.0, 1.678674107142860 ] [Y - 16]
+// [G] = [1.16438356164384, -0.187326104219343, -0.650424318505057 ] x [U - 128]
+// [B] [1.16438356164384, 2.14177232142857, 0.0 ] [V - 128]
+//
+// For the range [0,1] instead of [0,255]:
+//
+// The matrix is stored in column-major.
+const mat3 YuvColorMatrixRec2020 = mat3(
+ 1.16438356164384 , 1.164383561643840, 1.16438356164384,
+ 0.0 , -0.187326104219343, 2.14177232142857,
+ 1.67867410714286 , -0.650424318505057, 0.0
+);
+
+// The matrix is stored in column-major.
+// Identity is stored as GBR
+const mat3 IdentityColorMatrix = mat3(
+ 0.0 , 1.0, 0.0,
+ 0.0 , 0.0, 1.0,
+ 1.0 , 0.0, 0.0
+);
+
+mat3 get_yuv_color_matrix(int color_space) {
+ if (color_space == YUV_COLOR_SPACE_REC601) {
+ return YuvColorMatrixRec601;
+ } else if (color_space == YUV_COLOR_SPACE_REC709) {
+ return YuvColorMatrixRec709;
+ } else if (color_space == YUV_COLOR_SPACE_IDENTITY) {
+ return IdentityColorMatrix;
+ } else {
+ return YuvColorMatrixRec2020;
+ }
+}
+
+vec3 get_yuv_offset_vector(int color_space) {
+ // Float conversion to work around a macOS Intel shader compiler bug
+ if (float(color_space) == float(YUV_COLOR_SPACE_IDENTITY)) {
+ return vec3(0.0, 0.0, 0.0);
+ } else {
+ return vec3(0.06275, 0.50196, 0.50196);
+ }
+}
+
+void write_uv_rect(
+ vec2 uv0,
+ vec2 uv1,
+ vec2 f,
+ vec2 texture_size,
+ out vec2 uv,
+ out vec4 uv_bounds
+) {
+ uv = mix(uv0, uv1, f);
+
+ uv_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5));
+
+ #ifndef WR_FEATURE_TEXTURE_RECT
+ uv /= texture_size;
+ uv_bounds /= texture_size.xyxy;
+ #endif
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+
+vec4 sample_yuv(
+ int format,
+ mat3 yuv_color_matrix,
+ vec3 yuv_offset_vector,
+ float coefficient,
+ vec3 yuv_layers,
+ vec2 in_uv_y,
+ vec2 in_uv_u,
+ vec2 in_uv_v,
+ vec4 uv_bounds_y,
+ vec4 uv_bounds_u,
+ vec4 uv_bounds_v
+) {
+ vec3 yuv_value;
+
+ switch (format) {
+ case YUV_FORMAT_PLANAR:
+ {
+ // The yuv_planar format should have this third texture coordinate.
+ vec2 uv_y = clamp(in_uv_y, uv_bounds_y.xy, uv_bounds_y.zw);
+ vec2 uv_u = clamp(in_uv_u, uv_bounds_u.xy, uv_bounds_u.zw);
+ vec2 uv_v = clamp(in_uv_v, uv_bounds_v.xy, uv_bounds_v.zw);
+ yuv_value.x = TEX_SAMPLE(sColor0, vec3(uv_y, yuv_layers.x)).r;
+ yuv_value.y = TEX_SAMPLE(sColor1, vec3(uv_u, yuv_layers.y)).r;
+ yuv_value.z = TEX_SAMPLE(sColor2, vec3(uv_v, yuv_layers.z)).r;
+ }
+ break;
+
+ case YUV_FORMAT_NV12:
+ {
+ vec2 uv_y = clamp(in_uv_y, uv_bounds_y.xy, uv_bounds_y.zw);
+ vec2 uv_uv = clamp(in_uv_u, uv_bounds_u.xy, uv_bounds_u.zw);
+ yuv_value.x = TEX_SAMPLE(sColor0, vec3(uv_y, yuv_layers.x)).r;
+ yuv_value.yz = TEX_SAMPLE(sColor1, vec3(uv_uv, yuv_layers.y)).rg;
+ }
+ break;
+
+ case YUV_FORMAT_INTERLEAVED:
+ {
+ // "The Y, Cb and Cr color channels within the 422 data are mapped into
+ // the existing green, blue and red color channels."
+ // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+ vec2 uv_y = clamp(in_uv_y, uv_bounds_y.xy, uv_bounds_y.zw);
+ yuv_value = TEX_SAMPLE(sColor0, vec3(uv_y, yuv_layers.x)).gbr;
+ }
+ break;
+
+ default:
+ yuv_value = vec3(0.0);
+ break;
+ }
+
+ // See the YuvColorMatrix definition for an explanation of where the constants come from.
+ vec3 rgb = yuv_color_matrix * (yuv_value * coefficient - yuv_offset_vector);
+ vec4 color = vec4(rgb, 1.0);
+
+ return color;
+}
+#endif