Browse Source

Introduce GetRequiredBufferLength method

pull/2241/head
Ynse Hoornenborg 4 years ago
parent
commit
d42d8df88d
  1. 7
      src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
  2. 7
      src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs
  3. 16
      src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
  4. 10
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  5. 5
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
  6. 5
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  7. 17
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  8. 5
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs
  9. 10
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  10. 5
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  11. 5
      src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
  12. 5
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs
  13. 5
      src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs
  14. 5
      src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs
  15. 5
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs
  16. 5
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs
  17. 5
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
  18. 5
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
  19. 6
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  20. 3
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

7
src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs

@ -12,6 +12,13 @@ namespace SixLabors.ImageSharp.Advanced;
public interface IRowIntervalOperation<TBuffer> public interface IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged 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> /// <summary>
/// Invokes the method passing the row interval and a buffer. /// Invokes the method passing the row interval and a buffer.
/// </summary> /// </summary>

7
src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs

@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp.Advanced;
public interface IRowOperation<TBuffer> public interface IRowOperation<TBuffer>
where TBuffer : unmanaged 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> /// <summary>
/// Invokes the method passing the row and a buffer. /// Invokes the method passing the row and a buffer.
/// </summary> /// </summary>

16
src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs

@ -63,7 +63,7 @@ public static partial class ParallelRowIterator
private readonly int minY; private readonly int minY;
private readonly int maxY; private readonly int maxY;
private readonly int stepY; private readonly int stepY;
private readonly int width; private readonly int bufferLength;
private readonly MemoryAllocator allocator; private readonly MemoryAllocator allocator;
private readonly T action; private readonly T action;
@ -72,14 +72,14 @@ public static partial class ParallelRowIterator
int minY, int minY,
int maxY, int maxY,
int stepY, int stepY,
int width, int bufferLength,
MemoryAllocator allocator, MemoryAllocator allocator,
in T action) in T action)
{ {
this.minY = minY; this.minY = minY;
this.maxY = maxY; this.maxY = maxY;
this.stepY = stepY; this.stepY = stepY;
this.width = width; this.bufferLength = bufferLength;
this.allocator = allocator; this.allocator = allocator;
this.action = action; this.action = action;
} }
@ -96,7 +96,7 @@ public static partial class ParallelRowIterator
int yMax = Math.Min(yMin + this.stepY, this.maxY); 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; Span<TBuffer> span = buffer.Memory.Span;
@ -153,7 +153,7 @@ public static partial class ParallelRowIterator
private readonly int minY; private readonly int minY;
private readonly int maxY; private readonly int maxY;
private readonly int stepY; private readonly int stepY;
private readonly int width; private readonly int bufferLength;
private readonly MemoryAllocator allocator; private readonly MemoryAllocator allocator;
private readonly T operation; private readonly T operation;
@ -162,14 +162,14 @@ public static partial class ParallelRowIterator
int minY, int minY,
int maxY, int maxY,
int stepY, int stepY,
int width, int bufferLength,
MemoryAllocator allocator, MemoryAllocator allocator,
in T operation) in T operation)
{ {
this.minY = minY; this.minY = minY;
this.maxY = maxY; this.maxY = maxY;
this.stepY = stepY; this.stepY = stepY;
this.width = width; this.bufferLength = bufferLength;
this.allocator = allocator; this.allocator = allocator;
this.operation = operation; this.operation = operation;
} }
@ -187,7 +187,7 @@ public static partial class ParallelRowIterator
int yMax = Math.Min(yMin + this.stepY, this.maxY); int yMax = Math.Min(yMin + this.stepY, this.maxY);
var rows = new RowInterval(yMin, yMax); 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); Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
} }

10
src/ImageSharp/Advanced/ParallelRowIterator.cs

