mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-21 06:54:45 +00:00
Manually clip primitive geometry
This commit is contained in:
parent
7bddd16a09
commit
bb2095a51d
4 changed files with 264 additions and 145 deletions
|
|
@ -16,10 +16,13 @@ float4 to_device_position_transformed(float2 unit_vertex, Bounds_ScaledPixels bo
|
|||
|
||||
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size);
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds);
|
||||
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds, TransformationMatrix transformation);
|
||||
Bounds_ScaledPixels clip_to_mask(Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels mask);
|
||||
bool transform_is_axis_aligned(TransformationMatrix transformation);
|
||||
Bounds_ScaledPixels mask_in_transform_space(Bounds_ScaledPixels mask,
|
||||
TransformationMatrix transformation);
|
||||
float corner_dash_velocity(float dv1, float dv2);
|
||||
float dash_alpha(float t, float period, float length, float dash_velocity,
|
||||
float antialias_threshold);
|
||||
|
|
@ -51,7 +54,6 @@ struct QuadVertexOutput {
|
|||
float4 background_solid [[flat]];
|
||||
float4 background_color0 [[flat]];
|
||||
float4 background_color1 [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct QuadFragmentInput {
|
||||
|
|
@ -73,10 +75,9 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
|||
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Quad quad = quads[quad_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, quad.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
|
||||
quad.content_mask.bounds);
|
||||
float4 device_position = to_device_position(
|
||||
unit_vertex, clip_to_mask(quad.bounds, quad.content_mask.bounds),
|
||||
viewport_size);
|
||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||
|
||||
GradientColor gradient = prepare_fill_color(
|
||||
|
|
@ -93,8 +94,7 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
|||
border_color,
|
||||
gradient.solid,
|
||||
gradient.color0,
|
||||
gradient.color1,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
gradient.color1};
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
|
|
@ -450,7 +450,6 @@ struct ShadowVertexOutput {
|
|||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct ShadowFragmentInput {
|
||||
|
|
@ -481,17 +480,15 @@ vertex ShadowVertexOutput shadow_vertex(
|
|||
bounds.size.height += 2. * margin;
|
||||
}
|
||||
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, bounds, viewport_size);
|
||||
float4 clip_distance =
|
||||
distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
|
||||
float4 device_position = to_device_position(
|
||||
unit_vertex, clip_to_mask(bounds, shadow.content_mask.bounds),
|
||||
viewport_size);
|
||||
float4 color = hsla_to_rgba(shadow.color);
|
||||
|
||||
return ShadowVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
shadow_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
shadow_id};
|
||||
}
|
||||
|
||||
fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
|
||||
|
|
@ -558,7 +555,6 @@ struct UnderlineVertexOutput {
|
|||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct UnderlineFragmentInput {
|
||||
|
|
@ -575,16 +571,14 @@ vertex UnderlineVertexOutput underline_vertex(
|
|||
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Underline underline = underlines[underline_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, underline.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
|
||||
underline.content_mask.bounds);
|
||||
float4 device_position = to_device_position(
|
||||
unit_vertex, clip_to_mask(underline.bounds, underline.content_mask.bounds),
|
||||
viewport_size);
|
||||
float4 color = hsla_to_rgba(underline.color);
|
||||
return UnderlineVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
underline_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
underline_id};
|
||||
}
|
||||
|
||||
fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
|
||||
|
|
@ -642,11 +636,32 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
|||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
MonochromeSprite sprite = sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds, sprite.transformation);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 device_position;
|
||||
float2 tile_position;
|
||||
float4 clip_distance;
|
||||
if (transform_is_axis_aligned(sprite.transformation)) {
|
||||
Bounds_ScaledPixels mask =
|
||||
mask_in_transform_space(sprite.content_mask.bounds, sprite.transformation);
|
||||
Bounds_ScaledPixels clipped = clip_to_mask(sprite.bounds, mask);
|
||||
device_position = to_device_position_transformed(
|
||||
unit_vertex, clipped, sprite.transformation, viewport_size);
|
||||
float2 local_position =
|
||||
unit_vertex * float2(clipped.size.width, clipped.size.height) +
|
||||
float2(clipped.origin.x, clipped.origin.y);
|
||||
float2 fraction =
|
||||
(local_position - float2(sprite.bounds.origin.x, sprite.bounds.origin.y)) /
|
||||
float2(sprite.bounds.size.width, sprite.bounds.size.height);
|
||||
tile_position = to_tile_position(fraction, sprite.tile, atlas_size);
|
||||
clip_distance = float4(1.0);
|
||||
} else {
|
||||
// A rotated sprite intersected with the axis-aligned mask isn't
|
||||
// representable as a quad, so fall back to per-fragment clipping.
|
||||
device_position = to_device_position_transformed(
|
||||
unit_vertex, sprite.bounds, sprite.transformation, viewport_size);
|
||||
tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
clip_distance = distance_from_clip_rect_transformed(
|
||||
unit_vertex, sprite.bounds, sprite.content_mask.bounds, sprite.transformation);
|
||||
}
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return MonochromeSpriteVertexOutput{
|
||||
device_position,
|
||||
|
|
@ -659,6 +674,8 @@ fragment float4 monochrome_sprite_fragment(
|
|||
MonochromeSpriteFragmentInput input [[stage_in]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
// Only rotated sprites need per-fragment clipping; axis-aligned sprites are
|
||||
// clipped geometrically in the vertex shader.
|
||||
if (any(input.clip_distance < float4(0.0))) {
|
||||
return float4(0.0);
|
||||
}
|
||||
|
|
@ -676,7 +693,6 @@ struct PolychromeSpriteVertexOutput {
|
|||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PolychromeSpriteFragmentInput {
|
||||
|
|
@ -696,16 +712,21 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
|||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PolychromeSprite sprite = sprites[sprite_id];
|
||||
Bounds_ScaledPixels clipped =
|
||||
clip_to_mask(sprite.bounds, sprite.content_mask.bounds);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
to_device_position(unit_vertex, clipped, viewport_size);
|
||||
float2 position =
|
||||
unit_vertex * float2(clipped.size.width, clipped.size.height) +
|
||||
float2(clipped.origin.x, clipped.origin.y);
|
||||
float2 fraction =
|
||||
(position - float2(sprite.bounds.origin.x, sprite.bounds.origin.y)) /
|
||||
float2(sprite.bounds.size.width, sprite.bounds.size.height);
|
||||
float2 tile_position = to_tile_position(fraction, sprite.tile, atlas_size);
|
||||
return PolychromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
sprite_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
sprite_id};
|
||||
}
|
||||
|
||||
fragment float4 polychrome_sprite_fragment(
|
||||
|
|
@ -850,7 +871,6 @@ fragment float4 path_sprite_fragment(
|
|||
struct SurfaceVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct SurfaceFragmentInput {
|
||||
|
|
@ -868,17 +888,21 @@ vertex SurfaceVertexOutput surface_vertex(
|
|||
[[buffer(SurfaceInputIndex_TextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
SurfaceBounds surface = surfaces[surface_id];
|
||||
Bounds_ScaledPixels clipped =
|
||||
clip_to_mask(surface.bounds, surface.content_mask.bounds);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, surface.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
|
||||
surface.content_mask.bounds);
|
||||
// We are going to copy the whole texture, so the texture position corresponds
|
||||
// to the current vertex of the unit triangle.
|
||||
float2 texture_position = unit_vertex;
|
||||
to_device_position(unit_vertex, clipped, viewport_size);
|
||||
// We are going to copy the whole texture, so the texture position
|
||||
// corresponds to the vertex's fraction within the surface bounds.
|
||||
float2 position =
|
||||
unit_vertex * float2(clipped.size.width, clipped.size.height) +
|
||||
float2(clipped.origin.x, clipped.origin.y);
|
||||
float2 texture_position =
|
||||
(position - float2(surface.bounds.origin.x, surface.bounds.origin.y)) /
|
||||
float2(surface.bounds.size.width, surface.bounds.size.height);
|
||||
return SurfaceVertexOutput{
|
||||
device_position,
|
||||
texture_position,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
texture_position};
|
||||
}
|
||||
|
||||
fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
|
||||
|
|
@ -1113,15 +1137,61 @@ float blur_along_x(float x, float y, float sigma, float corner,
|
|||
return integral.y - integral.x;
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
return float4(position.x - clip_bounds.origin.x,
|
||||
clip_bounds.origin.x + clip_bounds.size.width - position.x,
|
||||
position.y - clip_bounds.origin.y,
|
||||
clip_bounds.origin.y + clip_bounds.size.height - position.y);
|
||||
// Intersects `bounds` with `mask` so the emitted geometry never covers pixels
|
||||
// outside the content mask, making per-fragment clipping unnecessary. An empty
|
||||
// intersection collapses to zero size, which rasterizes to nothing. Fragment
|
||||
// shaders reload the original bounds by instance id, so their math is
|
||||
// unaffected by the shrunken geometry.
|
||||
Bounds_ScaledPixels clip_to_mask(Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels mask) {
|
||||
float2 origin = max(float2(bounds.origin.x, bounds.origin.y),
|
||||
float2(mask.origin.x, mask.origin.y));
|
||||
float2 extent =
|
||||
min(float2(bounds.origin.x + bounds.size.width,
|
||||
bounds.origin.y + bounds.size.height),
|
||||
float2(mask.origin.x + mask.size.width,
|
||||
mask.origin.y + mask.size.height));
|
||||
float2 size = max(extent - origin, float2(0.));
|
||||
Bounds_ScaledPixels result = bounds;
|
||||
result.origin.x = origin.x;
|
||||
result.origin.y = origin.y;
|
||||
result.size.width = size.x;
|
||||
result.size.height = size.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Whether the transformation only scales and translates, keeping rectangles
|
||||
// axis-aligned in screen space. Zero scale is excluded so callers can safely
|
||||
// invert the transformation.
|
||||
bool transform_is_axis_aligned(TransformationMatrix transformation) {
|
||||
return transformation.rotation_scale[0][1] == 0. &&
|
||||
transformation.rotation_scale[1][0] == 0. &&
|
||||
transformation.rotation_scale[0][0] != 0. &&
|
||||
transformation.rotation_scale[1][1] != 0.;
|
||||
}
|
||||
|
||||
// Maps the screen-space mask into pre-transform space. Only valid for
|
||||
// axis-aligned transformations; min/max normalization handles negative scale
|
||||
// (e.g. rotation by 180 degrees).
|
||||
Bounds_ScaledPixels mask_in_transform_space(Bounds_ScaledPixels mask,
|
||||
TransformationMatrix transformation) {
|
||||
float2 scale = float2(transformation.rotation_scale[0][0],
|
||||
transformation.rotation_scale[1][1]);
|
||||
float2 translation = float2(transformation.translation[0],
|
||||
transformation.translation[1]);
|
||||
float2 p0 = (float2(mask.origin.x, mask.origin.y) - translation) / scale;
|
||||
float2 p1 = (float2(mask.origin.x + mask.size.width,
|
||||
mask.origin.y + mask.size.height) -
|
||||
translation) /
|
||||
scale;
|
||||
float2 origin = min(p0, p1);
|
||||
float2 size = max(p0, p1) - origin;
|
||||
Bounds_ScaledPixels result = mask;
|
||||
result.origin.x = origin.x;
|
||||
result.origin.y = origin.y;
|
||||
result.size.width = size.x;
|
||||
result.size.height = size.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
|
|
|
|||
|
|
@ -195,17 +195,42 @@ fn distance_from_clip_rect_impl(position: vec2<f32>, clip_bounds: Bounds) -> vec
|
|||
return vec4<f32>(tl.x, br.x, tl.y, br.y);
|
||||
}
|
||||
|
||||
fn distance_from_clip_rect(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds) -> vec4<f32> {
|
||||
let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
|
||||
return distance_from_clip_rect_impl(position, clip_bounds);
|
||||
}
|
||||
|
||||
fn distance_from_clip_rect_transformed(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds, transform: TransformationMatrix) -> vec4<f32> {
|
||||
let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
|
||||
let transformed = transpose(transform.rotation_scale) * position + transform.translation;
|
||||
return distance_from_clip_rect_impl(transformed, clip_bounds);
|
||||
}
|
||||
|
||||
// Intersects `bounds` with `mask` so the emitted geometry never covers pixels
|
||||
// outside the content mask, making per-fragment clipping unnecessary. An empty
|
||||
// intersection collapses to zero size, which rasterizes to nothing. Fragment
|
||||
// shaders reload the original bounds by instance id, so their math is
|
||||
// unaffected by the shrunken geometry.
|
||||
fn clip_to_mask(bounds: Bounds, mask: Bounds) -> Bounds {
|
||||
let origin = max(bounds.origin, mask.origin);
|
||||
let extent = min(bounds.origin + bounds.size, mask.origin + mask.size);
|
||||
return Bounds(origin, max(extent - origin, vec2<f32>(0.0)));
|
||||
}
|
||||
|
||||
// Whether the transformation only scales and translates, keeping rectangles
|
||||
// axis-aligned in screen space. Zero scale is excluded so callers can safely
|
||||
// invert the transformation.
|
||||
fn transform_is_axis_aligned(transform: TransformationMatrix) -> bool {
|
||||
let m = transform.rotation_scale;
|
||||
return m[0][1] == 0.0 && m[1][0] == 0.0 && m[0][0] != 0.0 && m[1][1] != 0.0;
|
||||
}
|
||||
|
||||
// Maps the screen-space mask into pre-transform space. Only valid for
|
||||
// axis-aligned transformations; min/max normalization handles negative scale
|
||||
// (e.g. rotation by 180 degrees).
|
||||
fn mask_in_transform_space(mask: Bounds, transform: TransformationMatrix) -> Bounds {
|
||||
let scale = vec2<f32>(transform.rotation_scale[0][0], transform.rotation_scale[1][1]);
|
||||
let p0 = (mask.origin - transform.translation) / scale;
|
||||
let p1 = (mask.origin + mask.size - transform.translation) / scale;
|
||||
let origin = min(p0, p1);
|
||||
return Bounds(origin, max(p0, p1) - origin);
|
||||
}
|
||||
|
||||
// https://gamedev.stackexchange.com/questions/92015/optimized-linear-to-srgb-glsl
|
||||
fn srgb_to_linear(srgb: vec3<f32>) -> vec3<f32> {
|
||||
let cutoff = srgb < vec3<f32>(0.04045);
|
||||
|
|
@ -531,11 +556,9 @@ struct QuadVarying {
|
|||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) @interpolate(flat) border_color: vec4<f32>,
|
||||
@location(1) @interpolate(flat) quad_id: u32,
|
||||
// TODO: use `clip_distance` once Naga supports it
|
||||
@location(2) clip_distances: vec4<f32>,
|
||||
@location(3) @interpolate(flat) background_solid: vec4<f32>,
|
||||
@location(4) @interpolate(flat) background_color0: vec4<f32>,
|
||||
@location(5) @interpolate(flat) background_color1: vec4<f32>,
|
||||
@location(2) @interpolate(flat) background_solid: vec4<f32>,
|
||||
@location(3) @interpolate(flat) background_color0: vec4<f32>,
|
||||
@location(4) @interpolate(flat) background_color1: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -544,7 +567,7 @@ fn vs_quad(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
|
|||
let quad = load_quad(instance_id);
|
||||
|
||||
var out = QuadVarying();
|
||||
out.position = to_device_position(unit_vertex, quad.bounds);
|
||||
out.position = to_device_position(unit_vertex, clip_to_mask(quad.bounds, quad.content_mask));
|
||||
|
||||
let gradient = prepare_gradient_color(
|
||||
quad.background.tag,
|
||||
|
|
@ -557,17 +580,11 @@ fn vs_quad(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
|
|||
out.background_color1 = gradient.color1;
|
||||
out.border_color = hsla_to_rgba(quad.border_color);
|
||||
out.quad_id = instance_id;
|
||||
out.clip_distances = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
|
||||
// Alpha clip first, since we don't have `clip_distance`.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
||||
let quad = load_quad(input.quad_id);
|
||||
|
||||
let background_color = gradient_color(quad.background, input.position.xy, quad.bounds,
|
||||
|
|
@ -968,8 +985,6 @@ struct ShadowVarying {
|
|||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) @interpolate(flat) color: vec4<f32>,
|
||||
@location(1) @interpolate(flat) shadow_id: u32,
|
||||
//TODO: use `clip_distance` once Naga supports it
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -989,20 +1004,14 @@ fn vs_shadow(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) ins
|
|||
}
|
||||
|
||||
var out = ShadowVarying();
|
||||
out.position = to_device_position(unit_vertex, geometry);
|
||||
out.position = to_device_position(unit_vertex, clip_to_mask(geometry, shadow.content_mask));
|
||||
out.color = hsla_to_rgba(shadow.color);
|
||||
out.shadow_id = instance_id;
|
||||
out.clip_distances = distance_from_clip_rect(unit_vertex, geometry, shadow.content_mask);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
|
||||
// Alpha clip first, since we don't have `clip_distance`.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
||||
let shadow = load_shadow(input.shadow_id);
|
||||
let half_size = shadow.bounds.size / 2.0;
|
||||
let center = shadow.bounds.origin + half_size;
|
||||
|
|
@ -1161,8 +1170,6 @@ struct UnderlineVarying {
|
|||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) @interpolate(flat) color: vec4<f32>,
|
||||
@location(1) @interpolate(flat) underline_id: u32,
|
||||
//TODO: use `clip_distance` once Naga supports it
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -1171,10 +1178,9 @@ fn vs_underline(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index)
|
|||
let underline = load_underline(instance_id);
|
||||
|
||||
var out = UnderlineVarying();
|
||||
out.position = to_device_position(unit_vertex, underline.bounds);
|
||||
out.position = to_device_position(unit_vertex, clip_to_mask(underline.bounds, underline.content_mask));
|
||||
out.color = hsla_to_rgba(underline.color);
|
||||
out.underline_id = instance_id;
|
||||
out.clip_distances = distance_from_clip_rect(unit_vertex, underline.bounds, underline.content_mask);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -1183,11 +1189,6 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
|
|||
const WAVE_FREQUENCY: f32 = 2.0;
|
||||
const WAVE_HEIGHT_RATIO: f32 = 0.8;
|
||||
|
||||
// Alpha clip first, since we don't have `clip_distance`.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
||||
let underline = load_underline(input.underline_id);
|
||||
if (underline.wavy == 0u)
|
||||
{
|
||||
|
|
@ -1227,7 +1228,7 @@ struct MonoSpriteVarying {
|
|||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) tile_position: vec2<f32>,
|
||||
@location(1) @interpolate(flat) color: vec4<f32>,
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
@location(2) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -1236,11 +1237,21 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index
|
|||
let sprite = load_mono_sprite(instance_id);
|
||||
|
||||
var out = MonoSpriteVarying();
|
||||
out.position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
if (transform_is_axis_aligned(sprite.transformation)) {
|
||||
let mask = mask_in_transform_space(sprite.content_mask, sprite.transformation);
|
||||
let clipped = clip_to_mask(sprite.bounds, mask);
|
||||
out.position = to_device_position_transformed(unit_vertex, clipped, sprite.transformation);
|
||||
let local_position = unit_vertex * clipped.size + clipped.origin;
|
||||
out.tile_position = to_tile_position((local_position - sprite.bounds.origin) / sprite.bounds.size, sprite.tile);
|
||||
out.clip_distances = vec4<f32>(1.0);
|
||||
} else {
|
||||
// A rotated sprite intersected with the axis-aligned mask isn't
|
||||
// representable as a quad, so fall back to per-fragment clipping.
|
||||
out.position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
}
|
||||
out.color = hsla_to_rgba(sprite.color);
|
||||
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -1249,7 +1260,9 @@ fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
|
|||
let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
|
||||
let alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, gamma_params.grayscale_enhanced_contrast, gamma_params.gamma_ratios);
|
||||
|
||||
// Alpha clip after using the derivatives.
|
||||
// Only rotated sprites need per-fragment clipping; axis-aligned sprites
|
||||
// are clipped geometrically in the vertex shader. Alpha clip after using
|
||||
// the derivatives.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
|
@ -1275,30 +1288,25 @@ struct PolySpriteVarying {
|
|||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) tile_position: vec2<f32>,
|
||||
@location(1) @interpolate(flat) sprite_id: u32,
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_poly_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PolySpriteVarying {
|
||||
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
|
||||
let sprite = load_poly_sprite(instance_id);
|
||||
let clipped = clip_to_mask(sprite.bounds, sprite.content_mask);
|
||||
|
||||
var out = PolySpriteVarying();
|
||||
out.position = to_device_position(unit_vertex, sprite.bounds);
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
out.position = to_device_position(unit_vertex, clipped);
|
||||
let position = unit_vertex * clipped.size + clipped.origin;
|
||||
out.tile_position = to_tile_position((position - sprite.bounds.origin) / sprite.bounds.size, sprite.tile);
|
||||
out.sprite_id = instance_id;
|
||||
out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4<f32> {
|
||||
let sample = textureSample(t_sprite, s_sprite, input.tile_position);
|
||||
// Alpha clip after using the derivatives.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
||||
let sprite = load_poly_sprite(input.sprite_id);
|
||||
let distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
|
||||
|
||||
|
|
@ -1332,27 +1340,22 @@ const ycbcr_to_RGB = mat4x4<f32>(
|
|||
struct SurfaceVarying {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) texture_position: vec2<f32>,
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_surface(@builtin(vertex_index) vertex_id: u32) -> SurfaceVarying {
|
||||
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
|
||||
let clipped = clip_to_mask(surface_locals.bounds, surface_locals.content_mask);
|
||||
|
||||
var out = SurfaceVarying();
|
||||
out.position = to_device_position(unit_vertex, surface_locals.bounds);
|
||||
out.texture_position = unit_vertex;
|
||||
out.clip_distances = distance_from_clip_rect(unit_vertex, surface_locals.bounds, surface_locals.content_mask);
|
||||
out.position = to_device_position(unit_vertex, clipped);
|
||||
let position = unit_vertex * clipped.size + clipped.origin;
|
||||
out.texture_position = (position - surface_locals.bounds.origin) / surface_locals.bounds.size;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_surface(input: SurfaceVarying) -> @location(0) vec4<f32> {
|
||||
// Alpha clip after using the derivatives.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
|
||||
let y_cb_cr = vec4<f32>(
|
||||
textureSampleLevel(t_y, s_surface, input.texture_position, 0.0).r,
|
||||
textureSampleLevel(t_cb_cr, s_surface, input.texture_position, 0.0).rg,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ struct SubpixelSpriteOutput {
|
|||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) tile_position: vec2<f32>,
|
||||
@location(1) @interpolate(flat) color: vec4<f32>,
|
||||
@location(3) clip_distances: vec4<f32>,
|
||||
@location(2) clip_distances: vec4<f32>,
|
||||
}
|
||||
|
||||
struct SubpixelSpriteFragmentOutput {
|
||||
|
|
@ -29,10 +29,21 @@ fn vs_subpixel_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_i
|
|||
let sprite = b_subpixel_sprites[instance_id];
|
||||
|
||||
var out = SubpixelSpriteOutput();
|
||||
out.position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
if (transform_is_axis_aligned(sprite.transformation)) {
|
||||
let mask = mask_in_transform_space(sprite.content_mask, sprite.transformation);
|
||||
let clipped = clip_to_mask(sprite.bounds, mask);
|
||||
out.position = to_device_position_transformed(unit_vertex, clipped, sprite.transformation);
|
||||
let local_position = unit_vertex * clipped.size + clipped.origin;
|
||||
out.tile_position = to_tile_position((local_position - sprite.bounds.origin) / sprite.bounds.size, sprite.tile);
|
||||
out.clip_distances = vec4<f32>(1.0);
|
||||
} else {
|
||||
// A rotated sprite intersected with the axis-aligned mask isn't
|
||||
// representable as a quad, so fall back to per-fragment clipping.
|
||||
out.position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
}
|
||||
out.color = hsla_to_rgba(sprite.color);
|
||||
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +55,9 @@ fn fs_subpixel_sprite(input: SubpixelSpriteOutput) -> SubpixelSpriteFragmentOutp
|
|||
}
|
||||
let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, gamma_params.subpixel_enhanced_contrast, gamma_params.gamma_ratios);
|
||||
|
||||
// Alpha clip after using the derivatives.
|
||||
// Only rotated sprites need per-fragment clipping; axis-aligned sprites
|
||||
// are clipped geometrically in the vertex shader. Alpha clip after using
|
||||
// the derivatives.
|
||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
||||
return SubpixelSpriteFragmentOutput(vec4<f32>(0.0), vec4<f32>(0.0));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,17 +114,48 @@ float4 distance_from_clip_rect_impl(float2 position, Bounds clip_bounds) {
|
|||
return float4(tl.x, br.x, tl.y, br.y);
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bounds) {
|
||||
float2 position = unit_vertex * bounds.size + bounds.origin;
|
||||
return distance_from_clip_rect_impl(position, clip_bounds);
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds bounds, Bounds clip_bounds, TransformationMatrix transformation) {
|
||||
float2 position = unit_vertex * bounds.size + bounds.origin;
|
||||
float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation;
|
||||
return distance_from_clip_rect_impl(transformed, clip_bounds);
|
||||
}
|
||||
|
||||
// Intersects `bounds` with `mask` so the emitted geometry never covers pixels
|
||||
// outside the content mask, making per-fragment clipping unnecessary. An empty
|
||||
// intersection collapses to zero size, which rasterizes to nothing. Fragment
|
||||
// shaders reload the original bounds by instance id, so their math is
|
||||
// unaffected by the shrunken geometry.
|
||||
Bounds clip_to_mask(Bounds bounds, Bounds mask) {
|
||||
Bounds result;
|
||||
result.origin = max(bounds.origin, mask.origin);
|
||||
float2 extent = min(bounds.origin + bounds.size, mask.origin + mask.size);
|
||||
result.size = max(extent - result.origin, float2(0.0, 0.0));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Whether the transformation only scales and translates, keeping rectangles
|
||||
// axis-aligned in screen space. Zero scale is excluded so callers can safely
|
||||
// invert the transformation.
|
||||
bool transform_is_axis_aligned(TransformationMatrix transformation) {
|
||||
return transformation.rotation_scale[0][1] == 0.0 &&
|
||||
transformation.rotation_scale[1][0] == 0.0 &&
|
||||
transformation.rotation_scale[0][0] != 0.0 &&
|
||||
transformation.rotation_scale[1][1] != 0.0;
|
||||
}
|
||||
|
||||
// Maps the screen-space mask into pre-transform space. Only valid for
|
||||
// axis-aligned transformations; min/max normalization handles negative scale
|
||||
// (e.g. rotation by 180 degrees).
|
||||
Bounds mask_in_transform_space(Bounds mask, TransformationMatrix transformation) {
|
||||
float2 scale = float2(transformation.rotation_scale[0][0], transformation.rotation_scale[1][1]);
|
||||
float2 p0 = (mask.origin - transformation.translation) / scale;
|
||||
float2 p1 = (mask.origin + mask.size - transformation.translation) / scale;
|
||||
Bounds result;
|
||||
result.origin = min(p0, p1);
|
||||
result.size = max(p0, p1) - result.origin;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Convert linear RGB to sRGB
|
||||
float3 linear_to_srgb(float3 color) {
|
||||
return pow(color, float3(2.2, 2.2, 2.2));
|
||||
|
|
@ -516,7 +547,6 @@ struct QuadVertexOutput {
|
|||
nointerpolation float4 background_solid: COLOR1;
|
||||
nointerpolation float4 background_color0: COLOR2;
|
||||
nointerpolation float4 background_color1: COLOR3;
|
||||
float4 clip_distance: SV_ClipDistance;
|
||||
};
|
||||
|
||||
struct QuadFragmentInput {
|
||||
|
|
@ -534,7 +564,7 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_I
|
|||
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
|
||||
uint quad_id = batch_start_index + instance_id;
|
||||
Quad quad = quads[quad_id];
|
||||
float4 device_position = to_device_position(unit_vertex, quad.bounds);
|
||||
float4 device_position = to_device_position(unit_vertex, clip_to_mask(quad.bounds, quad.content_mask));
|
||||
|
||||
GradientColor gradient = prepare_gradient_color(
|
||||
quad.background.tag,
|
||||
|
|
@ -542,7 +572,6 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_I
|
|||
quad.background.solid,
|
||||
quad.background.colors
|
||||
);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
|
||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||
|
||||
QuadVertexOutput output;
|
||||
|
|
@ -552,7 +581,6 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_I
|
|||
output.background_solid = gradient.solid;
|
||||
output.background_color0 = gradient.color0;
|
||||
output.background_color1 = gradient.color1;
|
||||
output.clip_distance = clip_distance;
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
@ -870,7 +898,6 @@ struct ShadowVertexOutput {
|
|||
nointerpolation uint shadow_id: TEXCOORD0;
|
||||
float4 position: SV_Position;
|
||||
nointerpolation float4 color: COLOR;
|
||||
float4 clip_distance: SV_ClipDistance;
|
||||
};
|
||||
|
||||
struct ShadowFragmentInput {
|
||||
|
|
@ -897,15 +924,13 @@ ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint instance_id:
|
|||
bounds.size += 2.0 * margin;
|
||||
}
|
||||
|
||||
float4 device_position = to_device_position(unit_vertex, bounds);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
|
||||
float4 device_position = to_device_position(unit_vertex, clip_to_mask(bounds, shadow.content_mask));
|
||||
float4 color = hsla_to_rgba(shadow.color);
|
||||
|
||||
ShadowVertexOutput output;
|
||||
output.position = device_position;
|
||||
output.color = color;
|
||||
output.shadow_id = shadow_id;
|
||||
output.clip_distance = clip_distance;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
@ -1076,7 +1101,6 @@ struct UnderlineVertexOutput {
|
|||
nointerpolation uint underline_id: TEXCOORD0;
|
||||
float4 position: SV_Position;
|
||||
nointerpolation float4 color: COLOR;
|
||||
float4 clip_distance: SV_ClipDistance;
|
||||
};
|
||||
|
||||
struct UnderlineFragmentInput {
|
||||
|
|
@ -1091,16 +1115,14 @@ UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint instanc
|
|||
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
|
||||
uint underline_id = batch_start_index + instance_id;
|
||||
Underline underline = underlines[underline_id];
|
||||
float4 device_position = to_device_position(unit_vertex, underline.bounds);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
|
||||
underline.content_mask);
|
||||
float4 device_position = to_device_position(
|
||||
unit_vertex, clip_to_mask(underline.bounds, underline.content_mask));
|
||||
float4 color = hsla_to_rgba(underline.color);
|
||||
|
||||
UnderlineVertexOutput output;
|
||||
output.position = device_position;
|
||||
output.color = color;
|
||||
output.underline_id = underline_id;
|
||||
output.clip_distance = clip_distance;
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
@ -1167,10 +1189,23 @@ MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexI
|
|||
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
|
||||
uint sprite_id = batch_start_index + instance_id;
|
||||
MonochromeSprite sprite = mono_sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
float4 clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
float4 device_position;
|
||||
float2 tile_position;
|
||||
float4 clip_distance;
|
||||
if (transform_is_axis_aligned(sprite.transformation)) {
|
||||
Bounds mask = mask_in_transform_space(sprite.content_mask, sprite.transformation);
|
||||
Bounds clipped = clip_to_mask(sprite.bounds, mask);
|
||||
device_position = to_device_position_transformed(unit_vertex, clipped, sprite.transformation);
|
||||
float2 local_position = unit_vertex * clipped.size + clipped.origin;
|
||||
tile_position = to_tile_position((local_position - sprite.bounds.origin) / sprite.bounds.size, sprite.tile);
|
||||
clip_distance = float4(1.0, 1.0, 1.0, 1.0);
|
||||
} else {
|
||||
// A rotated sprite intersected with the axis-aligned mask isn't
|
||||
// representable as a quad, so fall back to hardware clip distances.
|
||||
device_position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
}
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
|
||||
MonochromeSpriteVertexOutput output;
|
||||
|
|
@ -1225,7 +1260,6 @@ struct PolychromeSpriteVertexOutput {
|
|||
nointerpolation uint sprite_id: TEXCOORD0;
|
||||
float4 position: SV_Position;
|
||||
float2 tile_position: POSITION;
|
||||
float4 clip_distance: SV_ClipDistance;
|
||||
};
|
||||
|
||||
struct PolychromeSpriteFragmentInput {
|
||||
|
|
@ -1240,16 +1274,15 @@ PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexI
|
|||
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
|
||||
uint sprite_id = batch_start_index + instance_id;
|
||||
PolychromeSprite sprite = poly_sprites[sprite_id];
|
||||
float4 device_position = to_device_position(unit_vertex, sprite.bounds);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
Bounds clipped = clip_to_mask(sprite.bounds, sprite.content_mask);
|
||||
float4 device_position = to_device_position(unit_vertex, clipped);
|
||||
float2 position = unit_vertex * clipped.size + clipped.origin;
|
||||
float2 tile_position = to_tile_position((position - sprite.bounds.origin) / sprite.bounds.size, sprite.tile);
|
||||
|
||||
PolychromeSpriteVertexOutput output;
|
||||
output.position = device_position;
|
||||
output.tile_position = tile_position;
|
||||
output.sprite_id = sprite_id;
|
||||
output.clip_distance = clip_distance;
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue