340 lines
13 KiB
GLSL
340 lines
13 KiB
GLSL
/* 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
|