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