Skip to content

Commit f4a3b87

Browse files
Include binaries use streaming for composition
1 parent 3fc0488 commit f4a3b87

2 files changed

Lines changed: 30 additions & 61 deletions

File tree

src/ImageSharp.Drawing/Processing/Backends/DefaultDrawingBackend.cs

Lines changed: 26 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
2929
/// <item>
3030
/// <description>
3131
/// <see cref="FlushPreparedBatch{TPixel}(Configuration, ICanvasFrame{TPixel}, CompositionBatch)"/>
32-
/// rasterizes one shared coverage map per batch and applies brushes in original command order.
32+
/// rasterizes shared coverage scanlines per batch and applies brushes in original command order.
3333
/// </description>
3434
/// </item>
3535
/// </list>
@@ -177,15 +177,12 @@ internal void FlushPreparedBatch<TPixel>(
177177
}
178178

179179
CompositionCoverageDefinition definition = compositionBatch.Definition;
180-
using Buffer2D<float> coverageMap = this.CreateCoverageMap(definition, configuration.MemoryAllocator);
181-
182180
Rectangle destinationBounds = destinationFrame.Rectangle;
183181
IReadOnlyList<PreparedCompositionCommand> commands = compositionBatch.Commands;
184182
int commandCount = commands.Count;
185183
BrushApplicator<TPixel>[] applicators = new BrushApplicator<TPixel>[commandCount];
186184
try
187185
{
188-
int maxHeight = 0;
189186
for (int i = 0; i < commandCount; i++)
190187
{
191188
PreparedCompositionCommand command = commands[i];
@@ -195,17 +192,22 @@ internal void FlushPreparedBatch<TPixel>(
195192
command.GraphicsOptions,
196193
commandRegion,
197194
command.BrushBounds);
198-
199-
if (command.DestinationRegion.Height > maxHeight)
200-
{
201-
maxHeight = command.DestinationRegion.Height;
202-
}
203195
}
204196

205-
// Iterate by row so we slice the already-rasterized coverage map once per command row.
206-
// We can do this in parallel since the applicators are thread-safe and each row is independent.
207-
RowOperation<TPixel> operation = new(coverageMap, commands, applicators, destinationBounds, maxHeight);
208-
ParallelRowIterator.IterateRows(configuration, destinationBounds, in operation);
197+
// Stream composition directly from rasterizer scanlines so we do not allocate
198+
// and then re-read an intermediate coverage map.
199+
RowOperation<TPixel> operation = new(
200+
commands,
201+
applicators,
202+
destinationBounds,
203+
definition.RasterizerOptions.Interest.Top);
204+
this.PrimaryRasterizer.Rasterize(
205+
definition.Path,
206+
definition.RasterizerOptions,
207+
configuration.MemoryAllocator,
208+
ref operation,
209+
static (int y, Span<float> scanline, ref RowOperation<TPixel> callbackState) =>
210+
callbackState.InvokeScanline(y, scanline));
209211
}
210212
finally
211213
{
@@ -216,80 +218,43 @@ internal void FlushPreparedBatch<TPixel>(
216218
}
217219
}
218220

