Skip to content

Commit

Permalink
Merge pull request #2241 from ynse01/buffer-length-in-row-operation
Browse files Browse the repository at this point in the history
Extend row operation interfaces with buffer length method
  • Loading branch information
JimBobSquarePants committed Sep 27, 2022
2 parents 0e0c236 + ec79e87 commit e7f701e
Show file tree
Hide file tree
Showing 25 changed files with 211 additions and 71 deletions.
7 changes: 7 additions & 0 deletions src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ namespace SixLabors.ImageSharp.Advanced;
public interface IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Return the minimal required number of items in the buffer passed on <see cref="Invoke" />.
/// </summary>
/// <param name="bounds">The bounds of the operation.</param>
/// <returns>The required buffer length.</returns>
int GetRequiredBufferLength(Rectangle bounds);

/// <summary>
/// Invokes the method passing the row interval and a buffer.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp.Advanced;
public interface IRowOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Return the minimal required number of items in the buffer passed on <see cref="Invoke" />.
/// </summary>
/// <param name="bounds">The bounds of the operation.</param>
/// <returns>The required buffer length.</returns>
int GetRequiredBufferLength(Rectangle bounds);

/// <summary>
/// Invokes the method passing the row and a buffer.
/// </summary>
Expand Down
16 changes: 8 additions & 8 deletions src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private readonly struct RowOperationWrapper<T, TBuffer>
private readonly int minY;
private readonly int maxY;
private readonly int stepY;
private readonly int width;
private readonly int bufferLength;
private readonly MemoryAllocator allocator;
private readonly T action;

Expand All @@ -72,14 +72,14 @@ public RowOperationWrapper(
int minY,
int maxY,
int stepY,
int width,
int bufferLength,
MemoryAllocator allocator,
in T action)
{
this.minY = minY;
this.maxY = maxY;
this.stepY = stepY;
this.width = width;
this.bufferLength = bufferLength;
this.allocator = allocator;
this.action = action;
}
Expand All @@ -96,7 +96,7 @@ public void Invoke(int i)

int yMax = Math.Min(yMin + this.stepY, this.maxY);

using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.width);
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.bufferLength);

Span<TBuffer> span = buffer.Memory.Span;

Expand Down Expand Up @@ -153,7 +153,7 @@ private readonly struct RowIntervalOperationWrapper<T, TBuffer>
private readonly int minY;
private readonly int maxY;
private readonly int stepY;
private readonly int width;
private readonly int bufferLength;
private readonly MemoryAllocator allocator;
private readonly T operation;

Expand All @@ -162,14 +162,14 @@ public RowIntervalOperationWrapper(
int minY,
int maxY,
int stepY,
int width,
int bufferLength,
MemoryAllocator allocator,
in T operation)
{
this.minY = minY;
this.maxY = maxY;
this.stepY = stepY;
this.width = width;
this.bufferLength = bufferLength;
this.allocator = allocator;
this.operation = operation;
}
Expand All @@ -187,7 +187,7 @@ public void Invoke(int i)
int yMax = Math.Min(yMin + this.stepY, this.maxY);
var rows = new RowInterval(yMin, yMax);

using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.width);
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.bufferLength);

Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
}
Expand Down
10 changes: 6 additions & 4 deletions src/ImageSharp/Advanced/ParallelRowIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,12 @@ public static void IterateRows<T, TBuffer>(
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);

// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
using IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width);
using IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(bufferLength);
Span<TBuffer> span = buffer.Memory.Span;

