Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/ImageSharp.Drawing.WebGPU/GpuSceneDrawTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ internal static class GpuSceneDrawTag
// These values are not a plain enum because each word also encodes the path-count, clip-count,
// scene-word-count, and info-word-count increments consumed by the scan/reduction stages.
// Visible-fill tags carry five extra info words: coverage data plus raster interest. Must match drawtag.wgsl.
// Every gradient payload starts with the six words of the drawing-to-gradient transform.
public const uint Nop = 0U;
public const uint FillColor = 0x188U;
public const uint FillRecolor = 0x184U;
public const uint FillLinGradient = 0x254U;
public const uint FillRadGradient = 0x3DCU;
public const uint FillEllipticGradient = 0x35CU;
public const uint FillSweepGradient = 0x394U;
public const uint FillLinGradient = 0x26CU;
public const uint FillRadGradient = 0x3F4U;
public const uint FillEllipticGradient = 0x374U;
public const uint FillSweepGradient = 0x3ACU;
public const uint FillPathGradient = 0x190U;
public const uint FillImage = 0x3D4U;
public const uint BeginClip = 0x49U;
public const uint EndClip = 0x21U;
public const uint EndClip = 0x401U;
public const uint FillInfoFlagsFillRuleBit = 1U;
public const uint FillInfoFlagsAliasedBit = 0x40000000U;

Expand All @@ -37,6 +38,6 @@ public static GpuSceneDrawMonoid Map(uint tagWord)
=> new(
tagWord != Nop ? 1U : 0U,
tagWord & 1U,
(tagWord >> 2) & 0x07U,
(tagWord >> 2) & 0x0FU,
(tagWord >> 6) & 0x0FU);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ struct DrawMonoid {
}

// Each draw object has a 32-bit draw tag, which is a bit-packed
// version of the draw monoid: bit 0 = clip count, bits 2..4 = scene words,
// version of the draw monoid: bit 0 = clip count, bits 2..5 = scene words,
// bits 6..9 = info words (see map_draw_tag).
// Visible-fill draw tags carry five extra info words: coverage data plus raster interest.
// Every gradient payload starts with the six words of the drawing-to-gradient transform.
const DRAWTAG_NOP = 0u;
const DRAWTAG_FILL_COLOR = 0x188u;
const DRAWTAG_FILL_RECOLOR = 0x184u;
const DRAWTAG_FILL_LIN_GRADIENT = 0x254u;
const DRAWTAG_FILL_RAD_GRADIENT = 0x3dcu;
const DRAWTAG_FILL_ELLIPTIC_GRADIENT = 0x35cu;
const DRAWTAG_FILL_SWEEP_GRADIENT = 0x394u;
const DRAWTAG_FILL_LIN_GRADIENT = 0x26cu;
const DRAWTAG_FILL_RAD_GRADIENT = 0x3f4u;
const DRAWTAG_FILL_ELLIPTIC_GRADIENT = 0x374u;
const DRAWTAG_FILL_SWEEP_GRADIENT = 0x3acu;
const DRAWTAG_FILL_PATH_GRADIENT = 0x190u;
const DRAWTAG_FILL_IMAGE = 0x3d4u;
const DRAWTAG_BEGIN_CLIP = 0x49u;
const DRAWTAG_END_CLIP = 0x21u;
const DRAWTAG_END_CLIP = 0x401u;