@ -118,11 +118,12 @@ public static partial class ParallelRowIterator
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator; MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
// Avoid TPL overhead in this trivial case: // Avoid TPL overhead in this trivial case:
if (numOfSteps == 1) 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; Span<TBuffer> span = buffer.Memory.Span;
for (int y = top; y < bottom; y++) for (int y = top; y < bottom; y++)
@ -135,7 +136,7 @@ public static partial class ParallelRowIterator
int verticalStep = DivideCeil(height, numOfSteps); int verticalStep = DivideCeil(height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 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( Parallel.For(
0, 0,
@ -244,12 +245,13 @@ public static partial class ParallelRowIterator
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator; MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
// Avoid TPL overhead in this trivial case: // Avoid TPL overhead in this trivial case:
if (numOfSteps == 1) if (numOfSteps == 1)
{ {
var rows = new RowInterval(top, bottom); 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); Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span);
@ -258,7 +260,7 @@ public static partial class ParallelRowIterator
int verticalStep = DivideCeil(height, numOfSteps); int verticalStep = DivideCeil(height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 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( Parallel.For(
0, 0,

5
src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs

@ -85,6 +85,11 @@ internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel>
this.clusterSize = clusterSize; this.clusterSize = clusterSize;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<L8> span) public void Invoke(int y, Span<L8> span)

5
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

@ -86,6 +86,11 @@ internal class BinaryThresholdProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration; this.configuration = configuration;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Rgb24> span) public void Invoke(int y, Span<Rgb24> span)

17
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

@ -220,6 +220,11 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration; this.configuration = configuration;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)
@ -289,6 +294,11 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
this.gamma = gamma; this.gamma = gamma;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)
@ -329,6 +339,13 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration; this.configuration = configuration;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
{
return bounds.Width;
}
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs

@ -46,6 +46,11 @@ internal readonly struct Convolution2DRowOperation<TPixel> : IRowOperation<Vecto
this.preserveAlpha = preserveAlpha; this.preserveAlpha = preserveAlpha;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

10
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -143,6 +143,11 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
this.preserveAlpha = preserveAlpha; this.preserveAlpha = preserveAlpha;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)
@ -304,6 +309,11 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
this.preserveAlpha = preserveAlpha; this.preserveAlpha = preserveAlpha;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -106,6 +106,11 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
this.preserveAlpha = preserveAlpha; this.preserveAlpha = preserveAlpha;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs

@ -43,6 +43,11 @@ internal readonly struct MedianRowOperation<TPixel> : IRowOperation<Vector4>
this.wChannelStart = this.zChannelStart + kernelCount; this.wChannelStart = this.zChannelStart + kernelCount;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)
{ {
// Span has kernelSize^2 followed by bound width. // Span has kernelSize^2 followed by bound width.

5
src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs

@ -83,6 +83,11 @@ internal sealed class PixelRowDelegateProcessor<TPixel, TDelegate> : ImageProces
this.rowProcessor = rowProcessor; this.rowProcessor = rowProcessor;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs

@ -66,6 +66,11 @@ internal class FilterProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration; this.configuration = configuration;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs

@ -46,6 +46,11 @@ internal sealed class OpaqueProcessor<TPixel> : ImageProcessor<TPixel>
this.bounds = bounds; this.bounds = bounds;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs

@ -93,6 +93,11 @@ internal class GlowProcessor<TPixel> : ImageProcessor<TPixel>
this.source = source; this.source = source;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<float> span) public void Invoke(int y, Span<float> span)
{ {

5
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs

@ -101,6 +101,11 @@ internal class VignetteProcessor<TPixel> : ImageProcessor<TPixel>
this.source = source; this.source = source;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<float> span) public void Invoke(int y, Span<float> span)
{ {

5
src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs

@ -176,6 +176,11 @@ internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>, IR
this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width); this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width);
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span) public void Invoke(in RowInterval rows, Span<Vector4> span)
{ {

5
src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs

@ -176,6 +176,11 @@ internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width); this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width);
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span) public void Invoke(in RowInterval rows, Span<Vector4> span)
{ {

6
tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs

@ -413,6 +413,9 @@ public class ParallelRowIteratorTests
public TestRowIntervalOperation(Action<RowInterval> action) public TestRowIntervalOperation(Action<RowInterval> action)
=> this.action = action; => this.action = action;
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
public void Invoke(in RowInterval rows) => this.action(rows); public void Invoke(in RowInterval rows) => this.action(rows);
} }
@ -424,6 +427,9 @@ public class ParallelRowIteratorTests
public TestRowIntervalOperation(RowIntervalAction<TBuffer> action) public TestRowIntervalOperation(RowIntervalAction<TBuffer> action)
=> this.action = action; => this.action = action;
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
public void Invoke(in RowInterval rows, Span<TBuffer> span) public void Invoke(in RowInterval rows, Span<TBuffer> span)
=> this.action(rows, span); => this.action(rows, span);
} }

3
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -755,6 +755,9 @@ public static class TestImageExtensions
this.source = source; this.source = source;
} }
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
public void Invoke(in RowInterval rows, Span<Vector4> span) public void Invoke(in RowInterval rows, Span<Vector4> span)
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)

Loading…
Cancel
Save