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