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#include shared,prim_shared
6
7flat varying vec4 v_color;
8flat varying vec3 v_mask_swizzle;
9// Normalized bounds of the source image in the texture.
10flat varying vec4 v_uv_bounds;
11
12// Interpolated UV coordinates to sample.
13varying vec2 v_uv;
14
15
16#if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST)
17varying vec4 v_uv_clip;
18#endif
19
20#ifdef WR_VERTEX_SHADER
21
22#define VECS_PER_TEXT_RUN           2
23#define GLYPHS_PER_GPU_BLOCK        2U
24
25#ifdef WR_FEATURE_GLYPH_TRANSFORM
26RectWithEndpoint transform_rect(RectWithEndpoint rect, mat2 transform) {
27    vec2 size = rect_size(rect);
28    vec2 center = transform * (rect.p0 + size * 0.5);
29    vec2 radius = mat2(abs(transform[0]), abs(transform[1])) * (size * 0.5);
30    return RectWithEndpoint(center - radius, center + radius);
31}
32
33bool rect_inside_rect(RectWithEndpoint little, RectWithEndpoint big) {
34    return all(lessThanEqual(vec4(big.p0, little.p1), vec4(little.p0, big.p1)));
35}
36#endif //WR_FEATURE_GLYPH_TRANSFORM
37
38struct Glyph {
39    vec2 offset;
40};
41
42Glyph fetch_glyph(int specific_prim_address,
43                  int glyph_index) {
44    // Two glyphs are packed in each texel in the GPU cache.
45    int glyph_address = specific_prim_address +
46                        VECS_PER_TEXT_RUN +
47                        int(uint(glyph_index) / GLYPHS_PER_GPU_BLOCK);
48    vec4 data = fetch_from_gpu_cache_1(glyph_address);
49    // Select XY or ZW based on glyph index.
50    vec2 glyph = mix(data.xy, data.zw,
51                     bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK == 1U));
52
53    return Glyph(glyph);
54}
55
56struct GlyphResource {
57    vec4 uv_rect;
58    vec2 offset;
59    float scale;
60};
61
62GlyphResource fetch_glyph_resource(int address) {
63    vec4 data[2] = fetch_from_gpu_cache_2(address);
64    return GlyphResource(data[0], data[1].xy, data[1].z);
65}
66
67struct TextRun {
68    vec4 color;
69    vec4 bg_color;
70};
71
72TextRun fetch_text_run(int address) {
73    vec4 data[2] = fetch_from_gpu_cache_2(address);
74    return TextRun(data[0], data[1]);
75}
76
77vec2 get_snap_bias(int subpx_dir) {
78    // In subpixel mode, the subpixel offset has already been
79    // accounted for while rasterizing the glyph. However, we
80    // must still round with a subpixel bias rather than rounding
81    // to the nearest whole pixel, depending on subpixel direciton.
82    switch (subpx_dir) {
83        case SUBPX_DIR_NONE:
84        default:
85            return vec2(0.5);
86        case SUBPX_DIR_HORIZONTAL:
87            // Glyphs positioned [-0.125, 0.125] get a
88            // subpx position of zero. So include that
89            // offset in the glyph position to ensure
90            // we round to the correct whole position.
91            return vec2(0.125, 0.5);
92        case SUBPX_DIR_VERTICAL:
93            return vec2(0.5, 0.125);
94        case SUBPX_DIR_MIXED:
95            return vec2(0.125);
96    }
97}
98
99void main() {
100    Instance instance = decode_instance_attributes();
101    PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address);
102    Transform transform = fetch_transform(ph.transform_id);
103    ClipArea clip_area = fetch_clip_area(instance.clip_address);
104    PictureTask task = fetch_picture_task(instance.picture_task_address);
105
106    int glyph_index = instance.segment_index;
107    int subpx_dir = (instance.flags >> 8) & 0xff;
108    int color_mode = instance.flags & 0xff;
109
110    // Note that the reference frame relative offset is stored in the prim local
111    // rect size during batching, instead of the actual size of the primitive.
112    TextRun text = fetch_text_run(ph.specific_prim_address);
113    vec2 text_offset = ph.local_rect.p1;
114
115    if (color_mode == COLOR_MODE_FROM_PASS) {
116        color_mode = uMode;
117    }
118
119    // Note that the unsnapped reference frame relative offset has already
120    // been subtracted from the prim local rect origin during batching.
121    // It was done this way to avoid pushing both the snapped and the
122    // unsnapped offsets to the shader.
123    Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
124    glyph.offset += ph.local_rect.p0;
125
126    GlyphResource res = fetch_glyph_resource(instance.resource_address);
127
128    vec2 snap_bias = get_snap_bias(subpx_dir);
129
130    // Glyph space refers to the pixel space used by glyph rasterization during frame
131    // building. If a non-identity transform was used, WR_FEATURE_GLYPH_TRANSFORM will
132    // be set. Otherwise, regardless of whether the raster space is LOCAL or SCREEN,
133    // we ignored the transform during glyph rasterization, and need to snap just using
134    // the device pixel scale and the raster scale.
135#ifdef WR_FEATURE_GLYPH_TRANSFORM
136    // Transform from local space to glyph space.
137    mat2 glyph_transform = mat2(transform.m) * task.device_pixel_scale;
138    vec2 glyph_translation = transform.m[3].xy * task.device_pixel_scale;
139
140    // Transform from glyph space back to local space.
141    mat2 glyph_transform_inv = inverse(glyph_transform);
142
143    // Glyph raster pixels include the impact of the transform. This path can only be
144    // entered for 3d transforms that can be coerced into a 2d transform; they have no
145    // perspective, and have a 2d inverse. This is a looser condition than axis aligned
146    // transforms because it also allows 2d rotations.
147    vec2 raster_glyph_offset = floor(glyph_transform * glyph.offset + snap_bias);
148
149    // We want to eliminate any subpixel translation in device space to ensure glyph
150    // snapping is stable for equivalent glyph subpixel positions. Note that we must take
151    // into account the translation from the transform for snapping purposes.
152    vec2 raster_text_offset = floor(glyph_transform * text_offset + glyph_translation + 0.5) - glyph_translation;
153
154    vec2 glyph_origin = res.offset + raster_glyph_offset + raster_text_offset;
155    // Compute the glyph rect in glyph space.
156    RectWithEndpoint glyph_rect = RectWithEndpoint(
157        glyph_origin,
158        glyph_origin + res.uv_rect.zw - res.uv_rect.xy
159    );
160
161    // The glyph rect is in glyph space, so transform it back to local space.
162    RectWithEndpoint local_rect = transform_rect(glyph_rect, glyph_transform_inv);
163
164    // Select the corner of the glyph's local space rect that we are processing.
165    vec2 local_pos = mix(local_rect.p0, local_rect.p1, aPosition.xy);
166
167    // If the glyph's local rect would fit inside the local clip rect, then select a corner from
168    // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
169    // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
170    if (rect_inside_rect(local_rect, ph.local_clip_rect)) {
171        local_pos = glyph_transform_inv * mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy);
172    }
173#else
174    float raster_scale = float(ph.user_data.x) / 65535.0;
175
176    // Scale in which the glyph is snapped when rasterized.
177    float glyph_raster_scale = raster_scale * task.device_pixel_scale;
178
179    // Scale from glyph space to local space.
180    float glyph_scale_inv = res.scale / glyph_raster_scale;
181
182    // Glyph raster pixels do not include the impact of the transform. Instead it was
183    // replaced with an identity transform during glyph rasterization. As such only the
184    // impact of the raster scale (if in local space) and the device pixel scale (for both
185    // local and screen space) are included.
186    //
187    // This implies one or more of the following conditions:
188    // - The transform is an identity. In that case, setting WR_FEATURE_GLYPH_TRANSFORM
189    //   should have the same output result as not. We just distingush which path to use
190    //   based on the transform used during glyph rasterization. (Screen space).
191    // - The transform contains an animation. We will imply local raster space in such
192    //   cases to avoid constantly rerasterizing the glyphs.
193    // - The transform has perspective or does not have a 2d inverse (Screen or local space).
194    // - The transform's scale will result in result in very large rasterized glyphs and
195    //   we clamped the size. This will imply local raster space.
196    vec2 raster_glyph_offset = floor(glyph.offset * glyph_raster_scale + snap_bias) / res.scale;
197
198    // Compute the glyph rect in local space.
199    //
200    // The transform may be animated, so we don't want to do any snapping here for the
201    // text offset to avoid glyphs wiggling. The text offset should have been snapped
202    // already for axis aligned transforms excluding any animations during frame building.
203    vec2 glyph_origin = glyph_scale_inv * (res.offset + raster_glyph_offset) + text_offset;
204    RectWithEndpoint glyph_rect = RectWithEndpoint(
205        glyph_origin,
206        glyph_origin + glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy)
207    );
208
209    // Select the corner of the glyph rect that we are processing.
210    vec2 local_pos = mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy);
211#endif
212
213    VertexInfo vi = write_vertex(
214        local_pos,
215        ph.local_clip_rect,
216        ph.z,
217        transform,
218        task
219    );
220
221#ifdef WR_FEATURE_GLYPH_TRANSFORM
222    vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect);
223    #ifdef SWGL_CLIP_DIST
224        gl_ClipDistance[0] = f.x;
225        gl_ClipDistance[1] = f.y;
226        gl_ClipDistance[2] = 1.0 - f.x;
227        gl_ClipDistance[3] = 1.0 - f.y;
228    #else
229        v_uv_clip = vec4(f, 1.0 - f);
230    #endif
231#else
232    vec2 f = (vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect);
233#endif
234
235    write_clip(vi.world_pos, clip_area, task);
236
237    switch (color_mode) {
238        case COLOR_MODE_ALPHA:
239            v_mask_swizzle = vec3(0.0, 1.0, 1.0);
240            v_color = text.color;
241            break;
242        case COLOR_MODE_BITMAP_SHADOW:
243            #ifdef SWGL_BLEND
244                swgl_blendDropShadow(text.color);
245                v_mask_swizzle = vec3(1.0, 0.0, 0.0);
246                v_color = vec4(1.0);
247            #else
248                v_mask_swizzle = vec3(0.0, 1.0, 0.0);
249                v_color = text.color;
250            #endif
251            break;
252        case COLOR_MODE_SUBPX_BG_PASS2:
253            v_mask_swizzle = vec3(1.0, 0.0, 0.0);
254            v_color = text.color;
255            break;
256        case COLOR_MODE_SUBPX_CONST_COLOR:
257        case COLOR_MODE_SUBPX_BG_PASS0:
258        case COLOR_MODE_COLOR_BITMAP:
259            v_mask_swizzle = vec3(1.0, 0.0, 0.0);
260            v_color = vec4(text.color.a);
261            break;
262        case COLOR_MODE_SUBPX_BG_PASS1:
263            v_mask_swizzle = vec3(-1.0, 1.0, 0.0);
264            v_color = vec4(text.color.a) * text.bg_color;
265            break;
266        case COLOR_MODE_SUBPX_DUAL_SOURCE:
267            #ifdef SWGL_BLEND
268                swgl_blendSubpixelText(text.color);
269                v_mask_swizzle = vec3(1.0, 0.0, 0.0);
270                v_color = vec4(1.0);
271            #else
272                v_mask_swizzle = vec3(text.color.a, 0.0, 0.0);
273                v_color = text.color;
274            #endif
275            break;
276        default:
277            v_mask_swizzle = vec3(0.0, 0.0, 0.0);
278            v_color = vec4(1.0);
279    }
280
281    vec2 texture_size = vec2(TEX_SIZE(sColor0));
282    vec2 st0 = res.uv_rect.xy / texture_size;
283    vec2 st1 = res.uv_rect.zw / texture_size;
284
285    v_uv = mix(st0, st1, f);
286    v_uv_bounds = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
287}
288
289#endif // WR_VERTEX_SHADER
290
291#ifdef WR_FRAGMENT_SHADER
292
293Fragment text_fs(void) {
294    Fragment frag;
295
296    vec2 tc = clamp(v_uv, v_uv_bounds.xy, v_uv_bounds.zw);
297    vec4 mask = texture(sColor0, tc);
298    // v_mask_swizzle.z != 0 means we are using an R8 texture as alpha,
299    // and therefore must swizzle from the r channel to all channels.
300    mask = mix(mask, mask.rrrr, bvec4(v_mask_swizzle.z != 0.0));
301    #ifndef WR_FEATURE_DUAL_SOURCE_BLENDING
302        mask.rgb = mask.rgb * v_mask_swizzle.x + mask.aaa * v_mask_swizzle.y;
303    #endif
304
305    #if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST)
306        mask *= float(all(greaterThanEqual(v_uv_clip, vec4(0.0))));
307    #endif
308
309    frag.color = v_color * mask;
310
311    #if defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND)
312        frag.blend = mask * v_mask_swizzle.x + mask.aaaa * v_mask_swizzle.y;
313    #endif
314
315    return frag;
316}
317
318
319void main() {
320    Fragment frag = text_fs();
321
322    float clip_mask = do_clip();
323    frag.color *= clip_mask;
324
325    #if defined(WR_FEATURE_DEBUG_OVERDRAW)
326        oFragColor = WR_DEBUG_OVERDRAW_COLOR;
327    #elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND)
328        oFragColor = frag.color;
329        oFragBlend = frag.blend * clip_mask;
330    #else
331        write_output(frag.color);
332    #endif
333}
334
335#if defined(SWGL_DRAW_SPAN) && defined(SWGL_BLEND) && defined(SWGL_CLIP_DIST)
336void swgl_drawSpanRGBA8() {
337    // Only support simple swizzles for now. More complex swizzles must either
338    // be handled by blend overrides or the slow path.
339    if (v_mask_swizzle.x != 0.0 && v_mask_swizzle.x != 1.0) {
340        return;
341    }
342
343    #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
344        swgl_commitTextureLinearRGBA8(sColor0, v_uv, v_uv_bounds);
345    #else
346        if (swgl_isTextureR8(sColor0)) {
347            swgl_commitTextureLinearColorR8ToRGBA8(sColor0, v_uv, v_uv_bounds, v_color);
348        } else {
349            swgl_commitTextureLinearColorRGBA8(sColor0, v_uv, v_uv_bounds, v_color);
350        }
351    #endif
352}
353#endif
354
355#endif // WR_FRAGMENT_SHADER
356