// The first word of each draw info stream entry contains the flags. This is not part of the
// draw object stream but is used after the draw objects have been reduced on the GPU.
Expand Down Expand Up @@ -88,7 +89,7 @@ fn map_draw_tag(tag_word: u32) -> DrawMonoid {
var c: DrawMonoid;
c.path_ix = u32(tag_word != DRAWTAG_NOP);
c.clip_ix = tag_word & 1u;
c.scene_offset = (tag_word >> 2u) & 0x07u;
c.scene_offset = (tag_word >> 2u) & 0x0fu;
c.info_offset = (tag_word >> 6u) & 0x0fu;
return c;
}
81 changes: 56 additions & 25 deletions src/ImageSharp.Drawing.WebGPU/Shaders/WgslSource/draw_leaf.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ fn main(
{
let bbox = path_bbox[m.path_ix];
let draw_flags = bbox.draw_flags;
// Every gradient payload starts with the drawing-to-gradient transform, the inverse
// of the brush's gradient transform, so the geometry that follows is in the
// gradient's own space.
var user_to_gradient = transform_identity();
if tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT ||
tag_word == DRAWTAG_FILL_ELLIPTIC_GRADIENT || tag_word == DRAWTAG_FILL_SWEEP_GRADIENT
{
user_to_gradient = Transform(
bitcast<vec4<f32>>(vec4(scene[dd + 1u], scene[dd + 2u], scene[dd + 3u], scene[dd + 4u])),
bitcast<vec2<f32>>(vec2(scene[dd + 5u], scene[dd + 6u])),
vec3(0.0, 0.0, 1.0));
}
switch tag_word {
case DRAWTAG_FILL_COLOR: {
info[di] = draw_flags;
Expand All @@ -159,8 +171,8 @@ fn main(
}
case DRAWTAG_FILL_LIN_GRADIENT: {
info[di] = draw_flags;
let p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
let p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
let p0 = bitcast<vec2<f32>>(vec2(scene[dd + 7u], scene[dd + 8u]));
let p1 = bitcast<vec2<f32>>(vec2(scene[dd + 9u], scene[dd + 10u]));
// Encode the gradient as a line equation so fine can
// evaluate the parameter as t = dot(p, line_xy) + line_c.
let dxy = p1 - p0;
Expand All @@ -173,6 +185,15 @@ fn main(
line_c = -dot(p0, line_xy);
}

// The projection is linear, so the drawing-to-gradient transform folds into
// the line equation and fine evaluates it directly in drawing space.
let u = user_to_gradient;
let folded_xy = vec2(
u.matrx.x * line_xy.x + u.matrx.y * line_xy.y,
u.matrx.z * line_xy.x + u.matrx.w * line_xy.y);
line_c = line_c + dot(u.translate, line_xy);
line_xy = folded_xy;

// The CPU brush defines a zero-length axis as the gradient end. The
// initialized equation therefore evaluates t=1 everywhere without NaN.
info[di + 1u] = bitcast<u32>(line_xy.x);
Expand All @@ -185,11 +206,10 @@ fn main(
// This epsilon matches what Skia uses
let GRADIENT_EPSILON = 1.0 / f32(1u << 12u);
info[di] = draw_flags;
var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
var r0 = bitcast<f32>(scene[dd + 5u]);
var r1 = bitcast<f32>(scene[dd + 6u]);
let user_to_gradient = transform_identity();
var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 7u], scene[dd + 8u]));
var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 9u], scene[dd + 10u]));
var r0 = bitcast<f32>(scene[dd + 11u]);
var r1 = bitcast<f32>(scene[dd + 12u]);
var xform = transform_identity();
var focal_x = 0.0;
var radius = 0.0;
Expand Down Expand Up @@ -257,9 +277,9 @@ fn main(
}
case DRAWTAG_FILL_ELLIPTIC_GRADIENT: {
info[di] = draw_flags;
let center = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
let axis_end = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
let second_end = bitcast<vec2<f32>>(vec2(scene[dd + 5u], scene[dd + 6u]));
let center = bitcast<vec2<f32>>(vec2(scene[dd + 7u], scene[dd + 8u]));
let axis_end = bitcast<vec2<f32>>(vec2(scene[dd + 9u], scene[dd + 10u]));
let second_end = bitcast<vec2<f32>>(vec2(scene[dd + 11u], scene[dd + 12u]));
let dxy = axis_end - center;
let axis = length(dxy);
let second_axis_len = length(second_end - center);
Expand Down Expand Up @@ -305,25 +325,36 @@ fn main(
xlat_y = -(m1 * center.x + m3 * center.y);
}

info[di + 1u] = bitcast<u32>(m0);
info[di + 2u] = bitcast<u32>(m1);
info[di + 3u] = bitcast<u32>(m2);
info[di + 4u] = bitcast<u32>(m3);
info[di + 5u] = bitcast<u32>(xlat_x);
info[di + 6u] = bitcast<u32>(xlat_y);
// Samples pass through the drawing-to-gradient transform before the ellipse
// mapping. Degenerate kinds keep the center as their translation, so it moves
// to its drawing position and the transform folds into the matrix only.
let ellipse = Transform(vec4(m0, m1, m2, m3), vec2(xlat_x, xlat_y), vec3(0.0, 0.0, 1.0));
var composed = transform_mul(ellipse, user_to_gradient);
if kind != ELLIPTIC_GRAD_KIND_NORMAL {
composed.translate = transform_apply(transform_inverse(user_to_gradient), center);
}

info[di + 1u] = bitcast<u32>(composed.matrx.x);
info[di + 2u] = bitcast<u32>(composed.matrx.y);
info[di + 3u] = bitcast<u32>(composed.matrx.z);
info[di + 4u] = bitcast<u32>(composed.matrx.w);
info[di + 5u] = bitcast<u32>(composed.translate.x);
info[di + 6u] = bitcast<u32>(composed.translate.y);
info[di + 7u] = kind;
}
case DRAWTAG_FILL_SWEEP_GRADIENT: {
info[di] = draw_flags;
let p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
info[di + 1u] = bitcast<u32>(1.0);
info[di + 2u] = bitcast<u32>(0.0);
info[di + 3u] = bitcast<u32>(0.0);
info[di + 4u] = bitcast<u32>(1.0);
info[di + 5u] = bitcast<u32>(-p0.x);
info[di + 6u] = bitcast<u32>(-p0.y);
info[di + 7u] = scene[dd + 3u];
info[di + 8u] = scene[dd + 4u];
let p0 = bitcast<vec2<f32>>(vec2(scene[dd + 7u], scene[dd + 8u]));
// The sample maps into the gradient's space and then becomes center-relative.
let u = user_to_gradient;
info[di + 1u] = bitcast<u32>(u.matrx.x);
info[di + 2u] = bitcast<u32>(u.matrx.y);
info[di + 3u] = bitcast<u32>(u.matrx.z);
info[di + 4u] = bitcast<u32>(u.matrx.w);
info[di + 5u] = bitcast<u32>(u.translate.x - p0.x);
info[di + 6u] = bitcast<u32>(u.translate.y - p0.y);
info[di + 7u] = scene[dd + 9u];
info[di + 8u] = scene[dd + 10u];
}
case DRAWTAG_FILL_PATH_GRADIENT: {
info[di] = draw_flags;
Expand Down
35 changes: 31 additions & 4 deletions src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6358,10 +6358,10 @@ private static int GetDrawDataWordCount(uint drawTag)
GpuSceneDrawTag.BeginClip => 2,
GpuSceneDrawTag.FillColor => 2,
GpuSceneDrawTag.FillRecolor => 1,
GpuSceneDrawTag.FillLinGradient => 5,
GpuSceneDrawTag.FillRadGradient => 7,
GpuSceneDrawTag.FillEllipticGradient => 7,
GpuSceneDrawTag.FillSweepGradient => 5,
GpuSceneDrawTag.FillLinGradient => 11,
GpuSceneDrawTag.FillRadGradient => 13,
GpuSceneDrawTag.FillEllipticGradient => 13,
GpuSceneDrawTag.FillSweepGradient => 11,
GpuSceneDrawTag.FillPathGradient => 4,
GpuSceneDrawTag.FillImage => 5,
GpuSceneDrawTag.EndClip => 0,
Expand Down Expand Up @@ -6948,12 +6948,36 @@ private static void AppendLinearGradientData(
gradientRowCount++;

drawData.Add(indexMode);
AppendGradientTransform(brush, ref drawData);
drawData.Add(BitcastSingle(brush.StartPoint.X));
drawData.Add(BitcastSingle(brush.StartPoint.Y));
drawData.Add(BitcastSingle(brush.EndPoint.X));
drawData.Add(BitcastSingle(brush.EndPoint.Y));
}

/// <summary>
/// Appends the transform from drawing coordinates to the gradient's coordinate space, which
/// is the affine part of the brush's gradient transform inverted.
/// </summary>
/// <param name="brush">The gradient brush.</param>
/// <param name="drawData">The draw-data stream.</param>
private static void AppendGradientTransform(GradientBrush brush, ref OwnedStream<uint> drawData)
{
// A transform that cannot be inverted has no gradient space. The zero matrix maps every
// sample to the origin, so the brush paints one color where the CPU renderer paints nothing.
if (!brush.TryGetInverseTransform(out Matrix3x2 m))
{
m = default;
}

drawData.Add(BitcastSingle(m.M11));
drawData.Add(BitcastSingle(m.M12));
drawData.Add(BitcastSingle(m.M21));
drawData.Add(BitcastSingle(m.M22));
drawData.Add(BitcastSingle(m.M31));
drawData.Add(BitcastSingle(m.M32));
}

/// <summary>
/// Appends the radial gradient payload and its packed ramp row.
/// </summary>
Expand Down Expand Up @@ -6995,6 +7019,7 @@ private static void AppendRadialGradientData(
}

drawData.Add(indexMode);
AppendGradientTransform(brush, ref drawData);
drawData.Add(BitcastSingle(center0.X));
drawData.Add(BitcastSingle(center0.Y));
drawData.Add(BitcastSingle(center1.X));
Expand Down Expand Up @@ -7033,6 +7058,7 @@ private static void AppendEllipticGradientData(
PointF localSecondEnd = new(localCenter.X + localPerpendicular.X, localCenter.Y + localPerpendicular.Y);

drawData.Add(indexMode);
AppendGradientTransform(brush, ref drawData);
drawData.Add(BitcastSingle(localCenter.X));
drawData.Add(BitcastSingle(localCenter.Y));
drawData.Add(BitcastSingle(localAxisEnd.X));
Expand Down Expand Up @@ -7074,6 +7100,7 @@ private static void AppendSweepGradientData(
float t1 = t0 + (sweepDegrees / 360F);

drawData.Add(indexMode);
AppendGradientTransform(brush, ref drawData);
drawData.Add(BitcastSingle(brush.Center.X));
drawData.Add(BitcastSingle(brush.Center.Y));
drawData.Add(BitcastSingle(t0));
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<InternalsVisibleTo Include="SixLabors.ImageSharp.Drawing.WebGPU" Key="$(SixLaborsPublicKey)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Fonts" Version="3.1.2" />
<PackageReference Include="SixLabors.Fonts" Version="3.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="4.1.2" />
<PackageReference Include="SixLabors.PolygonClipper" Version="1.0.1" />
</ItemGroup>
Expand Down
Loading
Loading