/* 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/. */ #include shared,prim_shared,gpu_buffer flat varying mediump vec4 v_color; flat varying mediump vec3 v_mask_swizzle; // Normalized bounds of the source image in the texture. flat varying highp vec4 v_uv_bounds; // Interpolated UV coordinates to sample. varying highp vec2 v_uv; #if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST) varying highp vec4 v_uv_clip; #endif #ifdef WR_VERTEX_SHADER #define VECS_PER_TEXT_RUN 1 #define GLYPHS_PER_GPU_BLOCK 2U #ifdef WR_FEATURE_GLYPH_TRANSFORM bool rect_inside_rect(RectWithEndpoint little, RectWithEndpoint big) { return all(lessThanEqual(vec4(big.p0, little.p1), vec4(little.p0, big.p1))); } #endif //WR_FEATURE_GLYPH_TRANSFORM struct Glyph { vec2 offset; }; Glyph fetch_glyph(int specific_prim_address, int glyph_index) { // Two glyphs are packed in each texel in the GPU cache. int glyph_address = specific_prim_address + VECS_PER_TEXT_RUN + int(uint(glyph_index) / GLYPHS_PER_GPU_BLOCK); vec4 data = fetch_from_gpu_buffer_1f(glyph_address); // Select XY or ZW based on glyph index. vec2 glyph = mix(data.xy, data.zw, bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK == 1U)); return Glyph(glyph); } struct GlyphResource { vec4 uv_rect; vec2 offset; float scale; }; GlyphResource fetch_glyph_resource(int address) { vec4 data[2] = fetch_from_gpu_buffer_2f(address); return GlyphResource(data[0], data[1].xy, data[1].z); } struct TextRun { vec4 color; }; TextRun fetch_text_run(int address) { vec4 data = fetch_from_gpu_buffer_1f(address); return TextRun(data); } void main() { Instance instance = decode_instance_attributes(); PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address); Transform transform = fetch_transform(ph.transform_id); ClipArea clip_area = fetch_clip_area(instance.clip_address); PictureTask task = fetch_picture_task(ph.picture_task_address); int glyph_index = instance.segment_index; int color_mode = instance.flags & 0xF; int subpx_offset_x = (instance.flags >> 4) & 0x3; int subpx_offset_y = (instance.flags >> 6) & 0x3; int subpx_dir = (instance.flags >> 8) & 0x3; int is_packed_glyph = (instance.flags >> 10) & 0x1; TextRun text = fetch_text_run(ph.specific_prim_address); // Per-glyph device-space offset: the glyph pen position snapped to the // device grid on the CPU (`request_resources`), expressed relative to the // transformed run anchor. No transform or snapping is applied to it here. Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index); GlyphResource res = fetch_glyph_resource(instance.resource_address); // For multi-variant glyphs, adjust the UV rect to select the correct quarter // of the packed texture based on subpixel offset. This must happen before // geometry calculations since the glyph rect size depends on the UV rect. if (is_packed_glyph != 0) { int variant_index = (subpx_dir == SUBPX_DIR_HORIZONTAL) ? subpx_offset_x : subpx_offset_y; float quarter_width = (res.uv_rect.z - res.uv_rect.x) * 0.25; res.uv_rect.x = res.uv_rect.x + float(variant_index) * quarter_width; res.uv_rect.z = res.uv_rect.x + quarter_width; } // Device-space position of the run anchor (`ph.local_rect.p0`, the prim // rect origin), via the same prim -> raster transform + device pixel scale // the rest of the pipeline uses. The CPU computed the per-glyph offsets // relative to this exact value, so the absolute device positions // reconstruct here. vec2 device_anchor = (transform.m * vec4(ph.local_rect.p0, 0.0, 1.0)).xy * task.device_pixel_scale; float inv_dps = 1.0 / task.device_pixel_scale; VertexInfo vi; vec2 f; #ifdef WR_FEATURE_GLYPH_TRANSFORM // Device mode, transformed (2D rotated/skewed) glyph. The glyph rect is // axis-aligned in device (glyph-raster) space; `glyph.offset` is the // device-grid-snapped pen offset. `write_vertex` clamps to the axis-aligned // local clip rect, which would shear a rotated quad — so by default build // the quad from the local-space AABB of the four mapped corners (clamping an // AABB stays clean) and let `v_uv_clip` mask the rotated glyph within it. // When the glyph fits entirely inside the clip rect there is nothing to // clamp, so use the exact rotated corners to avoid the AABB's overdraw. vec2 device_origin = device_anchor + glyph.offset + res.scale * res.offset; vec2 device_size = res.scale * (res.uv_rect.zw - res.uv_rect.xy); vec2 c0 = (transform.inv_m * vec4(device_origin * inv_dps, 0.0, 1.0)).xy; vec2 c1 = (transform.inv_m * vec4(vec2(device_origin.x + device_size.x, device_origin.y) * inv_dps, 0.0, 1.0)).xy; vec2 c2 = (transform.inv_m * vec4(vec2(device_origin.x, device_origin.y + device_size.y) * inv_dps, 0.0, 1.0)).xy; vec2 c3 = (transform.inv_m * vec4((device_origin + device_size) * inv_dps, 0.0, 1.0)).xy; RectWithEndpoint local_aabb = RectWithEndpoint(min(min(c0, c1), min(c2, c3)), max(max(c0, c1), max(c2, c3))); vec2 local_pos = mix(local_aabb.p0, local_aabb.p1, aPosition.xy); if (rect_inside_rect(local_aabb, ph.local_clip_rect)) { vec2 device_corner = mix(device_origin, device_origin + device_size, aPosition.xy); local_pos = (transform.inv_m * vec4(device_corner * inv_dps, 0.0, 1.0)).xy; } vi = write_vertex(local_pos, ph.local_clip_rect, ph.z, transform, task); // UV fraction within the glyph rect, in device space from the (possibly // clip-clamped) vertex, so clipping is handled correctly for rotated glyphs. vec2 device_clamped = (transform.m * vec4(vi.local_pos, 0.0, 1.0)).xy * task.device_pixel_scale; f = (device_clamped - device_origin) / device_size; #else int raster_mode = ph.user_data.y; if (raster_mode == 0) { // Device mode, axis-aligned: the device rect maps to an axis-aligned // local rect, so the clip clamp is clean — map this vertex's device // corner straight to local. vec2 device_origin = device_anchor + glyph.offset + res.scale * res.offset; vec2 device_size = res.scale * (res.uv_rect.zw - res.uv_rect.xy); vec2 device_corner = mix(device_origin, device_origin + device_size, aPosition.xy); vec2 local_pos = (transform.inv_m * vec4(device_corner * inv_dps, 0.0, 1.0)).xy; vi = write_vertex(local_pos, ph.local_clip_rect, ph.z, transform, task); vec2 device_clamped = (transform.m * vec4(vi.local_pos, 0.0, 1.0)).xy * task.device_pixel_scale; f = (device_clamped - device_origin) / device_size; } else { // Local-raster mode: the glyph was rasterized at `raster_scale` with an // identity transform. Position and scale it in local space — mapping the // raster-space glyph rect to local by `glyph_scale_inv` — and let // `write_vertex` apply the (possibly animated / scaling / perspective) // transform. No device snapping happens here (it was done in raster space // on the CPU) so glyphs don't wiggle under animation. `glyph.offset` is // the absolute snapped raster-space position of the glyph pen. float raster_scale = float(ph.user_data.x) / 65535.0; float glyph_raster_scale = raster_scale * task.device_pixel_scale; float glyph_scale_inv = res.scale / glyph_raster_scale; vec2 glyph_origin = glyph_scale_inv * (res.offset + glyph.offset / res.scale); RectWithEndpoint glyph_rect = RectWithEndpoint( glyph_origin, glyph_origin + glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy) ); vec2 local_pos = mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy); vi = write_vertex(local_pos, ph.local_clip_rect, ph.z, transform, task); f = (vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect); } #endif #ifdef WR_FEATURE_GLYPH_TRANSFORM // For transformed glyphs the local clip rect is axis-aligned but the glyph // quad is rotated, so `write_vertex`'s clamp can pull a corner off the glyph. // Clip in glyph space instead: discard fragments outside [0,1] of the rect. #ifdef SWGL_CLIP_DIST gl_ClipDistance[0] = f.x; gl_ClipDistance[1] = f.y; gl_ClipDistance[2] = 1.0 - f.x; gl_ClipDistance[3] = 1.0 - f.y; #else v_uv_clip = vec4(f, 1.0 - f); #endif #endif write_clip(vi.world_pos, clip_area, task); switch (color_mode) { case COLOR_MODE_ALPHA: v_mask_swizzle = vec3(0.0, 1.0, 1.0); v_color = text.color; break; case COLOR_MODE_BITMAP_SHADOW: #ifdef SWGL_BLEND swgl_blendDropShadow(text.color); v_mask_swizzle = vec3(1.0, 0.0, 0.0); v_color = vec4(1.0); #else v_mask_swizzle = vec3(0.0, 1.0, 0.0); v_color = text.color; #endif break; case COLOR_MODE_COLOR_BITMAP: v_mask_swizzle = vec3(1.0, 0.0, 0.0); v_color = vec4(text.color.a); break; case COLOR_MODE_SUBPX_DUAL_SOURCE: #ifdef SWGL_BLEND swgl_blendSubpixelText(text.color); v_mask_swizzle = vec3(1.0, 0.0, 0.0); v_color = vec4(1.0); #else v_mask_swizzle = vec3(text.color.a, 0.0, 0.0); v_color = text.color; #endif break; default: v_mask_swizzle = vec3(0.0, 0.0, 0.0); v_color = vec4(1.0); } vec2 texture_size = vec2(TEX_SIZE(sColor0)); vec2 st0 = res.uv_rect.xy / texture_size; vec2 st1 = res.uv_rect.zw / texture_size; v_uv = mix(st0, st1, f); v_uv_bounds = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy; } #endif // WR_VERTEX_SHADER #ifdef WR_FRAGMENT_SHADER Fragment text_fs(void) { Fragment frag; vec2 tc = clamp(v_uv, v_uv_bounds.xy, v_uv_bounds.zw); vec4 mask = texture(sColor0, tc); // v_mask_swizzle.z != 0 means we are using an R8 texture as alpha, // and therefore must swizzle from the r channel to all channels. mask = mix(mask, mask.rrrr, bvec4(v_mask_swizzle.z != 0.0)); #ifndef WR_FEATURE_DUAL_SOURCE_BLENDING mask.rgb = mask.rgb * v_mask_swizzle.x + mask.aaa * v_mask_swizzle.y; #endif #if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST) mask *= float(all(greaterThanEqual(v_uv_clip, vec4(0.0)))); #endif frag.color = v_color * mask; #if defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND) frag.blend = mask * v_mask_swizzle.x + mask.aaaa * v_mask_swizzle.y; #endif return frag; } void main() { Fragment frag = text_fs(); float clip_mask = do_clip(); frag.color *= clip_mask; #if defined(WR_FEATURE_DEBUG_OVERDRAW) oFragColor = WR_DEBUG_OVERDRAW_COLOR; #elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND) oFragColor = frag.color; oFragBlend = frag.blend * clip_mask; #else write_output(frag.color); #endif } #if defined(SWGL_DRAW_SPAN) && defined(SWGL_BLEND) && defined(SWGL_CLIP_DIST) void swgl_drawSpanRGBA8() { // Only support simple swizzles for now. More complex swizzles must either // be handled by blend overrides or the slow path. if (v_mask_swizzle.x != 0.0 && v_mask_swizzle.x != 1.0) { return; } #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING swgl_commitTextureLinearRGBA8(sColor0, v_uv, v_uv_bounds); #else if (swgl_isTextureR8(sColor0)) { swgl_commitTextureLinearColorR8ToRGBA8(sColor0, v_uv, v_uv_bounds, v_color); } else { swgl_commitTextureLinearColorRGBA8(sColor0, v_uv, v_uv_bounds, v_color); } #endif } #endif #endif // WR_FRAGMENT_SHADER