219-
/// <summary>
220-
/// Rasterizes one batch coverage map into a dense floating-point buffer.
221-
/// </summary>
222-
/// <param name="definition">The path and rasterizer options shared by every command in the batch.</param>
223-
/// <param name="allocator">The allocator used for temporary coverage storage.</param>
224-
/// <returns>The populated coverage map for the batch interest region.</returns>
225-
private Buffer2D<float> CreateCoverageMap(
226-
in CompositionCoverageDefinition definition,
227-
MemoryAllocator allocator)
228-
{
229-
Size size = definition.RasterizerOptions.Interest.Size;
230-
Buffer2D<float> coverage = allocator.Allocate2D<float>(size, AllocationOptions.Clean);
231-
232-
(Buffer2D<float> Buffer, int DestinationTop) state = (coverage, definition.RasterizerOptions.Interest.Top);
233-
this.PrimaryRasterizer.Rasterize(
234-
definition.Path,
235-
definition.RasterizerOptions,
236-
allocator,
237-
ref state,
238-
static (int y, Span<float> scanline, ref (Buffer2D<float> Buffer, int DestinationTop) callbackState) =>
239-
{
240-
int row = y - callbackState.DestinationTop;
241-
scanline.CopyTo(callbackState.Buffer.DangerousGetRowSpan(row));
242-
});
243-
244-
return coverage;
245-
}
246-
247-
private readonly struct RowOperation<TPixel> : IRowOperation
221+
private readonly struct RowOperation<TPixel>
248222
where TPixel : unmanaged, IPixel<TPixel>
249223
{
250-
private readonly Buffer2D<float> coverageMap;
251224
private readonly IReadOnlyList<PreparedCompositionCommand> commands;
252225
private readonly BrushApplicator<TPixel>[] applicators;
253226
private readonly Rectangle destinationBounds;
254-
private readonly int maxHeight;
227+
private readonly int coverageTop;
255228

256229
public RowOperation(
257-
Buffer2D<float> coverageMap,
258230
IReadOnlyList<PreparedCompositionCommand> commands,
259231
BrushApplicator<TPixel>[] applicators,
260232
Rectangle destinationBounds,
261-
int maxHeight)
233+
int coverageTop)
262234
{
263-
this.coverageMap = coverageMap;
264235
this.commands = commands;
265236
this.applicators = applicators;
266237
this.destinationBounds = destinationBounds;
267-
this.maxHeight = maxHeight;
238+
this.coverageTop = coverageTop;
268239
}
269240

270-
public void Invoke(int y)
241+
public void InvokeScanline(int y, Span<float> scanline)
271242
{
272-
if (y >= this.maxHeight)
273-
{
274-
return;
275-
}
276-
243+
int sourceY = y - this.coverageTop;
277244
for (int i = 0; i < this.commands.Count; i++)
278245
{
279246
PreparedCompositionCommand command = this.commands[i];
280-
if (y >= command.DestinationRegion.Height)
247+
int commandY = sourceY - command.SourceOffset.Y;
248+
if ((uint)commandY >= (uint)command.DestinationRegion.Height)
281249
{
282250
continue;
283251
}
284252

285253
int destinationX = this.destinationBounds.X + command.DestinationRegion.X;
286-
int destinationY = this.destinationBounds.Y + command.DestinationRegion.Y;
254+
int destinationY = this.destinationBounds.Y + command.DestinationRegion.Y + commandY;
287255
int sourceStartX = command.SourceOffset.X;
288-
int sourceStartY = command.SourceOffset.Y;
289-
290-
Span<float> rowCoverage = this.coverageMap.DangerousGetRowSpan(sourceStartY + y);
291-
Span<float> rowSlice = rowCoverage.Slice(sourceStartX, command.DestinationRegion.Width);
292-
ApplyCoverageSpans(this.applicators[i], rowSlice, destinationX, destinationY + y);
256+
Span<float> rowSlice = scanline.Slice(sourceStartX, command.DestinationRegion.Width);
257+
ApplyCoverageSpans(this.applicators[i], rowSlice, destinationX, destinationY);
293258
}
294259
}
295260

tests/ImageSharp.Drawing.Tests/ImageSharp.Drawing.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
<PackageReference Include="System.Drawing.Common" />
3333
<PackageReference Include="GeoJSON.Net" />
3434
<PackageReference Include="Silk.NET.WebGPU" Version="2.23.0" />
35+
<PackageReference Include="Silk.NET.WebGPU.Extensions.WGPU" Version="2.23.0" />
36+
<PackageReference Include="Silk.NET.WebGPU.Native.WGPU" Version="2.23.0" />
37+
38+
3539
<PackageReference Include="SkiaSharp" />
3640
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Condition="'$(IsLinux)'=='true'" />
3741
</ItemGroup>

0 commit comments

Comments
 (0)