Source code

Revision control

Copy as Markdown

Other Tools

/* 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
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
RectWithEndpoint transform_rect(RectWithEndpoint rect, mat2 transform) {
vec2 size = rect_size(rect);
vec2 center = transform * (rect.p0 + size * 0.5);
vec2 radius = mat2(abs(transform[0]), abs(transform[1])) * (size * 0.5);
return RectWithEndpoint(center - radius, center + radius);
}
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_cache_1(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_cache_2(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_cache_1(address);
return TextRun(data);
}
vec2 get_snap_bias(int subpx_dir) {
// In subpixel mode, the subpixel offset has already been
// accounted for while rasterizing the glyph. However, we
// must still round with a subpixel bias rather than rounding
// to the nearest whole pixel, depending on subpixel direciton.
switch (subpx_dir) {
case SUBPX_DIR_NONE:
default:
return vec2(0.5);
case SUBPX_DIR_HORIZONTAL:
// Glyphs positioned [-0.125, 0.125] get a
// subpx position of zero. So include that
// offset in the glyph position to ensure
// we round to the correct whole position.
return vec2(0.125, 0.5);
case SUBPX_DIR_VERTICAL:
return vec2(0.5, 0.125);
case SUBPX_DIR_MIXED:
return vec2(0.125);
}
}
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 subpx_dir = (instance.flags >> 8) & 0xff;
int color_mode = instance.flags & 0xff;
// Note that the reference frame relative offset is stored in the prim local
// rect size during batching, instead of the actual size of the primitive.
TextRun text = fetch_text_run(ph.specific_prim_address);
vec2 text_offset = ph.local_rect.p1;
// Note that the unsnapped reference frame relative offset has already
// been subtracted from the prim local rect origin during batching.
// It was done this way to avoid pushing both the snapped and the
// unsnapped offsets to the shader.
Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
glyph.offset += ph.local_rect.p0;
GlyphResource res = fetch_glyph_resource(instance.resource_address);
vec2 snap_bias = get_snap_bias(subpx_dir);
// Glyph space refers to the pixel space used by glyph rasterization during frame
// building. If a non-identity transform was used, WR_FEATURE_GLYPH_TRANSFORM will
// be set. Otherwise, regardless of whether the raster space is LOCAL or SCREEN,
// we ignored the transform during glyph rasterization, and need to snap just using
// the device pixel scale and the raster scale.
#ifdef WR_FEATURE_GLYPH_TRANSFORM
// Transform from local space to glyph space.
mat2 glyph_transform = mat2(transform.m) * task.device_pixel_scale;
vec2 glyph_translation = transform.m[3].xy * task.device_pixel_scale;
// Transform from glyph space back to local space.
mat2 glyph_transform_inv = inverse(glyph_transform);
// Glyph raster pixels include the impact of the transform. This path can only be
// entered for 3d transforms that can be coerced into a 2d transform; they have no
// perspective, and have a 2d inverse. This is a looser condition than axis aligned
// transforms because it also allows 2d rotations.
vec2 raster_glyph_offset = floor(glyph_transform * glyph.offset + snap_bias);
// We want to eliminate any subpixel translation in device space to ensure glyph
// snapping is stable for equivalent glyph subpixel positions. Note that we must take
// into account the translation from the transform for snapping purposes.
vec2 raster_text_offset = floor(glyph_transform * text_offset + glyph_translation + 0.5) - glyph_translation;
vec2 glyph_origin = res.offset + raster_glyph_offset + raster_text_offset;
// Compute the glyph rect in glyph space.
RectWithEndpoint glyph_rect = RectWithEndpoint(
glyph_origin,
glyph_origin + res.uv_rect.zw - res.uv_rect.xy
);
// The glyph rect is in glyph space, so transform it back to local space.
RectWithEndpoint local_rect = transform_rect(glyph_rect, glyph_transform_inv);
// Select the corner of the glyph's local space rect that we are processing.
vec2 local_pos = mix(local_rect.p0, local_rect.p1, aPosition.xy);
// If the glyph's local rect would fit inside the local clip rect, then select a corner from
// the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
// Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
if (rect_inside_rect(local_rect, ph.local_clip_rect)) {
local_pos = glyph_transform_inv * mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy);
}
#else
float raster_scale = float(ph.user_data.x) / 65535.0;
// Scale in which the glyph is snapped when rasterized.
float glyph_raster_scale = raster_scale * task.device_pixel_scale;
// Scale from glyph space to local space.
float glyph_scale_inv = res.scale / glyph_raster_scale;
// Glyph raster pixels do not include the impact of the transform. Instead it was
// replaced with an identity transform during glyph rasterization. As such only the
// impact of the raster scale (if in local space) and the device pixel scale (for both
// local and screen space) are included.
//
// This implies one or more of the following conditions:
// - The transform is an identity. In that case, setting WR_FEATURE_GLYPH_TRANSFORM
// should have the same output result as not. We just distingush which path to use
// based on the transform used during glyph rasterization. (Screen space).
// - The transform contains an animation. We will imply local raster space in such
// cases to avoid constantly rerasterizing the glyphs.
// - The transform has perspective or does not have a 2d inverse (Screen or local space).
// - The transform's scale will result in result in very large rasterized glyphs and
// we clamped the size. This will imply local raster space.
vec2 raster_glyph_offset = floor(glyph.offset * glyph_raster_scale + snap_bias) / res.scale;
// Compute the glyph rect in local space.
//
// The transform may be animated, so we don't want to do any snapping here for the
// text offset to avoid glyphs wiggling. The text offset should have been snapped
// already for axis aligned transforms excluding any animations during frame building.
vec2 glyph_origin = glyph_scale_inv * (res.offset + raster_glyph_offset) + text_offset;
RectWithEndpoint glyph_rect = RectWithEndpoint(
glyph_origin,
glyph_origin + glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy)
);
// Select the corner of the glyph rect that we are processing.
vec2 local_pos = mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy);
#endif
VertexInfo vi = write_vertex(
local_pos,
ph.local_clip_rect,
ph.z,
transform,
task
);
#ifdef WR_FEATURE_GLYPH_TRANSFORM
vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / rect_size(glyph_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
#else
vec2 f = (vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect);
#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