1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#define COMPONENT_TRANSFER_IDENTITY 0
6#define COMPONENT_TRANSFER_TABLE 1
7#define COMPONENT_TRANSFER_DISCRETE 2
8#define COMPONENT_TRANSFER_LINEAR 3
9#define COMPONENT_TRANSFER_GAMMA 4
10
11// Must be kept in sync with `Filter::as_int` in internal_types.rs
12// Not all filters are defined here because some filter use different shaders.
13#define FILTER_CONTRAST            0
14#define FILTER_GRAYSCALE           1
15#define FILTER_HUE_ROTATE          2
16#define FILTER_INVERT              3
17#define FILTER_SATURATE            4
18#define FILTER_SEPIA               5
19#define FILTER_BRIGHTNESS          6
20#define FILTER_COLOR_MATRIX        7
21#define FILTER_SRGB_TO_LINEAR      8
22#define FILTER_LINEAR_TO_SRGB      9
23#define FILTER_FLOOD               10
24#define FILTER_COMPONENT_TRANSFER  11
25
26#ifdef WR_VERTEX_SHADER
27void SetupFilterParams(
28    int op,
29    float amount,
30    int gpu_data_address,
31    out vec4 color_offset,
32    out mat4 color_mat,
33    out int table_address
34) {
35    float lumR = 0.2126;
36    float lumG = 0.7152;
37    float lumB = 0.0722;
38    float oneMinusLumR = 1.0 - lumR;
39    float oneMinusLumG = 1.0 - lumG;
40    float oneMinusLumB = 1.0 - lumB;
41    float invAmount = 1.0 - amount;
42
43    if (op == FILTER_GRAYSCALE) {
44        color_mat = mat4(
45            vec4(lumR + oneMinusLumR * invAmount, lumR - lumR * invAmount, lumR - lumR * invAmount, 0.0),
46            vec4(lumG - lumG * invAmount, lumG + oneMinusLumG * invAmount, lumG - lumG * invAmount, 0.0),
47            vec4(lumB - lumB * invAmount, lumB - lumB * invAmount, lumB + oneMinusLumB * invAmount, 0.0),
48            vec4(0.0, 0.0, 0.0, 1.0)
49        );
50        color_offset = vec4(0.0);
51    } else if (op ==  FILTER_HUE_ROTATE) {
52        float c = cos(amount);
53        float s = sin(amount);
54        color_mat = mat4(
55            vec4(lumR + oneMinusLumR * c - lumR * s, lumR - lumR * c + 0.143 * s, lumR - lumR * c - oneMinusLumR * s, 0.0),
56            vec4(lumG - lumG * c - lumG * s, lumG + oneMinusLumG * c + 0.140 * s, lumG - lumG * c + lumG * s, 0.0),
57            vec4(lumB - lumB * c + oneMinusLumB * s, lumB - lumB * c - 0.283 * s, lumB + oneMinusLumB * c + lumB * s, 0.0),
58            vec4(0.0, 0.0, 0.0, 1.0)
59        );
60        color_offset = vec4(0.0);
61    } else if (op ==   FILTER_SATURATE) {
62        color_mat = mat4(
63            vec4(invAmount * lumR + amount, invAmount * lumR, invAmount * lumR, 0.0),
64            vec4(invAmount * lumG, invAmount * lumG + amount, invAmount * lumG, 0.0),
65            vec4(invAmount * lumB, invAmount * lumB, invAmount * lumB + amount, 0.0),
66            vec4(0.0, 0.0, 0.0, 1.0)
67        );
68        color_offset = vec4(0.0);
69    } else if (op == FILTER_SEPIA) {
70        color_mat = mat4(
71            vec4(0.393 + 0.607 * invAmount, 0.349 - 0.349 * invAmount, 0.272 - 0.272 * invAmount, 0.0),
72            vec4(0.769 - 0.769 * invAmount, 0.686 + 0.314 * invAmount, 0.534 - 0.534 * invAmount, 0.0),
73            vec4(0.189 - 0.189 * invAmount, 0.168 - 0.168 * invAmount, 0.131 + 0.869 * invAmount, 0.0),
74            vec4(0.0, 0.0, 0.0, 1.0)
75        );
76        color_offset = vec4(0.0);
77    } else if (op == FILTER_COLOR_MATRIX) {
78        vec4 mat_data[4] = fetch_from_gpu_cache_4(gpu_data_address);
79        vec4 offset_data = fetch_from_gpu_cache_1(gpu_data_address + 4);
80        color_mat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]);
81        color_offset = offset_data;
82    } else if (op == FILTER_COMPONENT_TRANSFER) {
83        table_address = gpu_data_address;
84    } else if (op == FILTER_FLOOD) {
85        color_offset = fetch_from_gpu_cache_1(gpu_data_address);
86    }
87}
88#endif
89
90#ifdef WR_FRAGMENT_SHADER
91vec3 Contrast(vec3 Cs, float amount) {
92    return clamp(Cs.rgb * amount - 0.5 * amount + 0.5, 0.0, 1.0);
93}
94
95vec3 Invert(vec3 Cs, float amount) {
96    return mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount);
97}
98
99vec3 Brightness(vec3 Cs, float amount) {
100    // Apply the brightness factor.
101    // Resulting color needs to be clamped to output range
102    // since we are pre-multiplying alpha in the shader.
103    return clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
104}
105
106// Based on the Gecko's implementation in
107// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24
108// These could be made faster by sampling a lookup table stored in a float texture
109// with linear interpolation.
110
111vec3 SrgbToLinear(vec3 color) {
112    vec3 c1 = color / 12.92;
113    vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4));
114    return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2);
115}
116
117vec3 LinearToSrgb(vec3 color) {
118    vec3 c1 = color * 12.92;
119    vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
120    return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
121}
122
123// This function has to be factored out due to the following issue:
124// 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
125// (and now the words "default: default:" so angle_shader_validation.rs passes)
126vec4 ComponentTransfer(vec4 colora, ivec4 vfuncs, int table_address) {
127    // We push a different amount of data to the gpu cache depending on the
128    // function type.
129    // Identity => 0 blocks
130    // Table/Discrete => 64 blocks (256 values)
131    // Linear => 1 block (2 values)
132    // Gamma => 1 block (3 values)
133    // We loop through the color components and increment the offset (for the
134    // next color component) into the gpu cache based on how many blocks that
135    // function type put into the gpu cache.
136    // Table/Discrete use a 256 entry look up table.
137    // Linear/Gamma are a simple calculation.
138    int offset = 0;
139    vec4 texel;
140    int k;
141
142    // Dynamically indexing a vector is buggy on some platforms, so use a temporary array
143    int[4] funcs = int[4](vfuncs.r, vfuncs.g, vfuncs.b, vfuncs.a);
144    for (int i = 0; i < 4; i++) {
145        switch (funcs[i]) {
146            case COMPONENT_TRANSFER_IDENTITY:
147                break;
148            case COMPONENT_TRANSFER_TABLE:
149            case COMPONENT_TRANSFER_DISCRETE: {
150                // fetch value from lookup table
151                k = int(floor(colora[i]*255.0));
152                texel = fetch_from_gpu_cache_1(table_address + offset + k/4);
153                colora[i] = clamp(texel[k % 4], 0.0, 1.0);
154                // offset plus 256/4 blocks
155                offset = offset + 64;
156                break;
157            }
158            case COMPONENT_TRANSFER_LINEAR: {
159                // fetch the two values for use in the linear equation
160                texel = fetch_from_gpu_cache_1(table_address + offset);
161                colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
162                // offset plus 1 block
163                offset = offset + 1;
164                break;
165            }
166            case COMPONENT_TRANSFER_GAMMA: {
167                // fetch the three values for use in the gamma equation
168                texel = fetch_from_gpu_cache_1(table_address + offset);
169                colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
170                // offset plus 1 block
171                offset = offset + 1;
172                break;
173            }
174            default:
175                // shouldn't happen
176                break;
177        }
178    }
179    return colora;
180}
181
182void CalculateFilter(
183    vec4 Cs,
184    int op,
185    float amount,
186    int table_address,
187    vec4 color_offset,
188    mat4 color_mat,
189    ivec4 v_funcs,
190    out vec3 color,
191    out float alpha
192) {
193    // Un-premultiply the input.
194    alpha = Cs.a;
195    color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb;
196
197    switch (op) {
198        case FILTER_CONTRAST:
199            color = Contrast(color, amount);
200            break;
201        case FILTER_INVERT:
202            color = Invert(color, amount);
203            break;
204        case FILTER_BRIGHTNESS:
205            color = Brightness(color, amount);
206            break;
207        case FILTER_SRGB_TO_LINEAR:
208            color = SrgbToLinear(color);
209            break;
210        case FILTER_LINEAR_TO_SRGB:
211            color = LinearToSrgb(color);
212            break;
213        case FILTER_COMPONENT_TRANSFER: {
214            // Get the unpremultiplied color with alpha.
215            vec4 colora = vec4(color, alpha);
216            colora = ComponentTransfer(colora, v_funcs, table_address);
217            color = colora.rgb;
218            alpha = colora.a;
219            break;
220        }
221        case FILTER_FLOOD:
222            color = color_offset.rgb;
223            alpha = color_offset.a;
224            break;
225        default:
226            // Color matrix type filters (sepia, hue-rotate, etc...)
227            vec4 result = color_mat * vec4(color, alpha) + color_offset;
228            result = clamp(result, vec4(0.0), vec4(1.0));
229            color = result.rgb;
230            alpha = result.a;
231    }
232}
233#endif
234