/* 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