Skip to content

Port GrayscalConverter to Arm #2409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

29 changes: 29 additions & 0 deletions src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.PixelFormats;

Expand Down Expand Up @@ -554,6 +555,34 @@ public static Vector256<float> MultiplyAdd(
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}

/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector128{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.AlwaysInline)]
public static Vector128<float> MultiplyAdd(
Vector128<float> va,
Vector128<float> vm0,
Vector128<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(vm1, vm0, va);
}

if (AdvSimd.IsSupported)
{
return AdvSimd.Add(AdvSimd.Multiply(vm0, vm1), va);
}

return Sse.Add(Sse.Multiply(vm0, vm1), va);
}

/// <summary>
/// Performs a multiplication and a subtraction of the <see cref="Vector256{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using static SixLabors.ImageSharp.SimdUtils;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components;

internal abstract partial class JpegColorConverterBase
{
internal sealed class GrayscaleArm : JpegColorConverterArm
{
public GrayscaleArm(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}

/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));

// Used for the color conversion
var scale = Vector128.Create(1 / this.MaximumValue);

nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = AdvSimd.Multiply(c0, scale);
}
}

/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destLuminance =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));

ref Vector128<float> srcRed =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcGreen =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcBlue =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));

// Used for the color conversion
var f0299 = Vector128.Create(0.299f);
var f0587 = Vector128.Create(0.587f);
var f0114 = Vector128.Create(0.114f);

nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> r = ref Unsafe.Add(ref srcRed, i);
ref Vector128<float> g = ref Unsafe.Add(ref srcGreen, i);
ref Vector128<float> b = ref Unsafe.Add(ref srcBlue, i);

// luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b)
Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ private static JpegColorConverterBase GetGrayScaleConverter(int precision)
return new GrayscaleAvx(precision);
}

if (JpegColorConverterArm.IsSupported)
{
return new GrayscaleArm(precision);
}

if (JpegColorConverterVector.IsSupported)
{
return new GrayScaleVector(precision);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ public void SimdVectorAvx()

new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values);
}

[Benchmark]
public void SimdVectorArm()
{
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);

new JpegColorConverterBase.GrayscaleArm(8).ConvertToRgbInplace(values);
}
}
19 changes: 18 additions & 1 deletion tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,23 @@ public void FromRgbToGrayscaleAvx2(int seed) =>
new JpegColorConverterBase.GrayscaleScalar(8),
precísion: 3);

[Theory]
[MemberData(nameof(Seeds))]
public void FromGrayscaleArm(int seed) =>
this.TestConversionToRgb(new JpegColorConverterBase.GrayscaleArm(8),
1,
seed,
new JpegColorConverterBase.GrayscaleScalar(8));

[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbToGrayscaleArm(int seed) =>
this.TestConversionFromRgb(new JpegColorConverterBase.GrayscaleArm(8),
1,
seed,
new JpegColorConverterBase.GrayscaleScalar(8),
precísion: 3);

[Theory]
[MemberData(nameof(Seeds))]
public void FromRgbAvx2(int seed) =>
Expand Down Expand Up @@ -480,7 +497,7 @@ private static void ValidateConversionFromRgb(
JpegColorConverterBase baseLineConverter,
int precision = 4)
{
// arrange
// arrange
JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed);
JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed);
Random rnd = new(seed);
Expand Down