diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /gfx/wr/webrender/res | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
46 files changed, 6983 insertions, 0 deletions
diff --git a/gfx/wr/webrender/res/Proggy.ttf b/gfx/wr/webrender/res/Proggy.ttf Binary files differnew file mode 100644 index 0000000000..308d3e1ac9 --- /dev/null +++ b/gfx/wr/webrender/res/Proggy.ttf diff --git a/gfx/wr/webrender/res/area-lut.tga b/gfx/wr/webrender/res/area-lut.tga Binary files differnew file mode 100644 index 0000000000..5edcddc3d1 --- /dev/null +++ b/gfx/wr/webrender/res/area-lut.tga diff --git a/gfx/wr/webrender/res/base.glsl b/gfx/wr/webrender/res/base.glsl new file mode 100644 index 0000000000..e381ff6ca9 --- /dev/null +++ b/gfx/wr/webrender/res/base.glsl @@ -0,0 +1,70 @@ +/* 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 + // 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 + #if defined(PLATFORM_MACOS) && !defined(SWGL) + // texelFetchOffset introduces a variety of shader compilation bugs on macOS Intel so avoid it. + #define TEXEL_FETCH(sampler, position, lod, offset) texelFetch(sampler, position + offset, lod) + #else + #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset) + #endif +#endif + +#ifdef SWGL + #define SWGL_DRAW_SPAN + #define SWGL_CLIP_MASK + #define SWGL_ANTIALIAS + #define SWGL_BLEND + #define SWGL_CLIP_DIST +#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 + + #if __VERSION__ != 100 + #define varying out + #define attribute in + #endif +#endif + +#ifdef WR_FRAGMENT_SHADER + precision highp float; + #if __VERSION__ != 100 + #define varying in + #endif +#endif + +// Flat interpolation is not supported on ESSL 1 +#if __VERSION__ == 100 + #define flat +#endif diff --git a/gfx/wr/webrender/res/blend.glsl b/gfx/wr/webrender/res/blend.glsl new file mode 100644 index 0000000000..2deed01143 --- /dev/null +++ b/gfx/wr/webrender/res/blend.glsl @@ -0,0 +1,238 @@ +/* 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 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 + +#ifdef WR_VERTEX_SHADER +void SetupFilterParams( + int op, + float amount, + int gpu_data_address, + out vec4 color_offset, + out mat4 color_mat, + out highp int table_address +) { + 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 invAmount = 1.0 - amount; + + if (op == FILTER_GRAYSCALE) { + color_mat = 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) + ); + color_offset = vec4(0.0); + } else if (op == FILTER_HUE_ROTATE) { + float c = cos(amount); + float s = sin(amount); + color_mat = 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) + ); + color_offset = vec4(0.0); + } else if (op == FILTER_SATURATE) { + color_mat = 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) + ); + color_offset = vec4(0.0); + } else if (op == FILTER_SEPIA) { + color_mat = 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) + ); + color_offset = vec4(0.0); + } else if (op == FILTER_COLOR_MATRIX) { + vec4 mat_data[4] = fetch_from_gpu_cache_4(gpu_data_address); + vec4 offset_data = fetch_from_gpu_cache_1(gpu_data_address + 4); + color_mat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]); + color_offset = offset_data; + } else if (op == FILTER_COMPONENT_TRANSFER) { + table_address = gpu_data_address; + } else if (op == FILTER_FLOOD) { + color_offset = fetch_from_gpu_cache_1(gpu_data_address); + } +} +#endif + +#ifdef WR_FRAGMENT_SHADER +vec3 Contrast(vec3 Cs, float amount) { + return clamp(Cs.rgb * amount - 0.5 * amount + 0.5, 0.0, 1.0); +} + +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, vec4 vfuncs, highp int table_address) { + // 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. + + // Both offset and k must be marked as highp due to a Adreno 3xx bug likely + // to do with converting between precisions (as they would otherwise be + // promoted when adding to table_address). + highp int offset = 0; + highp int k; + + vec4 texel; + + // Dynamically indexing a vector is buggy on some platforms, so use a temporary array + int[4] funcs = int[4](int(vfuncs.r), int(vfuncs.g), int(vfuncs.b), int(vfuncs.a)); + for (int i = 0; i < 4; i++) { + switch (funcs[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 + 0.5)); + texel = fetch_from_gpu_cache_1(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(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(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; +} + +void CalculateFilter( + vec4 Cs, + int op, + float amount, + highp int table_address, + vec4 color_offset, + mat4 color_mat, + vec4 v_funcs, + out vec3 color, + out float alpha +) { + // Un-premultiply the input. + alpha = Cs.a; + color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb; + + switch (op) { + case FILTER_CONTRAST: + color = Contrast(color, amount); + break; + case FILTER_INVERT: + color = Invert(color, amount); + break; + case FILTER_BRIGHTNESS: + color = Brightness(color, 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, v_funcs, table_address); + color = colora.rgb; + alpha = colora.a; + break; + } + case FILTER_FLOOD: + color = color_offset.rgb; + alpha = color_offset.a; + break; + default: + // Color matrix type filters (sepia, hue-rotate, etc...) + vec4 result = color_mat * vec4(color, alpha) + color_offset; + result = clamp(result, vec4(0.0), vec4(1.0)); + color = result.rgb; + alpha = result.a; + } +} +#endif diff --git a/gfx/wr/webrender/res/brush.glsl b/gfx/wr/webrender/res/brush.glsl new file mode 100644 index 0000000000..48b2286012 --- /dev/null +++ b/gfx/wr/webrender/res/brush.glsl @@ -0,0 +1,256 @@ +/* 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. +/// + +#if (defined(WR_FEATURE_ALPHA_PASS) || defined(WR_FEATURE_ANTIALIASING)) && !defined(SWGL_ANTIALIAS) +varying vec2 v_local_pos; +#endif + +#ifdef WR_VERTEX_SHADER + +void brush_vs( + VertexInfo vi, + int prim_address, + RectWithEndpoint local_rect, + RectWithEndpoint 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 BRUSH_FLAG_FORCE_AA 256 + +#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 >> 12) & 0xf; + int brush_flags = instance.flags & 0xfff; + + // Fetch the segment of this brush primitive we are drawing. + vec4 segment_data; + RectWithEndpoint 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 = RectWithEndpoint(segment_info[0].xy, segment_info[0].zw); + segment_rect.p0 += ph.local_rect.p0; + segment_rect.p1 += ph.local_rect.p0; + segment_data = segment_info[1]; + } + + // Most of the time this is the segment rect, but when doing the edge AA + // it is inflated. + RectWithEndpoint adjusted_segment_rect = segment_rect; + + bool antialiased = !transform.is_axis_aligned || ((brush_flags & BRUSH_FLAG_FORCE_AA) != 0); + + // Write the normal vertex information out. + if (antialiased) { + adjusted_segment_rect = clip_and_init_antialiasing( + segment_rect, + ph.local_rect, + ph.local_clip_rect, + edge_flags, + ph.z, + transform, + pic_task + ); + + // The clip was taken into account in clip_and_init_antialiasing, remove + // it so that it doesn't interfere with the aa. + ph.local_clip_rect.p0 = vec2(-1.0e16); + ph.local_clip_rect.p1 = vec2(1.0e16); + } else { + // The common case for most CSS content. + + // 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. +#if defined(WR_FEATURE_ALPHA_PASS) && !defined(SWGL_ANTIALIAS) + init_transform_vs(vec4(vec2(-1.0e16), vec2(1.0e16))); +#endif + } + + // Select the corner of the local rect that we are processing. + vec2 local_pos = mix(adjusted_segment_rect.p0, adjusted_segment_rect.p1, aPosition.xy); + + VertexInfo vi = write_vertex( + local_pos, + ph.local_clip_rect, + 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. + // SW-WR may decay some requests for alpha-pass shaders to + // the opaque version if only the clip-mask is required. In + // that case the opaque vertex shader must still write out + // the clip information, which is cheap to do for SWGL. +#if defined(WR_FEATURE_ALPHA_PASS) || defined(SWGL_CLIP_MASK) + 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 + ); + +#if (defined(WR_FEATURE_ALPHA_PASS) || defined(WR_FEATURE_ANTIALIASING)) && !defined(SWGL_ANTIALIAS) + v_local_pos = vi.local_pos; +#endif +} + +#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 + +float antialias_brush() { +#if (defined(WR_FEATURE_ALPHA_PASS) || defined(WR_FEATURE_ANTIALIASING)) && !defined(SWGL_ANTIALIAS) + return init_transform_fs(v_local_pos); +#else + return 1.0; +#endif +} + +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..529267f0e7 --- /dev/null +++ b/gfx/wr/webrender/res/brush_blend.glsl @@ -0,0 +1,119 @@ +/* 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,blend + +// Interpolated UV coordinates to sample. +varying vec2 v_uv; + +// Normalized bounds of the source image in the texture, adjusted to avoid +// sampling artifacts. +flat varying vec4 v_uv_sample_bounds; + +// x: Flag to allow perspective interpolation of UV. +// y: Filter-dependent "amount" parameter. +// Packed in to a vector to work around bug 1630356. +flat varying vec2 v_perspective_amount; +#define v_perspective v_perspective_amount.x +#define v_amount v_perspective_amount.y + +// x: Blend op, y: Lookup table GPU cache address. +// Packed in to a vector to work around bug 1630356. +// Must be explicitly marked as highp, as the default integer precision in +// fragment shaders is mediump which may only be 16 bits in ESSL 3, and GPU +// cache address can exceed that maximum representable value. +flat varying highp ivec2 v_op_table_address_vec; +#define v_op v_op_table_address_vec.x +#define v_table_address v_op_table_address_vec.y + +flat varying mat4 v_color_mat; +// The function to use for each component of a component transfer filter. Using a int[4] +// or ivec4 (with each element or component containing the function for each component) has +// ran in to bugs 1695912 and 1731758, so instead use a vec4 and cast the values to/from floats. +flat varying vec4 v_funcs; +flat varying vec4 v_color_offset; + +#ifdef WR_VERTEX_SHADER +void brush_vs( + VertexInfo vi, + int prim_address, + RectWithEndpoint local_rect, + RectWithEndpoint segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + ImageSource res = fetch_image_source(prim_user_data.x); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 inv_texture_size = vec2(1.0) / vec2(TEX_SIZE(sColor0).xy); + vec2 f = (vi.local_pos - local_rect.p0) / rect_size(local_rect); + 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; + + float amount = float(prim_user_data.z) / 65536.0; + + v_op = prim_user_data.y & 0xffff; + v_amount = amount; + + v_funcs.r = float((prim_user_data.y >> 28) & 0xf); + v_funcs.g = float((prim_user_data.y >> 24) & 0xf); + v_funcs.b = float((prim_user_data.y >> 20) & 0xf); + v_funcs.a = float((prim_user_data.y >> 16) & 0xf); + + SetupFilterParams( + v_op, + amount, + prim_user_data.z, + v_color_offset, + v_color_mat, + v_table_address + ); +} +#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); + + vec4 Cs = texture(sColor0, uv); + + float alpha; + vec3 color; + CalculateFilter( + Cs, + v_op, + v_amount, + v_table_address, + v_color_offset, + v_color_mat, + v_funcs, + color, + alpha + ); + + #ifdef WR_FEATURE_ALPHA_PASS + alpha *= antialias_brush(); + #endif + + // Pre-multiply the alpha into the output value. + return Fragment(alpha * vec4(color, 1.0)); +} +#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..f40be949b9 --- /dev/null +++ b/gfx/wr/webrender/res/brush_image.glsl @@ -0,0 +1,393 @@ +/* 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 + +// 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; + +// Flag to allow perspective interpolation of UV. +// Packed in to vector to work around bug 1630356. +flat varying vec2 v_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, + RectWithEndpoint prim_rect, + RectWithEndpoint 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(TEX_SIZE(sColor0)); +#endif + + ImageSource res = fetch_image_source(specific_resource_address); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + RectWithEndpoint local_rect = prim_rect; + vec2 stretch_size = image_data.stretch_size; + if (stretch_size.x < 0.0) { + stretch_size = rect_size(local_rect); + } + + // 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 = rect_size(local_rect); + + 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.p1.x - segment_rect.p1.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.p1.y - segment_rect.p1.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 segment_rect_width = segment_rect.p1.x - segment_rect.p0.x; + float nx = max(1.0, round(segment_rect_width / stretch_size.x)); + stretch_size.x = segment_rect_width / nx; + } + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND) != 0) { + float segment_rect_height = segment_rect.p1.y - segment_rect.p0.y; + float ny = max(1.0, round(segment_rect_height / stretch_size.y)); + stretch_size.y = segment_rect_height / ny; + } + #endif + } + + float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0; + v_perspective.x = 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) / rect_size(local_rect); + +#ifdef WR_FEATURE_ALPHA_PASS + int color_mode = prim_user_data.x & 0xffff; + int blend_mode = prim_user_data.x >> 16; + + if (color_mode == COLOR_MODE_FROM_PASS) { + color_mode = uMode; + } + +#endif + + // Derive the texture coordinates for this image, based on + // whether the source image is a local-space or screen-space + // image. + int raster_space = prim_user_data.y; + if (raster_space == 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); + } + + // Offset and scale v_uv here to avoid doing it in the fragment shader. + vec2 repeat = rect_size(local_rect) / 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_SHADOW: + #ifdef SWGL_BLEND + swgl_blendDropShadow(image_data.color); + v_mask_swizzle = vec2(1.0, 0.0); + v_color = vec4(1.0); + #else + v_mask_swizzle = vec2(0.0, 1.0); + v_color = image_data.color; + #endif + break; + case COLOR_MODE_SUBPX_BG_PASS2: + case COLOR_MODE_IMAGE: + v_mask_swizzle = vec2(1.0, 0.0); + v_color = image_data.color; + break; + 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; + case COLOR_MODE_SUBPX_DUAL_SOURCE: + v_mask_swizzle = vec2(image_data.color.a, 0.0); + v_color = image_data.color; + break; + case COLOR_MODE_MULTIPLY_DUAL_SOURCE: + v_mask_swizzle = vec2(-image_data.color.a, image_data.color.a); + v_color = image_data.color; + break; + default: + v_mask_swizzle = vec2(0.0); + v_color = vec4(1.0); + } +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +vec2 compute_repeated_uvs(float perspective_divisor) { +#ifdef WR_FEATURE_REPETITION + 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; +#else + return v_uv * perspective_divisor + v_uv_bounds.xy; +#endif +} + +Fragment brush_fs() { + float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective.x); + vec2 repeated_uv = compute_repeated_uvs(perspective_divisor); + + // 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, uv); + + Fragment frag; + +#ifdef WR_FEATURE_ALPHA_PASS + #ifdef WR_FEATURE_ANTIALIASING + float alpha = antialias_brush(); + #else + float alpha = 1.0; + #endif + #ifndef WR_FEATURE_DUAL_SOURCE_BLENDING + texel.rgb = texel.rgb * v_mask_swizzle.x + texel.aaa * v_mask_swizzle.y; + #endif + + vec4 alpha_mask = texel * alpha; + frag.color = v_color * alpha_mask; + + #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + frag.blend = alpha_mask * v_mask_swizzle.x + alpha_mask.aaaa * v_mask_swizzle.y; + #endif +#else + frag.color = texel; +#endif + + return frag; +} + +#if defined(SWGL_DRAW_SPAN) && (!defined(WR_FEATURE_ALPHA_PASS) || !defined(WR_FEATURE_DUAL_SOURCE_BLENDING)) +void swgl_drawSpanRGBA8() { + if (!swgl_isTextureRGBA8(sColor0)) { + return; + } + + #ifdef WR_FEATURE_ALPHA_PASS + if (v_mask_swizzle != vec2(1.0, 0.0)) { + return; + } + #endif + + float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, v_perspective.x); + + #ifdef WR_FEATURE_REPETITION + // Get the UVs before any repetition, scaling, or offsetting has occurred... + vec2 uv = v_uv * perspective_divisor; + #else + vec2 uv = compute_repeated_uvs(perspective_divisor); + #endif + + #ifdef WR_FEATURE_ALPHA_PASS + if (v_color != vec4(1.0)) { + #ifdef WR_FEATURE_REPETITION + swgl_commitTextureRepeatColorRGBA8(sColor0, uv, v_tile_repeat, v_uv_bounds, v_uv_sample_bounds, v_color); + #else + swgl_commitTextureColorRGBA8(sColor0, uv, v_uv_sample_bounds, v_color); + #endif + return; + } + // No color scaling required, so just fall through to a normal textured span... + #endif + + #ifdef WR_FEATURE_REPETITION + #ifdef WR_FEATURE_ALPHA_PASS + swgl_commitTextureRepeatRGBA8(sColor0, uv, v_tile_repeat, v_uv_bounds, v_uv_sample_bounds); + #else + swgl_commitTextureRepeatRGBA8(sColor0, uv, vec2(0.0), v_uv_bounds, v_uv_sample_bounds); + #endif + #else + swgl_commitTextureRGBA8(sColor0, uv, v_uv_sample_bounds); + #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..2c2e9a3a24 --- /dev/null +++ b/gfx/wr/webrender/res/brush_linear_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,gpu_buffer,gradient_shared + +// Start offset. Packed in to vector to work around bug 1630356. +flat varying vec2 v_start_offset; + +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, + RectWithEndpoint local_rect, + RectWithEndpoint 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; + + // Normalize UV and offsets to 0..1 scale. + v_scale_dir = dir / dot(dir, dir); + v_start_offset.x = dot(start_point, v_scale_dir); + v_scale_dir *= v_repeated_size; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +float get_gradient_offset(vec2 pos) { + // Project position onto a direction vector to compute offset. + return dot(pos, v_scale_dir) - v_start_offset.x; +} + +Fragment brush_fs() { + vec4 color = sample_gradient(get_gradient_offset(compute_repeated_pos())); + +#ifdef WR_FEATURE_ALPHA_PASS + color *= antialias_brush(); +#endif + + return Fragment(color); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { + int address = swgl_validateGradient(sGpuBuffer, get_gpu_buffer_uv(v_gradient_address.x), int(GRADIENT_ENTRIES + 2.0)); + if (address < 0) { + return; + } + + swgl_commitLinearGradientRGBA8(sGpuBuffer, address, GRADIENT_ENTRIES, true, v_gradient_repeat.x != 0.0, + v_pos, v_scale_dir, v_start_offset.x); +} +#endif + +#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..363f383667 --- /dev/null +++ b/gfx/wr/webrender/res/brush_mix_blend.glsl @@ -0,0 +1,332 @@ +/* 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 + +// UV and bounds for the source image +varying vec2 v_src_uv; +flat varying vec4 v_src_uv_sample_bounds; + +// UV and bounds for the backdrop image +varying vec2 v_backdrop_uv; +flat varying vec4 v_backdrop_uv_sample_bounds; + +// Flag to allow perspective interpolation of UV. +// Packed in to vector to work around bug 1630356. +flat varying vec2 v_perspective; +// mix-blend op. Packed in to vector to work around bug 1630356. +flat varying ivec2 v_op; + +#ifdef WR_VERTEX_SHADER + +void get_uv( + int res_address, + vec2 f, + ivec2 texture_size, + float perspective_f, + out vec2 out_uv, + out vec4 out_uv_sample_bounds +) { + ImageSource res = fetch_image_source(res_address); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 inv_texture_size = vec2(1.0) / vec2(texture_size); + f = get_image_quad_uv(res_address, f); + vec2 uv = mix(uv0, uv1, f); + + out_uv = uv * inv_texture_size * perspective_f; + out_uv_sample_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) * inv_texture_size.xyxy; +} + +void brush_vs( + VertexInfo vi, + int prim_address, + RectWithEndpoint local_rect, + RectWithEndpoint 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) / rect_size(local_rect); + float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0; + float perspective_f = mix(vi.world_pos.w, 1.0, perspective_interpolate); + v_perspective.x = perspective_interpolate; + v_op.x = prim_user_data.x; + + get_uv( + prim_user_data.y, + f, + TEX_SIZE(sColor0).xy, + 1.0, + v_backdrop_uv, + v_backdrop_uv_sample_bounds + ); + + get_uv( + prim_user_data.z, + f, + TEX_SIZE(sColor1).xy, + perspective_f, + v_src_uv, + v_src_uv_sample_bounds + ); +} +#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); +} + +// 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; +const int MixBlendMode_PlusLighter = 16; + +Fragment brush_fs() { + float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective.x); + + vec2 src_uv = v_src_uv * perspective_divisor; + src_uv = clamp(src_uv, v_src_uv_sample_bounds.xy, v_src_uv_sample_bounds.zw); + + vec2 backdrop_uv = clamp(v_backdrop_uv, v_backdrop_uv_sample_bounds.xy, v_backdrop_uv_sample_bounds.zw); + + vec4 Cb = texture(sColor0, backdrop_uv); + vec4 Cs = texture(sColor1, 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); + + // On Android v_op has been packed in to a vector to avoid a driver bug + // on Adreno 3xx. However, this runs in to another Adreno 3xx driver bug + // where the switch doesn't match any cases. Unpacking the value from the + // vec in to a local variable prior to the switch works around this, but + // gets optimized away by glslopt. Adding a bitwise AND prevents that. + // See bug 1726755. + // default: default: to appease angle_shader_validation + switch (v_op.x & 0xFF) { + case MixBlendMode_Multiply: + result.rgb = Multiply(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_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; + case MixBlendMode_Screen: + case MixBlendMode_Exclusion: + case MixBlendMode_PlusLighter: + // This should be unreachable, since we implement + // MixBlendMode::Screen, MixBlendMode::Exclusion and + // MixBlendMode::PlusLighter using glBlendFuncSeparate. + 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..e6b30af69b --- /dev/null +++ b/gfx/wr/webrender/res/brush_opacity.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/. */ + +#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; + +// Normalized bounds of the source image in the texture, adjusted to avoid +// sampling artifacts. +flat varying vec4 v_uv_sample_bounds; + +flat varying vec2 v_opacity_perspective_vec; +#define v_opacity v_opacity_perspective_vec.x +// Flag to allow perspective interpolation of UV. +#define v_perspective v_opacity_perspective_vec.y + +#ifdef WR_VERTEX_SHADER +void brush_vs( + VertexInfo vi, + int prim_address, + RectWithEndpoint local_rect, + RectWithEndpoint segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + ImageSource res = fetch_image_source(prim_user_data.x); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 texture_size = vec2(TEX_SIZE(sColor0).xy); + vec2 f = (vi.local_pos - local_rect.p0) / rect_size(local_rect); + 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; + + v_opacity = clamp(float(prim_user_data.y) / 65536.0, 0.0, 1.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_ALPHA_PASS + alpha *= antialias_brush(); + #endif + + // Pre-multiply the contribution of the opacity factor. + return Fragment(alpha * color); +} + +#if defined(SWGL_DRAW_SPAN) && !defined(WR_FEATURE_DUAL_SOURCE_BLENDING) +void swgl_drawSpanRGBA8() { + float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, v_perspective); + vec2 uv = v_uv * perspective_divisor; + + swgl_commitTextureLinearColorRGBA8(sColor0, uv, v_uv_sample_bounds, v_opacity); +} +#endif + +#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..588ed3ac45 --- /dev/null +++ b/gfx/wr/webrender/res/brush_solid.glsl @@ -0,0 +1,60 @@ +/* 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_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, + RectWithEndpoint local_rect, + RectWithEndpoint 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; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +Fragment brush_fs() { + vec4 color = v_color; +#ifdef WR_FEATURE_ALPHA_PASS + color *= antialias_brush(); +#endif + return Fragment(color); +} + +#if defined(SWGL_DRAW_SPAN) && (!defined(WR_FEATURE_ALPHA_PASS) || !defined(WR_FEATURE_DUAL_SOURCE_BLENDING)) +void swgl_drawSpanRGBA8() { + swgl_commitSolidRGBA8(v_color); +} + +void swgl_drawSpanR8() { + 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..28abcab66f --- /dev/null +++ b/gfx/wr/webrender/res/brush_yuv_image.glsl @@ -0,0 +1,140 @@ +/* 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 + +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; + +YUV_PRECISION flat varying vec3 vYcbcrBias; +YUV_PRECISION flat varying mat3 vRgbFromDebiasedYcbcr; + +// YUV format. Packed in to vector to work around bug 1630356. +flat varying ivec2 vFormat; + +#ifdef SWGL_DRAW_SPAN +flat varying int vRescaleFactor; +#endif + +#ifdef WR_VERTEX_SHADER + +YuvPrimitive fetch_yuv_primitive(int address) { + vec4 data = fetch_from_gpu_cache_1(address); + // From YuvImageData.write_prim_gpu_blocks: + int channel_bit_depth = int(data.x); + int color_space = int(data.y); + int yuv_format = int(data.z); + return YuvPrimitive(channel_bit_depth, color_space, yuv_format); +} + +void brush_vs( + VertexInfo vi, + int prim_address, + RectWithEndpoint local_rect, + RectWithEndpoint 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) / rect_size(local_rect); + + YuvPrimitive prim = fetch_yuv_primitive(prim_address); + +#ifdef SWGL_DRAW_SPAN + // 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. Note that MSB HDR formats don't need renormalization. + vRescaleFactor = 0; + if (prim.channel_bit_depth > 8 && prim.yuv_format != YUV_FORMAT_P010) { + vRescaleFactor = 16 - prim.channel_bit_depth; + } +#endif + + YuvColorMatrixInfo mat_info = get_rgb_from_ycbcr_info(prim); + vYcbcrBias = mat_info.ycbcr_bias; + vRgbFromDebiasedYcbcr = mat_info.rgb_from_debiased_ycbrc; + + vFormat.x = prim.yuv_format; + + // The additional test for 99 works around a gen6 shader compiler bug: 1708937 + if (vFormat.x == YUV_FORMAT_PLANAR || vFormat.x == 99) { + ImageSource res_y = fetch_image_source(prim_user_data.x); + ImageSource res_u = fetch_image_source(prim_user_data.y); + ImageSource res_v = fetch_image_source(prim_user_data.z); + write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE_YUV(sColor0), vUv_Y, vUvBounds_Y); + write_uv_rect(res_u.uv_rect.p0, res_u.uv_rect.p1, f, TEX_SIZE_YUV(sColor1), vUv_U, vUvBounds_U); + write_uv_rect(res_v.uv_rect.p0, res_v.uv_rect.p1, f, TEX_SIZE_YUV(sColor2), vUv_V, vUvBounds_V); + } else if (vFormat.x == YUV_FORMAT_NV12 || vFormat.x == YUV_FORMAT_P010) { + ImageSource res_y = fetch_image_source(prim_user_data.x); + ImageSource res_u = fetch_image_source(prim_user_data.y); + write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE_YUV(sColor0), vUv_Y, vUvBounds_Y); + write_uv_rect(res_u.uv_rect.p0, res_u.uv_rect.p1, f, TEX_SIZE_YUV(sColor1), vUv_U, vUvBounds_U); + } else if (vFormat.x == YUV_FORMAT_INTERLEAVED) { + ImageSource res_y = fetch_image_source(prim_user_data.x); + write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE_YUV(sColor0), vUv_Y, vUvBounds_Y); + } +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +Fragment brush_fs() { + vec4 color = sample_yuv( + vFormat.x, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vUv_Y, + vUv_U, + vUv_V, + vUvBounds_Y, + vUvBounds_U, + vUvBounds_V + ); + +#ifdef WR_FEATURE_ALPHA_PASS + color *= antialias_brush(); +#endif + + //color.r = float(100+vFormat) / 255.0; + //color.g = vYcbcrBias.x; + //color.b = vYcbcrBias.y; + return Fragment(color); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { + if (vFormat.x == YUV_FORMAT_PLANAR) { + swgl_commitTextureLinearYUV(sColor0, vUv_Y, vUvBounds_Y, + sColor1, vUv_U, vUvBounds_U, + sColor2, vUv_V, vUvBounds_V, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vRescaleFactor); + } else if (vFormat.x == YUV_FORMAT_NV12 || vFormat.x == YUV_FORMAT_P010) { + swgl_commitTextureLinearYUV(sColor0, vUv_Y, vUvBounds_Y, + sColor1, vUv_U, vUvBounds_U, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vRescaleFactor); + } else if (vFormat.x == YUV_FORMAT_INTERLEAVED) { + swgl_commitTextureLinearYUV(sColor0, vUv_Y, vUvBounds_Y, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vRescaleFactor); + } +} +#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..ef28bfde22 --- /dev/null +++ b/gfx/wr/webrender/res/clip_shared.glsl @@ -0,0 +1,80 @@ +/* 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 { + RectWithEndpoint 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 = RectWithEndpoint(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; + RectWithEndpoint clipped_local_rect; +}; + +// 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(RectWithEndpoint local_clip_rect, + Transform prim_transform, + Transform clip_transform, + RectWithEndpoint sub_rect, + vec2 task_origin, + vec2 screen_origin, + float device_pixel_scale) { + vec2 device_pos = screen_origin + mix(sub_rect.p0, sub_rect.p1, aPosition.xy); + 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 + mix(sub_rect.p0, sub_rect.p1, aPosition.xy), + 0.0, + 1.0 + ); + + gl_Position = uTransform * vertex_pos; + + init_transform_vs(vec4(local_clip_rect.p0, local_clip_rect.p1)); + + 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..dbd799f129 --- /dev/null +++ b/gfx/wr/webrender/res/composite.glsl @@ -0,0 +1,242 @@ +/* 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. + +// This shader must remain compatible with ESSL 1, at least for the +// WR_FEATURE_TEXTURE_EXTERNAL_ESSL1 feature, so that it can be used to render +// video on GLES devices without GL_OES_EGL_image_external_essl3 support. +// This means we cannot use textureSize(), int inputs/outputs, etc. + +#include shared + +#ifdef WR_FEATURE_YUV +#include yuv +#endif + +#ifdef WR_FEATURE_YUV +YUV_PRECISION flat varying vec3 vYcbcrBias; +YUV_PRECISION flat varying mat3 vRgbFromDebiasedYcbcr; +// YUV format. Packed in to vector to avoid bug 1630356. +flat varying ivec2 vYuvFormat; + +#ifdef SWGL_DRAW_SPAN +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 +varying vec2 vUv; +#ifndef WR_FEATURE_FAST_PATH +flat varying vec4 vColor; +flat varying vec4 vUVBounds; +#endif +#ifdef WR_FEATURE_TEXTURE_EXTERNAL_ESSL1 +uniform vec2 uTextureSize; +#endif +#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 attribute vec4 aLocalRect; +PER_INSTANCE attribute vec4 aDeviceClipRect; +PER_INSTANCE attribute vec4 aColor; +PER_INSTANCE attribute vec4 aParams; +PER_INSTANCE attribute vec4 aTransform; + +#ifdef WR_FEATURE_YUV +// YUV treats these as a UV clip rect (clamp) +PER_INSTANCE attribute vec4 aUvRect0; +PER_INSTANCE attribute vec4 aUvRect1; +PER_INSTANCE attribute vec4 aUvRect2; +#else +PER_INSTANCE attribute vec4 aUvRect0; +#endif + +vec2 apply_transform(vec2 p, vec4 transform) { + return p * transform.xy + transform.zw; +} + +#ifdef WR_FEATURE_YUV +YuvPrimitive fetch_yuv_primitive() { + // From ExternalSurfaceDependency::Yuv: + int color_space = int(aParams.y); + int yuv_format = int(aParams.z); + int channel_bit_depth = int(aParams.w); + return YuvPrimitive(channel_bit_depth, color_space, yuv_format); +} +#endif + +void main(void) { + // Get world position + vec2 world_p0 = apply_transform(aLocalRect.xy, aTransform); + vec2 world_p1 = apply_transform(aLocalRect.zw, aTransform); + vec2 world_pos = mix(world_p0, world_p1, aPosition.xy); + + // Clip the position to the world space clip rect + vec2 clipped_world_pos = clamp(world_pos, aDeviceClipRect.xy, aDeviceClipRect.zw); + + // Derive the normalized UV from the clipped vertex position + vec2 uv = (clipped_world_pos - world_p0) / (world_p1 - world_p0); + +#ifdef WR_FEATURE_YUV + YuvPrimitive prim = fetch_yuv_primitive(); + +#ifdef SWGL_DRAW_SPAN + // 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. Note that MSB HDR formats don't need renormalization. + vRescaleFactor = 0; + if (prim.channel_bit_depth > 8 && prim.yuv_format != YUV_FORMAT_P010) { + vRescaleFactor = 16 - prim.channel_bit_depth; + } +#endif + + YuvColorMatrixInfo mat_info = get_rgb_from_ycbcr_info(prim); + vYcbcrBias = mat_info.ycbcr_bias; + vRgbFromDebiasedYcbcr = mat_info.rgb_from_debiased_ycbrc; + + vYuvFormat.x = prim.yuv_format; + + write_uv_rect( + aUvRect0.xy, + aUvRect0.zw, + uv, + TEX_SIZE_YUV(sColor0), + vUV_y, + vUVBounds_y + ); + write_uv_rect( + aUvRect1.xy, + aUvRect1.zw, + uv, + TEX_SIZE_YUV(sColor1), + vUV_u, + vUVBounds_u + ); + write_uv_rect( + aUvRect2.xy, + aUvRect2.zw, + uv, + TEX_SIZE_YUV(sColor2), + vUV_v, + vUVBounds_v + ); +#else + uv = mix(aUvRect0.xy, aUvRect0.zw, uv); + // The uvs may be inverted, so use the min and max for the bounds + vec4 uvBounds = vec4(min(aUvRect0.xy, aUvRect0.zw), max(aUvRect0.xy, aUvRect0.zw)); + 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. +#if defined(WR_FEATURE_TEXTURE_RECT) + vec2 texture_size = vec2(1.0, 1.0); +#elif defined(WR_FEATURE_TEXTURE_EXTERNAL_ESSL1) + vec2 texture_size = uTextureSize; +#else + vec2 texture_size = vec2(TEX_SIZE(sColor0)); +#endif + uvBounds += vec4(0.5, 0.5, -0.5, -0.5); + #ifndef WR_FEATURE_TEXTURE_RECT + uv /= texture_size; + uvBounds /= texture_size.xyxy; + #endif + } + + vUv = uv; +#ifndef WR_FEATURE_FAST_PATH + vUVBounds = uvBounds; + // Pass through color + vColor = aColor; +#endif +#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.x, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + 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. + // In the fast path we avoid clamping the UV coordinates and modulating by the color. +#ifdef WR_FEATURE_FAST_PATH + vec2 uv = vUv; +#else + vec2 uv = clamp(vUv, vUVBounds.xy, vUVBounds.zw); +#endif + vec4 texel = TEX_SAMPLE(sColor0, uv); +#ifdef WR_FEATURE_FAST_PATH + vec4 color = texel; +#else + vec4 color = vColor * texel; +#endif +#endif + write_output(color); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { +#ifdef WR_FEATURE_YUV + if (vYuvFormat.x == YUV_FORMAT_PLANAR) { + swgl_commitTextureLinearYUV(sColor0, vUV_y, vUVBounds_y, + sColor1, vUV_u, vUVBounds_u, + sColor2, vUV_v, vUVBounds_v, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vRescaleFactor); + } else if (vYuvFormat.x == YUV_FORMAT_NV12 || vYuvFormat.x == YUV_FORMAT_P010) { + swgl_commitTextureLinearYUV(sColor0, vUV_y, vUVBounds_y, + sColor1, vUV_u, vUVBounds_u, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vRescaleFactor); + } else if (vYuvFormat.x == YUV_FORMAT_INTERLEAVED) { + swgl_commitTextureLinearYUV(sColor0, vUV_y, vUVBounds_y, + vYcbcrBias, + vRgbFromDebiasedYcbcr, + vRescaleFactor); + } +#else +#ifdef WR_FEATURE_FAST_PATH + vec4 color = vec4(1.0); +#ifdef WR_FEATURE_TEXTURE_RECT + vec4 uvBounds = vec4(vec2(0.0), vec2(textureSize(sColor0))); +#else + vec4 uvBounds = vec4(0.0, 0.0, 1.0, 1.0); +#endif +#else + vec4 color = vColor; + vec4 uvBounds = vUVBounds; +#endif + if (color != vec4(1.0)) { + swgl_commitTextureColorRGBA8(sColor0, vUv, uvBounds, color); + } else { + swgl_commitTextureRGBA8(sColor0, vUv, uvBounds); + } +#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..f29e3c4bf4 --- /dev/null +++ b/gfx/wr/webrender/res/cs_blur.glsl @@ -0,0 +1,196 @@ +/* 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. +// Packed in to vector to work around bug 1630356. +flat varying ivec2 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 { + RectWithEndpoint task_rect; + 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.task_rect, + 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.x; 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); + RectWithEndpoint src_rect = fetch_render_task_rect(aBlurSourceTaskAddress); + + RectWithEndpoint target_rect = blur_task.task_rect; + + vec2 texture_size = vec2(TEX_SIZE(sColor0).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.x = int(ceil(1.5 * blur_task.blur_radius)) * 2; + + if (vSupport.x > 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 = mix(target_rect.p0, target_rect.p1, aPosition.xy); + + vec2 uv0 = src_rect.p0 / texture_size; + vec2 uv1 = src_rect.p1 / 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. + // + // Clamp loop condition variable to a statically known value to workaround + // driver bug on Adreno 3xx. vSupport should not exceed 300 anyway, due to + // the max blur radius being 100. See bug 1720841 for details. + int support = min(vSupport.x, 300); + for (int i = 1; i <= support; 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_DRAW_SPAN + #ifdef WR_FEATURE_COLOR_TARGET +void swgl_drawSpanRGBA8() { + swgl_commitGaussianBlurRGBA8(sColor0, vUv, vUvRect, vOffsetScale.x != 0.0, + vSupport.x, vGaussCoefficients); +} + #else +void swgl_drawSpanR8() { + swgl_commitGaussianBlurR8(sColor0, vUv, vUvRect, vOffsetScale.x != 0.0, + vSupport.x, vGaussCoefficients); +} + #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..66c7d2d798 --- /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,rect,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: clip mode +// We cast these to/from floats rather than using an ivec due to a driver bug +// on Adreno 3xx. See bug 1730458. +flat varying mediump vec2 vSegmentClipMode; +// x, y: styles, z, w: edge axes +// We cast these to/from floats rather than using an ivec (and bitshifting) +// due to a driver bug on Adreno 3xx. See bug 1730458. +flat varying mediump vec4 vStyleEdgeAxis; + +// 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 size = aRect.zw - aRect.xy; + vec2 outer_scale = get_outer_corner_scale(segment); + vec2 outer = outer_scale * size; + 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; + } + + vSegmentClipMode = vec2(float(segment), float(clip_mode)); + vStyleEdgeAxis = vec4(float(style0), float(style1), float(edge_axis.x), float(edge_axis.y)); + + vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0); + vPos = size * 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), size); + } 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 = int(vSegmentClipMode.x); + int clip_mode = int(vSegmentClipMode.y); + ivec2 style = ivec2(int(vStyleEdgeAxis.x), int(vStyleEdgeAxis.y)); + ivec2 edge_axis = ivec2(int(vStyleEdgeAxis.z), int(vStyleEdgeAxis.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..ff7ac2f780 --- /dev/null +++ b/gfx/wr/webrender/res/cs_border_solid.glsl @@ -0,0 +1,178 @@ +/* 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,rect,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. +// Packed in to a vector to work around bug 1630356. +flat varying ivec2 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 size = aRect.zw - aRect.xy; + vec2 outer = outer_scale * size; + 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.x = mix_colors; + vPos = size * 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.x != MIX_NO_AA; + + float mix_factor = 0.0; + if (vMixColors.x != 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..8087098f4c --- /dev/null +++ b/gfx/wr/webrender/res/cs_clip_box_shadow.glsl @@ -0,0 +1,327 @@ +/* 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 vec4 vEdge; +flat varying vec4 vUvBounds_NoClamp; +// Clip mode. Packed in to a vector to avoid bug 1630356. +flat varying vec2 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 base; + ivec2 resource_address; +}; + +ClipMaskInstanceBoxShadow fetch_clip_item() { + ClipMaskInstanceBoxShadow cmi; + + cmi.base = 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; + RectWithEndpoint dest_rect; +}; + +BoxShadowData fetch_data() { + BoxShadowData bs_data = BoxShadowData( + aClipSrcRectSize, + aClipMode, + aStretchMode.x, + aStretchMode.y, + RectWithEndpoint(aClipDestRect.xy, aClipDestRect.zw) + ); + return bs_data; +} + +void main(void) { + ClipMaskInstanceBoxShadow cmi = fetch_clip_item(); + Transform clip_transform = fetch_transform(cmi.base.clip_transform_id); + Transform prim_transform = fetch_transform(cmi.base.prim_transform_id); + BoxShadowData bs_data = fetch_data(); + ImageSource res = fetch_image_source_direct(cmi.resource_address); + + RectWithEndpoint dest_rect = bs_data.dest_rect; + + ClipVertexInfo vi = write_clip_tile_vertex( + dest_rect, + prim_transform, + clip_transform, + cmi.base.sub_rect, + cmi.base.task_origin, + cmi.base.screen_origin, + cmi.base.device_pixel_scale + ); + vClipMode.x = float(bs_data.clip_mode); + + vec2 texture_size = vec2(TEX_SIZE(sColor0)); + vec2 local_pos = vi.local_pos.xy / vi.local_pos.w; + vLocalPos = vi.local_pos; + vec2 dest_rect_size = rect_size(dest_rect); + + 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, uv).r; + + float alpha = mix(texel, 1.0 - texel, vClipMode.x); + float result = vLocalPos.w > 0.0 ? mix(vClipMode.x, alpha, in_shadow_rect) : 0.0; + + oFragColor = vec4(result); +} + +#ifdef SWGL_DRAW_SPAN +// As with cs_clip_rectangle, this shader spends a lot of time doing clipping and +// combining for every fragment, even if outside of the primitive to initialize +// the clip tile, or inside the inner bounds of the primitive, where the shadow +// is unnecessary. To alleviate this, the span shader attempts to first intersect +// the the local clip bounds, outside of which we can just use a solid fill +// to initialize those clip tile fragments. Once inside the primitive bounds, +// we further intersect with the inner region where no shadow is necessary either +// so that we can commit entire spans of texture within this nine-patch region +// instead of having to do the work of mapping per fragment. +void swgl_drawSpanR8() { + // Perspective is not supported. + if (swgl_interpStep(vLocalPos).w != 0.0) { + return; + } + + // If the span is completely outside the Z-range and clipped out, just + // output clear so we don't need to consider invalid W in the rest of the + // shader. + float w = swgl_forceScalar(vLocalPos.w); + if (w <= 0.0) { + swgl_commitSolidR8(0.0); + return; + } + + // To start, we evaluate the box shadow in both UV and local space relative + // to the local-space position. This will be interpolated across the span to + // track whether we intersect the nine-patch. + w = 1.0 / w; + vec2 uv_linear = vUv * w; + vec2 uv_linear0 = swgl_forceScalar(uv_linear); + vec2 uv_linear_step = swgl_interpStep(vUv).xy * w; + vec2 local_pos = vLocalPos.xy * w; + vec2 local_pos0 = swgl_forceScalar(local_pos); + vec2 local_step = swgl_interpStep(vLocalPos).xy * w; + + // We need to compute the local-space distance to the bounding box and then + // figure out how many processing steps that maps to. If we are stepping in + // a negative direction on an axis, we need to swap the sides of the box + // which we consider as the start or end. If there is no local-space step + // on an axis (i.e. constant Y), we need to take care to force the steps to + // either the start or end of the span depending on if we are inside or + // outside of the bounding box. + vec4 clip_dist = + mix(vTransformBounds, vTransformBounds.zwxy, lessThan(local_step, vec2(0.0)).xyxy) + - local_pos0.xyxy; + clip_dist = + mix(1.0e6 * step(0.0, clip_dist), + clip_dist * recip(local_step).xyxy, + notEqual(local_step, vec2(0.0)).xyxy); + + // Find the start and end of the shadowed region on this span. + float shadow_start = max(clip_dist.x, clip_dist.y); + float shadow_end = min(clip_dist.z, clip_dist.w); + + // Flip the offsets from the start of the span so we can compare against the + // remaining span length which automatically deducts as we commit fragments. + ivec2 shadow_steps = ivec2(clamp( + swgl_SpanLength - swgl_StepSize * vec2(floor(shadow_start), ceil(shadow_end)), + 0.0, swgl_SpanLength)); + int shadow_start_len = shadow_steps.x; + int shadow_end_len = shadow_steps.y; + + // Likewise, once inside the primitive bounds, we also need to track which + // sector of the nine-patch we are in which requires intersecting against + // the inner box instead of the outer box. + vec4 opaque_dist = + mix(vEdge, vEdge.zwxy, lessThan(uv_linear_step, vec2(0.0)).xyxy) + - uv_linear0.xyxy; + opaque_dist = + mix(1.0e6 * step(0.0, opaque_dist), + opaque_dist * recip(uv_linear_step).xyxy, + notEqual(uv_linear_step, vec2(0.0)).xyxy); + + // Unlike for the shadow clipping bounds, here we need to rather find the floor of all + // the offsets so that we don't accidentally process any chunks in the transitional areas + // between sectors of the nine-patch. + ivec4 opaque_steps = ivec4(clamp( + swgl_SpanLength - + swgl_StepSize * + vec4(floor(opaque_dist.x), floor(opaque_dist.y), floor(opaque_dist.z), floor(opaque_dist.w)), + shadow_end_len, swgl_SpanLength)); + + // Fill any initial sections of the span that are clipped out based on clip mode. + if (swgl_SpanLength > shadow_start_len) { + int num_before = swgl_SpanLength - shadow_start_len; + swgl_commitPartialSolidR8(num_before, vClipMode.x); + float steps_before = float(num_before / swgl_StepSize); + uv_linear += steps_before * uv_linear_step; + local_pos += steps_before * local_step; + } + + // This loop tries to repeatedly process entire spans of the nine-patch that map + // to a contiguous spans of texture in the source box shadow. First, we process + // a chunk with per-fragment clipping and mapping in case we're starting on a + // transitional region between sectors of the nine-patch which may need to map + // to different spans of texture per-fragment. After, we find the largest span + // within the current sector before we hit the next transitional region, and + // attempt to commit an entire span of texture therein. + while (swgl_SpanLength > 0) { + // Here we might be in a transitional chunk, so do everything per-fragment. + { + 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(local_pos); + + float texel = TEX_SAMPLE(sColor0, uv).r; + + float alpha = mix(texel, 1.0 - texel, vClipMode.x); + float result = mix(vClipMode.x, alpha, in_shadow_rect); + swgl_commitColorR8(result); + + uv_linear += uv_linear_step; + local_pos += local_step; + } + // If we now hit the end of the clip bounds, just bail out since there is + // no more shadow to map. + if (swgl_SpanLength <= shadow_end_len) { + break; + } + // By here we've determined to be still inside the nine-patch. We need to + // compare against the inner rectangle thresholds to see which sector of + // the nine-patch to use and thus how to map the box shadow texture. Stop + // at least one step before the end of the shadow region to properly clip + // on the boundary. + int num_inside = swgl_SpanLength - swgl_StepSize - shadow_end_len; + vec4 uv_bounds = vUvBounds; + if (swgl_SpanLength >= opaque_steps.y) { + // We're in the top Y band of the nine-patch. + num_inside = min(num_inside, swgl_SpanLength - opaque_steps.y); + } else if (swgl_SpanLength >= opaque_steps.w) { + // We're in the middle Y band of the nine-patch. Set the UV clamp bounds + // to the vertical center texel of the box shadow. + num_inside = min(num_inside, swgl_SpanLength - opaque_steps.w); + uv_bounds.yw = vec2(clamp(mix(vUvBounds_NoClamp.y, vUvBounds_NoClamp.w, vEdge.y), + vUvBounds.y, vUvBounds.w)); + } + if (swgl_SpanLength >= opaque_steps.x) { + // We're in the left X column of the nine-patch. + num_inside = min(num_inside, swgl_SpanLength - opaque_steps.x); + } else if (swgl_SpanLength >= opaque_steps.z) { + // We're in the middle X band of the nine-patch. Set the UV clamp bounds + // to the horizontal center texel of the box shadow. + num_inside = min(num_inside, swgl_SpanLength - opaque_steps.z); + uv_bounds.xz = vec2(clamp(mix(vUvBounds_NoClamp.x, vUvBounds_NoClamp.z, vEdge.x), + vUvBounds.x, vUvBounds.z)); + } + if (num_inside > 0) { + // We have a non-zero span of fragments within the sector. Map to the UV + // start offset of the sector and the UV offset within the sector. + 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); + // If we're in the center sector of the nine-patch, then we only need to + // sample from a single texel of the box shadow. Just sample that single + // texel once and output it for the entire span. Otherwise, we just need + // to commit an actual span of texture from the box shadow. Depending on + // if we are in clip-out mode, we may need to invert the source texture. + if (uv_bounds.xy == uv_bounds.zw) { + uv = clamp(uv, uv_bounds.xy, uv_bounds.zw); + float texel = TEX_SAMPLE(sColor0, uv).r; + float alpha = mix(texel, 1.0 - texel, vClipMode.x); + swgl_commitPartialSolidR8(num_inside, alpha); + } else if (vClipMode.x != 0.0) { + swgl_commitPartialTextureLinearInvertR8(num_inside, sColor0, uv, uv_bounds); + } else { + swgl_commitPartialTextureLinearR8(num_inside, sColor0, uv, uv_bounds); + } + float steps_inside = float(num_inside / swgl_StepSize); + uv_linear += steps_inside * uv_linear_step; + local_pos += steps_inside * local_step; + } + // By here we're probably in a transitional chunk of the nine-patch that + // requires per-fragment processing, so loop around again to the handler + // for that case. + } + + // Fill any remaining sections of the span that are clipped out. + if (swgl_SpanLength > 0) { + swgl_commitPartialSolidR8(swgl_SpanLength, vClipMode.x); + } +} +#endif + +#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..94d5d87cbe --- /dev/null +++ b/gfx/wr/webrender/res/cs_clip_image.glsl @@ -0,0 +1,117 @@ +/* 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 vec2 vLocalPos; +varying vec2 vClipMaskImageUv; + +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 base; + RectWithEndpoint tile_rect; + ivec2 resource_address; + RectWithEndpoint local_rect; +}; + +ClipMaskInstanceImage fetch_clip_item() { + ClipMaskInstanceImage cmi; + + cmi.base = fetch_clip_item_common(); + + cmi.tile_rect = RectWithEndpoint(aClipTileRect.xy, aClipTileRect.zw); + cmi.resource_address = aClipDataResourceAddress; + cmi.local_rect = RectWithEndpoint(aClipLocalRect.xy, aClipLocalRect.zw); + + return cmi; +} + +struct ClipImageVertexInfo { + vec2 local_pos; + vec4 world_pos; +}; + +// This differs from write_clip_tile_vertex in that we forward transform the +// primitive's local-space tile rect into the target space. We use scissoring +// to ensure that the primitive does not draw outside the target bounds. +ClipImageVertexInfo write_clip_image_vertex(RectWithEndpoint tile_rect, + RectWithEndpoint local_clip_rect, + Transform prim_transform, + Transform clip_transform, + RectWithEndpoint sub_rect, + vec2 task_origin, + vec2 screen_origin, + float device_pixel_scale) { + vec2 local_pos = rect_clamp(local_clip_rect, mix(tile_rect.p0, tile_rect.p1, aPosition.xy)); + vec4 world_pos = prim_transform.m * vec4(local_pos, 0.0, 1.0); + vec4 final_pos = vec4( + world_pos.xy * device_pixel_scale + (task_origin - screen_origin) * world_pos.w, + 0.0, + world_pos.w + ); + gl_Position = uTransform * final_pos; + + init_transform_vs( + clip_transform.is_axis_aligned + ? vec4(vec2(-1.0e16), vec2(1.0e16)) + : vec4(local_clip_rect.p0, local_clip_rect.p1)); + + ClipImageVertexInfo vi = ClipImageVertexInfo(local_pos, world_pos); + return vi; +} + +void main(void) { + ClipMaskInstanceImage cmi = fetch_clip_item(); + Transform clip_transform = fetch_transform(cmi.base.clip_transform_id); + Transform prim_transform = fetch_transform(cmi.base.prim_transform_id); + ImageSource res = fetch_image_source_direct(cmi.resource_address); + + ClipImageVertexInfo vi = write_clip_image_vertex( + cmi.tile_rect, + cmi.local_rect, + prim_transform, + clip_transform, + cmi.base.sub_rect, + cmi.base.task_origin, + cmi.base.screen_origin, + cmi.base.device_pixel_scale + ); + vLocalPos = vi.local_pos; + vec2 uv = (vi.local_pos - cmi.tile_rect.p0) / rect_size(cmi.tile_rect); + + vec2 texture_size = vec2(TEX_SIZE(sColor0)); + vec4 uv_rect = vec4(res.uv_rect.p0, res.uv_rect.p1); + vClipMaskImageUv = mix(uv_rect.xy, uv_rect.zw, uv) / texture_size; + + // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside + vClipMaskUvInnerRect = (uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + float alpha = init_transform_rough_fs(vLocalPos); + vec2 source_uv = clamp(vClipMaskImageUv, vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw); + float clip_alpha = texture(sColor0, source_uv).r; //careful: texture has type A8 + oFragColor = vec4(mix(1.0, clip_alpha, alpha), 0.0, 0.0, 1.0); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanR8() { + if (has_valid_transform_bounds()) { + return; + } + + swgl_commitTextureLinearR8(sColor0, vClipMaskImageUv, vClipMaskUvInnerRect); +} +#endif + +#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..1835770bf6 --- /dev/null +++ b/gfx/wr/webrender/res/cs_clip_rectangle.glsl @@ -0,0 +1,495 @@ +/* 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; + #ifdef SWGL_DRAW_SPAN + flat varying vec4 vClipCorner_TL; + flat varying vec4 vClipCorner_TR; + flat varying vec4 vClipCorner_BL; + flat varying vec4 vClipCorner_BR; + #endif +#endif +// Clip mode. Packed in to a vector to work around bug 1630356. +flat varying vec2 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 base; + vec2 local_pos; +}; + +ClipMaskInstanceRect fetch_clip_item() { + ClipMaskInstanceRect cmi; + + cmi.base = fetch_clip_item_common(); + cmi.local_pos = aClipLocalPos; + + return cmi; +} + +struct ClipRect { + RectWithEndpoint rect; + float mode; +}; + +struct ClipCorner { + RectWithEndpoint 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(RectWithEndpoint(aClipLocalRect.xy, aClipLocalRect.zw), aClipMode); + clip.top_left = ClipCorner(RectWithEndpoint(aClipRect_TL.xy, aClipRect_TL.zw), aClipRadii_TL); + clip.top_right = ClipCorner(RectWithEndpoint(aClipRect_TR.xy, aClipRect_TR.zw), aClipRadii_TR); + clip.bottom_left = ClipCorner(RectWithEndpoint(aClipRect_BL.xy, aClipRect_BL.zw), aClipRadii_BL); + clip.bottom_right = ClipCorner(RectWithEndpoint(aClipRect_BR.xy, aClipRect_BR.zw), aClipRadii_BR); + + return clip; +} + +void main(void) { + ClipMaskInstanceRect cmi = fetch_clip_item(); + Transform clip_transform = fetch_transform(cmi.base.clip_transform_id); + Transform prim_transform = fetch_transform(cmi.base.prim_transform_id); + ClipData clip = fetch_clip(); + + RectWithEndpoint local_rect = clip.rect.rect; + vec2 diff = cmi.local_pos - local_rect.p0; + local_rect.p0 = cmi.local_pos; + local_rect.p1 += diff; + + ClipVertexInfo vi = write_clip_tile_vertex( + local_rect, + prim_transform, + clip_transform, + cmi.base.sub_rect, + cmi.base.task_origin, + cmi.base.screen_origin, + cmi.base.device_pixel_scale + ); + + vClipMode.x = 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 * rect_size(local_rect); + 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 = 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)); + + #ifdef SWGL_DRAW_SPAN + // For the half-space span shader, we need to know the half-spaces of + // the corners separate from the center and radius. We compute a point + // that falls on the diagonal (which is just an inner vertex pushed out + // along one axis, but not on both). We also compute the direction of + // the half-space, which is a perpendicular vertex (-y,x) of the vector + // of the diagonal. We leave the scales of the vectors unchanged. + vClipCorner_TL = vec4(clip_rect.p0.x, + clip_rect.p0.y + r_tl.y, + -r_tl.yx); + vClipCorner_TR = vec4(clip_rect.p1.x - r_tr.x, + clip_rect.p0.y, + vec2(r_tr.y, -r_tr.x)); + vClipCorner_BR = vec4(clip_rect.p1.x, + clip_rect.p1.y - r_br.y, + r_br.yx); + vClipCorner_BL = vec4(clip_rect.p0.x + r_bl.x, + clip_rect.p1.y, + vec2(-r_bl.y, r_bl.x)); + #endif +#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 dist = sd_rounded_box(local_pos, vClipParams.xy, vClipParams.z); +#else + float dist = distance_to_rounded_rect( + local_pos, + vClipCenter_Radius_TL, + vClipCenter_Radius_TR, + vClipCenter_Radius_BR, + vClipCenter_Radius_BL, + vTransformBounds + ); +#endif + + // Compute AA for the given dist and range. + float alpha = distance_aa(aa_range, dist); + + // Select alpha or inverse alpha depending on clip in/out. + float final_alpha = mix(alpha, 1.0 - alpha, vClipMode.x); + + float final_final_alpha = vLocalPos.w > 0.0 ? final_alpha : 0.0; + oFragColor = vec4(final_final_alpha, 0.0, 0.0, 1.0); +} + +#ifdef SWGL_DRAW_SPAN +// Currently the cs_clip_rectangle shader is slow because it always evaluates +// the corner ellipse segments and the rectangle AA for every fragment the +// shader is run on. To alleviate this for now with SWGL, this essentially +// implements a rounded-rectangle span rasterizer inside the span shader. The +// motivation is that we can separate out the parts of the span which are fully +// opaque and fully transparent, outputting runs of fixed color in those areas, +// while only evaluating the ellipse segments and AA in the smaller outlying +// parts of the span that actually need it. +// The shader conceptually represents a rounded rectangle as an inner octagon +// (8 half-spaces) bounding the opaque region and an outer octagon bounding the +// curve and AA parts. Everything outside is transparent. The line of the span +// is intersected with half-spaces, looking for interior spans that minimally +// intersect the half-spaces (start max, end min). In the ideal case we hit a +// start corner ellipse segment and an end corner ellipse segment, rendering +// the two curves on the ends with an opaque run in between, outputting clear +// for any transparent runs before and after the start and end curves. +// This is slightly complicated by the fact that the results here must agree +// with the main results of the fragment shader, in case SWGL has to fall back +// to the main fragment shader for any reason. So, we make an effort to handle +// both ways of operating - the uniform radius fast-path and the varying radius +// slow-path. +void swgl_drawSpanR8() { + // Perspective is not supported. + if (swgl_interpStep(vLocalPos).w != 0.0) { + return; + } + + // If the span is completely outside the Z-range and clipped out, just + // output clear so we don't need to consider invalid W in the rest of the + // shader. + float w = swgl_forceScalar(vLocalPos.w); + if (w <= 0.0) { + swgl_commitSolidR8(0.0); + return; + } + + // To start, we evaluate the rounded-rectangle in local space relative to + // the local-space position. This will be interpolated across the span to + // track whether we intersect any half-spaces. + w = 1.0 / w; + vec2 local_pos = vLocalPos.xy * w; + vec2 local_pos0 = swgl_forceScalar(local_pos); + vec2 local_step = swgl_interpStep(vLocalPos).xy * w; + float step_scale = max(dot(local_step, local_step), 1.0e-6); + + // Get the local-space AA range. This range represents 1/fwidth(local_pos), + // essentially the scale of how much local-space maps to an AA pixel. We + // need to know the inverse, how much local-space we traverse per AA pixel + // pixel step. We then scale this to represent the amount of span steps + // traversed per AA pixel step. + float aa_range = compute_aa_range(local_pos); + float aa_margin = inversesqrt(aa_range * aa_range * step_scale); + + // We need to know the bounds of the aligned rectangle portion of the rrect + // in local-space. If we're using the fast-path, this is specified as the + // inner bounding-box half-width of the rrect and the uniform outer radius + // of the corners in vClipParams, which we map to the outer bounding-box. + // For the general case, we have already stored the outer bounding box in + // vTransformBounds. + #ifdef WR_FEATURE_FAST_PATH + vec4 clip_rect = vec4(-vClipParams.xy - vClipParams.z, vClipParams.xy + vClipParams.z); + #else + vec4 clip_rect = vTransformBounds; + #endif + + // We need to compute the local-space distance to the bounding box and then + // figure out how many processing steps that maps to. If we are stepping in + // a negative direction on an axis, we need to swap the sides of the box + // which we consider as the start or end. If there is no local-space step + // on an axis (i.e. constant Y), we need to take care to force the steps to + // either the start or end of the span depending on if we are inside or + // outside of the bounding box. + vec4 clip_dist = + mix(clip_rect, clip_rect.zwxy, lessThan(local_step, vec2(0.0)).xyxy) + - local_pos0.xyxy; + clip_dist = + mix(1.0e6 * step(0.0, clip_dist), + clip_dist * recip(local_step).xyxy, + notEqual(local_step, vec2(0.0)).xyxy); + + // Initially, the opaque region is bounded by the further start intersect + // with the bounding box and the nearest end intersect with the bounding + // box. + float opaque_start = max(clip_dist.x, clip_dist.y); + float opaque_end = min(clip_dist.z, clip_dist.w); + float aa_start = opaque_start; + float aa_end = opaque_end; + + // Here we actually intersect with the half-space of the corner. We get the + // plane distance of the local-space position from the diagonal bounding + // ellipse segment from the opaque region. The half-space is defined by the + // direction vector of the plane and an offset point that falls on the + // dividing line (which is a vertex on the corner box, which is actually on + // the outer radius of the bounding box, but not a corner vertex). This + // distance is positive if on the curve side and negative if on the inner + // opaque region. If we are on the curve side, we need to verify we are + // traveling in direction towards the opaque region so that we will + // eventually intersect the diagonal so we can calculate when the start + // corner segment will end, otherwise we are going away from the rrect. + // If we are inside the opaque interior, we need to verify we are traveling + // in direction towards the curve, so that we can calculate when the end + // corner segment will start. Further, if we intersect, we calculate the + // offset of the outer octagon where AA starts from the inner octagon of + // where the opaque region starts using the apex vector (which is transpose + // of the half-space's direction). + // + // We need to intersect the corner ellipse segments. Significantly, we need + // to know where the apex of the ellipse segment is and how far to push the + // outer diagonal of the octagon from the inner diagonal. The position of + // the inner diagonal simply runs diagonal across the corner box and has a + // constant offset from vertex on the inner bounding box. The apex also has + // a constant offset along the opposite diagonal relative to the diagonal + // intersect which is 1/sqrt(2) - 0.5 assuming unit length for the diagonal. + // We then need to project the vector to the apex onto the local-space step + // scale, but we do this with reference to the normal vector of the diagonal + // using dot(normal, apex) / dot(normal, local_step), where the apex vector + // is (0.7071 - 0.5) * abs(normal).yx * sign(normal). + vec4 start_plane = vec4(1.0e6); + vec4 end_plane = vec4(1.0e6); + + #define CLIP_CORNER(offset, normal, info) do { \ + float dist = dot(local_pos0 - (offset), (normal)); \ + float scale = -dot(local_step, (normal)); \ + if (scale >= 0.0) { \ + if (dist > opaque_start * scale) { \ + SET_CORNER(start_corner, info); \ + start_plane = vec4(offset, normal); \ + float inv_scale = recip(max(scale, 1.0e-6)); \ + opaque_start = dist * inv_scale; \ + float apex = (0.7071 - 0.5) * 2.0 * abs(normal.x * normal.y); \ + aa_start = opaque_start - apex * inv_scale; \ + } \ + } else if (dist > opaque_end * scale) { \ + SET_CORNER(end_corner, info); \ + end_plane = vec4(offset, normal); \ + float inv_scale = recip(min(scale, -1.0e-6)); \ + opaque_end = dist * inv_scale; \ + float apex = (0.7071 - 0.5) * 2.0 * abs(normal.x * normal.y); \ + aa_end = opaque_end - apex * inv_scale; \ + } \ + } while (false) + + #ifdef WR_FEATURE_FAST_PATH + // For the fast-path, we only have the half-width of the inner bounding + // box. We need to map this to points that fall on the diagonal of the + // half-space for each corner. To do this we just need to push out the + // vertex in the right direction on a single axis, leaving the other + // unchanged. + vec2 corner_tl = -vClipParams.xy - vec2(vClipParams.z, 0.0); + vec2 corner_tr = vec2(vClipParams.x, -vClipParams.y - vClipParams.z); + vec2 corner_br = vClipParams.xy + vec2(vClipParams.z, 0.0); + vec2 corner_bl = vec2(-vClipParams.x, vClipParams.y + vClipParams.z); + // The direction vector of the corner half-space has constant length, + // but just needs an appropriate direction set. + vec2 n_tl = -vClipParams.zz; + vec2 n_tr = vec2(vClipParams.z, -vClipParams.z); + vec2 n_br = vClipParams.zz; + vec2 n_bl = vec2(-vClipParams.z, vClipParams.z); + + #define SET_CORNER(corner, info) + + // Clip against the corner half-spaces. + CLIP_CORNER(corner_tl, n_tl, ); + CLIP_CORNER(corner_tr, n_tr, ); + CLIP_CORNER(corner_br, n_br, ); + CLIP_CORNER(corner_bl, n_bl, ); + + // Later we need to calculate distance AA for both corners and the + // outer bounding rect. For the fast-path, this is all done inside + // sd_rounded_box. + #define AA_RECT(local_pos) \ + sd_rounded_box(local_pos, vClipParams.xy, vClipParams.z) + #else + // For the general case, we need to remember which of the actual start + // and end corners we intersect, so that we can evaluate the curve AA + // against only those corners rather than having to try against all 4 + // corners for both sides of the span. Initialize these values so that + // if no corner is intersected, they will just zero the AA. + vec4 start_corner = vec4(vec2(1.0e6), vec2(1.0)); + vec4 end_corner = vec4(vec2(1.0e6), vec2(1.0)); + + #define SET_CORNER(corner, info) corner = info + + // Clip against the corner half-spaces. We have already computed the + // corner half-spaces in the vertex shader. + CLIP_CORNER(vClipCorner_TL.xy, vClipCorner_TL.zw, vClipCenter_Radius_TL); + CLIP_CORNER(vClipCorner_TR.xy, vClipCorner_TR.zw, vClipCenter_Radius_TR); + CLIP_CORNER(vClipCorner_BR.xy, vClipCorner_BR.zw, vClipCenter_Radius_BR); + CLIP_CORNER(vClipCorner_BL.xy, vClipCorner_BL.zw, vClipCenter_Radius_BL); + + // Later we need to calculate distance AA for both corners and the + // outer bounding rect. For the general case, we need to explicitly + // evaluate either the ellipse segment distance or the rect distance. + #define AA_RECT(local_pos) \ + signed_distance_rect(local_pos, vTransformBounds.xy, vTransformBounds.zw) + #define AA_CORNER(local_pos, corner) \ + distance_to_ellipse_approx(local_pos - corner.xy, corner.zw, 1.0) + #endif + + // Pad the AA region by a margin, as the intersections take place assuming + // pixel centers, but AA actually starts half a pixel away from the center. + // If the AA region narrows to nothing, be careful not to inflate so much + // that we start processing AA for fragments that don't need it. + aa_margin = max(aa_margin - max(aa_start - aa_end, 0.0), 0.0); + aa_start -= aa_margin; + aa_end += aa_margin; + + // Compute the thresholds at which we need to transition between various + // segments of the span, from fully transparent outside to the start of + // the outer octagon where AA starts, from there to where the inner opaque + // octagon starts, from there to where the opaque inner octagon ends and + // AA starts again, to finally where the outer octagon/AA ends and we're + // back to fully transparent. These thresholds are just flipped offsets + // from the start of the span so we can compare against the remaining + // span length which automatically deducts as we commit fragments. + ivec4 steps = ivec4(clamp( + swgl_SpanLength - + swgl_StepSize * + vec4(floor(aa_start), ceil(opaque_start), floor(opaque_end), ceil(aa_end)), + 0.0, swgl_SpanLength)); + int aa_start_len = steps.x; + int opaque_start_len = steps.y; + int opaque_end_len = steps.z; + int aa_end_len = steps.w; + + // Output fully clear while we're outside the AA region. + if (swgl_SpanLength > aa_start_len) { + int num_aa = swgl_SpanLength - aa_start_len; + swgl_commitPartialSolidR8(num_aa, vClipMode.x); + local_pos += float(num_aa / swgl_StepSize) * local_step; + } + #ifdef AA_CORNER + if (start_plane.x < 1.0e5) { + // We're now in the outer octagon which requires AA. Evaluate the corner + // distance of the start corner here and output AA for it. Before we hit + // the actual opaque inner octagon, we have a transitional step where the + // diagonal might intersect mid-way through the step. We have consider + // either the corner or rect distance depending on which side we're on. + while (swgl_SpanLength > opaque_start_len) { + float alpha = distance_aa(aa_range, + dot(local_pos - start_plane.xy, start_plane.zw) > 0.0 + ? AA_CORNER(local_pos, start_corner) + : AA_RECT(local_pos)); + swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); + local_pos += local_step; + } + } + #endif + // If there's no start corner, just do rect AA until opaque. + while (swgl_SpanLength > opaque_start_len) { + float alpha = distance_aa(aa_range, AA_RECT(local_pos)); + swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); + local_pos += local_step; + } + // Now we're finally in the opaque inner octagon part of the span. Just + // output a solid run. + if (swgl_SpanLength > opaque_end_len) { + int num_opaque = swgl_SpanLength - opaque_end_len; + swgl_commitPartialSolidR8(num_opaque, 1.0 - vClipMode.x); + local_pos += float(num_opaque / swgl_StepSize) * local_step; + } + #ifdef AA_CORNER + if (end_plane.x < 1.0e5) { + // Finally we're in the AA region on the other side, inside the outer + // octagon again. Just evaluate the distance to the end corner and + // compute AA for it. We're leaving the opaque inner octagon, but like + // before, we have to be careful we're not dealing with a step partially + // intersected by the end corner's diagonal. Check which side we are on + // and use either the corner or rect distance as appropriate. + while (swgl_SpanLength > aa_end_len) { + float alpha = distance_aa(aa_range, + dot(local_pos - end_plane.xy, end_plane.zw) > 0.0 + ? AA_CORNER(local_pos, end_corner) + : AA_RECT(local_pos)); + swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); + local_pos += local_step; + } + } + #endif + // If there's no end corner, just do rect AA until clear. + while (swgl_SpanLength > aa_end_len) { + float alpha = distance_aa(aa_range, AA_RECT(local_pos)); + swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); + local_pos += local_step; + } + // We're now outside the outer AA octagon on the other side. Just output + // fully clear. + if (swgl_SpanLength > 0) { + swgl_commitPartialSolidR8(swgl_SpanLength, vClipMode.x); + } +} +#endif + +#endif diff --git a/gfx/wr/webrender/res/cs_conic_gradient.glsl b/gfx/wr/webrender/res/cs_conic_gradient.glsl new file mode 100644 index 0000000000..e788ca260c --- /dev/null +++ b/gfx/wr/webrender/res/cs_conic_gradient.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,rect,render_task,gpu_cache,gpu_buffer,gradient + +#define PI 3.141592653589793 + +varying vec2 v_pos; + +flat varying vec2 v_center; + +// x: start offset, y: offset scale, z: angle +// Packed in to a vector to work around bug 1630356. +flat varying vec3 v_start_offset_offset_scale_angle_vec; +#define v_start_offset v_start_offset_offset_scale_angle_vec.x +#define v_offset_scale v_start_offset_offset_scale_angle_vec.y +#define v_angle v_start_offset_offset_scale_angle_vec.z + +#ifdef WR_VERTEX_SHADER + +#define EXTEND_MODE_REPEAT 1 + +PER_INSTANCE in vec4 aTaskRect; +PER_INSTANCE in vec2 aCenter; +PER_INSTANCE in vec2 aScale; +PER_INSTANCE in float aStartOffset; +PER_INSTANCE in float aEndOffset; +PER_INSTANCE in float aAngle; +PER_INSTANCE in int aExtendMode; +PER_INSTANCE in int aGradientStopsAddress; + +void main(void) { + // Store 1/d where d = end_offset - start_offset + // If d = 0, we can't get its reciprocal. Instead, just use a zero scale. + float d = aEndOffset - aStartOffset; + v_offset_scale = d != 0.0 ? 1.0 / d : 0.0; + + vec2 pos = mix(aTaskRect.xy, aTaskRect.zw, aPosition.xy); + gl_Position = uTransform * vec4(pos, 0.0, 1.0); + + v_angle = PI / 2.0 - aAngle; + v_start_offset = aStartOffset * v_offset_scale; + + // v_pos and v_center are in a coordinate space relative to the task rect + // (so they are independent of the task origin). + v_center = aCenter * v_offset_scale; + v_pos = (aTaskRect.zw - aTaskRect.xy) * aPosition.xy * v_offset_scale * aScale; + + v_gradient_repeat.x = float(aExtendMode == EXTEND_MODE_REPEAT); + v_gradient_address.x = aGradientStopsAddress; +} +#endif + + +#ifdef WR_FRAGMENT_SHADER + +void main(void) { + // Use inverse trig to find the angle offset from the relative position. + vec2 current_dir = v_pos - v_center; + float current_angle = atan(current_dir.y, current_dir.x) + v_angle; + float offset = fract(current_angle / (2.0 * PI)) * v_offset_scale - v_start_offset; + + oFragColor = sample_gradient(offset); +} + +#endif diff --git a/gfx/wr/webrender/res/cs_fast_linear_gradient.glsl b/gfx/wr/webrender/res/cs_fast_linear_gradient.glsl new file mode 100644 index 0000000000..23b52e22c4 --- /dev/null +++ b/gfx/wr/webrender/res/cs_fast_linear_gradient.glsl @@ -0,0 +1,32 @@ +/* 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 vColor0; +flat varying vec4 vColor1; + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in vec4 aTaskRect; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in float aAxisSelect; + +void main(void) { + vPos = mix(0.0, 1.0, mix(aPosition.x, aPosition.y, aAxisSelect)); + + vColor0 = aColor0; + vColor1 = aColor1; + + gl_Position = uTransform * vec4(mix(aTaskRect.xy, aTaskRect.zw, aPosition.xy), 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + oFragColor = mix(vColor0, vColor1, vPos); +} +#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..81de807a14 --- /dev/null +++ b/gfx/wr/webrender/res/cs_line_decoration.glsl @@ -0,0 +1,165 @@ +/* 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; + +// Line style. Packed in to a vector to work around bug 1630356. +flat varying ivec2 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.x = aStyle; + + switch (vStyle.x) { + 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(mix(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.x) { + 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_linear_gradient.glsl b/gfx/wr/webrender/res/cs_linear_gradient.glsl new file mode 100644 index 0000000000..4ab95cf6f6 --- /dev/null +++ b/gfx/wr/webrender/res/cs_linear_gradient.glsl @@ -0,0 +1,68 @@ +/* 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,rect,render_task,gpu_cache,gpu_buffer,gradient + +varying vec2 v_pos; + +flat varying vec2 v_scale_dir; + +// Start offset. Packed in to a vector to work around bug 1630356. +flat varying vec2 v_start_offset; + +#ifdef WR_VERTEX_SHADER + +#define EXTEND_MODE_REPEAT 1 + +PER_INSTANCE in vec4 aTaskRect; +PER_INSTANCE in vec2 aStartPoint; +PER_INSTANCE in vec2 aEndPoint; +PER_INSTANCE in vec2 aScale; +PER_INSTANCE in int aExtendMode; +PER_INSTANCE in int aGradientStopsAddress; + +void main(void) { + vec2 pos = mix(aTaskRect.xy, aTaskRect.zw, aPosition.xy); + gl_Position = uTransform * vec4(pos, 0.0, 1.0); + + v_pos = aPosition.xy * aScale; + + vec2 dir = aEndPoint - aStartPoint; + + // Normalize UV and offsets to 0..1 scale. + v_scale_dir = dir / dot(dir, dir); + v_start_offset.x = dot(aStartPoint, v_scale_dir); + + v_scale_dir *= (aTaskRect.zw - aTaskRect.xy); + + v_gradient_repeat.x = float(aExtendMode == EXTEND_MODE_REPEAT); + v_gradient_address.x = aGradientStopsAddress; +} +#endif + + +#ifdef WR_FRAGMENT_SHADER + +void main(void) { + // Project position onto a direction vector to compute offset. + float offset = dot(v_pos, v_scale_dir) - v_start_offset.x; + + oFragColor = sample_gradient(offset); +} + + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { + int address = swgl_validateGradient(sGpuBuffer, get_gpu_buffer_uv(v_gradient_address.x), int(GRADIENT_ENTRIES + 2.0)); + if (address < 0) { + return; + } + + swgl_commitLinearGradientRGBA8(sGpuBuffer, address, GRADIENT_ENTRIES, false, v_gradient_repeat.x != 0.0, + v_pos, v_scale_dir, v_start_offset.x); +} +#endif + + +#endif diff --git a/gfx/wr/webrender/res/cs_radial_gradient.glsl b/gfx/wr/webrender/res/cs_radial_gradient.glsl new file mode 100644 index 0000000000..cdc6bfc517 --- /dev/null +++ b/gfx/wr/webrender/res/cs_radial_gradient.glsl @@ -0,0 +1,71 @@ +/* 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,rect,render_task,gpu_cache,gpu_buffer,gradient + +varying vec2 v_pos; + +// Start radius. Packed in to a vector to work around bug 1630356. +flat varying vec2 v_start_radius; + +#ifdef WR_VERTEX_SHADER + +#define EXTEND_MODE_REPEAT 1 + +PER_INSTANCE in vec4 aTaskRect; +PER_INSTANCE in vec2 aCenter; +PER_INSTANCE in vec2 aScale; +PER_INSTANCE in float aStartRadius; +PER_INSTANCE in float aEndRadius; +PER_INSTANCE in float aXYRatio; +PER_INSTANCE in int aExtendMode; +PER_INSTANCE in int aGradientStopsAddress; + +void main(void) { + // Store 1/rd where rd = end_radius - start_radius + // If rd = 0, we can't get its reciprocal. Instead, just use a zero scale. + float rd = aEndRadius - aStartRadius; + float radius_scale = rd != 0.0 ? 1.0 / rd : 0.0; + + vec2 pos = mix(aTaskRect.xy, aTaskRect.zw, aPosition.xy); + gl_Position = uTransform * vec4(pos, 0.0, 1.0); + + v_start_radius.x = aStartRadius * radius_scale; + + // Transform all coordinates by the y scale so the + // fragment shader can work with circles + + // v_pos is in a coordinate space relative to the task rect + // (so it is independent of the task origin). + v_pos = ((aTaskRect.zw - aTaskRect.xy) * aPosition.xy * aScale - aCenter) * radius_scale; + v_pos.y *= aXYRatio; + + v_gradient_repeat.x = float(aExtendMode == EXTEND_MODE_REPEAT); + v_gradient_address.x = aGradientStopsAddress; +} +#endif + + +#ifdef WR_FRAGMENT_SHADER + +void main(void) { + // Solve for t in length(pd) = v_start_radius + t * rd + float offset = length(v_pos) - v_start_radius.x; + + oFragColor = sample_gradient(offset); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { + int address = swgl_validateGradient(sGpuBuffer, get_gpu_buffer_uv(v_gradient_address.x), + int(GRADIENT_ENTRIES + 2.0)); + if (address < 0) { + return; + } + swgl_commitRadialGradientRGBA8(sGpuBuffer, address, GRADIENT_ENTRIES, v_gradient_repeat.x != 0.0, + v_pos, v_start_radius.x); +} +#endif + +#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..6bbfd16429 --- /dev/null +++ b/gfx/wr/webrender/res/cs_scale.glsl @@ -0,0 +1,62 @@ +/* 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/. */ + +// This shader must remain compatible with ESSL 1, at least for the +// WR_FEATURE_TEXTURE_EXTERNAL_ESSL1 feature, so that it can be used to render +// video on GLES devices without GL_OES_EGL_image_external_essl3 support. +// This means we cannot use textureSize(), int inputs/outputs, etc. + +#include shared + +varying vec2 vUv; +flat varying vec4 vUvRect; +#ifdef WR_FEATURE_TEXTURE_EXTERNAL_ESSL1 +uniform vec2 uTextureSize; +#endif + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE attribute vec4 aScaleTargetRect; +PER_INSTANCE attribute vec4 aScaleSourceRect; + +void main(void) { + vec2 src_offset = aScaleSourceRect.xy; + vec2 src_size = aScaleSourceRect.zw - aScaleSourceRect.xy; + + // 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); +#elif defined(WR_FEATURE_TEXTURE_EXTERNAL_ESSL1) + vec2 texture_size = uTextureSize; +#else + vec2 texture_size = vec2(TEX_SIZE(sColor0)); +#endif + + // The uvs may be inverted, so use the min and max for the bounds + vUvRect = vec4(min(aScaleSourceRect.xy, aScaleSourceRect.zw) + vec2(0.5), + max(aScaleSourceRect.xy, aScaleSourceRect.zw) - vec2(0.5)) / texture_size.xyxy; + + vec2 pos = mix(aScaleTargetRect.xy, aScaleTargetRect.zw, aPosition.xy); + vUv = (src_offset + src_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, st); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { + swgl_commitTextureLinearRGBA8(sColor0, vUv, vUvRect); +} +#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..77c5332fbe --- /dev/null +++ b/gfx/wr/webrender/res/cs_svg_filter.glsl @@ -0,0 +1,594 @@ +/* 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 ivec4 vData; +flat varying vec4 vFilterData0; +flat varying vec4 vFilterData1; + +// x: Filter input count, y: Filter kind. +// Packed in to a vector to work around bug 1630356. +flat varying ivec2 vFilterInputCountFilterKindVec; +#define vFilterInputCount vFilterInputCountFilterKindVec.x +#define vFilterKind vFilterInputCountFilterKindVec.y +// Packed in to a vector to work around bug 1630356. +flat varying vec2 vFloat0; + +flat varying mat4 vColorMat; +flat varying ivec4 vFuncs; + +#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 { + RectWithEndpoint task_rect; + vec3 user_data; +}; + +FilterTask fetch_filter_task(int address) { + RenderTaskData task_data = fetch_render_task_data(address); + + FilterTask task = FilterTask( + task_data.task_rect, + task_data.user_data.xyz + ); + + return task; +} + +vec4 compute_uv_rect(RectWithEndpoint task_rect, vec2 texture_size) { + vec4 uvRect = vec4(task_rect.p0 + vec2(0.5), + task_rect.p1 - vec2(0.5)); + uvRect /= texture_size.xyxy; + return uvRect; +} + +vec2 compute_uv(RectWithEndpoint task_rect, vec2 texture_size) { + vec2 uv0 = task_rect.p0 / texture_size; + vec2 uv1 = floor(task_rect.p1) / texture_size; + return mix(uv0, uv1, aPosition.xy); +} + +void main(void) { + FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress); + RectWithEndpoint target_rect = filter_task.task_rect; + + vec2 pos = mix(target_rect.p0, target_rect.p1, aPosition.xy); + + RectWithEndpoint input_1_task; + if (aFilterInputCount > 0) { + vec2 texture_size = vec2(TEX_SIZE(sColor0).xy); + input_1_task = fetch_render_task_rect(aFilterInput1TaskAddress); + vInput1UvRect = compute_uv_rect(input_1_task, texture_size); + vInput1Uv = compute_uv(input_1_task, texture_size); + } + + RectWithEndpoint input_2_task; + if (aFilterInputCount > 1) { + vec2 texture_size = vec2(TEX_SIZE(sColor1).xy); + input_2_task = fetch_render_task_rect(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.r = (aFilterGenericInt >> 12) & 0xf; // R + vFuncs.g = (aFilterGenericInt >> 8) & 0xf; // G + vFuncs.b = (aFilterGenericInt >> 4) & 0xf; // B + vFuncs.a = (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.x = 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(TEX_SIZE(sColor0).xy); + vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0)); + + RectWithEndpoint task_rect = input_1_task; + vec4 clipRect = vec4(task_rect.p0, task_rect.p1); + 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; + + // Dynamically indexing a vector is buggy on some devices, so use a temporary array. + int[4] funcs = int[4](vFuncs.r, vFuncs.g, vFuncs.b, vFuncs.a); + for (int i = 0; i < 4; i++) { + switch (funcs[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 + 0.5)); + 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.x; + 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..36d20b8a5d --- /dev/null +++ b/gfx/wr/webrender/res/ellipse.glsl @@ -0,0 +1,85 @@ +/* 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 1.0 / max(radii * radii, 1.0e-6); +} + +#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. +// +// A scale relative to the unit scale of the ellipse may be passed in to cause +// the math to degenerate to length(p) when scale is 0, or otherwise give the +// normal distance approximation if scale is 1. +float distance_to_ellipse_approx(vec2 p, vec2 inv_radii_sq, float scale) { + vec2 p_r = p * inv_radii_sq; + float g = dot(p, p_r) - scale; + vec2 dG = (1.0 + scale) * p_r; + 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 distance_to_ellipse_approx(p, inverse_radii_squared(radii), + float(all(greaterThan(radii, vec2(0.0))))); +} + +float distance_to_rounded_rect( + vec2 pos, + vec4 center_radius_tl, + vec4 center_radius_tr, + vec4 center_radius_br, + vec4 center_radius_bl, + vec4 rect_bounds +) { + // Clip against each ellipse. If the fragment is in a corner, one of the + // branches below will select it as the corner to calculate the distance + // to. We want to choose the smallest distance inside either of the axis + // bounds as the overall distance we use to compare which corner is closer + // than another. If outside any ellipse, default to a small offset so a + // negative distance is returned for it. + vec4 corner = vec4(vec2(1.0e-6), vec2(1.0)); + center_radius_tl.xy = center_radius_tl.xy - pos; + center_radius_tr.xy = (center_radius_tr.xy - pos) * vec2(-1.0, 1.0); + center_radius_br.xy = pos - center_radius_br.xy; + center_radius_bl.xy = (center_radius_bl.xy - pos) * vec2(1.0, -1.0); + if (min(center_radius_tl.x, center_radius_tl.y) > min(corner.x, corner.y)) { + corner = center_radius_tl; + } + if (min(center_radius_tr.x, center_radius_tr.y) > min(corner.x, corner.y)) { + corner = center_radius_tr; + } + if (min(center_radius_br.x, center_radius_br.y) > min(corner.x, corner.y)) { + corner = center_radius_br; + } + if (min(center_radius_bl.x, center_radius_bl.y) > min(corner.x, corner.y)) { + corner = center_radius_bl; + } + + // Calculate the distance of the selected corner and the rectangle bounds, + // whichever is greater. + return max(distance_to_ellipse_approx(corner.xy, corner.zw, 1.0), + signed_distance_rect(pos, rect_bounds.xy, rect_bounds.zw)); +} +#endif diff --git a/gfx/wr/webrender/res/gpu_buffer.glsl b/gfx/wr/webrender/res/gpu_buffer.glsl new file mode 100644 index 0000000000..fa9ba6f318 --- /dev/null +++ b/gfx/wr/webrender/res/gpu_buffer.glsl @@ -0,0 +1,23 @@ +/* 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 sGpuBuffer; + +ivec2 get_gpu_buffer_uv(HIGHP_FS_ADDRESS int address) { + return ivec2(uint(address) % WR_MAX_VERTEX_TEXTURE_WIDTH, + uint(address) / WR_MAX_VERTEX_TEXTURE_WIDTH); +} + +vec4 fetch_from_gpu_buffer_1(HIGHP_FS_ADDRESS int address) { + ivec2 uv = get_gpu_buffer_uv(address); + return texelFetch(sGpuBuffer, uv, 0); +} + +vec4[2] fetch_from_gpu_buffer_2(HIGHP_FS_ADDRESS int address) { + ivec2 uv = get_gpu_buffer_uv(address); + return vec4[2]( + TEXEL_FETCH(sGpuBuffer, uv, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuBuffer, uv, 0, ivec2(1, 0)) + ); +} diff --git a/gfx/wr/webrender/res/gpu_cache.glsl b/gfx/wr/webrender/res/gpu_cache.glsl new file mode 100644 index 0000000000..cd5e41fec4 --- /dev/null +++ b/gfx/wr/webrender/res/gpu_cache.glsl @@ -0,0 +1,137 @@ +/* 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 ImageSource { + RectWithEndpoint uv_rect; + vec4 user_data; +}; + +ImageSource fetch_image_source(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 ImageSource(uv_rect, data[1]); +} + +ImageSource fetch_image_source_direct(ivec2 address) { + vec4 data[2] = fetch_from_gpu_cache_2_direct(address); + RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw); + return ImageSource(uv_rect, data[1]); +} + +// 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 ImageSourceExtra { + vec4 st_tl; + vec4 st_tr; + vec4 st_bl; + vec4 st_br; +}; + +ImageSourceExtra fetch_image_source_extra(int address) { + vec4 data[4] = fetch_from_gpu_cache_4(address + VECS_PER_IMAGE_RESOURCE); + return ImageSourceExtra( + 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.glsl b/gfx/wr/webrender/res/gradient.glsl new file mode 100644 index 0000000000..103059fdf9 --- /dev/null +++ b/gfx/wr/webrender/res/gradient.glsl @@ -0,0 +1,63 @@ +/* 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/. */ + +// Gradient GPU cache address. +// Packed in to a vector to work around bug 1630356. +flat varying HIGHP_FS_ADDRESS ivec2 v_gradient_address; +// Repetition along the gradient stops. +// Packed in to a vector to work around bug 1630356. +flat varying vec2 v_gradient_repeat; + +#ifdef WR_FRAGMENT_SHADER + +#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 + +#define GRADIENT_ENTRIES 128.0 + +float clamp_gradient_entry(float offset) { + // 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. + return clamp(1.0 + offset * GRADIENT_ENTRIES, 0.0, 1.0 + GRADIENT_ENTRIES); +} + +vec4 sample_gradient(float offset) { + // Modulo the offset if the gradient repeats. + offset -= floor(offset) * v_gradient_repeat.x; + + // 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 x = clamp_gradient_entry(offset); + 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_buffer_2(v_gradient_address.x + 2 * int(entry_index)); + + // Finally interpolate and apply dithering + return dither(texels[0] + texels[1] * entry_fract); +} + +#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..f32502a9d1 --- /dev/null +++ b/gfx/wr/webrender/res/gradient_shared.glsl @@ -0,0 +1,78 @@ +/* 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 gradient + +// 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; + +varying vec2 v_pos; + +#ifdef WR_FEATURE_ALPHA_PASS +flat varying vec2 v_tile_repeat; +#endif + +#ifdef WR_VERTEX_SHADER +void write_gradient_vertex( + VertexInfo vi, + RectWithEndpoint local_rect, + RectWithEndpoint 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) / rect_size(segment_rect); + v_pos = v_pos * (texel_rect.zw - texel_rect.xy) + texel_rect.xy; + v_pos = v_pos * rect_size(local_rect); + } else { + v_pos = vi.local_pos - local_rect.p0; + } + + vec2 tile_repeat = rect_size(local_rect) / stretch_size; + v_repeated_size = stretch_size; + + // Normalize UV to 0..1 scale. + v_pos /= v_repeated_size; + + v_gradient_address.x = prim_user_data.x; + + // Whether to repeat the gradient along the line instead of clamping. + v_gradient_repeat.x = float(extend_mode == EXTEND_MODE_REPEAT); + +#ifdef WR_FEATURE_ALPHA_PASS + v_tile_repeat = tile_repeat; +#endif +} +#endif //WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER +vec2 compute_repeated_pos() { +#if defined(WR_FEATURE_ALPHA_PASS) && !defined(SWGL_ANTIALIAS) + // 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 +} + +#endif //WR_FRAGMENT_SHADER + diff --git a/gfx/wr/webrender/res/prim_shared.glsl b/gfx/wr/webrender/res/prim_shared.glsl new file mode 100644 index 0000000000..f8c42892e1 --- /dev/null +++ b/gfx/wr/webrender/res/prim_shared.glsl @@ -0,0 +1,250 @@ +/* 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; + +#ifndef SWGL_CLIP_MASK +// TODO: convert back to RectWithEndpoint if driver issues are resolved, if ever. +flat varying vec4 vClipMaskUvBounds; +varying vec2 vClipMaskUv; +#endif + +#ifdef WR_VERTEX_SHADER + +#define COLOR_MODE_FROM_PASS 0 +#define COLOR_MODE_ALPHA 1 +#define COLOR_MODE_SUBPX_BG_PASS0 2 +#define COLOR_MODE_SUBPX_BG_PASS1 3 +#define COLOR_MODE_SUBPX_BG_PASS2 4 +#define COLOR_MODE_SUBPX_DUAL_SOURCE 5 +#define COLOR_MODE_BITMAP_SHADOW 6 +#define COLOR_MODE_COLOR_BITMAP 7 +#define COLOR_MODE_IMAGE 8 +#define COLOR_MODE_MULTIPLY_DUAL_SOURCE 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 { + RectWithEndpoint local_rect; + RectWithEndpoint 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 = RectWithEndpoint(local_rect.xy, local_rect.zw); + ph.local_clip_rect = RectWithEndpoint(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, + RectWithEndpoint local_clip_rect, + float z, + Transform transform, + PictureTask task) { + // Clamp to the two local clip rects. + vec2 clamped_local_pos = rect_clamp(local_clip_rect, local_pos); + + // 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.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; +} + +RectWithEndpoint clip_and_init_antialiasing(RectWithEndpoint segment_rect, + RectWithEndpoint prim_rect, + RectWithEndpoint clip_rect, + int edge_flags, + float z, + Transform transform, + PictureTask task) { +#ifdef SWGL_ANTIALIAS + // Check if the bounds are smaller than the unmodified segment rect. If so, + // it is safe to enable AA on those edges. + bvec4 clipped = bvec4(greaterThan(clip_rect.p0, segment_rect.p0), + lessThan(clip_rect.p1, segment_rect.p1)); + swgl_antiAlias(edge_flags | (clipped.x ? 1 : 0) | (clipped.y ? 2 : 0) | + (clipped.z ? 4 : 0) | (clipped.w ? 8 : 0)); +#endif + + 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); + +#ifndef SWGL_ANTIALIAS + 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); + + // Select between the segment and prim edges based on edge mask. + // We must perform the bitwise-and for each component individually, as a + // vector bitwise-and followed by conversion to bvec4 causes shader + // compilation crashes on some Adreno devices. See bug 1715746. + bvec4 clip_edge_mask = bvec4(bool(edge_flags & 1), bool(edge_flags & 2), bool(edge_flags & 4), bool(edge_flags & 8)); + init_transform_vs(mix( + vec4(vec2(-1e16), vec2(1e16)), + vec4(segment_rect.p0, segment_rect.p1), + clip_edge_mask + )); + + // 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 = mix(vec4(0.0), vec4(extrude_amount), clip_edge_mask); + segment_rect.p0 -= extrude_distance.xy; + segment_rect.p1 += extrude_distance.zw; +#endif + + return segment_rect; +} + +void write_clip(vec4 world_pos, ClipArea area, PictureTask task) { +#ifdef SWGL_CLIP_MASK + swgl_clipMask( + sClipMask, + (task.task_rect.p0 - task.content_origin) - (area.task_rect.p0 - area.screen_origin), + area.task_rect.p0, + rect_size(area.task_rect) + ); +#else + vec2 uv = world_pos.xy * area.device_pixel_scale + + world_pos.w * (area.task_rect.p0 - area.screen_origin); + vClipMaskUvBounds = vec4( + area.task_rect.p0, + area.task_rect.p1 + ); + vClipMaskUv = uv; +#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) { + ImageSourceExtra extra_data = fetch_image_source_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_CLIP_MASK + // 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 * 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..0ef691a55e --- /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 = mix(aRect.xy, aRect.zw, aPosition.xy); + 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_copy.glsl b/gfx/wr/webrender/res/ps_copy.glsl new file mode 100644 index 0000000000..7bcb469b5c --- /dev/null +++ b/gfx/wr/webrender/res/ps_copy.glsl @@ -0,0 +1,41 @@ +/* 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 + +#ifdef WR_VERTEX_SHADER + +attribute vec2 aPosition; + +// See CopyInstance struct. +attribute vec4 a_src_rect; +attribute vec4 a_dst_rect; +attribute vec2 a_dst_texture_size; + +varying vec2 v_uv; + +void main(void) { + // We use texel fetch so v_uv is in unnormalized device space. + v_uv = mix(a_src_rect.xy, a_src_rect.zw, aPosition.xy); + + // Transform into framebuffer [-1, 1] space. + vec2 pos = mix(a_dst_rect.xy, a_dst_rect.zw, aPosition.xy); + gl_Position = vec4(pos / (a_dst_texture_size * 0.5) - vec2(1.0, 1.0), 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER + + +out vec4 oFragColor; + +varying vec2 v_uv; + +uniform sampler2D sColor0; + +void main(void) { + oFragColor = texelFetch(sColor0, ivec2(v_uv), 0); +} + +#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..4b134950f5 --- /dev/null +++ b/gfx/wr/webrender/res/ps_split_composite.glsl @@ -0,0 +1,134 @@ +/* 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. +// Packed in to a vector to work around bug 1630356. +flat varying vec2 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); + ImageSource res = fetch_image_source(ph.user_data.x); + ClipArea clip_area = fetch_clip_area(ph.user_data.w); + + vec2 dest_origin = dest_task.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(TEX_SIZE(sColor0)); + 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) / rect_size(ph.local_rect); + 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.x = perspective_interpolate; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + float alpha = do_clip(); + float perspective_divisor = mix(gl_FragCoord.w, 1.0, vPerspective.x); + vec2 uv = clamp(vUv * perspective_divisor, vUvSampleBounds.xy, vUvSampleBounds.zw); + write_output(alpha * texture(sColor0, uv)); +} + +#ifdef SWGL_DRAW_SPAN +void swgl_drawSpanRGBA8() { + float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, vPerspective.x); + vec2 uv = vUv * perspective_divisor; + + swgl_commitTextureRGBA8(sColor0, uv, vUvSampleBounds); +} +#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..e7faa1247b --- /dev/null +++ b/gfx/wr/webrender/res/ps_text_run.glsl @@ -0,0 +1,354 @@ +/* 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 vec3 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; + + +#if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST) +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 +RectWithEndpoint transform_rect(RectWithEndpoint rect, mat2 transform) { + vec2 size = rect_size(rect); + vec2 center = transform * (rect.p0 + size * 0.5); + vec2 radius = mat2(abs(transform[0]), abs(transform[1])) * (size * 0.5); + return RectWithEndpoint(center - radius, center + radius); +} + +bool rect_inside_rect(RectWithEndpoint little, RectWithEndpoint big) { + return all(lessThanEqual(vec4(big.p0, little.p1), vec4(little.p0, big.p1))); +} +#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. + vec2 glyph = mix(data.xy, data.zw, + bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK == 1U)); + + return Glyph(glyph); +} + +struct GlyphResource { + vec4 uv_rect; + 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].xy, data[1].z); +} + +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.p1; + + 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; + + vec2 glyph_origin = res.offset + raster_glyph_offset + raster_text_offset; + // Compute the glyph rect in glyph space. + RectWithEndpoint glyph_rect = RectWithEndpoint( + glyph_origin, + glyph_origin + res.uv_rect.zw - res.uv_rect.xy + ); + + // The glyph rect is in glyph space, so transform it back to local space. + RectWithEndpoint 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 = mix(local_rect.p0, local_rect.p1, 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 * mix(glyph_rect.p0, glyph_rect.p1, 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. + vec2 glyph_origin = glyph_scale_inv * (res.offset + raster_glyph_offset) + text_offset; + RectWithEndpoint glyph_rect = RectWithEndpoint( + glyph_origin, + glyph_origin + 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 = mix(glyph_rect.p0, glyph_rect.p1, 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) / rect_size(glyph_rect); + #ifdef SWGL_CLIP_DIST + gl_ClipDistance[0] = f.x; + gl_ClipDistance[1] = f.y; + gl_ClipDistance[2] = 1.0 - f.x; + gl_ClipDistance[3] = 1.0 - f.y; + #else + v_uv_clip = vec4(f, 1.0 - f); + #endif +#else + vec2 f = (vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect); +#endif + + write_clip(vi.world_pos, clip_area, task); + + switch (color_mode) { + case COLOR_MODE_ALPHA: + v_mask_swizzle = vec3(0.0, 1.0, 1.0); + v_color = text.color; + break; + case COLOR_MODE_BITMAP_SHADOW: + #ifdef SWGL_BLEND + swgl_blendDropShadow(text.color); + v_mask_swizzle = vec3(1.0, 0.0, 0.0); + v_color = vec4(1.0); + #else + v_mask_swizzle = vec3(0.0, 1.0, 0.0); + v_color = text.color; + #endif + break; + case COLOR_MODE_SUBPX_BG_PASS2: + v_mask_swizzle = vec3(1.0, 0.0, 0.0); + v_color = text.color; + break; + case COLOR_MODE_SUBPX_BG_PASS0: + case COLOR_MODE_COLOR_BITMAP: + v_mask_swizzle = vec3(1.0, 0.0, 0.0); + v_color = vec4(text.color.a); + break; + case COLOR_MODE_SUBPX_BG_PASS1: + v_mask_swizzle = vec3(-1.0, 1.0, 0.0); + v_color = vec4(text.color.a) * text.bg_color; + break; + case COLOR_MODE_SUBPX_DUAL_SOURCE: + #ifdef SWGL_BLEND + swgl_blendSubpixelText(text.color); + v_mask_swizzle = vec3(1.0, 0.0, 0.0); + v_color = vec4(1.0); + #else + v_mask_swizzle = vec3(text.color.a, 0.0, 0.0); + v_color = text.color; + #endif + break; + default: + v_mask_swizzle = vec3(0.0, 0.0, 0.0); + v_color = vec4(1.0); + } + + vec2 texture_size = vec2(TEX_SIZE(sColor0)); + 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); + // v_mask_swizzle.z != 0 means we are using an R8 texture as alpha, + // and therefore must swizzle from the r channel to all channels. + mask = mix(mask, mask.rrrr, bvec4(v_mask_swizzle.z != 0.0)); + #ifndef WR_FEATURE_DUAL_SOURCE_BLENDING + mask.rgb = mask.rgb * v_mask_swizzle.x + mask.aaa * v_mask_swizzle.y; + #endif + + #if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST) + mask *= float(all(greaterThanEqual(v_uv_clip, vec4(0.0)))); + #endif + + frag.color = v_color * mask; + + #if defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND) + frag.blend = mask * v_mask_swizzle.x + mask.aaaa * v_mask_swizzle.y; + #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) && !defined(SWGL_BLEND) + oFragColor = frag.color; + oFragBlend = frag.blend * clip_mask; + #else + write_output(frag.color); + #endif +} + +#if defined(SWGL_DRAW_SPAN) && defined(SWGL_BLEND) && defined(SWGL_CLIP_DIST) +void swgl_drawSpanRGBA8() { + // Only support simple swizzles for now. More complex swizzles must either + // be handled by blend overrides or the slow path. + if (v_mask_swizzle.x != 0.0 && v_mask_swizzle.x != 1.0) { + return; + } + + #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + swgl_commitTextureLinearRGBA8(sColor0, v_uv, v_uv_bounds); + #else + if (swgl_isTextureR8(sColor0)) { + swgl_commitTextureLinearColorR8ToRGBA8(sColor0, v_uv, v_uv_bounds, v_color); + } else { + swgl_commitTextureLinearColorRGBA8(sColor0, v_uv, v_uv_bounds, v_color); + } + #endif +} +#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..2a080ee393 --- /dev/null +++ b/gfx/wr/webrender/res/rect.glsl @@ -0,0 +1,40 @@ +/* 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; +}; + +float point_inside_rect(vec2 p, vec2 p0, vec2 p1) { + vec2 s = step(p0, p) - step(p1, p); + return s.x * s.y; +} + +vec2 signed_distance_rect_xy(vec2 pos, vec2 p0, vec2 p1) { + // Instead of using a true signed distance to rect here, we just use the + // simpler approximation of the maximum distance on either axis from the + // outside of the rectangle. This avoids expensive use of length() and only + // causes mostly imperceptible differences at corner pixels. + return max(p0 - pos, pos - p1); +} + +float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) { + // Collapse the per-axis distances to edges to a single approximate value. + vec2 d = signed_distance_rect_xy(pos, p0, p1); + return max(d.x, d.y); +} + +vec2 rect_clamp(RectWithEndpoint rect, vec2 pt) { + return clamp(pt, rect.p0, rect.p1); +} + +vec2 rect_size(RectWithEndpoint rect) { + return rect.p1 - rect.p0; +} diff --git a/gfx/wr/webrender/res/render_task.glsl b/gfx/wr/webrender/res/render_task.glsl new file mode 100644 index 0000000000..cd9aea402c --- /dev/null +++ b/gfx/wr/webrender/res/render_task.glsl @@ -0,0 +1,102 @@ +/* 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 RenderTaskData { + RectWithEndpoint task_rect; + vec4 user_data; +}; + +// See RenderTaskData in render_task.rs +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)); + + RectWithEndpoint task_rect = RectWithEndpoint( + texel0.xy, + texel0.zw + ); + + RenderTaskData data = RenderTaskData( + task_rect, + texel1 + ); + + return data; +} + +RectWithEndpoint fetch_render_task_rect(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)); + + RectWithEndpoint task_rect = RectWithEndpoint( + texel0.xy, + texel0.zw + ); + + return task_rect; +} + +#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 { + RectWithEndpoint task_rect; + 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.task_rect, + task_data.user_data.x, + task_data.user_data.yz + ); + + return task; +} + +#define CLIP_TASK_EMPTY 0x7FFF + +struct ClipArea { + RectWithEndpoint task_rect; + float device_pixel_scale; + vec2 screen_origin; +}; + +ClipArea fetch_clip_area(int index) { + ClipArea area; + + if (index >= CLIP_TASK_EMPTY) { + area.task_rect = RectWithEndpoint(vec2(0.0), vec2(0.0)); + area.device_pixel_scale = 0.0; + area.screen_origin = vec2(0.0); + } else { + RenderTaskData task_data = fetch_render_task_data(index); + + area.task_rect = task_data.task_rect; + 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..4f21bd205d --- /dev/null +++ b/gfx/wr/webrender/res/shared.glsl @@ -0,0 +1,230 @@ +/* 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_TEXTURE_EXTERNAL_ESSL1 +// Some GLES 3 devices do not support GL_OES_EGL_image_external_essl3, so we +// must use GL_OES_EGL_image_external instead and make the shader ESSL1 +// compatible. +#extension GL_OES_EGL_image_external : 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_ESSL1) +#define TEX_SAMPLE(sampler, tex_coord) texture2D(sampler, tex_coord.xy) +#else +#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy) +#endif + +#if defined(WR_FEATURE_TEXTURE_EXTERNAL) && defined(PLATFORM_ANDROID) +// On some Mali GPUs we have encountered crashes in glDrawElements when using +// textureSize(samplerExternalOES) in a vertex shader without potentially +// sampling from the texture. This tricks the driver in to thinking the texture +// may be sampled from, avoiding the crash. See bug 1692848. +uniform bool u_mali_workaround_dummy; +#define TEX_SIZE(sampler) (u_mali_workaround_dummy ? ivec2(texture(sampler, vec2(0.0, 0.0)).rr) : textureSize(sampler, 0)) +#else +#define TEX_SIZE(sampler) textureSize(sampler, 0) +#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 + attribute 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 + + #if __VERSION__ == 100 + #define oFragColor gl_FragColor + #elif defined(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); + } + +// fwidth is not defined in ESSL 1, but that's okay because we don't need +// it for any ESSL 1 shader variants. +#if __VERSION__ != 100 + /// 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. + vec2 compute_aa_range_xy(vec2 position) { + return fwidth(position); + } + + 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. + // + // 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). + // + // As a further optimization, we compute the reciprocal of this range, such that we + // can then use the cheaper inversesqrt() instead of length(). This also elides a + // division that would otherwise be necessary inside distance_aa. + #ifdef SWGL + // SWGL uses an approximation for fwidth() such that it returns equal x and y. + // Thus, sqrt(2)/length(w) = sqrt(2)/sqrt(x*x + x*x) = recip(x). + return recip(fwidth(position).x); + #else + // sqrt(2)/length(w) = inversesqrt(0.5 * dot(w, w)) + vec2 w = fwidth(position); + return inversesqrt(0.5 * dot(w, w)); + #endif + } +#endif + + /// Return the blending coefficient for distance antialiasing. + /// + /// 0.0 means inside the shape, 1.0 means outside. + /// + /// This makes the simplifying assumption that the area of a 1x1 pixel square + /// under a line is reasonably similar to just the signed Euclidian distance + /// from the center of the square to that line. This diverges slightly from + /// better approximations of the exact area, but the difference between the + /// methods is not perceptibly noticeable, while this approximation is much + /// faster to compute. + /// + /// See the comments in `compute_aa_range()` for more information on the + /// cutoff values of -0.5 and 0.5. + float distance_aa_xy(vec2 aa_range, vec2 signed_distance) { + // The aa_range is the raw per-axis filter width, so we need to divide + // the local signed distance by the filter width to get an approximation + // of screen distance. + #ifdef SWGL + // The SWGL fwidth() approximation returns uniform X and Y ranges. + vec2 dist = signed_distance * recip(aa_range.x); + #else + vec2 dist = signed_distance / aa_range; + #endif + // Choose whichever axis is further outside the rectangle for AA. + return clamp(0.5 - max(dist.x, dist.y), 0.0, 1.0); + } + + float distance_aa(float aa_range, float signed_distance) { + // The aa_range is already stored as a reciprocal with uniform scale, + // so just multiply it, then use that for AA. + float dist = signed_distance * aa_range; + return clamp(0.5 - dist, 0.0, 1.0); + } + + /// 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) || defined(WR_FEATURE_TEXTURE_EXTERNAL_ESSL1) +uniform samplerExternalOES sColor0; +uniform samplerExternalOES sColor1; +uniform samplerExternalOES 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..a3329918b6 --- /dev/null +++ b/gfx/wr/webrender/res/transform.glsl @@ -0,0 +1,140 @@ +/* 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 init_transform_fs(vec2 local_pos) { + // Ideally we want to track distances in screen space after transformation + // as signed distance calculations lose context about the direction vector + // to exit the geometry, merely remembering the minimum distance to the + // exit. However, we can't always sanely track distances in screen space + // due to perspective transforms, clipping, and other concerns, so we do + // this in local space. However, this causes problems tracking distances + // in local space when attempting to scale by a uniform AA range later in + // the presence of a transform which actually has non-uniform scaling. + // + // To work around this, we independently track the distances on the local + // space X and Y axes and then scale them by the independent AA ranges (as + // computed from fwidth derivatives) for the X and Y axes. This can break + // down at certain angles (45 degrees or close to it), but still gives a + // better approximation of screen-space distances in the presence of non- + // uniform scaling for other rotations. + // + // Get signed distance from local rect bounds. + vec2 d = signed_distance_rect_xy( + local_pos, + vTransformBounds.xy, + vTransformBounds.zw + ); + + // Find the appropriate distance to apply the AA smoothstep over. + vec2 aa_range = compute_aa_range_xy(local_pos); + + // Only apply AA to fragments outside the signed distance field. + return distance_aa_xy(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..064ba3b8af --- /dev/null +++ b/gfx/wr/webrender/res/yuv.glsl @@ -0,0 +1,237 @@ +/* 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_P010 1 +#define YUV_FORMAT_PLANAR 2 +#define YUV_FORMAT_INTERLEAVED 3 + +//#define YUV_PRECISION mediump +#define YUV_PRECISION + +#ifdef WR_VERTEX_SHADER + +#ifdef WR_FEATURE_TEXTURE_RECT + #define TEX_SIZE_YUV(sampler) vec2(1.0) +#else + #define TEX_SIZE_YUV(sampler) vec2(TEX_SIZE(sampler).xy) +#endif + +// `YuvRangedColorSpace` +#define YUV_COLOR_SPACE_REC601_NARROW 0 +#define YUV_COLOR_SPACE_REC601_FULL 1 +#define YUV_COLOR_SPACE_REC709_NARROW 2 +#define YUV_COLOR_SPACE_REC709_FULL 3 +#define YUV_COLOR_SPACE_REC2020_NARROW 4 +#define YUV_COLOR_SPACE_REC2020_FULL 5 +#define YUV_COLOR_SPACE_GBR_IDENTITY 6 + +// The constants added to the Y, U and V components are applied in the fragment shader. + +// `rgbFromYuv` from https://jdashg.github.io/misc/colors/from-coeffs.html +// The matrix is stored in column-major. +const mat3 RgbFromYuv_Rec601 = mat3( + 1.00000, 1.00000, 1.00000, + 0.00000,-0.17207, 0.88600, + 0.70100,-0.35707, 0.00000 +); +const mat3 RgbFromYuv_Rec709 = mat3( + 1.00000, 1.00000, 1.00000, + 0.00000,-0.09366, 0.92780, + 0.78740,-0.23406, 0.00000 +); +const mat3 RgbFromYuv_Rec2020 = mat3( + 1.00000, 1.00000, 1.00000, + 0.00000,-0.08228, 0.94070, + 0.73730,-0.28568, 0.00000 +); + +// The matrix is stored in column-major. +// Identity is stored as GBR +const mat3 RgbFromYuv_GbrIdentity = mat3( + 0.0 , 1.0, 0.0, + 0.0 , 0.0, 1.0, + 1.0 , 0.0, 0.0 +); + +// - + +struct YuvPrimitive { + int channel_bit_depth; + int color_space; + int yuv_format; +}; + +struct YuvColorSamplingInfo { + mat3 rgb_from_yuv; + vec4 packed_zero_one_vals; +}; + +struct YuvColorMatrixInfo { + vec3 ycbcr_bias; + mat3 rgb_from_debiased_ycbrc; +}; + +// - + +vec4 yuv_channel_zero_one_identity(int bit_depth, float channel_max) { + float all_ones_normalized = float((1 << bit_depth) - 1) / channel_max; + return vec4(0.0, 0.0, all_ones_normalized, all_ones_normalized); +} + +vec4 yuv_channel_zero_one_narrow_range(int bit_depth, float channel_max) { + // Note: 512/1023 != 128/255 + ivec4 zero_one_ints = ivec4(16, 128, 235, 240) << (bit_depth - 8); + return vec4(zero_one_ints) / channel_max; +} + +vec4 yuv_channel_zero_one_full_range(int bit_depth, float channel_max) { + vec4 narrow = yuv_channel_zero_one_narrow_range(bit_depth, channel_max); + vec4 identity = yuv_channel_zero_one_identity(bit_depth, channel_max); + return vec4(0.0, narrow.y, identity.z, identity.w); +} + +YuvColorSamplingInfo get_yuv_color_info(YuvPrimitive prim) { + float channel_max = 255.0; + if (prim.channel_bit_depth > 8) { + if (prim.yuv_format == YUV_FORMAT_P010) { + // This is an MSB format. + channel_max = float((1 << prim.channel_bit_depth) - 1); + } else { + // For >8bpc, we get the low bits, not the high bits: + // 10bpc(1.0): 0b0000_0011_1111_1111 + channel_max = 65535.0; + } + } + if (prim.color_space == YUV_COLOR_SPACE_REC601_NARROW) { + return YuvColorSamplingInfo(RgbFromYuv_Rec601, + yuv_channel_zero_one_narrow_range(prim.channel_bit_depth, channel_max)); + } else if (prim.color_space == YUV_COLOR_SPACE_REC601_FULL) { + return YuvColorSamplingInfo(RgbFromYuv_Rec601, + yuv_channel_zero_one_full_range(prim.channel_bit_depth, channel_max)); + + } else if (prim.color_space == YUV_COLOR_SPACE_REC709_NARROW) { + return YuvColorSamplingInfo(RgbFromYuv_Rec709, + yuv_channel_zero_one_narrow_range(prim.channel_bit_depth, channel_max)); + } else if (prim.color_space == YUV_COLOR_SPACE_REC709_FULL) { + return YuvColorSamplingInfo(RgbFromYuv_Rec709, + yuv_channel_zero_one_full_range(prim.channel_bit_depth, channel_max)); + + } else if (prim.color_space == YUV_COLOR_SPACE_REC2020_NARROW) { + return YuvColorSamplingInfo(RgbFromYuv_Rec2020, + yuv_channel_zero_one_narrow_range(prim.channel_bit_depth, channel_max)); + } else if (prim.color_space == YUV_COLOR_SPACE_REC2020_FULL) { + return YuvColorSamplingInfo(RgbFromYuv_Rec2020, + yuv_channel_zero_one_full_range(prim.channel_bit_depth, channel_max)); + + } else { + // Identity + return YuvColorSamplingInfo(RgbFromYuv_GbrIdentity, + yuv_channel_zero_one_identity(prim.channel_bit_depth, channel_max)); + } +} + +YuvColorMatrixInfo get_rgb_from_ycbcr_info(YuvPrimitive prim) { + YuvColorSamplingInfo info = get_yuv_color_info(prim); + + vec2 zero = info.packed_zero_one_vals.xy; + vec2 one = info.packed_zero_one_vals.zw; + // Such that yuv_value = (ycbcr_sample - zero) / (one - zero) + vec2 scale = 1.0 / (one - zero); + + YuvColorMatrixInfo mat_info; + mat_info.ycbcr_bias = zero.xyy; + mat3 yuv_from_debiased_ycbcr = mat3(scale.x, 0.0, 0.0, + 0.0, scale.y, 0.0, + 0.0, 0.0, scale.y); + mat_info.rgb_from_debiased_ycbrc = info.rgb_from_yuv * yuv_from_debiased_ycbcr; + return mat_info; +} + +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, + YUV_PRECISION vec3 ycbcr_bias, + YUV_PRECISION mat3 rgb_from_debiased_ycbrc, + vec2 in_uv_y, + vec2 in_uv_u, + vec2 in_uv_v, + vec4 uv_bounds_y, + vec4 uv_bounds_u, + vec4 uv_bounds_v +) { + YUV_PRECISION vec3 ycbcr_sample; + + 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); + ycbcr_sample.x = TEX_SAMPLE(sColor0, uv_y).r; + ycbcr_sample.y = TEX_SAMPLE(sColor1, uv_u).r; + ycbcr_sample.z = TEX_SAMPLE(sColor2, uv_v).r; + } + break; + + case YUV_FORMAT_NV12: + case YUV_FORMAT_P010: + { + 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); + ycbcr_sample.x = TEX_SAMPLE(sColor0, uv_y).r; + ycbcr_sample.yz = TEX_SAMPLE(sColor1, uv_uv).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); + ycbcr_sample = TEX_SAMPLE(sColor0, uv_y).gbr; + } + break; + + default: + ycbcr_sample = vec3(0.0); + break; + } + //if (true) return vec4(ycbcr_sample, 1.0); + + // See the YuvColorMatrix definition for an explanation of where the constants come from. + YUV_PRECISION vec3 rgb = rgb_from_debiased_ycbrc * (ycbcr_sample - ycbcr_bias); + + #if defined(WR_FEATURE_ALPHA_PASS) && defined(SWGL_CLIP_MASK) + // Avoid out-of-range RGB values that can mess with blending. These occur due to invalid + // YUV values outside the mappable space that never the less can be generated. + rgb = clamp(rgb, 0.0, 1.0); + #endif + return vec4(rgb, 1.0); +} +#endif |