Copy as Markdown

Other Tools

#define SWGL 1
#define __VERSION__ 150
#define WR_MAX_VERTEX_TEXTURE_WIDTH 1024U
#define WR_FEATURE_ANTIALIASING
#define WR_FEATURE_REPETITION
#define WR_FEATURE_TEXTURE_2D
/* 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/. */
#define VECS_PER_SPECIFIC_BRUSH 3
/* 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/. */
#ifdef WR_FEATURE_TEXTURE_EXTERNAL
// for this extension.
#endif
#ifdef WR_FEATURE_TEXTURE_EXTERNAL_ESSL1
// Some GLES 3 devices do not support GL_OES_EGL_image_external_essl3, so we
// must use GL_OES_EGL_image_external instead and make the shader ESSL1
// compatible.
#endif
#ifdef WR_FEATURE_TEXTURE_EXTERNAL_BT709
#endif
#ifdef WR_FEATURE_ADVANCED_BLEND
#endif
#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
#ifdef GL_ES
#else
#endif
#endif
/* 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/. */
#if defined(GL_ES)
#if GL_ES == 1
// Sampler default precision is lowp on mobile GPUs.
// This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880).
// Define highp precision macro to allow lossless FLOAT texture sampling.
#define HIGHP_SAMPLER_FLOAT highp
// Default int precision in GLES 3 is highp (32 bits) in vertex shaders
// and mediump (16 bits) in fragment shaders. If an int is being used as
// a texel address in a fragment shader it, and therefore requires > 16
// bits, it must be qualified with this.
#define HIGHP_FS_ADDRESS highp
// texelFetchOffset is buggy on some Android GPUs (see issue #1694).
// Fallback to texelFetch on mobile GPUs.
#define TEXEL_FETCH(sampler, position, lod, offset) texelFetch(sampler, position + offset, lod)
#else
#define HIGHP_SAMPLER_FLOAT
#define HIGHP_FS_ADDRESS
#define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset)
#endif
#else
#define HIGHP_SAMPLER_FLOAT
#define HIGHP_FS_ADDRESS
#if defined(PLATFORM_MACOS) && !defined(SWGL)
// texelFetchOffset introduces a variety of shader compilation bugs on macOS Intel so avoid it.
#define TEXEL_FETCH(sampler, position, lod, offset) texelFetch(sampler, position + offset, lod)
#else
#define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset)
#endif
#endif
#ifdef SWGL
#define SWGL_DRAW_SPAN
#define SWGL_CLIP_MASK
#define SWGL_ANTIALIAS
#define SWGL_BLEND
#define SWGL_CLIP_DIST
#endif
#ifdef WR_VERTEX_SHADER
#ifdef SWGL
// Annotate a vertex attribute as being flat per each drawn primitive instance.
// SWGL can use this information to avoid redundantly loading the attribute in all SIMD lanes.
#define PER_INSTANCE flat
#else
#define PER_INSTANCE
#endif
#if __VERSION__ != 100
#define varying out
#define attribute in
#endif
#endif
#ifdef WR_FRAGMENT_SHADER
precision highp float;
#if __VERSION__ != 100
#define varying in
#endif
#endif
// Flat interpolation is not supported on ESSL 1
#if __VERSION__ == 100
#define flat
#endif
#if defined(WR_FEATURE_TEXTURE_EXTERNAL_ESSL1)
#define TEX_SAMPLE(sampler, tex_coord) texture2D(sampler, tex_coord.xy)
#elif defined(WR_FEATURE_TEXTURE_EXTERNAL_BT709)
// Force conversion from yuv to rgb using BT709 colorspace
#define TEX_SAMPLE(sampler, tex_coord) vec4(yuv_2_rgb(texture(sampler, tex_coord.xy).xyz, itu_709), 1.0)
#else
#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy)
#endif
#if defined(WR_FEATURE_TEXTURE_EXTERNAL) && defined(PLATFORM_ANDROID)
// On some Mali GPUs we have encountered crashes in glDrawElements when using
// textureSize(samplerExternalOES) in a vertex shader without potentially
// sampling from the texture. This tricks the driver in to thinking the texture
// may be sampled from, avoiding the crash. See bug 1692848.
uniform bool u_mali_workaround_dummy;
#define TEX_SIZE(sampler) (u_mali_workaround_dummy ? ivec2(texture(sampler, vec2(0.0, 0.0)).rr) : textureSize(sampler, 0))
#else
#define TEX_SIZE(sampler) textureSize(sampler, 0)
#endif
//======================================================================================
// Vertex shader attributes and uniforms
//======================================================================================
#ifdef WR_VERTEX_SHADER
// Uniform inputs
uniform mat4 uTransform; // Orthographic projection
// Attribute inputs
attribute vec2 aPosition;
// get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
// TODO: convert back to a function once the driver issues are resolved, if ever.
// Do the division with unsigned ints because that's more efficient with D3D
#define get_fetch_uv(i, vpi) ivec2(int(vpi * (uint(i) % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)))
#endif
//======================================================================================
// Fragment shader attributes and uniforms
//======================================================================================
#ifdef WR_FRAGMENT_SHADER
// Uniform inputs
// Fragment shader outputs
#ifdef WR_FEATURE_ADVANCED_BLEND
layout(blend_support_all_equations) out;
#endif
#if __VERSION__ == 100
#define oFragColor gl_FragColor
#elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING)
layout(location = 0, index = 0) out vec4 oFragColor;
layout(location = 0, index = 1) out vec4 oFragBlend;
#else
out vec4 oFragColor;
#endif
// Write an output color in normal shaders.
void write_output(vec4 color) {
oFragColor = color;
}
#define EPSILON 0.0001
// "Show Overdraw" color. Premultiplied.
#define WR_DEBUG_OVERDRAW_COLOR vec4(0.110, 0.077, 0.027, 0.125)
float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
vec2 dir_to_p0 = p0 - p;
return dot(normalize(perp_dir), dir_to_p0);
}
// fwidth is not defined in ESSL 1, but that's okay because we don't need
// it for any ESSL 1 shader variants.
#if __VERSION__ != 100
/// Find the appropriate half range to apply the AA approximation over.
/// This range represents a coefficient to go from one CSS pixel to half a device pixel.
vec2 compute_aa_range_xy(vec2 position) {
return fwidth(position);
}
float compute_aa_range(vec2 position) {
// The constant factor is chosen to compensate for the fact that length(fw) is equal
// to sqrt(2) times the device pixel ratio in the typical case.
//
// This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of
// the shape has no anti-aliasing applied to it (since pixels are sampled at their center,
// such a pixel (axis aligned) is fully inside the border). We need this so that antialiased
// curves properly connect with non-antialiased vertical or horizontal lines, among other things.
//
// Lines over a half-pixel away from the pixel center *can* intersect with the pixel square;
// indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing
// a nonzero area for such pixels causes noticeable artifacts at the junction between an anti-
// aliased corner and a straight edge.
//
// We may want to adjust this constant in specific scenarios (for example keep the principled
// value for straight edges where we want pixel-perfect equivalence with non antialiased lines
// when axis aligned, while selecting a larger and smoother aa range on curves).
//
// As a further optimization, we compute the reciprocal of this range, such that we
// can then use the cheaper inversesqrt() instead of length(). This also elides a
// division that would otherwise be necessary inside distance_aa.
#ifdef SWGL
// SWGL uses an approximation for fwidth() such that it returns equal x and y.
// Thus, sqrt(2)/length(w) = sqrt(2)/sqrt(x*x + x*x) = recip(x).
return recip(fwidth(position).x);
#else
// sqrt(2)/length(w) = inversesqrt(0.5 * dot(w, w))
vec2 w = fwidth(position);
return inversesqrt(0.5 * dot(w, w));
#endif
}
#endif
/// Return the blending coefficient for distance antialiasing.
///
/// 0.0 means inside the shape, 1.0 means outside.
///
/// This makes the simplifying assumption that the area of a 1x1 pixel square
/// under a line is reasonably similar to just the signed Euclidian distance
/// from the center of the square to that line. This diverges slightly from
/// better approximations of the exact area, but the difference between the
/// methods is not perceptibly noticeable, while this approximation is much
/// faster to compute.
///
/// See the comments in `compute_aa_range()` for more information on the
/// cutoff values of -0.5 and 0.5.
float distance_aa_xy(vec2 aa_range, vec2 signed_distance) {
// The aa_range is the raw per-axis filter width, so we need to divide
// the local signed distance by the filter width to get an approximation
// of screen distance.
#ifdef SWGL
// The SWGL fwidth() approximation returns uniform X and Y ranges.
vec2 dist = signed_distance * recip(aa_range.x);
#else
vec2 dist = signed_distance / aa_range;
#endif
// Choose whichever axis is further outside the rectangle for AA.
return clamp(0.5 - max(dist.x, dist.y), 0.0, 1.0);
}
float distance_aa(float aa_range, float signed_distance) {
// The aa_range is already stored as a reciprocal with uniform scale,
// so just multiply it, then use that for AA.
float dist = signed_distance * aa_range;
return clamp(0.5 - dist, 0.0, 1.0);
}
/// Component-wise selection.
///
/// The idea of using this is to ensure both potential branches are executed before
/// selecting the result, to avoid observable timing differences based on the condition.
///
/// Example usage: color = if_then_else(LessThanEqual(color, vec3(0.5)), vec3(0.0), vec3(1.0));
///
/// The above example sets each component to 0.0 or 1.0 independently depending on whether
/// their values are below or above 0.5.
///
/// This is written as a macro in order to work with vectors of any dimension.
///
/// Note: Some older android devices don't support mix with bvec. If we ever run into them
/// the only option we have is to polyfill it with a branch per component.
#define if_then_else(cond, then_branch, else_branch) mix(else_branch, then_branch, cond)
#endif
//======================================================================================
// Shared shader uniforms
//======================================================================================
#ifdef WR_FEATURE_TEXTURE_2D
uniform sampler2D sColor0;
uniform sampler2D sColor1;
uniform sampler2D sColor2;
#elif defined WR_FEATURE_TEXTURE_RECT
uniform sampler2DRect sColor0;
uniform sampler2DRect sColor1;
uniform sampler2DRect sColor2;
#elif defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_EXTERNAL_ESSL1)
uniform samplerExternalOES sColor0;
uniform samplerExternalOES sColor1;
uniform samplerExternalOES sColor2;
#elif defined(WR_FEATURE_TEXTURE_EXTERNAL_BT709)
uniform __samplerExternal2DY2YEXT sColor0;
uniform __samplerExternal2DY2YEXT sColor1;
uniform __samplerExternal2DY2YEXT sColor2;
#endif
#ifdef WR_FEATURE_DITHERING
uniform sampler2D sDither;
#endif
//======================================================================================
// Interpolator definitions
//======================================================================================
//======================================================================================
// VS only types and UBOs
//======================================================================================
//======================================================================================
// VS only functions
//======================================================================================
/* 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/. */
/* 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/. */
struct RectWithSize {
vec2 p0;
vec2 size;
};
struct RectWithEndpoint {
vec2 p0;
vec2 p1;
};
float point_inside_rect(vec2 p, vec2 p0, vec2 p1) {
vec2 s = step(p0, p) - step(p1, p);
return s.x * s.y;
}
vec2 signed_distance_rect_xy(vec2 pos, vec2 p0, vec2 p1) {
// Instead of using a true signed distance to rect here, we just use the
// simpler approximation of the maximum distance on either axis from the
// outside of the rectangle. This avoids expensive use of length() and only
// causes mostly imperceptible differences at corner pixels.
return max(p0 - pos, pos - p1);
}
float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
// Collapse the per-axis distances to edges to a single approximate value.
vec2 d = signed_distance_rect_xy(pos, p0, p1);
return max(d.x, d.y);
}
vec2 rect_clamp(RectWithEndpoint rect, vec2 pt) {
return clamp(pt, rect.p0, rect.p1);
}
vec2 rect_size(RectWithEndpoint rect) {
return rect.p1 - rect.p0;
}
/* 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/. */
#ifdef WR_VERTEX_SHADER
#define VECS_PER_RENDER_TASK 2U
uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
struct RenderTaskData {
RectWithEndpoint task_rect;
vec4 user_data;
};
// See RenderTaskData in render_task.rs
RenderTaskData fetch_render_task_data(int index) {
ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
RectWithEndpoint task_rect = RectWithEndpoint(
texel0.xy,
texel0.zw
);
RenderTaskData data = RenderTaskData(
task_rect,
texel1
);
return data;
}
RectWithEndpoint fetch_render_task_rect(int index) {
ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK);
vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0));
vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0));
RectWithEndpoint task_rect = RectWithEndpoint(
texel0.xy,
texel0.zw
);
return task_rect;
}
#define PIC_TYPE_IMAGE 1
#define PIC_TYPE_TEXT_SHADOW 2
/*
The dynamic picture that this brush exists on. Right now, it
contains minimal information. In the future, it will describe
the transform mode of primitives on this picture, among other things.
*/
struct PictureTask {
RectWithEndpoint task_rect;
float device_pixel_scale;
vec2 content_origin;
};
PictureTask fetch_picture_task(int address) {
RenderTaskData task_data = fetch_render_task_data(address);
PictureTask task = PictureTask(
task_data.task_rect,
task_data.user_data.x,
task_data.user_data.yz
);
return task;
}
#define CLIP_TASK_EMPTY 0x7FFFFFFF
struct ClipArea {
RectWithEndpoint task_rect;
float device_pixel_scale;
vec2 screen_origin;
};
ClipArea fetch_clip_area(int index) {
RenderTaskData task_data;
if (index >= CLIP_TASK_EMPTY) {
// We deliberately create a dummy RenderTaskData here then convert to a
// ClipArea after this if-else statement, rather than initialize the
// ClipArea in separate branches, to avoid a miscompile in some Adreno
// drivers. See bug 1884791. Unfortunately the specific details of the bug
// are unknown, so please take extra care not to regress this when
// refactoring.
task_data = RenderTaskData(RectWithEndpoint(vec2(0.0), vec2(0.0)),
vec4(0.0));
} else {
task_data = fetch_render_task_data(index);
}
return ClipArea(task_data.task_rect, task_data.user_data.x,
task_data.user_data.yz);
}
#endif //WR_VERTEX_SHADER
/* 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/. */
uniform HIGHP_SAMPLER_FLOAT sampler2D sGpuCache;
#define VECS_PER_IMAGE_RESOURCE 2
// TODO(gw): This is here temporarily while we have
// both GPU store and cache. When the GPU
// store code is removed, we can change the
// PrimitiveInstance instance structure to
// use 2x unsigned shorts as vertex attributes
// instead of an int, and encode the UV directly
// in the vertices.
ivec2 get_gpu_cache_uv(HIGHP_FS_ADDRESS int address) {
return ivec2(uint(address) % WR_MAX_VERTEX_TEXTURE_WIDTH,
uint(address) / WR_MAX_VERTEX_TEXTURE_WIDTH);
}
vec4[2] fetch_from_gpu_cache_2_direct(ivec2 address) {
return vec4[2](
TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0))
);
}
vec4[2] fetch_from_gpu_cache_2(HIGHP_FS_ADDRESS int address) {
ivec2 uv = get_gpu_cache_uv(address);
return vec4[2](
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0))
);
}
vec4 fetch_from_gpu_cache_1_direct(ivec2 address) {
return texelFetch(sGpuCache, address, 0);
}
vec4 fetch_from_gpu_cache_1(HIGHP_FS_ADDRESS int address) {
ivec2 uv = get_gpu_cache_uv(address);
return texelFetch(sGpuCache, uv, 0);
}
#ifdef WR_VERTEX_SHADER
vec4[8] fetch_from_gpu_cache_8(int address) {
ivec2 uv = get_gpu_cache_uv(address);
return vec4[8](
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(4, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(5, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(6, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(7, 0))
);
}
vec4[3] fetch_from_gpu_cache_3(int address) {
ivec2 uv = get_gpu_cache_uv(address);
return vec4[3](
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0))
);
}
vec4[3] fetch_from_gpu_cache_3_direct(ivec2 address) {
return vec4[3](
TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)),
TEXEL_FETCH(sGpuCache, address, 0, ivec2(2, 0))
);
}
vec4[4] fetch_from_gpu_cache_4_direct(ivec2 address) {
return vec4[4](
TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)),
TEXEL_FETCH(sGpuCache, address, 0, ivec2(2, 0)),
TEXEL_FETCH(sGpuCache, address, 0, ivec2(3, 0))
);
}
vec4[4] fetch_from_gpu_cache_4(int address) {
ivec2 uv = get_gpu_cache_uv(address);
return vec4[4](
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)),
TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0))
);
}
//TODO: image resource is too specific for this module
struct ImageSource {
RectWithEndpoint uv_rect;
vec4 user_data;
};
ImageSource fetch_image_source(int address) {
//Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
vec4 data[2] = fetch_from_gpu_cache_2(address);
RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
return ImageSource(uv_rect, data[1]);
}
ImageSource fetch_image_source_direct(ivec2 address) {
vec4 data[2] = fetch_from_gpu_cache_2_direct(address);
RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
return ImageSource(uv_rect, data[1]);
}
// Fetch optional extra data for a texture cache resource. This can contain
// a polygon defining a UV rect within the texture cache resource.
// Note: the polygon coordinates are in homogeneous space.
struct ImageSourceExtra {
vec4 st_tl;
vec4 st_tr;
vec4 st_bl;
vec4 st_br;
};
ImageSourceExtra fetch_image_source_extra(int address) {
vec4 data[4] = fetch_from_gpu_cache_4(address + VECS_PER_IMAGE_RESOURCE);
return ImageSourceExtra(
data[0],
data[1],
data[2],
data[3]
);
}
#endif //WR_VERTEX_SHADER
/* 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/. */
flat varying highp vec4 vTransformBounds;
#ifdef WR_VERTEX_SHADER
#define VECS_PER_TRANSFORM 8U
uniform HIGHP_SAMPLER_FLOAT sampler2D sTransformPalette;
void init_transform_vs(vec4 local_bounds) {
vTransformBounds = local_bounds;
}
struct Transform {
mat4 m;
mat4 inv_m;
bool is_axis_aligned;
};
Transform fetch_transform(int id) {
Transform transform;
transform.is_axis_aligned = (id >> 23) == 0;
int index = id & 0x007fffff;
// Create a UV base coord for each 8 texels.
// This is required because trying to use an offset
// of more than 8 texels doesn't work on some versions
// of macOS.
ivec2 uv = get_fetch_uv(index, VECS_PER_TRANSFORM);
ivec2 uv0 = ivec2(uv.x + 0, uv.y);
transform.m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(0, 0));
transform.m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(1, 0));
transform.m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(2, 0));
transform.m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(3, 0));
transform.inv_m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(4, 0));
transform.inv_m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(5, 0));
transform.inv_m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(6, 0));
transform.inv_m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(7, 0));
return transform;
}
// Return the intersection of the plane (set up by "normal" and "point")
// with the ray (set up by "ray_origin" and "ray_dir"),
// writing the resulting scaler into "t".
bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
{
float denom = dot(normal, ray_dir);
if (abs(denom) > 1e-6) {
vec3 d = pt - ray_origin;
t = dot(d, normal) / denom;
return t >= 0.0;
}
return false;
}
// Apply the inverse transform "inv_transform"
// to the reference point "ref" in CSS space,
// producing a local point on a Transform plane,
// set by a base point "a" and a normal "n".
vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) {
vec3 p = vec3(ref, -10000.0);
vec3 d = vec3(0, 0, 1.0);
float t = 0.0;
// get an intersection of the Transform plane with Z axis vector,
// originated from the "ref" point
ray_plane(n, a, p, d, t);
float z = p.z + d.z * t; // Z of the visible point on the Transform
vec4 r = inv_transform * vec4(ref, z, 1.0);
return r;
}
// Given a CSS space position, transform it back into the Transform space.
vec4 get_node_pos(vec2 pos, Transform transform) {
// get a point on the scroll node plane
vec4 ah = transform.m * vec4(0.0, 0.0, 0.0, 1.0);
vec3 a = ah.xyz / ah.w;
// get the normal to the scroll node plane
vec3 n = transpose(mat3(transform.inv_m)) * vec3(0.0, 0.0, 1.0);
return untransform(pos, n, a, transform.inv_m);
}
#endif //WR_VERTEX_SHADER
#ifdef WR_FRAGMENT_SHADER
// Assume transform bounds are set to a large scale to signal they are invalid.
bool has_valid_transform_bounds() {
return vTransformBounds.w < 1.0e15;
}
float init_transform_fs(vec2 local_pos) {
// Ideally we want to track distances in screen space after transformation
// as signed distance calculations lose context about the direction vector
// to exit the geometry, merely remembering the minimum distance to the
// exit. However, we can't always sanely track distances in screen space
// due to perspective transforms, clipping, and other concerns, so we do
// this in local space. However, this causes problems tracking distances
// in local space when attempting to scale by a uniform AA range later in
// the presence of a transform which actually has non-uniform scaling.
//
// To work around this, we independently track the distances on the local
// space X and Y axes and then scale them by the independent AA ranges (as
// computed from fwidth derivatives) for the X and Y axes. This can break
// down at certain angles (45 degrees or close to it), but still gives a
// better approximation of screen-space distances in the presence of non-
// uniform scaling for other rotations.
//
// Get signed distance from local rect bounds.
vec2 d = signed_distance_rect_xy(
local_pos,
vTransformBounds.xy,
vTransformBounds.zw
);
// Find the appropriate distance to apply the AA smoothstep over.
vec2 aa_range = compute_aa_range_xy(local_pos);
// Only apply AA to fragments outside the signed distance field.
return distance_aa_xy(aa_range, d);
}
float init_transform_rough_fs(vec2 local_pos) {
return point_inside_rect(
local_pos,
vTransformBounds.xy,
vTransformBounds.zw
);
}
#endif //WR_FRAGMENT_SHADER
#define EXTEND_MODE_CLAMP 0
#define EXTEND_MODE_REPEAT 1
#define SUBPX_DIR_NONE 0
#define SUBPX_DIR_HORIZONTAL 1
#define SUBPX_DIR_VERTICAL 2
#define SUBPX_DIR_MIXED 3
#define RASTER_LOCAL 0
#define RASTER_SCREEN 1
uniform sampler2D sClipMask;
#ifndef SWGL_CLIP_MASK
// TODO: convert back to RectWithEndpoint if driver issues are resolved, if ever.
flat varying mediump vec4 vClipMaskUvBounds;
varying highp vec2 vClipMaskUv;
#endif
#ifdef WR_VERTEX_SHADER
#define COLOR_MODE_ALPHA 0
#define COLOR_MODE_SUBPX_DUAL_SOURCE 1
#define COLOR_MODE_BITMAP_SHADOW 2
#define COLOR_MODE_COLOR_BITMAP 3
#define COLOR_MODE_IMAGE 4
#define COLOR_MODE_MULTIPLY_DUAL_SOURCE 5
uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF;
uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI;
// Instanced attributes
PER_INSTANCE in ivec4 aData;
#define VECS_PER_PRIM_HEADER_F 2U
#define VECS_PER_PRIM_HEADER_I 2U
struct Instance
{
int prim_header_address;
int clip_address;
int segment_index;
int flags;
int resource_address;
int brush_kind;
};
Instance decode_instance_attributes() {
Instance instance;
instance.prim_header_address = aData.x;
instance.clip_address = aData.y;
instance.segment_index = aData.z & 0xffff;
instance.flags = aData.z >> 16;
instance.resource_address = aData.w & 0xffffff;
instance.brush_kind = aData.w >> 24;
return instance;
}
struct PrimitiveHeader {
RectWithEndpoint local_rect;
RectWithEndpoint local_clip_rect;
float z;
int specific_prim_address;
int transform_id;
int picture_task_address;
ivec4 user_data;
};
PrimitiveHeader fetch_prim_header(int index) {
PrimitiveHeader ph;
ivec2 uv_f = get_fetch_uv(index, VECS_PER_PRIM_HEADER_F);
vec4 local_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(0, 0));
vec4 local_clip_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(1, 0));
ph.local_rect = RectWithEndpoint(local_rect.xy, local_rect.zw);
ph.local_clip_rect = RectWithEndpoint(local_clip_rect.xy, local_clip_rect.zw);
ivec2 uv_i = get_fetch_uv(index, VECS_PER_PRIM_HEADER_I);
ivec4 data0 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(0, 0));
ivec4 data1 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(1, 0));
ph.z = float(data0.x);
ph.specific_prim_address = data0.y;
ph.transform_id = data0.z;
ph.picture_task_address = data0.w;
ph.user_data = data1;
return ph;
}
struct VertexInfo {
vec2 local_pos;
vec4 world_pos;
};
VertexInfo write_vertex(vec2 local_pos,
RectWithEndpoint local_clip_rect,
float z,
Transform transform,
PictureTask task) {
// Clamp to the two local clip rects.
vec2 clamped_local_pos = rect_clamp(local_clip_rect, local_pos);
// Transform the current vertex to world space.
vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
// Convert the world positions to device pixel space.
vec2 device_pos = world_pos.xy * task.device_pixel_scale;
// Apply offsets for the render task to get correct screen location.
vec2 final_offset = -task.content_origin + task.task_rect.p0;
gl_Position = uTransform * vec4(device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w);
VertexInfo vi = VertexInfo(
clamped_local_pos,
world_pos
);
return vi;
}
RectWithEndpoint clip_and_init_antialiasing(RectWithEndpoint segment_rect,
RectWithEndpoint prim_rect,
RectWithEndpoint clip_rect,
int edge_flags,
float z,
Transform transform,
PictureTask task) {
#ifdef SWGL_ANTIALIAS
// Check if the bounds are smaller than the unmodified segment rect. If so,
// it is safe to enable AA on those edges.
bvec4 clipped = bvec4(greaterThan(clip_rect.p0, segment_rect.p0),
lessThan(clip_rect.p1, segment_rect.p1));
swgl_antiAlias(edge_flags | (clipped.x ? 1 : 0) | (clipped.y ? 2 : 0) |
(clipped.z ? 4 : 0) | (clipped.w ? 8 : 0));
#endif
segment_rect.p0 = clamp(segment_rect.p0, clip_rect.p0, clip_rect.p1);
segment_rect.p1 = clamp(segment_rect.p1, clip_rect.p0, clip_rect.p1);
#ifndef SWGL_ANTIALIAS
prim_rect.p0 = clamp(prim_rect.p0, clip_rect.p0, clip_rect.p1);
prim_rect.p1 = clamp(prim_rect.p1, clip_rect.p0, clip_rect.p1);
// Select between the segment and prim edges based on edge mask.
// We must perform the bitwise-and for each component individually, as a
// vector bitwise-and followed by conversion to bvec4 causes shader
// compilation crashes on some Adreno devices. See bug 1715746.
bvec4 clip_edge_mask = bvec4(bool(edge_flags & 1), bool(edge_flags & 2), bool(edge_flags & 4), bool(edge_flags & 8));
init_transform_vs(mix(
vec4(vec2(-1e16), vec2(1e16)),
vec4(segment_rect.p0, segment_rect.p1),
clip_edge_mask
));
// As this is a transform shader, extrude by 2 (local space) pixels
// in each direction. This gives enough space around the edge to
// apply distance anti-aliasing. Technically, it:
// (a) slightly over-estimates the number of required pixels in the simple case.
// (b) might not provide enough edge in edge case perspective projections.
// However, it's fast and simple. If / when we ever run into issues, we
// can do some math on the projection matrix to work out a variable
// amount to extrude.
// Only extrude along edges where we are going to apply AA.
float extrude_amount = 2.0;
vec4 extrude_distance = mix(vec4(0.0), vec4(extrude_amount), clip_edge_mask);
segment_rect.p0 -= extrude_distance.xy;
segment_rect.p1 += extrude_distance.zw;
#endif
return segment_rect;
}
void write_clip(vec4 world_pos, ClipArea area, PictureTask task) {
#ifdef SWGL_CLIP_MASK
swgl_clipMask(
sClipMask,
(task.task_rect.p0 - task.content_origin) - (area.task_rect.p0 - area.screen_origin),
area.task_rect.p0,
rect_size(area.task_rect)
);
#else
vec2 uv = world_pos.xy * area.device_pixel_scale +
world_pos.w * (area.task_rect.p0 - area.screen_origin);
vClipMaskUvBounds = vec4(
area.task_rect.p0,
area.task_rect.p1
);
vClipMaskUv = uv;
#endif
}
// Read the exta image data containing the homogeneous screen space coordinates
// of the corners, interpolate between them, and return real screen space UV.
vec2 get_image_quad_uv(int address, vec2 f) {
ImageSourceExtra extra_data = fetch_image_source_extra(address);
vec4 x = mix(extra_data.st_tl, extra_data.st_tr, f.x);
vec4 y = mix(extra_data.st_bl, extra_data.st_br, f.x);
vec4 z = mix(x, y, f.y);
return z.xy / z.w;
}
#endif //WR_VERTEX_SHADER
#ifdef WR_FRAGMENT_SHADER
struct Fragment {
vec4 color;
#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
vec4 blend;
#endif
};
float do_clip() {
#ifdef SWGL_CLIP_MASK
// SWGL relies on builtin clip-mask support to do this more efficiently,
// so no clipping is required here.
return 1.0;
#else
// check for the dummy bounds, which are given to the opaque objects
if (vClipMaskUvBounds.xy == vClipMaskUvBounds.zw) {
return 1.0;
}
// anything outside of the mask is considered transparent
//Note: we assume gl_FragCoord.w == interpolated(1 / vClipMaskUv.w)
vec2 mask_uv = vClipMaskUv * gl_FragCoord.w;
bvec2 left = lessThanEqual(vClipMaskUvBounds.xy, mask_uv); // inclusive
bvec2 right = greaterThan(vClipMaskUvBounds.zw, mask_uv); // non-inclusive
// bail out if the pixel is outside the valid bounds
if (!all(bvec4(left, right))) {
return 0.0;
}
// finally, the slow path - fetch the mask value from an image
return texelFetch(sClipMask, ivec2(mask_uv), 0).r;
#endif
}
#endif //WR_FRAGMENT_SHADER
/* 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/. */
/// # Brush vertex shaders memory layout
///
/// The overall memory layout is the same for all brush shaders.
///
/// The vertex shader receives a minimal amount of data from vertex attributes (packed into a single
/// ivec4 per instance) and the rest is fetched from various uniform samplers using offsets decoded
/// from the vertex attributes.
///
/// The diagram below shows the the various pieces of data fectched in the vertex shader:
///
///```ascii
/// (sPrimitiveHeadersI)
/// (VBO) +-----------------------+
/// +----------------------------+ +----------------------------> | Int header |
/// | Instance vertex attributes | | (sPrimitiveHeadersF) | |
/// | | | +---------------------+ | z |
/// | x: prim_header_address +-------+---> | Float header | | specific_address +-----+
/// | y: picture_task_address +---------+ | | | transform_address +---+ |
/// | clip_address +-----+ | | local_rect | | user_data | | |
/// | z: flags | | | | local_clip_rect | +-----------------------+ | |
/// | segment_index | | | +---------------------+ | |
/// | w: resource_address +--+ | | | |
/// +----------------------------+ | | | (sGpuCache) | |
/// | | | (sGpuCache) +------------+ | |
/// | | | +---------------+ | Transform | <--------+ |
/// (sGpuCache) | | +-> | Picture task | +------------+ |
/// +-------------+ | | | | |
/// | Resource | <---+ | | ... | |
/// | | | +---------------+ +--------------------------------+
/// | | | |
/// +-------------+ | (sGpuCache) v (sGpuCache)
/// | +---------------+ +--------------+---------------+-+-+
/// +-----> | Clip area | | Brush data | Segment data | | |
/// | | | | | | |
/// | ... | | ... | ... | | | ...
/// +---------------+ +--------------+---------------+-+-+
///```
///
/// - Segment data address is obtained by combining the address stored in the int header and the
/// segment index decoded from the vertex attributes.
/// - Resource data is optional, some brush types (such as images) store some extra data there while
/// other brush types don't use it.
///
#if (defined(WR_FEATURE_ALPHA_PASS) || defined(WR_FEATURE_ANTIALIASING)) && !defined(SWGL_ANTIALIAS)
varying highp vec2 v_local_pos;
#endif
#ifdef WR_VERTEX_SHADER
void brush_vs(
VertexInfo vi,
int prim_address,
RectWithEndpoint local_rect,
RectWithEndpoint segment_rect,
ivec4 prim_user_data,
int specific_resource_address,
mat4 transform,
PictureTask pic_task,
int brush_flags,
vec4 segment_data
);
// Forward-declare the text vertex shader entry point which is currently
// different from other brushes.
void text_shader_main(
Instance instance,
PrimitiveHeader ph,
Transform transform,
PictureTask task,
ClipArea clip_area
);
#define VECS_PER_SEGMENT 2
#define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION 1
#define BRUSH_FLAG_SEGMENT_RELATIVE 2
#define BRUSH_FLAG_SEGMENT_REPEAT_X 4
#define BRUSH_FLAG_SEGMENT_REPEAT_Y 8
#define BRUSH_FLAG_SEGMENT_REPEAT_X_ROUND 16
#define BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND 32
#define BRUSH_FLAG_SEGMENT_REPEAT_X_CENTERED 64
#define BRUSH_FLAG_SEGMENT_REPEAT_Y_CENTERED 128
#define BRUSH_FLAG_SEGMENT_NINEPATCH_MIDDLE 256
#define BRUSH_FLAG_TEXEL_RECT 512
#define BRUSH_FLAG_FORCE_AA 1024
#define INVALID_SEGMENT_INDEX 0xffff
void brush_shader_main_vs(
Instance instance,
PrimitiveHeader ph,
Transform transform,
PictureTask pic_task,
ClipArea clip_area
) {
int edge_flags = (instance.flags >> 12) & 0xf;
int brush_flags = instance.flags & 0xfff;
// Fetch the segment of this brush primitive we are drawing.
vec4 segment_data;
RectWithEndpoint segment_rect;
if (instance.segment_index == INVALID_SEGMENT_INDEX) {
segment_rect = ph.local_rect;
segment_data = vec4(0.0);
} else {
int segment_address = ph.specific_prim_address +
VECS_PER_SPECIFIC_BRUSH +
instance.segment_index * VECS_PER_SEGMENT;
vec4[2] segment_info = fetch_from_gpu_cache_2(segment_address);
segment_rect = RectWithEndpoint(segment_info[0].xy, segment_info[0].zw);
segment_rect.p0 += ph.local_rect.p0;
segment_rect.p1 += ph.local_rect.p0;
segment_data = segment_info[1];
}
// Most of the time this is the segment rect, but when doing the edge AA
// it is inflated.
RectWithEndpoint adjusted_segment_rect = segment_rect;
bool antialiased = !transform.is_axis_aligned || ((brush_flags & BRUSH_FLAG_FORCE_AA) != 0);
// Write the normal vertex information out.
if (antialiased) {
adjusted_segment_rect = clip_and_init_antialiasing(
segment_rect,
ph.local_rect,
ph.local_clip_rect,
edge_flags,
ph.z,
transform,
pic_task
);
// The clip was taken into account in clip_and_init_antialiasing, remove
// it so that it doesn't interfere with the aa.
ph.local_clip_rect.p0 = vec2(-1.0e16);
ph.local_clip_rect.p1 = vec2(1.0e16);
} else {
// The common case for most CSS content.
// TODO(gw): transform bounds may be referenced by
// the fragment shader when running in
// the alpha pass, even on non-transformed
// items. For now, just ensure it has no
// effect. We can tidy this up as we move
// more items to be brush shaders.
#if defined(WR_FEATURE_ALPHA_PASS) && !defined(SWGL_ANTIALIAS)
init_transform_vs(vec4(vec2(-1.0e16), vec2(1.0e16)));
#endif
}
// Select the corner of the local rect that we are processing.
vec2 local_pos = mix(adjusted_segment_rect.p0, adjusted_segment_rect.p1, aPosition.xy);
VertexInfo vi = write_vertex(
local_pos,
ph.local_clip_rect,
ph.z,
transform,
pic_task
);
// For brush instances in the alpha pass, always write
// out clip information.
// TODO(gw): It's possible that we might want alpha
// shaders that don't clip in the future,
// but it's reasonable to assume that one
// implies the other, for now.
// SW-WR may decay some requests for alpha-pass shaders to
// the opaque version if only the clip-mask is required. In
// that case the opaque vertex shader must still write out
// the clip information, which is cheap to do for SWGL.
#if defined(WR_FEATURE_ALPHA_PASS) || defined(SWGL_CLIP_MASK)
write_clip(
vi.world_pos,
clip_area,
pic_task
);
#endif
// Run the specific brush VS code to write interpolators.
brush_vs(
vi,
ph.specific_prim_address,
ph.local_rect,
segment_rect,
ph.user_data,
instance.resource_address,
transform.m,
pic_task,
brush_flags,
segment_data
);
#if (defined(WR_FEATURE_ALPHA_PASS) || defined(WR_FEATURE_ANTIALIASING)) && !defined(SWGL_ANTIALIAS)
v_local_pos = vi.local_pos;
#endif
}
#ifndef WR_VERTEX_SHADER_MAIN_FUNCTION
// If the entry-point was not overridden before including the brush shader,
// use the default one.
#define WR_VERTEX_SHADER_MAIN_FUNCTION brush_shader_main_vs
#endif
void main(void) {
Instance instance = decode_instance_attributes();
PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address);
Transform transform = fetch_transform(ph.transform_id);
PictureTask task = fetch_picture_task(ph.picture_task_address);
ClipArea clip_area = fetch_clip_area(instance.clip_address);
WR_VERTEX_SHADER_MAIN_FUNCTION(instance, ph, transform, task, clip_area);
}
#endif // WR_VERTEX_SHADER
#ifdef WR_FRAGMENT_SHADER
float antialias_brush() {
#if (defined(WR_FEATURE_ALPHA_PASS) || defined(WR_FEATURE_ANTIALIASING)) && !defined(SWGL_ANTIALIAS)
return init_transform_fs(v_local_pos);
#else
return 1.0;
#endif
}
Fragment brush_fs();
void main(void) {
#ifdef WR_FEATURE_DEBUG_OVERDRAW
oFragColor = WR_DEBUG_OVERDRAW_COLOR;
#else
Fragment frag = brush_fs();
#ifdef WR_FEATURE_ALPHA_PASS
// Apply the clip mask
float clip_alpha = do_clip();
frag.color *= clip_alpha;
#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
oFragBlend = frag.blend * clip_alpha;
#endif
#endif
write_output(frag.color);
#endif
}
#endif
// Interpolated UV coordinates to sample.
varying highp vec2 v_uv;
#ifdef WR_FEATURE_ALPHA_PASS
flat varying mediump vec4 v_color;
flat varying mediump vec2 v_mask_swizzle;
flat varying mediump vec2 v_tile_repeat_bounds;
#endif
// Normalized bounds of the source image in the texture.
flat varying highp vec4 v_uv_bounds;
// Normalized bounds of the source image in the texture, adjusted to avoid
// sampling artifacts.
flat varying highp vec4 v_uv_sample_bounds;
// Flag to allow perspective interpolation of UV.
// Packed in to vector to work around bug 1630356.
flat varying mediump vec2 v_perspective;
#ifdef WR_VERTEX_SHADER
// Must match the AlphaType enum.
#define BLEND_MODE_ALPHA 0
#define BLEND_MODE_PREMUL_ALPHA 1
struct ImageBrushData {
vec4 color;
vec4 background_color;
vec2 stretch_size;
};
ImageBrushData fetch_image_data(int address) {
vec4[3] raw_data = fetch_from_gpu_cache_3(address);
ImageBrushData data = ImageBrushData(
raw_data[0],
raw_data[1],
raw_data[2].xy
);
return data;
}
vec2 modf2(vec2 x, vec2 y) {
return x - y * floor(x/y);
}
void brush_vs(
VertexInfo vi,
int prim_address,
RectWithEndpoint prim_rect,
RectWithEndpoint segment_rect,
ivec4 prim_user_data,
int specific_resource_address,
mat4 transform,
PictureTask pic_task,
int brush_flags,
vec4 segment_data
) {
ImageBrushData image_data = fetch_image_data(prim_address);
// If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
// non-normalized texture coordinates.
#ifdef WR_FEATURE_TEXTURE_RECT
vec2 texture_size = vec2(1, 1);
#else
vec2 texture_size = vec2(TEX_SIZE(sColor0));
#endif
ImageSource res = fetch_image_source(specific_resource_address);
vec2 uv0 = res.uv_rect.p0;
vec2 uv1 = res.uv_rect.p1;
RectWithEndpoint local_rect = prim_rect;
vec2 stretch_size = image_data.stretch_size;
if (stretch_size.x < 0.0) {
stretch_size = rect_size(local_rect);
}
// If this segment should interpolate relative to the
// segment, modify the parameters for that.
if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
local_rect = segment_rect;
stretch_size = rect_size(local_rect);
if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
// If the extra data is a texel rect, modify the UVs.
vec2 uv_size = res.uv_rect.p1 - res.uv_rect.p0;
uv0 = res.uv_rect.p0 + segment_data.xy * uv_size;
uv1 = res.uv_rect.p0 + segment_data.zw * uv_size;
}
#ifdef WR_FEATURE_REPETITION
// TODO(bug 1609893): Move this logic to the CPU as well as other sources of
// branchiness in this shader.
if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
// Value of the stretch size with repetition. We have to compute it for
// both axis even if we only repeat on one axis because the value for
// each axis depends on what the repeated value would have been for the
// other axis.
vec2 repeated_stretch_size = stretch_size;
// Size of the uv rect of the segment we are considering when computing
// the repetitions. For the fill area it is a tad more complicated as we
// have to use the uv size of the top-middle segment to drive horizontal
// repetitions, and the size of the left-middle segment to drive vertical
// repetitions. So we track the reference sizes for both axis separately
// even though in the common case (the border segments) they are the same.
vec2 horizontal_uv_size = uv1 - uv0;
vec2 vertical_uv_size = uv1 - uv0;
// We use top and left sizes by default and fall back to bottom and right
// when a size is empty.
if ((brush_flags & BRUSH_FLAG_SEGMENT_NINEPATCH_MIDDLE) != 0) {
repeated_stretch_size = segment_rect.p0 - prim_rect.p0;
float epsilon = 0.001;
// Adjust the the referecne uv size to compute vertical repetitions for
// the fill area.
vertical_uv_size.x = uv0.x - res.uv_rect.p0.x;
if (vertical_uv_size.x < epsilon || repeated_stretch_size.x < epsilon) {
vertical_uv_size.x = res.uv_rect.p1.x - uv1.x;
repeated_stretch_size.x = prim_rect.p1.x - segment_rect.p1.x;
}
// Adjust the the referecne uv size to compute horizontal repetitions
// for the fill area.
horizontal_uv_size.y = uv0.y - res.uv_rect.p0.y;
if (horizontal_uv_size.y < epsilon || repeated_stretch_size.y < epsilon) {
horizontal_uv_size.y = res.uv_rect.p1.y - uv1.y;
repeated_stretch_size.y = prim_rect.p1.y - segment_rect.p1.y;
}
}
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
float uv_ratio = horizontal_uv_size.x / horizontal_uv_size.y;
stretch_size.x = repeated_stretch_size.y * uv_ratio;
}
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
float uv_ratio = vertical_uv_size.y / vertical_uv_size.x;
stretch_size.y = repeated_stretch_size.x * uv_ratio;
}
} else {
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
stretch_size.x = segment_data.z - segment_data.x;
}
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
stretch_size.y = segment_data.w - segment_data.y;
}
}
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X_ROUND) != 0) {
float segment_rect_width = segment_rect.p1.x - segment_rect.p0.x;
float nx = max(1.0, round(segment_rect_width / stretch_size.x));
stretch_size.x = segment_rect_width / nx;
}
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND) != 0) {
float segment_rect_height = segment_rect.p1.y - segment_rect.p0.y;
float ny = max(1.0, round(segment_rect_height / stretch_size.y));
stretch_size.y = segment_rect_height / ny;
}
#endif
}
float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;
v_perspective.x = perspective_interpolate;
// Handle case where the UV coords are inverted (e.g. from an
// external image).
vec2 min_uv = min(uv0, uv1);
vec2 max_uv = max(uv0, uv1);
v_uv_sample_bounds = vec4(
min_uv + vec2(0.5),
max_uv - vec2(0.5)
) / texture_size.xyxy;
vec2 f = (vi.local_pos - local_rect.p0) / rect_size(local_rect);
#ifdef WR_FEATURE_ALPHA_PASS
int color_mode = prim_user_data.x & 0xffff;
int blend_mode = prim_user_data.x >> 16;
#endif
// Derive the texture coordinates for this image, based on
// whether the source image is a local-space or screen-space
// image.
int raster_space = prim_user_data.y;
if (raster_space == RASTER_SCREEN) {
// Since the screen space UVs specify an arbitrary quad, do
// a bilinear interpolation to get the correct UV for this
// local position.
f = get_image_quad_uv(specific_resource_address, f);
}
// Offset and scale v_uv here to avoid doing it in the fragment shader.
vec2 repeat = rect_size(local_rect) / stretch_size;
v_uv = mix(uv0, uv1, f) - min_uv;
v_uv *= repeat.xy;
vec2 normalized_offset = vec2(0.0);
#ifdef WR_FEATURE_REPETITION
// In the case of border-image-repeat: repeat, we must apply an offset so that
// the first tile is centered.
//
// This is derived from:
// uv_size = max_uv - min_uv
// repeat = local_rect.size / stetch_size
// layout_offset = local_rect.size / 2 - strecth_size / 2
// texel_offset = layout_offset * uv_size / stretch_size
// texel_offset = uv_size / 2 * (local_rect.size / stretch_size - stretch_size / stretch_size)
// texel_offset = uv_size / 2 * (repeat - 1)
//
// The offset is then adjusted so that it loops in the [0, uv_size] range.
// In principle this is simply a modulo:
//
// adjusted_offset = fact((repeat - 1)/2) * uv_size
//
// However we don't want fract's behavior with negative numbers which happens when the pattern
// is larger than the local rect (repeat is between 0 and 1), so we shift the content by 1 to
// remain positive.
//
// adjusted_offset = fract(repeat/2 - 1/2 + 1) * uv_size
//
// `uv - offset` will go through another modulo in the fragment shader for which we again don't
// want the behavior for nagative numbers. We rearrange this here in the form
// `uv + (uv_size - offset)` to prevent that.
//
// adjusted_offset = (1 - fract(repeat/2 - 1/2 + 1)) * uv_size
//
// We then separate the normalized part of the offset which we also need elsewhere.
bvec2 centered = bvec2(brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X_CENTERED,
brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y_CENTERED);
// Use mix() rather than if statements due to a miscompilation on Adreno 3xx. See bug 1853573.
normalized_offset = mix(vec2(0.0), 1.0 - fract(repeat * 0.5 + 0.5), centered);
v_uv += normalized_offset * (max_uv - min_uv);
#endif
v_uv /= texture_size;
if (perspective_interpolate == 0.0) {
v_uv *= vi.world_pos.w;
}
#ifdef WR_FEATURE_TEXTURE_RECT
v_uv_bounds = vec4(0.0, 0.0, vec2(textureSize(sColor0)));
#else
v_uv_bounds = vec4(min_uv, max_uv) / texture_size.xyxy;
#endif
#ifdef WR_FEATURE_REPETITION
// Normalize UV to 0..1 scale only if using repetition. Otherwise, leave
// UVs unnormalized since we won't compute a modulus without repetition
// enabled.
v_uv /= (v_uv_bounds.zw - v_uv_bounds.xy);
#endif
#ifdef WR_FEATURE_ALPHA_PASS
v_tile_repeat_bounds = repeat.xy + normalized_offset;
float opacity = float(prim_user_data.z) / 65535.0;
switch (blend_mode) {
case BLEND_MODE_ALPHA:
image_data.color.a *= opacity;
break;
case BLEND_MODE_PREMUL_ALPHA:
default:
image_data.color *= opacity;
break;
}
switch (color_mode) {
case COLOR_MODE_ALPHA:
case COLOR_MODE_BITMAP_SHADOW:
#ifdef SWGL_BLEND
swgl_blendDropShadow(image_data.color);
v_mask_swizzle = vec2(1.0, 0.0);
v_color = vec4(1.0);
#else
v_mask_swizzle = vec2(0.0, 1.0);
v_color = image_data.color;
#endif
break;
case COLOR_MODE_IMAGE:
v_mask_swizzle = vec2(1.0, 0.0);
v_color = image_data.color;
break;
case COLOR_MODE_COLOR_BITMAP:
v_mask_swizzle = vec2(1.0, 0.0);
v_color = vec4(image_data.color.a);
break;
case COLOR_MODE_SUBPX_DUAL_SOURCE:
v_mask_swizzle = vec2(image_data.color.a, 0.0);
v_color = image_data.color;
break;
case COLOR_MODE_MULTIPLY_DUAL_SOURCE:
v_mask_swizzle = vec2(-image_data.color.a, image_data.color.a);
v_color = image_data.color;
break;
default:
v_mask_swizzle = vec2(0.0);
v_color = vec4(1.0);
}
#endif
}
#endif
#ifdef WR_FRAGMENT_SHADER
vec2 compute_repeated_uvs(float perspective_divisor) {
#ifdef WR_FEATURE_REPETITION
vec2 uv_size = v_uv_bounds.zw - v_uv_bounds.xy;
#ifdef WR_FEATURE_ALPHA_PASS
vec2 local_uv = v_uv * perspective_divisor;
// This prevents the uv on the top and left parts of the primitive that was inflated
// for anti-aliasing purposes from going beyound the range covered by the regular
// (non-inflated) primitive.
local_uv = max(local_uv, vec2(0.0));
// Handle horizontal and vertical repetitions.
vec2 repeated_uv = fract(local_uv) * uv_size + v_uv_bounds.xy;
// This takes care of the bottom and right inflated parts.
// We do it after the modulo because the latter wraps around the values exactly on
// the right and bottom edges, which we do not want.
if (local_uv.x >= v_tile_repeat_bounds.x) {
repeated_uv.x = v_uv_bounds.z;
}
if (local_uv.y >= v_tile_repeat_bounds.y) {
repeated_uv.y = v_uv_bounds.w;
}
#else
vec2 repeated_uv = fract(v_uv * perspective_divisor) * uv_size + v_uv_bounds.xy;
#endif
return repeated_uv;
#else
return v_uv * perspective_divisor + v_uv_bounds.xy;
#endif
}
Fragment brush_fs() {
float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective.x);
vec2 repeated_uv = compute_repeated_uvs(perspective_divisor);
// Clamp the uvs to avoid sampling artifacts.
vec2 uv = clamp(repeated_uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);
vec4 texel = TEX_SAMPLE(sColor0, uv);
Fragment frag;
#ifdef WR_FEATURE_ALPHA_PASS
#ifdef WR_FEATURE_ANTIALIASING
float alpha = antialias_brush();
#else
float alpha = 1.0;
#endif
#ifndef WR_FEATURE_DUAL_SOURCE_BLENDING
texel.rgb = texel.rgb * v_mask_swizzle.x + texel.aaa * v_mask_swizzle.y;
#endif
vec4 alpha_mask = texel * alpha;
frag.color = v_color * alpha_mask;
#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
frag.blend = alpha_mask * v_mask_swizzle.x + alpha_mask.aaaa * v_mask_swizzle.y;
#endif
#else
frag.color = texel;
#endif
return frag;
}
#if defined(SWGL_DRAW_SPAN) && (!defined(WR_FEATURE_ALPHA_PASS) || !defined(WR_FEATURE_DUAL_SOURCE_BLENDING))
void swgl_drawSpanRGBA8() {
if (!swgl_isTextureRGBA8(sColor0)) {
return;
}
#ifdef WR_FEATURE_ALPHA_PASS
if (v_mask_swizzle != vec2(1.0, 0.0)) {
return;
}
#endif
float perspective_divisor = mix(swgl_forceScalar(gl_FragCoord.w), 1.0, v_perspective.x);
#ifdef WR_FEATURE_REPETITION
// Get the UVs before any repetition, scaling, or offsetting has occurred...
vec2 uv = v_uv * perspective_divisor;
#else
vec2 uv = compute_repeated_uvs(perspective_divisor);
#endif
#ifdef WR_FEATURE_ALPHA_PASS
if (v_color != vec4(1.0)) {
#ifdef WR_FEATURE_REPETITION
swgl_commitTextureRepeatColorRGBA8(sColor0, uv, v_tile_repeat_bounds, v_uv_bounds, v_uv_sample_bounds, v_color);
#else
swgl_commitTextureColorRGBA8(sColor0, uv, v_uv_sample_bounds, v_color);
#endif
return;
}
// No color scaling required, so just fall through to a normal textured span...
#endif
#ifdef WR_FEATURE_REPETITION
#ifdef WR_FEATURE_ALPHA_PASS
swgl_commitTextureRepeatRGBA8(sColor0, uv, v_tile_repeat_bounds, v_uv_bounds, v_uv_sample_bounds);
#else
swgl_commitTextureRepeatRGBA8(sColor0, uv, vec2(0.0), v_uv_bounds, v_uv_sample_bounds);
#endif
#else
swgl_commitTextureRGBA8(sColor0, uv, v_uv_sample_bounds);
#endif
}
#endif
#endif