Skip to content

Commit b75449f

Browse files
tmp - progress capture only
1 parent a801d03 commit b75449f

43 files changed

Lines changed: 7910 additions & 1075 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ImageSharp.Drawing.sln

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUWindowDemo", "samples
346346
EndProject
347347
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Drawing.WebGPU.ShaderGen", "src\ImageSharp.Drawing.WebGPU.ShaderGen\ImageSharp.Drawing.WebGPU.ShaderGen.csproj", "{C7606104-5D58-4670-912C-3F336606B02D}"
348348
EndProject
349+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp", "..\ImageSharp\src\ImageSharp\ImageSharp.csproj", "{FE71764E-E7BD-B378-8A4C-86929A28AD25}"
350+
EndProject
349351
Global
350352
GlobalSection(SolutionConfigurationPlatforms) = preSolution
351353
Debug|Any CPU = Debug|Any CPU
@@ -452,6 +454,18 @@ Global
452454
{C7606104-5D58-4670-912C-3F336606B02D}.Release|x64.Build.0 = Release|Any CPU
453455
{C7606104-5D58-4670-912C-3F336606B02D}.Release|x86.ActiveCfg = Release|Any CPU
454456
{C7606104-5D58-4670-912C-3F336606B02D}.Release|x86.Build.0 = Release|Any CPU
457+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
458+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|Any CPU.Build.0 = Debug|Any CPU
459+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x64.ActiveCfg = Debug|Any CPU
460+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x64.Build.0 = Debug|Any CPU
461+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x86.ActiveCfg = Debug|Any CPU
462+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x86.Build.0 = Debug|Any CPU
463+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|Any CPU.ActiveCfg = Release|Any CPU
464+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|Any CPU.Build.0 = Release|Any CPU
465+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x64.ActiveCfg = Release|Any CPU
466+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x64.Build.0 = Release|Any CPU
467+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x86.ActiveCfg = Release|Any CPU
468+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x86.Build.0 = Release|Any CPU
455469
EndGlobalSection
456470
GlobalSection(SolutionProperties) = preSolution
457471
HideSolutionNode = FALSE
@@ -483,6 +497,7 @@ Global
483497
{061582C2-658F-40AE-A978-7D74A4EB2C0A} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
484498
{2541FDCD-78AC-40DB-B5E3-6A715DC132BA} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29}
485499
{C7606104-5D58-4670-912C-3F336606B02D} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
500+
{FE71764E-E7BD-B378-8A4C-86929A28AD25} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
486501
EndGlobalSection
487502
GlobalSection(ExtensibilityGlobals) = postSolution
488503
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
@@ -491,6 +506,7 @@ Global
491506
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{061582c2-658f-40ae-a978-7d74a4eb2c0a}*SharedItemsImports = 5
492507
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5
493508
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
509+
..\ImageSharp\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{fe71764e-e7bd-b378-8a4c-86929a28ad25}*SharedItemsImports = 5
494510
EndGlobalSection
495511
GlobalSection(Performance) = preSolution
496512
HasPerformanceSessions = true

samples/DrawingBackendBenchmark/SkiaSharpBenchmarkBackend.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ public SkiaSharpBenchmarkBackend(GRContext? context = null)
2424

2525
public bool IsGpu => this.context is not null;
2626

