diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
index e312703368..60dadb617b 100644
--- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs
+++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
@@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp
/// The at the specified position.
public ref T this[int row, int column]
{
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.CheckCoordinates(row, column);
@@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp
///
/// The representation on the source data.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator DenseMatrix(T[,] data) => new DenseMatrix(data);
///
@@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp
///
/// The representation on the source data.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
#pragma warning disable SA1008 // Opening parenthesis should be spaced correctly
public static implicit operator T[,](in DenseMatrix data)
#pragma warning restore SA1008 // Opening parenthesis should be spaced correctly
@@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp
/// Transposes the rows and columns of the dense matrix.
///
/// The .
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public DenseMatrix Transpose()
{
var result = new DenseMatrix(this.Rows, this.Columns);
@@ -196,13 +196,13 @@ namespace SixLabors.ImageSharp
/// Fills the matrix with the given value
///
/// The value to fill each item with
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Fill(T value) => this.Span.Fill(value);
///
/// Clears the matrix setting each value to the default value for the element type
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear() => this.Span.Clear();
///
@@ -232,14 +232,14 @@ namespace SixLabors.ImageSharp
=> obj is DenseMatrix other && this.Equals(other);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(DenseMatrix other) =>
this.Columns == other.Columns
&& this.Rows == other.Rows
&& this.Span.SequenceEqual(other.Span);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
HashCode code = default;
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
index 8f1d373556..249c73e8d6 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
@@ -43,12 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
///
- /// Gets the horizontal gradient operator.
+ /// Gets the horizontal convolution kernel.
///
public DenseMatrix KernelX { get; }
///
- /// Gets the vertical gradient operator.
+ /// Gets the vertical convolution kernel.
///
public DenseMatrix KernelY { get; }
@@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
- Span yOffsets = this.map.GetYOffsetSpan();
- Span xOffsets = this.map.GetXOffsetSpan();
+
+ var state = new Convolution2DState(this.kernelY, this.kernelX, this.map);
int row = y - this.bounds.Y;
if (this.preserveAlpha)
@@ -141,10 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int column = 0; column < this.bounds.Width; column++)
{
Convolver.Convolve2D3(
- in this.kernelY,
- in this.kernelX,
- yOffsets,
- xOffsets,
+ in state,
this.sourcePixels,
ref targetRowRef,
row,
@@ -156,10 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int column = 0; column < this.bounds.Width; column++)
{
Convolver.Convolve2D4(
- in this.kernelY,
- in this.kernelX,
- yOffsets,
- xOffsets,
+ in state,
this.sourcePixels,
ref targetRowRef,
row,
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs
new file mode 100644
index 0000000000..e36d458a4a
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// A stack only struct used for reducing reference indirection during 2D convolution operations.
+ ///
+ internal readonly ref struct Convolution2DState
+ {
+ private readonly Span rowOffsetMap;
+ private readonly Span columnOffsetMap;
+ private readonly int kernelHeight;
+ private readonly int kernelWidth;
+
+ public Convolution2DState(
+ in DenseMatrix kernelY,
+ in DenseMatrix kernelX,
+ KernelSamplingMap map)
+ {
+ // We check the kernels are the same size upstream.
+ this.KernelY = new ReadOnlyKernel(kernelY);
+ this.KernelX = new ReadOnlyKernel(kernelX);
+ this.kernelHeight = kernelY.Rows;
+ this.kernelWidth = kernelY.Columns;
+ this.rowOffsetMap = map.GetRowOffsetSpan();
+ this.columnOffsetMap = map.GetColumnOffsetSpan();
+ }
+
+ public ReadOnlyKernel KernelY
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ public ReadOnlyKernel KernelX
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetRowSampleOffset(int row, int kernelRow)
+ => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetColumnSampleOffset(int column, int kernelColumn)
+ => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
index 2ea062e281..95fd3b83cc 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
@@ -42,12 +42,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
///
- /// Gets the horizontal gradient operator.
+ /// Gets the horizontal convolution kernel.
///
public DenseMatrix KernelX { get; }
///
- /// Gets the vertical gradient operator.
+ /// Gets the vertical convolution kernel.
///
public DenseMatrix KernelY { get; }
@@ -143,8 +143,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
- Span yOffsets = this.map.GetYOffsetSpan();
- Span xOffsets = this.map.GetXOffsetSpan();
+
+ var state = new ConvolutionState(this.kernel, this.map);
int row = y - this.bounds.Y;
if (this.preserveAlpha)
@@ -152,9 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int column = 0; column < this.bounds.Width; column++)
{
Convolver.Convolve3(
- in this.kernel,
- yOffsets,
- xOffsets,
+ in state,
this.sourcePixels,
ref targetRowRef,
row,
@@ -166,9 +164,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int column = 0; column < this.bounds.Width; column++)
{
Convolver.Convolve4(
- in this.kernel,
- yOffsets,
- xOffsets,
+ in state,
this.sourcePixels,
ref targetRowRef,
row,
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
index 999fba22be..191460f40b 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
@@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
///
- /// Gets the 2d gradient operator.
+ /// Gets the 2d convolution kernel.
///
public DenseMatrix KernelXY { get; }
@@ -110,8 +110,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
- Span yOffsets = this.map.GetYOffsetSpan();
- Span xOffsets = this.map.GetXOffsetSpan();
+
+ var state = new ConvolutionState(this.kernel, this.map);
int row = y - this.bounds.Y;
if (this.preserveAlpha)
@@ -119,9 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int column = 0; column < this.bounds.Width; column++)
{
Convolver.Convolve3(
- in this.kernel,
- yOffsets,
- xOffsets,
+ in state,
this.sourcePixels,
ref targetRowRef,
row,
@@ -133,9 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int column = 0; column < this.bounds.Width; column++)
{
Convolver.Convolve4(
- in this.kernel,
- yOffsets,
- xOffsets,
+ in state,
this.sourcePixels,
ref targetRowRef,
row,
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs
new file mode 100644
index 0000000000..97a3af342e
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// A stack only struct used for reducing reference indirection during convolution operations.
+ ///
+ internal readonly ref struct ConvolutionState
+ {
+ private readonly Span rowOffsetMap;
+ private readonly Span columnOffsetMap;
+ private readonly int kernelHeight;
+ private readonly int kernelWidth;
+
+ public ConvolutionState(
+ in DenseMatrix kernel,
+ KernelSamplingMap map)
+ {
+ this.Kernel = new ReadOnlyKernel(kernel);
+ this.kernelHeight = kernel.Rows;
+ this.kernelWidth = kernel.Columns;
+ this.rowOffsetMap = map.GetRowOffsetSpan();
+ this.columnOffsetMap = map.GetColumnOffsetSpan();
+ }
+
+ public ReadOnlyKernel Kernel
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetRowSampleOffset(int row, int kernelRow)
+ => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetColumnSampleOffset(int column, int kernelColumn)
+ => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs
index c9e9d74148..5ddc8e85c6 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs
@@ -1,11 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
-using System;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp
{
@@ -19,20 +20,14 @@ namespace SixLabors.ImageSharp
/// Using this method the convolution filter is not applied to alpha in addition to the color channels.
///
/// The pixel format.
- /// The vertical convolution kernel.
- /// The horizontal convolution kernel.
- /// The span containing precalculated kernel y-sampling offsets.
- /// The span containing precalculated kernel x-sampling offsets.
+ /// The 2D convolution kernels state.
/// The source frame.
/// The target row base reference.
/// The current row.
/// The current column.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve2D3(
- in DenseMatrix kernelY,
- in DenseMatrix kernelX,
- Span rowSampleOffsets,
- Span columnSampleOffsets,
+ in Convolution2DState state,
Buffer2D sourcePixels,
ref Vector4 targetRowRef,
int row,
@@ -42,10 +37,7 @@ namespace SixLabors.ImageSharp
Vector4 vector = default;
Convolve2DImpl(
- in kernelY,
- in kernelX,
- rowSampleOffsets,
- columnSampleOffsets,
+ in state,
sourcePixels,
row,
column,
@@ -63,20 +55,14 @@ namespace SixLabors.ImageSharp
/// Using this method the convolution filter is applied to alpha in addition to the color channels.
///
/// The pixel format.
- /// The vertical convolution kernel.
- /// The horizontal convolution kernel.
- /// The span containing precalculated kernel y-sampling offsets.
- /// The span containing precalculated kernel x-sampling offsets.
+ /// The 2D convolution kernels state.
/// The source frame.
/// The target row base reference.
/// The current row.
/// The current column.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve2D4(
- in DenseMatrix kernelY,
- in DenseMatrix kernelX,
- Span rowSampleOffsets,
- Span columnSampleOffsets,
+ in Convolution2DState state,
Buffer2D sourcePixels,
ref Vector4 targetRowRef,
int row,
@@ -86,10 +72,7 @@ namespace SixLabors.ImageSharp
Vector4 vector = default;
Convolve2DImpl(
- in kernelY,
- in kernelX,
- rowSampleOffsets,
- columnSampleOffsets,
+ in state,
sourcePixels,
row,
column,
@@ -100,34 +83,33 @@ namespace SixLabors.ImageSharp
target = vector;
}
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve2DImpl(
- in DenseMatrix kernelY,
- in DenseMatrix kernelX,
- Span rowSampleOffsets,
- Span columnSampleOffsets,
+ in Convolution2DState state,
Buffer2D sourcePixels,
int row,
int column,
ref Vector4 targetVector)
where TPixel : unmanaged, IPixel
{
- Vector4 vectorY = default;
- Vector4 vectorX = default;
+ ReadOnlyKernel kernelY = state.KernelY;
+ ReadOnlyKernel kernelX = state.KernelX;
int kernelHeight = kernelY.Rows;
int kernelWidth = kernelY.Columns;
+ Vector4 vectorY = default;
+ Vector4 vectorX = default;
+
for (int y = 0; y < kernelHeight; y++)
{
- int offsetY = rowSampleOffsets[(row * kernelHeight) + y];
- Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
+ int offsetY = state.GetRowSampleOffset(row, y);
+ ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY));
for (int x = 0; x < kernelWidth; x++)
{
- int offsetX = columnSampleOffsets[(column * kernelWidth) + x];
- var sample = sourceRowSpan[offsetX].ToVector4();
+ int offsetX = state.GetColumnSampleOffset(column, x);
+ var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4();
Numerics.Premultiply(ref sample);
-
vectorX += kernelX[y, x] * sample;
vectorY += kernelY[y, x] * sample;
}
@@ -141,18 +123,14 @@ namespace SixLabors.ImageSharp
/// Using this method the convolution filter is not applied to alpha in addition to the color channels.
///
/// The pixel format.
- /// The convolution kernel.
- /// The span containing precalculated kernel y-sampling offsets.
- /// The span containing precalculated kernel x-sampling offsets.
+ /// The convolution kernel state.
/// The source frame.
/// The target row base reference.
/// The current row.
/// The current column.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve3(
- in DenseMatrix kernel,
- Span rowSampleOffsets,
- Span columnSampleOffsets,
+ in ConvolutionState state,
Buffer2D sourcePixels,
ref Vector4 targetRowRef,
int row,
@@ -162,9 +140,7 @@ namespace SixLabors.ImageSharp
Vector4 vector = default;
ConvolveImpl(
- in kernel,
- rowSampleOffsets,
- columnSampleOffsets,
+ state,
sourcePixels,
row,
column,
@@ -182,18 +158,14 @@ namespace SixLabors.ImageSharp
/// Using this method the convolution filter is applied to alpha in addition to the color channels.
///
/// The pixel format.
- /// The convolution kernel.
- /// The span containing precalculated kernel y-offsets.
- /// The span containing precalculated kernel x-offsets.
+ /// The convolution kernel state.
/// The source frame.
/// The target row base reference.
/// The current row.
/// The current column.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve4(
- in DenseMatrix kernel,
- Span rowSampleOffsets,
- Span columnSampleOffsets,
+ in ConvolutionState state,
Buffer2D sourcePixels,
ref Vector4 targetRowRef,
int row,
@@ -203,9 +175,7 @@ namespace SixLabors.ImageSharp
Vector4 vector = default;
ConvolveImpl(
- in kernel,
- rowSampleOffsets,
- columnSampleOffsets,
+ state,
sourcePixels,
row,
column,
@@ -216,29 +186,28 @@ namespace SixLabors.ImageSharp
target = vector;
}
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ConvolveImpl(
- in DenseMatrix kernel,
- Span rowSampleOffsets,
- Span columnSampleOffsets,
+ in ConvolutionState state,
Buffer2D sourcePixels,
int row,
int column,
ref Vector4 targetVector)
where TPixel : unmanaged, IPixel
{
+ ReadOnlyKernel kernel = state.Kernel;
int kernelHeight = kernel.Rows;
int kernelWidth = kernel.Columns;
for (int y = 0; y < kernelHeight; y++)
{
- int offsetY = rowSampleOffsets[(row * kernelHeight) + y];
- Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
+ int offsetY = state.GetRowSampleOffset(row, y);
+ ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY));
for (int x = 0; x < kernelWidth; x++)
{
- int offsetX = columnSampleOffsets[(column * kernelWidth) + x];
- var sample = sourceRowSpan[offsetX].ToVector4();
+ int offsetX = state.GetColumnSampleOffset(column, x);
+ var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4();
Numerics.Premultiply(ref sample);
targetVector += kernel[y, x] * sample;
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
similarity index 95%
rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs
rename to src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
index 493c0d0fd2..73a4fa4004 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
@@ -76,10 +76,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span GetYOffsetSpan() => this.yOffsets.GetSpan();
+ public Span GetRowOffsetSpan() => this.yOffsets.GetSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span GetXOffsetSpan() => this.xOffsets.GetSpan();
+ public Span GetColumnOffsetSpan() => this.xOffsets.GetSpan();
///
public void Dispose()
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs
new file mode 100644
index 0000000000..37e0060054
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// A stack only, readonly, kernel matrix that can be indexed without
+ /// bounds checks when compiled in release mode.
+ ///
+ internal readonly ref struct ReadOnlyKernel
+ {
+ private readonly ReadOnlySpan values;
+
+ public ReadOnlyKernel(DenseMatrix matrix)
+ {
+ this.Columns = matrix.Columns;
+ this.Rows = matrix.Rows;
+ this.values = matrix.Span;
+ }
+
+ public int Columns
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ public int Rows
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ public float this[int row, int column]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ this.CheckCoordinates(row, column);
+ ref float vBase = ref MemoryMarshal.GetReference(this.values);
+ return Unsafe.Add(ref vBase, (row * this.Columns) + column);
+ }
+ }
+
+ [Conditional("DEBUG")]
+ private void CheckCoordinates(int row, int column)
+ {
+ if (row < 0 || row >= this.Rows)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds.");
+ }
+
+ if (column < 0 || column >= this.Columns)
+ {
+ throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds.");
+ }
+ }
+ }
+}