for (int y = top; y < bottom; y++)
Expand All @@ -135,7 +136,7 @@ public static void IterateRows<T, TBuffer>(

int verticalStep = DivideCeil(height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
var wrappingOperation = new RowOperationWrapper<T, TBuffer>(top, bottom, verticalStep, width, allocator, in operation);
var wrappingOperation = new RowOperationWrapper<T, TBuffer>(top, bottom, verticalStep, bufferLength, allocator, in operation);

Parallel.For(
0,
Expand Down Expand Up @@ -244,12 +245,13 @@ public static void IterateRowIntervals<T, TBuffer>(
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);

// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
using IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width);
using IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(bufferLength);

Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span);

Expand All @@ -258,7 +260,7 @@ public static void IterateRowIntervals<T, TBuffer>(

int verticalStep = DivideCeil(height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
var wrappingOperation = new RowIntervalOperationWrapper<T, TBuffer>(top, bottom, verticalStep, width, allocator, in operation);
var wrappingOperation = new RowIntervalOperationWrapper<T, TBuffer>(top, bottom, verticalStep, bufferLength, allocator, in operation);

Parallel.For(
0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public RowOperation(
this.clusterSize = clusterSize;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<L8> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ public RowOperation(
this.configuration = configuration;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Rgb24> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ public FirstPassConvolutionRowOperation(
this.configuration = configuration;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down Expand Up @@ -289,6 +294,11 @@ public ApplyGammaExposureRowOperation(
this.gamma = gamma;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down Expand Up @@ -329,6 +339,13 @@ public ApplyGamma3ExposureRowOperation(
this.configuration = configuration;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
{
return bounds.Width;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());

// We use a rectangle 3x the interest width to allocate a buffer big enough
// for source and target bulk pixel conversion.
var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 3, interest.Height);

using (var map = new KernelSamplingMap(allocator))
{
// Since the kernel sizes are identical we can use a single map.
Expand All @@ -85,7 +81,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

ParallelRowIterator.IterateRows<Convolution2DRowOperation<TPixel>, Vector4>(
this.Configuration,
operationBounds,
interest,
in operation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public Convolution2DRowOperation(
this.preserveAlpha = preserveAlpha;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 3 * bounds.Width;

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());

// We use a rectangle 2x the interest width to allocate a buffer big enough
// for source and target bulk pixel conversion.
var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height);

// We can create a single sampling map with the size as if we were using the non separated 2D kernel
// the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur.
using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator);
Expand All @@ -92,7 +88,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

ParallelRowIterator.IterateRows<HorizontalConvolutionRowOperation, Vector4>(
this.Configuration,
operationBounds,
interest,
in horizontalOperation);

// Vertical convolution
Expand All @@ -107,7 +103,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

ParallelRowIterator.IterateRows<VerticalConvolutionRowOperation, Vector4>(
this.Configuration,
operationBounds,
interest,
in verticalOperation);
}

Expand Down Expand Up @@ -143,6 +139,11 @@ public HorizontalConvolutionRowOperation(
this.preserveAlpha = preserveAlpha;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span)
Expand Down Expand Up @@ -304,6 +305,11 @@ public VerticalConvolutionRowOperation(
this.preserveAlpha = preserveAlpha;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,14 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());

// We use a rectangle 2x the interest width to allocate a buffer big enough
// for source and target bulk pixel conversion.
var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height);
using (var map = new KernelSamplingMap(allocator))
{
map.BuildSamplingOffsetMap(this.KernelXY, interest);

var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
operationBounds,
interest,
in operation);
}

Expand Down Expand Up @@ -106,6 +103,11 @@ public RowOperation(
this.preserveAlpha = preserveAlpha;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());

// We use a rectangle with width set wider, to allocate a buffer big enough
// for kernel source, channel buffers, source rows and target bulk pixel conversion.
int operationWidth = (2 * kernelSize * kernelSize) + interest.Width + (kernelSize * interest.Width);
Rectangle operationBounds = new(interest.X, interest.Y, operationWidth, interest.Height);

using KernelSamplingMap map = new(this.Configuration.MemoryAllocator);
map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY);

Expand All @@ -50,7 +45,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)

ParallelRowIterator.IterateRows<MedianRowOperation<TPixel>, Vector4>(
this.Configuration,
operationBounds,
interest,
in operation);

Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,14 @@ public MedianRowOperation(Rectangle bounds, Buffer2D<TPixel> targetPixels, Buffe
this.wChannelStart = this.zChannelStart + kernelCount;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> (2 * this.kernelSize * this.kernelSize) + bounds.Width + (this.kernelSize * bounds.Width);

public void Invoke(int y, Span<Vector4> span)
{
// Span has kernelSize^2 followed by bound width.
// Span has kernelSize^2 twice, then bound width followed by kernelsize * bounds width.
int boundsX = this.bounds.X;
int boundsWidth = this.bounds.Width;
int kernelCount = this.kernelSize * this.kernelSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ public RowOperation(
this.rowProcessor = rowProcessor;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ public RowOperation(
this.configuration = configuration;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public OpaqueRowOperation(
this.bounds = bounds;
}

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
Expand Down
Loading

0 comments on commit e7f701e

Please sign in to comment.