27+
/// <summary>
28+
/// Renders the benchmark scene through Skia and optionally captures a readback preview.
29+
/// </summary>
30+
/// <remarks>
31+
/// The returned duration measures submission through <c>Flush()</c>. Preview readback happens afterward, so the
32+
/// reported GPU timing is a submission timing rather than a fully synchronized render-complete timing.
33+
/// </remarks>
2734
public BenchmarkRenderResult Render(ReadOnlySpan<VisualLine> lines, int width, int height, bool capturePreview)
2835
{
2936
this.EnsureSurface(width, height);

samples/DrawingBackendBenchmark/VisualLine.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
using SixLabors.ImageSharp.Drawing.Processing;
@@ -23,6 +23,11 @@ internal readonly record struct VisualLine(PointF Start, PointF End, Color Color
2323
/// </summary>
2424
public SKColor SkiaColor { get; } = ToSkiaColor(Color);
2525

26+
/// <summary>
27+
/// Gets the pre-created ImageSharp pen for this line, avoiding one per-frame allocation in the benchmark hot path.
28+
/// </summary>
29+
public SolidPen Pen { get; } = new(Color, Width);
30+
2631
private static SKColor ToSkiaColor(Color color)
2732
{
2833
Rgba32 rgba = color.ToPixel<Rgba32>();
@@ -38,7 +43,7 @@ public static void RenderLinesToCanvas(DrawingCanvas<Bgra32> canvas, ReadOnlySpa
3843
canvas.Fill(BackgroundBrush);
3944
foreach (VisualLine visualLine in lines)
4045
{
41-
canvas.DrawLine(new SolidPen(visualLine.Color, visualLine.Width), [visualLine.Start, visualLine.End]);
46+
canvas.DrawLine(visualLine.Pen, visualLine.Start, visualLine.End);
4247
}
4348
}
4449
}

samples/DrawingBackendBenchmark/WebGpuBenchmarkBackend.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ public static bool TryCreate([NotNullWhen(true)] out WebGpuBenchmarkBackend? res
6363
/// <summary>
6464
/// Renders the benchmark scene through the WebGPU backend and optionally captures a readback preview.
6565
/// </summary>
66+
/// <remarks>
67+
/// The returned duration measures scene submission through <see cref="DrawingCanvas{TPixel}.Flush()"/>.
68+
/// Readback for preview capture happens afterward, so the reported GPU timing is a submission timing rather than
69+
/// a fully synchronized render-complete timing.
70+
/// </remarks>
6671
public BenchmarkRenderResult Render(ReadOnlySpan<VisualLine> lines, int width, int height, bool capturePreview)
6772
{
6873
this.ThrowIfUnavailable();

src/ImageSharp.Drawing.WebGPU/Shaders/WgslSource/Shared/pathtag.wgsl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const PATH_TAG_STYLE = 0x40u;
2121
const PATH_TAG_SUBPATH_END = 4u;
2222

2323
// Size of the `Style` data structure in words
24-
const STYLE_SIZE_IN_WORDS: u32 = 2u;
24+
const STYLE_SIZE_IN_WORDS: u32 = 3u;
2525

2626
const STYLE_FLAGS_STYLE: u32 = 0x80000000u;
2727
const STYLE_FLAGS_FILL: u32 = 0x40000000u;
@@ -38,6 +38,8 @@ const STYLE_FLAGS_JOIN_MASK: u32 = 0x30000000u;
3838
const STYLE_FLAGS_JOIN_BEVEL: u32 = 0u;
3939
const STYLE_FLAGS_JOIN_MITER: u32 = 0x10000000u;
4040
const STYLE_FLAGS_JOIN_ROUND: u32 = 0x20000000u;
41+
const STYLE_FLAGS_JOIN_MITER_REVERT: u32 = 0x00400000u;
42+
const STYLE_FLAGS_JOIN_MITER_ROUND: u32 = 0x00800000u;
4143

4244
// TODO: Declare the remaining STYLE flags here.
4345

src/ImageSharp.Drawing.WebGPU/Shaders/WgslSource/flatten.wgsl

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,11 @@ fn draw_join(
567567
case STYLE_FLAGS_JOIN_MITER: {
568568
let hypot = length(vec2f(cr, d));
569569
let miter_limit = unpack2x16float(style_flags & STYLE_MITER_LIMIT_MASK)[0];
570+
let is_backside = cr > 0.;
571+
let outer_prev = select(front0, back1, is_backside);
572+
let outer_next = select(front1, back0, is_backside);
573+
let inner_prev = select(back0, front0, is_backside);
574+
let inner_next = select(back1, front1, is_backside);
570575

571576
var line_ix: u32;
572577
// Given the two tangents `tan_prev` and `tan_next` arranged tail-to-tail, the
@@ -583,29 +588,43 @@ fn draw_join(
583588
if 2. * hypot < (hypot + d) * miter_limit * miter_limit
584589
&& abs(cr) > TANGENT_THRESH * TANGENT_THRESH
585590
{
586-
let is_backside = cr > 0.;
587-
let fp_last = select(front0, back1, is_backside);
588-
let fp_this = select(front1, back0, is_backside);
589-
let p = select(front0, back0, is_backside);
590-
591-
let v = fp_this - fp_last;
591+
let v = outer_next - outer_prev;
592592
let h = (tan_prev.x * v.y - tan_prev.y * v.x) / cr;
593-
let miter_pt = fp_this - tan_next * h;
593+
let miter_pt = outer_next - tan_next * h;
594594

595595
line_ix = atomicAdd(&bump.lines, 3u);
596-
write_line_with_transform(line_ix, path_ix, p, miter_pt, transform);
597-
line_ix += 1u;
598-
599-
if is_backside {
600-
back0 = miter_pt;
596+
write_line_with_transform(line_ix, path_ix, outer_prev, miter_pt, transform);
597+
write_line_with_transform(line_ix + 1u, path_ix, miter_pt, outer_next, transform);
598+
write_line_with_transform(line_ix + 2u, path_ix, inner_prev, inner_next, transform);
599+
} else {
600+
let use_round_overflow = (style_flags & STYLE_FLAGS_JOIN_MITER_ROUND) != 0u;
601+
let use_revert_overflow = (style_flags & STYLE_FLAGS_JOIN_MITER_REVERT) != 0u;
602+
if use_round_overflow {
603+
flatten_arc(path_ix, outer_prev, outer_next, p0, abs(atan2(cr, d)), transform);
604+
output_line_with_transform(path_ix, inner_prev, inner_next, transform);
605+
} else if use_revert_overflow || abs(cr) <= TANGENT_THRESH * TANGENT_THRESH {
606+
output_two_lines_with_transform(path_ix, front0, front1, back0, back1, transform);
601607
} else {
602-
front0 = miter_pt;
608+
let v = outer_next - outer_prev;
609+
let h = (tan_prev.x * v.y - tan_prev.y * v.x) / cr;
610+
let miter_pt = outer_next - tan_next * h;
611+
let limit = length(n_prev) * miter_limit;
612+
let bevel_distance = length(((outer_prev - p0) + (outer_next - p0)) * 0.5);
613+
let intersection_distance = distance(p0, miter_pt);
614+
if intersection_distance <= bevel_distance + TANGENT_THRESH {
615+
output_two_lines_with_transform(path_ix, front0, front1, back0, back1, transform);
616+
} else {
617+
let ratio = (limit - bevel_distance) / (intersection_distance - bevel_distance);
618+
let clipped_prev = outer_prev + ((miter_pt - outer_prev) * ratio);
619+
let clipped_next = outer_next + ((miter_pt - outer_next) * ratio);
620+
line_ix = atomicAdd(&bump.lines, 4u);
621+
write_line_with_transform(line_ix, path_ix, outer_prev, clipped_prev, transform);
622+
write_line_with_transform(line_ix + 1u, path_ix, clipped_prev, clipped_next, transform);
623+
write_line_with_transform(line_ix + 2u, path_ix, clipped_next, outer_next, transform);
624+
write_line_with_transform(line_ix + 3u, path_ix, inner_prev, inner_next, transform);
625+
}
603626
}
604-
} else {
605-
line_ix = atomicAdd(&bump.lines, 2u);
606627
}
607-
write_line_with_transform(line_ix, path_ix, front0, front1, transform);
608-
write_line_with_transform(line_ix + 1u, path_ix, back0, back1, transform);
609628
}
610629
case STYLE_FLAGS_JOIN_ROUND: {
611630
var arc0: vec2f;
@@ -691,10 +710,8 @@ fn compute_tag_monoid(ix: u32) -> PathTagData {
691710
// TODO: this can be a read buf overflow. Conditionalize by tag byte?
692711
tm = combine_tag_monoid(tag_monoids[ix >> 2u], tm);
693712
var tag_byte = (tag_word >> shift) & 0xffu;
694-
// We no longer encode an initial transform and style so these
695-
// are off by one.
696-
// Note: an alternative would be to adjust config.transform_base and
697-
// config.style_base.
713+
// The encoded streams begin after the implicit identity transform and current style,
714+
// so these indices are rebased to the actual payload starts.
698715
tm.trans_ix -= 1u;
699716
tm.style_ix -= STYLE_SIZE_IN_WORDS;
700717
return PathTagData(tag_byte, tm);
@@ -847,10 +864,9 @@ fn main(
847864

848865
let out = &path_bboxes[path_ix];
849866
let style_flags = scene[config.style_base + style_ix];
867+
let style_draw_flags = scene[config.style_base + style_ix + 2u];
850868
let fill_rule = select(DRAW_INFO_FLAGS_FILL_RULE_BIT, 0u, (style_flags & STYLE_FLAGS_FILL) == 0u);
851-
let blend_mode = ((style_flags & 0x00003ffeu) >> 1u) << DRAW_FLAGS_BLEND_MODE_SHIFT;
852-
let blend_alpha = ((style_flags & 0x3fffc000u) >> 14u) << DRAW_FLAGS_BLEND_ALPHA_SHIFT;
853-
let draw_flags = fill_rule | blend_mode | blend_alpha;
869+
let draw_flags = style_draw_flags | fill_rule;
854870
if (tag.tag_byte & PATH_TAG_PATH) != 0u {
855871
(*out).draw_flags = draw_flags;
856872
(*out).trans_ix = trans_ix;

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
using System.Collections.Generic;
54
using System.Runtime.CompilerServices;
65
using Silk.NET.WebGPU;
76
using SixLabors.ImageSharp.Memory;
@@ -217,7 +216,7 @@ public void FlushCompositions<TPixel>(
217216
where TPixel : unmanaged, IPixel<TPixel>
218217
{
219218
this.ThrowIfDisposed();
220-
if (compositionScene.Commands.Count == 0)
219+
if (compositionScene.CommandCount == 0)
221220
{
222221
return;
223222
}
@@ -227,7 +226,7 @@ public void FlushCompositions<TPixel>(
227226
WebGPUSceneBumpSizes currentBumpSizes = this.bumpSizes;
228227
for (int attempt = 0; attempt < MaxDynamicGrowthAttempts; attempt++)
229228
{
230-
if (!WebGPUSceneDispatch.TryCreateStagedScene(configuration, target, compositionScene.Commands, currentBumpSizes, out bool exceedsBindingLimit, out WebGPUSceneDispatch.BindingLimitFailure bindingLimitFailure, out WebGPUStagedScene stagedScene, out string? error))
229+
if (!WebGPUSceneDispatch.TryCreateStagedScene(configuration, target, compositionScene, currentBumpSizes, out bool exceedsBindingLimit, out WebGPUSceneDispatch.BindingLimitFailure bindingLimitFailure, out WebGPUStagedScene stagedScene, out string? error))
231230
{
232231
this.TestingLastGPUInitializationFailure = exceedsBindingLimit
233232
? error ?? "The staged WebGPU scene exceeded the current binding limits."

src/ImageSharp.Drawing.WebGPU/WebGPUSceneDispatch.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
#pragma warning disable SA1201 // Phase-1 staged scene types are grouped by pipeline role.
4+
#pragma warning disable SA1201 // Staged scene types are grouped by pipeline role.
55

66
using System.Diagnostics;
77
using System.IO;
@@ -100,14 +100,14 @@ public static bool TryCreateStagedScene<TPixel>(
100100
out WebGPUStagedScene stagedScene,
101101
out string? error)
102102
where TPixel : unmanaged, IPixel<TPixel>
103-
=> TryCreateStagedScene(configuration, target, scene.Commands, bumpSizes, out _, out _, out stagedScene, out error);
103+
=> TryCreateStagedScene(configuration, target, scene, bumpSizes, out _, out _, out stagedScene, out error);
104104

105105
/// <summary>
106106
/// Builds the flush-scoped encoded scene and uploads its GPU resources.
107107
/// </summary>
108108
/// <param name="configuration">The drawing configuration that owns allocators and backend settings.</param>
109109
/// <param name="target">The flush target that exposes the native WebGPU surface.</param>
110-
/// <param name="commands">The prepared composition commands for this flush.</param>
110+
/// <param name="scene">The prepared composition scene for this flush.</param>
111111
/// <param name="bumpSizes">The current dynamic scratch capacities to use for this attempt.</param>
112112
/// <param name="exceedsBindingLimit">Receives whether creation failed because a single WebGPU binding would be too large.</param>
113113
/// <param name="bindingLimitFailure">Receives the exact binding-limit failure when <paramref name="exceedsBindingLimit"/> is <see langword="true"/>.</param>
@@ -118,7 +118,7 @@ public static bool TryCreateStagedScene<TPixel>(
118118
public static bool TryCreateStagedScene<TPixel>(
119119
Configuration configuration,
120120
ICanvasFrame<TPixel> target,
121-
IReadOnlyList<CompositionCommand> commands,
121+
CompositionScene scene,
122122
WebGPUSceneBumpSizes bumpSizes,
123123
out bool exceedsBindingLimit,
124124
out BindingLimitFailure bindingLimitFailure,
@@ -137,13 +137,6 @@ public static bool TryCreateStagedScene<TPixel>(
137137
return false;
138138
}
139139

140-
WebGPUSceneSupportResult support = WebGPUSceneEncoder.ValidateSceneSupport(commands);
141-
if (!support.IsSupported)
142-
{
143-
error = support.Error;
144-
return false;
145-
}
146-
147140
TextureFormat textureFormat = WebGPUTextureFormatMapper.ToSilk(formatId);
148141

149142
WebGPUFlushContext? flushContext = WebGPUFlushContext.Create(
@@ -160,7 +153,14 @@ public static bool TryCreateStagedScene<TPixel>(
160153

161154
try
162155
{
163-
encodedScene = WebGPUSceneEncoder.Encode(commands, support, flushContext.TargetBounds, flushContext.MemoryAllocator);
156+
if (!WebGPUSceneEncoder.TryEncode(scene, flushContext.TargetBounds, flushContext.MemoryAllocator, out WebGPUEncodedScene createdScene, out error))
157+
{
158+
flushContext.Dispose();
159+
stagedScene = default;
160+
return false;
161+
}
162+
163+
encodedScene = createdScene;
164164
WebGPUSceneBumpSizes sceneBumpSizes = bumpSizes.WithSceneLowerBounds(encodedScene);
165165
WebGPUSceneConfig config = WebGPUSceneConfig.Create(encodedScene, sceneBumpSizes);
166166
uint baseColor = 0U;

0 commit comments

Comments
 (0)