diff options
Diffstat (limited to 'gfx/wr/webrender/res/cs_svg_filter.glsl')
-rw-r--r-- | gfx/wr/webrender/res/cs_svg_filter.glsl | 594 |
1 files changed, 594 insertions, 0 deletions
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..6ccdda90d6 --- /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 highp vec2 vInput1Uv; +varying highp vec2 vInput2Uv; +flat varying highp vec4 vInput1UvRect; +flat varying highp vec4 vInput2UvRect; +flat varying mediump ivec4 vData; +flat varying mediump vec4 vFilterData0; +flat varying mediump vec4 vFilterData1; + +// x: Filter input count, y: Filter kind. +// Packed in to a vector to work around bug 1630356. +flat varying mediump ivec2 vFilterInputCountFilterKindVec; +#define vFilterInputCount vFilterInputCountFilterKindVec.x +#define vFilterKind vFilterInputCountFilterKindVec.y +// Packed in to a vector to work around bug 1630356. +flat varying mediump vec2 vFloat0; + +flat varying mediump mat4 vColorMat; +flat varying mediump 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 |