mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
104 changed files with 3217 additions and 1170 deletions
@ -1,22 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats |
|||
{ |
|||
/// <summary>
|
|||
/// Low-performance extension methods to help conversion syntax, suitable for testing purposes.
|
|||
/// </summary>
|
|||
internal static class PixelExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the result of <see cref="IPixel.ToRgba32"/> as a new <see cref="Rgba32"/> instance.
|
|||
/// </summary>
|
|||
public static Rgba32 ToRgba32<TPixel>(this TPixel pixel) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Rgba32 result = default; |
|||
pixel.ToRgba32(ref result); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers.Binary; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.Utils |
|||
{ |
|||
/// <summary>
|
|||
/// Contains optimized implementations for conversion between pixel formats.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Implementations are based on ideas in:
|
|||
/// https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/Binary/Reader.cs#L84
|
|||
/// The JIT can detect and optimize rotation idioms ROTL (Rotate Left)
|
|||
/// and ROTR (Rotate Right) emitting efficient CPU instructions:
|
|||
/// https://github.com/dotnet/coreclr/pull/1830
|
|||
/// </remarks>
|
|||
internal static class PixelConverter |
|||
{ |
|||
public static class FromRgba32 |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Argb32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToArgb32(uint packedRgba) |
|||
{ |
|||
// packedRgba = [aa bb gg rr]
|
|||
// ROTL(8, packedRgba) = [bb gg rr aa]
|
|||
return (packedRgba << 8) | (packedRgba >> 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Bgra32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToBgra32(uint packedRgba) |
|||
{ |
|||
// packedRgba = [aa bb gg rr]
|
|||
// tmp1 = [aa 00 gg 00]
|
|||
// tmp2 = [00 bb 00 rr]
|
|||
// tmp3=ROTL(16, tmp2) = [00 rr 00 bb]
|
|||
// tmp1 + tmp3 = [aa rr gg bb]
|
|||
uint tmp1 = packedRgba & 0xFF00FF00; |
|||
uint tmp2 = packedRgba & 0x00FF00FF; |
|||
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); |
|||
return tmp1 + tmp3; |
|||
} |
|||
} |
|||
|
|||
public static class FromArgb32 |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Argb32"/> to <see cref="Rgba32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToRgba32(uint packedArgb) |
|||
{ |
|||
// packedArgb = [bb gg rr aa]
|
|||
// ROTR(8, packedArgb) = [aa bb gg rr]
|
|||
return (packedArgb >> 8) | (packedArgb << 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Argb32"/> to <see cref="Bgra32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToBgra32(uint packedArgb) |
|||
{ |
|||
// packedArgb = [bb gg rr aa]
|
|||
// REVERSE(packedArgb) = [aa rr gg bb]
|
|||
return BinaryPrimitives.ReverseEndianness(packedArgb); |
|||
} |
|||
} |
|||
|
|||
public static class FromBgra32 |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Bgra32"/> to <see cref="Argb32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToArgb32(uint packedBgra) |
|||
{ |
|||
// packedBgra = [aa rr gg bb]
|
|||
// REVERSE(packedBgra) = [bb gg rr aa]
|
|||
return BinaryPrimitives.ReverseEndianness(packedBgra); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Bgra32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToRgba32(uint packedBgra) |
|||
{ |
|||
// packedRgba = [aa rr gg bb]
|
|||
// tmp1 = [aa 00 gg 00]
|
|||
// tmp2 = [00 rr 00 bb]
|
|||
// tmp3=ROTL(16, tmp2) = [00 bb 00 rr]
|
|||
// tmp1 + tmp3 = [aa bb gg rr]
|
|||
uint tmp1 = packedBgra & 0xFF00FF00; |
|||
uint tmp2 = packedBgra & 0x00FF00FF; |
|||
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); |
|||
return tmp1 + tmp3; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.Utils |
|||
{ |
|||
/// <summary>
|
|||
/// Helper class for (bulk) conversion of <see cref="Vector4"/> buffers to/from other buffer types.
|
|||
/// </summary>
|
|||
internal static partial class Vector4Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Provides default implementations for batched to/from <see cref="Vector4"/> conversion.
|
|||
/// WARNING: The methods are operating without bounds checking and input validation!
|
|||
/// Input validation is the responsibility of the caller!
|
|||
/// </summary>
|
|||
public static class Default |
|||
{ |
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal static void DangerousFromVector4<TPixel>( |
|||
ReadOnlySpan<Vector4> sourceVectors, |
|||
Span<TPixel> destPixels) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); |
|||
ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); |
|||
|
|||
for (int i = 0; i < sourceVectors.Length; i++) |
|||
{ |
|||
ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref TPixel dp = ref Unsafe.Add(ref destRef, i); |
|||
dp.FromVector4(sp); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal static void DangerousToVector4<TPixel>( |
|||
ReadOnlySpan<TPixel> sourcePixels, |
|||
Span<Vector4> destVectors) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); |
|||
ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); |
|||
|
|||
for (int i = 0; i < sourcePixels.Length; i++) |
|||
{ |
|||
ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref Vector4 dp = ref Unsafe.Add(ref destRef, i); |
|||
dp = sp.ToVector4(); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal static void DangerousFromScaledVector4<TPixel>( |
|||
ReadOnlySpan<Vector4> sourceVectors, |
|||
Span<TPixel> destinationColors) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); |
|||
ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); |
|||
|
|||
for (int i = 0; i < sourceVectors.Length; i++) |
|||
{ |
|||
ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref TPixel dp = ref Unsafe.Add(ref destRef, i); |
|||
dp.FromScaledVector4(sp); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal static void DangerousToScaledVector4<TPixel>( |
|||
ReadOnlySpan<TPixel> sourceColors, |
|||
Span<Vector4> destinationVectors) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); |
|||
ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); |
|||
|
|||
for (int i = 0; i < sourceColors.Length; i++) |
|||
{ |
|||
ref TPixel sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref Vector4 dp = ref Unsafe.Add(ref destRef, i); |
|||
dp = sp.ToScaledVector4(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.Utils |
|||
{ |
|||
/// <content>
|
|||
/// Contains <see cref="RgbaCompatible"/>
|
|||
/// </content>
|
|||
internal static partial class Vector4Converters |
|||
{ |
|||
/// <summary>
|
|||
/// Provides efficient implementations for batched to/from <see cref="Vector4"/> conversion.
|
|||
/// which is applicable for <see cref="Rgba32"/>-compatible pixel types where <see cref="IPixel.ToVector4"/>
|
|||
/// returns the same scaled result as <see cref="IPixel.ToScaledVector4"/>.
|
|||
/// The method is works by internally converting to a <see cref="Rgba32"/> therefore it's not applicable for that type!
|
|||
/// </summary>
|
|||
public static class RgbaCompatible |
|||
{ |
|||
/// <summary>
|
|||
/// It's not worth to bother the transitive pixel conversion method below this limit.
|
|||
/// The value depends on the actual gain brought by the SIMD characteristics of the executing CPU and JIT.
|
|||
/// </summary>
|
|||
private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); |
|||
|
|||
/// <summary>
|
|||
/// Provides an efficient default implementation for <see cref="PixelOperations{TPixel}.ToVector4"/>
|
|||
/// and <see cref="PixelOperations{TPixel}.ToScaledVector4"/>
|
|||
/// which is applicable for <see cref="Rgba32"/>-compatible pixel types where <see cref="IPixel.ToVector4"/>
|
|||
/// returns the same scaled result as <see cref="IPixel.ToScaledVector4"/>.
|
|||
/// The method is works by internally converting to a <see cref="Rgba32"/> therefore it's not applicable for that type!
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal static void ToVector4<TPixel>( |
|||
Configuration configuration, |
|||
PixelOperations<TPixel> pixelOperations, |
|||
ReadOnlySpan<TPixel> sourcePixels, |
|||
Span<Vector4> destVectors, |
|||
bool scaled) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(configuration, nameof(configuration)); |
|||
Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); |
|||
|
|||
int count = sourcePixels.Length; |
|||
|
|||
// Not worth for small buffers:
|
|||
if (count < Vector4ConversionThreshold) |
|||
{ |
|||
ToVector4Fallback(sourcePixels, destVectors, scaled); |
|||
|
|||
return; |
|||
} |
|||
|
|||
// Using the last quarter of 'destVectors' as a temporary buffer to avoid allocation:
|
|||
int countWithoutLastItem = count - 1; |
|||
ReadOnlySpan<TPixel> reducedSource = sourcePixels.Slice(0, countWithoutLastItem); |
|||
Span<Rgba32> lastQuarterOfDestBuffer = MemoryMarshal.Cast<Vector4, Rgba32>(destVectors).Slice((3 * count) + 1, countWithoutLastItem); |
|||
pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestBuffer); |
|||
|
|||
// 'destVectors' and 'lastQuarterOfDestBuffer' are ovelapping buffers,
|
|||
// but we are always reading/writing at different positions:
|
|||
SimdUtils.BulkConvertByteToNormalizedFloat( |
|||
MemoryMarshal.Cast<Rgba32, byte>(lastQuarterOfDestBuffer), |
|||
MemoryMarshal.Cast<Vector4, float>(destVectors.Slice(0, countWithoutLastItem))); |
|||
|
|||
destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides an efficient default implementation for <see cref="PixelOperations{TPixel}.FromVector4"/>
|
|||
/// and <see cref="PixelOperations{TPixel}.FromScaledVector4"/>
|
|||
/// which is applicable for <see cref="Rgba32"/>-compatible pixel types where <see cref="IPixel.ToVector4"/>
|
|||
/// returns the same scaled result as <see cref="IPixel.ToScaledVector4"/>.
|
|||
/// The method is works by internally converting to a <see cref="Rgba32"/> therefore it's not applicable for that type!
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal static void FromVector4<TPixel>( |
|||
Configuration configuration, |
|||
PixelOperations<TPixel> pixelOperations, |
|||
ReadOnlySpan<Vector4> sourceVectors, |
|||
Span<TPixel> destPixels, |
|||
bool scaled) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(configuration, nameof(configuration)); |
|||
Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); |
|||
|
|||
int count = sourceVectors.Length; |
|||
|
|||
// Not worth for small buffers:
|
|||
if (count < Vector4ConversionThreshold) |
|||
{ |
|||
FromVector4Fallback(sourceVectors, destPixels, scaled); |
|||
|
|||
return; |
|||
} |
|||
|
|||
// For the opposite direction it's not easy to implement the trick used in RunRgba32CompatibleToVector4Conversion,
|
|||
// so let's allocate a temporary buffer as usually:
|
|||
using (IMemoryOwner<Rgba32> tempBuffer = configuration.MemoryAllocator.Allocate<Rgba32>(count)) |
|||
{ |
|||
Span<Rgba32> tempSpan = tempBuffer.Memory.Span; |
|||
|
|||
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( |
|||
MemoryMarshal.Cast<Vector4, float>(sourceVectors), |
|||
MemoryMarshal.Cast<Rgba32, byte>(tempSpan)); |
|||
|
|||
pixelOperations.FromRgba32(configuration, tempSpan, destPixels); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ColdPath)] |
|||
private static void ToVector4Fallback<TPixel>(ReadOnlySpan<TPixel> sourcePixels, Span<Vector4> destVectors, bool scaled) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
if (scaled) |
|||
{ |
|||
Default.DangerousToScaledVector4(sourcePixels, destVectors); |
|||
} |
|||
else |
|||
{ |
|||
Default.DangerousToVector4(sourcePixels, destVectors); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ColdPath)] |
|||
private static void FromVector4Fallback<TPixel>(ReadOnlySpan<Vector4> sourceVectors, Span<TPixel> destPixels, bool scaled) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
if (scaled) |
|||
{ |
|||
Default.DangerousFromScaledVector4(sourceVectors, destPixels); |
|||
} |
|||
else |
|||
{ |
|||
Default.DangerousFromVector4(sourceVectors, destPixels); |
|||
} |
|||
} |
|||
|
|||
private static int CalculateVector4ConversionThreshold() |
|||
{ |
|||
if (!Vector.IsHardwareAccelerated) |
|||
{ |
|||
return int.MaxValue; |
|||
} |
|||
|
|||
return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.IsAvx2CompatibleArchitecture ? 256 : 128; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System.Buffers; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk |
|||
{ |
|||
public abstract class Rgb24Bytes<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private IMemoryOwner<TPixel> source; |
|||
|
|||
private IMemoryOwner<byte> destination; |
|||
|
|||
private Configuration configuration; |
|||
|
|||
[Params(16, 128, 1024)] |
|||
public int Count { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.configuration = Configuration.Default; |
|||
this.source = this.configuration.MemoryAllocator.Allocate<TPixel>(this.Count); |
|||
this.destination = this.configuration.MemoryAllocator.Allocate<byte>(this.Count * 3); |
|||
} |
|||
|
|||
[GlobalCleanup] |
|||
public void Cleanup() |
|||
{ |
|||
this.source.Dispose(); |
|||
this.destination.Dispose(); |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void CommonBulk() => |
|||
new PixelOperations<TPixel>().ToRgb24Bytes( |
|||
this.configuration, |
|||
this.source.GetSpan(), |
|||
this.destination.GetSpan(), |
|||
this.Count); |
|||
|
|||
[Benchmark] |
|||
public void OptimizedBulk() => |
|||
PixelOperations<TPixel>.Instance.ToRgb24Bytes( |
|||
this.configuration, |
|||
this.source.GetSpan(), |
|||
this.destination.GetSpan(), |
|||
this.Count); |
|||
} |
|||
|
|||
public class Rgb24Bytes_Rgba32 : Rgb24Bytes<Rgba32> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class ToVector4_Bgra32 : ToVector4<Bgra32> |
|||
{ |
|||
[Benchmark(Baseline = true)] |
|||
public void PixelOperations_Base() |
|||
{ |
|||
new PixelOperations<Bgra32>().ToVector4( |
|||
this.Configuration, |
|||
this.source.GetSpan(), |
|||
this.destination.GetSpan()); |
|||
} |
|||
|
|||
// RESULTS:
|
|||
// Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
|||
// ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:|
|
|||
// PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B |
|
|||
// PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B |
|
|||
// | | | | | | | | | |
|
|||
// PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B |
|
|||
// PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B |
|
|||
// | | | | | | | | | |
|
|||
// PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B |
|
|||
// PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B |
|
|||
// | | | | | | | | | |
|
|||
// PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B |
|
|||
// PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B |
|
|||
// | | | | | | | | | |
|
|||
// PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B |
|
|||
// PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B |
|
|||
// | | | | | | | | | |
|
|||
// PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B |
|
|||
// PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B |
|
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class ToVector4_Rgba32 : ToVector4<Rgba32> |
|||
{ |
|||
[Benchmark] |
|||
public void FallbackIntrinsics128() |
|||
{ |
|||
Span<byte> sBytes = MemoryMarshal.Cast<Rgba32, byte>(this.source.GetSpan()); |
|||
Span<float> dFloats = MemoryMarshal.Cast<Vector4, float>(this.destination.GetSpan()); |
|||
|
|||
SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(sBytes, dFloats); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void PixelOperations_Base() |
|||
{ |
|||
new PixelOperations<Rgba32>().ToVector4( |
|||
this.Configuration, |
|||
this.source.GetSpan(), |
|||
this.destination.GetSpan()); |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void BasicIntrinsics256() |
|||
{ |
|||
Span<byte> sBytes = MemoryMarshal.Cast<Rgba32, byte>(this.source.GetSpan()); |
|||
Span<float> dFloats = MemoryMarshal.Cast<Vector4, float>(this.destination.GetSpan()); |
|||
|
|||
SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(sBytes, dFloats); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void ExtendedIntrinsics() |
|||
{ |
|||
Span<byte> sBytes = MemoryMarshal.Cast<Rgba32, byte>(this.source.GetSpan()); |
|||
Span<float> dFloats = MemoryMarshal.Cast<Vector4, float>(this.destination.GetSpan()); |
|||
|
|||
SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(sBytes, dFloats); |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() |
|||
{ |
|||
Span<byte> sBytes = MemoryMarshal.Cast<Rgba32, byte>(this.source.GetSpan()); |
|||
Span<float> dFloats = MemoryMarshal.Cast<Vector4, float>(this.destination.GetSpan()); |
|||
|
|||
int n = dFloats.Length / Vector<byte>.Count; |
|||
|
|||
ref Vector<byte> sourceBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference((ReadOnlySpan<byte>)sBytes)); |
|||
ref Vector<float> destBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(dFloats)); |
|||
ref Vector<uint> destBaseU = ref Unsafe.As<Vector<float>, Vector<uint>>(ref destBase); |
|||
|
|||
for (int i = 0; i < n; i++) |
|||
{ |
|||
Vector<byte> b = Unsafe.Add(ref sourceBase, i); |
|||
|
|||
Vector.Widen(b, out Vector<ushort> s0, out Vector<ushort> s1); |
|||
Vector.Widen(s0, out Vector<uint> w0, out Vector<uint> w1); |
|||
Vector.Widen(s1, out Vector<uint> w2, out Vector<uint> w3); |
|||
|
|||
ref Vector<uint> d = ref Unsafe.Add(ref destBaseU, i * 4); |
|||
d = w0; |
|||
Unsafe.Add(ref d, 1) = w1; |
|||
Unsafe.Add(ref d, 2) = w2; |
|||
Unsafe.Add(ref d, 3) = w3; |
|||
} |
|||
|
|||
n = dFloats.Length / Vector<float>.Count; |
|||
var scale = new Vector<float>(1f / 255f); |
|||
|
|||
for (int i = 0; i < n; i++) |
|||
{ |
|||
ref Vector<float> dRef = ref Unsafe.Add(ref destBase, i); |
|||
|
|||
Vector<int> du = Vector.AsVectorInt32(dRef); |
|||
Vector<float> v = Vector.ConvertToSingle(du); |
|||
v *= scale; |
|||
|
|||
dRef = v; |
|||
} |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() |
|||
{ |
|||
Span<byte> sBytes = MemoryMarshal.Cast<Rgba32, byte>(this.source.GetSpan()); |
|||
Span<float> dFloats = MemoryMarshal.Cast<Vector4, float>(this.destination.GetSpan()); |
|||
|
|||
int n = dFloats.Length / Vector<byte>.Count; |
|||
|
|||
ref Vector<byte> sourceBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference((ReadOnlySpan<byte>)sBytes)); |
|||
ref Vector<float> destBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(dFloats)); |
|||
var scale = new Vector<float>(1f / 255f); |
|||
|
|||
for (int i = 0; i < n; i++) |
|||
{ |
|||
Vector<byte> b = Unsafe.Add(ref sourceBase, i); |
|||
|
|||
Vector.Widen(b, out Vector<ushort> s0, out Vector<ushort> s1); |
|||
Vector.Widen(s0, out Vector<uint> w0, out Vector<uint> w1); |
|||
Vector.Widen(s1, out Vector<uint> w2, out Vector<uint> w3); |
|||
|
|||
Vector<float> f0 = ConvertToNormalizedSingle(w0, scale); |
|||
Vector<float> f1 = ConvertToNormalizedSingle(w1, scale); |
|||
Vector<float> f2 = ConvertToNormalizedSingle(w2, scale); |
|||
Vector<float> f3 = ConvertToNormalizedSingle(w3, scale); |
|||
|
|||
ref Vector<float> d = ref Unsafe.Add(ref destBase, i * 4); |
|||
d = f0; |
|||
Unsafe.Add(ref d, 1) = f1; |
|||
Unsafe.Add(ref d, 2) = f2; |
|||
Unsafe.Add(ref d, 3) = f3; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector<float> ConvertToNormalizedSingle(Vector<uint> u, Vector<float> scale) |
|||
{ |
|||
Vector<int> vi = Vector.AsVectorInt32(u); |
|||
Vector<float> v = Vector.ConvertToSingle(vi); |
|||
v *= scale; |
|||
return v; |
|||
} |
|||
|
|||
// RESULTS (2018 October):
|
|||
//
|
|||
// Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
|||
// ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:|
|
|||
// FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B |
|
|||
// BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B |
|
|||
// ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B |
|
|||
// PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B |
|
|||
// PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized!
|
|||
// | | | | | | | | | |
|
|||
// FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B |
|
|||
// BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B |
|
|||
// ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B |
|
|||
// PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B |
|
|||
// PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B |
|
|||
// | | | | | | | | | |
|
|||
// FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B |
|
|||
// BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B |
|
|||
// ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock!
|
|||
// PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B |
|
|||
// PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :(
|
|||
// | | | | | | | | | |
|
|||
// FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B |
|
|||
// BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B |
|
|||
// ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock!
|
|||
// PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B |
|
|||
// PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock!
|
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System.Buffers; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk |
|||
{ |
|||
public abstract class ToXyz<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private IMemoryOwner<TPixel> source; |
|||
|
|||
private IMemoryOwner<byte> destination; |
|||
|
|||
[Params(16, 128, 1024)] |
|||
public int Count { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.source = Configuration.Default.MemoryAllocator.Allocate<TPixel>(this.Count); |
|||
this.destination = Configuration.Default.MemoryAllocator.Allocate<byte>(this.Count * 3); |
|||
} |
|||
|
|||
[GlobalCleanup] |
|||
public void Cleanup() |
|||
{ |
|||
this.source.Dispose(); |
|||
this.destination.Dispose(); |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void CommonBulk() => new PixelOperations<TPixel>().ToRgb24Bytes(this.source.GetSpan(), this.destination.GetSpan(), this.Count); |
|||
|
|||
[Benchmark] |
|||
public void OptimizedBulk() => PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.source.GetSpan(), this.destination.GetSpan(), this.Count); |
|||
} |
|||
|
|||
public class ToXyz_Rgba32 : ToXyz<Rgba32> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion |
|||
{ |
|||
public class PixelConversion_Rgba32_To_Argb32 |
|||
{ |
|||
private Rgba32[] source; |
|||
|
|||
private Argb32[] dest; |
|||
|
|||
[Params(64)] |
|||
public int Count { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.source = new Rgba32[this.Count]; |
|||
this.dest = new Argb32[this.Count]; |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Default() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Argb32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count; i++) |
|||
{ |
|||
Rgba32 s = Unsafe.Add(ref sBase, i); |
|||
Unsafe.Add(ref dBase, i).FromRgba32(s); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private static void Default_GenericImpl<TPixel>(ReadOnlySpan<Rgba32> source, Span<TPixel> dest) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); |
|||
ref TPixel dBase = ref MemoryMarshal.GetReference(dest); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgba32 s = Unsafe.Add(ref sBase, i); |
|||
Unsafe.Add(ref dBase, i).FromRgba32(s); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Default_Generic() |
|||
{ |
|||
Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Default_Group2() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Argb32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count; i += 2) |
|||
{ |
|||
ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); |
|||
Rgba32 s1 = Unsafe.Add(ref s0, 1); |
|||
|
|||
ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); |
|||
d0.FromRgba32(s0); |
|||
Unsafe.Add(ref d0, 1).FromRgba32(s1); |
|||
} |
|||
} |
|||
|
|||
|
|||
[Benchmark] |
|||
public void Default_Group4() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Argb32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count; i += 4) |
|||
{ |
|||
ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); |
|||
ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); |
|||
ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); |
|||
Rgba32 s3 = Unsafe.Add(ref s2, 1); |
|||
|
|||
ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); |
|||
ref Argb32 d1 = ref Unsafe.Add(ref d0, 1); |
|||
ref Argb32 d2 = ref Unsafe.Add(ref d1, 1); |
|||
|
|||
d0.FromRgba32(s0); |
|||
d1.FromRgba32(s1); |
|||
d2.FromRgba32(s2); |
|||
Unsafe.Add(ref d2, 1).FromRgba32(s3); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void BitOps() |
|||
{ |
|||
ref uint sBase = ref Unsafe.As<Rgba32, uint>(ref this.source[0]); |
|||
ref uint dBase = ref Unsafe.As<Argb32, uint>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count; i++) |
|||
{ |
|||
uint s = Unsafe.Add(ref sBase, i); |
|||
Unsafe.Add(ref dBase, i) = FromRgba32.ToArgb32(s); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void BitOps_GroupAsULong() |
|||
{ |
|||
ref ulong sBase = ref Unsafe.As<Rgba32, ulong>(ref this.source[0]); |
|||
ref ulong dBase = ref Unsafe.As<Argb32, ulong>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count / 2; i++) |
|||
{ |
|||
ulong s = Unsafe.Add(ref sBase, i); |
|||
uint lo = (uint)s; |
|||
uint hi = (uint)(s >> 32); |
|||
lo = FromRgba32.ToArgb32(lo); |
|||
hi = FromRgba32.ToArgb32(hi); |
|||
|
|||
s = (ulong)(hi << 32) | lo; |
|||
|
|||
Unsafe.Add(ref dBase, i) = s; |
|||
} |
|||
} |
|||
|
|||
public static class FromRgba32 |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Argb32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToArgb32(uint packedRgba) |
|||
{ |
|||
// packedRgba = [aa bb gg rr]
|
|||
// ROL(8, packedRgba) = [bb gg rr aa]
|
|||
return (packedRgba << 8) | (packedRgba >> 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Bgra32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToBgra32(uint packedRgba) |
|||
{ |
|||
// packedRgba = [aa bb gg rr]
|
|||
// tmp1 = [aa 00 gg 00]
|
|||
// tmp2 = [00 bb 00 rr]
|
|||
// tmp3=ROL(16, tmp2) = [00 rr 00 bb]
|
|||
// tmp1 + tmp3 = [aa rr gg bb]
|
|||
uint tmp1 = packedRgba & 0xFF00FF00; |
|||
uint tmp2 = packedRgba & 0x00FF00FF; |
|||
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); |
|||
return tmp1 + tmp3; |
|||
} |
|||
} |
|||
|
|||
// RESULTS:
|
|||
// Method | Count | Mean | Error | StdDev | Scaled |
|
|||
// -------------------- |------ |----------:|----------:|----------:|-------:|
|
|||
// Default | 64 | 107.33 ns | 1.0633 ns | 0.9426 ns | 1.00 |
|
|||
// Default_Generic | 64 | 111.15 ns | 0.3789 ns | 0.3544 ns | 1.04 |
|
|||
// Default_Group2 | 64 | 90.36 ns | 0.7779 ns | 0.6896 ns | 0.84 |
|
|||
// Default_Group4 | 64 | 82.39 ns | 0.2726 ns | 0.2550 ns | 0.77 |
|
|||
// BitOps | 64 | 39.25 ns | 0.3266 ns | 0.2895 ns | 0.37 |
|
|||
// BitOps_GroupAsULong | 64 | 41.80 ns | 0.2227 ns | 0.2083 ns | 0.39 |
|
|||
} |
|||
} |
|||
@ -0,0 +1,392 @@ |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using BenchmarkDotNet.Attributes.Jobs; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tuples; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion |
|||
{ |
|||
//[MonoJob]
|
|||
//[RyuJitX64Job]
|
|||
public class PixelConversion_Rgba32_To_Bgra32 |
|||
{ |
|||
private Rgba32[] source; |
|||
|
|||
private Bgra32[] dest; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct Tuple4OfUInt32 |
|||
{ |
|||
public uint V0, V1, V2, V3; |
|||
|
|||
public void ConvertMe() |
|||
{ |
|||
this.V0 = FromRgba32.ToBgra32(this.V0); |
|||
this.V1 = FromRgba32.ToBgra32(this.V1); |
|||
this.V2 = FromRgba32.ToBgra32(this.V2); |
|||
this.V3 = FromRgba32.ToBgra32(this.V3); |
|||
} |
|||
} |
|||
|
|||
[Params(64)] |
|||
public int Count { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.source = new Rgba32[this.Count]; |
|||
this.dest = new Bgra32[this.Count]; |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void Default() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Bgra32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count; i++) |
|||
{ |
|||
ref Rgba32 s = ref Unsafe.Add(ref sBase, i); |
|||
Unsafe.Add(ref dBase, i).FromRgba32(s); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private static void Default_GenericImpl<TPixel>(ReadOnlySpan<Rgba32> source, Span<TPixel> dest) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); |
|||
ref TPixel dBase = ref MemoryMarshal.GetReference(dest); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
ref Rgba32 s = ref Unsafe.Add(ref sBase, i); |
|||
Unsafe.Add(ref dBase, i).FromRgba32(s); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Default_Generic() |
|||
{ |
|||
Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Default_Group2() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Bgra32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count; i+=2) |
|||
{ |
|||
ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); |
|||
Rgba32 s1 = Unsafe.Add(ref s0, 1); |
|||
|
|||
ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); |
|||
d0.FromRgba32(s0); |
|||
Unsafe.Add(ref d0, 1).FromRgba32(s1); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Default_Group4() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Bgra32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count; i += 4) |
|||
{ |
|||
ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); |
|||
ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); |
|||
ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); |
|||
Rgba32 s3 = Unsafe.Add(ref s2, 1); |
|||
|
|||
ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); |
|||
ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); |
|||
ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); |
|||
|
|||
d0.FromRgba32(s0); |
|||
d1.FromRgba32(s1); |
|||
d2.FromRgba32(s2); |
|||
Unsafe.Add(ref d2, 1).FromRgba32(s3); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private static void Group4GenericImpl<TPixel>(ReadOnlySpan<Rgba32> source, Span<TPixel> dest) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); |
|||
ref TPixel dBase = ref MemoryMarshal.GetReference(dest); |
|||
|
|||
for (int i = 0; i < source.Length; i += 4) |
|||
{ |
|||
ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); |
|||
ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); |
|||
ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); |
|||
Rgba32 s3 = Unsafe.Add(ref s2, 1); |
|||
|
|||
ref TPixel d0 = ref Unsafe.Add(ref dBase, i); |
|||
ref TPixel d1 = ref Unsafe.Add(ref d0, 1); |
|||
ref TPixel d2 = ref Unsafe.Add(ref d1, 1); |
|||
|
|||
d0.FromRgba32(s0); |
|||
d1.FromRgba32(s1); |
|||
d2.FromRgba32(s2); |
|||
Unsafe.Add(ref d2, 1).FromRgba32(s3); |
|||
} |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void Default_Group4_Generic() |
|||
{ |
|||
Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void Default_Group8() |
|||
{ |
|||
ref Rgba32 sBase = ref this.source[0]; |
|||
ref Bgra32 dBase = ref this.dest[0]; |
|||
|
|||
for (int i = 0; i < this.Count / 4; i += 4) |
|||
{ |
|||
ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); |
|||
ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); |
|||
ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); |
|||
ref Rgba32 s3 = ref Unsafe.Add(ref s1, 1); |
|||
|
|||
ref Rgba32 s4 = ref Unsafe.Add(ref s3, 1); |
|||
ref Rgba32 s5 = ref Unsafe.Add(ref s4, 1); |
|||
ref Rgba32 s6 = ref Unsafe.Add(ref s5, 1); |
|||
Rgba32 s7 = Unsafe.Add(ref s6, 1); |
|||
|
|||
ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); |
|||
ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); |
|||
ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); |
|||
ref Bgra32 d3 = ref Unsafe.Add(ref d2, 1); |
|||
ref Bgra32 d4 = ref Unsafe.Add(ref d3, 1); |
|||
|
|||
ref Bgra32 d5 = ref Unsafe.Add(ref d4, 1); |
|||
ref Bgra32 d6 = ref Unsafe.Add(ref d5, 1); |
|||
|
|||
|
|||
d0.FromRgba32(s0); |
|||
d1.FromRgba32(s1); |
|||
d2.FromRgba32(s2); |
|||
d3.FromRgba32(s3); |
|||
|
|||
d4.FromRgba32(s4); |
|||
d5.FromRgba32(s5); |
|||
d6.FromRgba32(s6); |
|||
Unsafe.Add(ref d6, 1).FromRgba32(s7); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void BitOps() |
|||
{ |
|||
ref uint sBase = ref Unsafe.As<Rgba32, uint>(ref this.source[0]); |
|||
ref uint dBase = ref Unsafe.As<Bgra32, uint>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count; i++) |
|||
{ |
|||
uint s = Unsafe.Add(ref sBase, i); |
|||
Unsafe.Add(ref dBase, i) = FromRgba32.ToBgra32(s); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Bitops_Tuple() |
|||
{ |
|||
ref Tuple4OfUInt32 sBase = ref Unsafe.As<Rgba32, Tuple4OfUInt32>(ref this.source[0]); |
|||
ref Tuple4OfUInt32 dBase = ref Unsafe.As<Bgra32, Tuple4OfUInt32>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count / 4; i++) |
|||
{ |
|||
ref Tuple4OfUInt32 d = ref Unsafe.Add(ref dBase, i); |
|||
d = Unsafe.Add(ref sBase, i); |
|||
d.ConvertMe(); |
|||
} |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void Bitops_SingleTuple() |
|||
{ |
|||
ref Tuple4OfUInt32 sBase = ref Unsafe.As<Rgba32, Tuple4OfUInt32>(ref this.source[0]); |
|||
|
|||
for (int i = 0; i < this.Count / 4; i++) |
|||
{ |
|||
Unsafe.Add(ref sBase, i).ConvertMe(); |
|||
} |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void Bitops_Simd() |
|||
{ |
|||
ref Octet.OfUInt32 sBase = ref Unsafe.As<Rgba32, Octet.OfUInt32>(ref this.source[0]); |
|||
ref Octet.OfUInt32 dBase = ref Unsafe.As<Bgra32, Octet.OfUInt32>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count / 8; i++) |
|||
{ |
|||
BitopsSimdImpl(ref Unsafe.Add(ref sBase, i), ref Unsafe.Add(ref dBase, i)); |
|||
} |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct B |
|||
{ |
|||
public uint tmp2, tmp5, tmp8, tmp11, tmp14, tmp17, tmp20, tmp23; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct C |
|||
{ |
|||
public uint tmp3, tmp6, tmp9, tmp12, tmp15, tmp18, tmp21, tmp24; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void BitopsSimdImpl(ref Octet.OfUInt32 s, ref Octet.OfUInt32 d) |
|||
{ |
|||
Vector<uint> sVec = Unsafe.As<Octet.OfUInt32, Vector<uint>>(ref s); |
|||
Vector<uint> aMask = new Vector<uint>(0xFF00FF00); |
|||
Vector<uint> bMask = new Vector<uint>(0x00FF00FF); |
|||
|
|||
Vector<uint> aa = sVec & aMask; |
|||
Vector<uint> bb = sVec & bMask; |
|||
|
|||
B b = Unsafe.As<Vector<uint>, B>(ref bb); |
|||
|
|||
C c = default; |
|||
|
|||
c.tmp3 = (b.tmp2 << 16) | (b.tmp2 >> 16); |
|||
c.tmp6 = (b.tmp5 << 16) | (b.tmp5 >> 16); |
|||
c.tmp9 = (b.tmp8 << 16) | (b.tmp8 >> 16); |
|||
c.tmp12 = (b.tmp11 << 16) | (b.tmp11 >> 16); |
|||
c.tmp15 = (b.tmp14 << 16) | (b.tmp14 >> 16); |
|||
c.tmp18 = (b.tmp17 << 16) | (b.tmp17 >> 16); |
|||
c.tmp21 = (b.tmp20 << 16) | (b.tmp20 >> 16); |
|||
c.tmp24 = (b.tmp23 << 16) | (b.tmp23 >> 16); |
|||
|
|||
Vector<uint> cc = Unsafe.As<C, Vector<uint>>(ref c); |
|||
Vector<uint> dd = aa + cc; |
|||
|
|||
d = Unsafe.As<Vector<uint>, Octet.OfUInt32>(ref dd); |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void BitOps_Group2() |
|||
{ |
|||
ref uint sBase = ref Unsafe.As<Rgba32, uint>(ref this.source[0]); |
|||
ref uint dBase = ref Unsafe.As<Bgra32, uint>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count; i++) |
|||
{ |
|||
ref uint s0 = ref Unsafe.Add(ref sBase, i); |
|||
uint s1 = Unsafe.Add(ref s0, 1); |
|||
|
|||
ref uint d0 = ref Unsafe.Add(ref dBase, i); |
|||
d0 = FromRgba32.ToBgra32(s0); |
|||
Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void BitOps_GroupAsULong() |
|||
{ |
|||
ref ulong sBase = ref Unsafe.As<Rgba32, ulong>(ref this.source[0]); |
|||
ref ulong dBase = ref Unsafe.As<Bgra32, ulong>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count / 2; i++) |
|||
{ |
|||
ulong s = Unsafe.Add(ref sBase, i); |
|||
uint lo = (uint)s; |
|||
uint hi = (uint)(s >> 32); |
|||
lo = FromRgba32.ToBgra32(lo); |
|||
hi = FromRgba32.ToBgra32(hi); |
|||
|
|||
s = (ulong)(hi << 32) | lo; |
|||
|
|||
Unsafe.Add(ref dBase, i) = s; |
|||
} |
|||
} |
|||
|
|||
//[Benchmark]
|
|||
public void BitOps_GroupAsULong_V2() |
|||
{ |
|||
ref ulong sBase = ref Unsafe.As<Rgba32, ulong>(ref this.source[0]); |
|||
ref ulong dBase = ref Unsafe.As<Bgra32, ulong>(ref this.dest[0]); |
|||
|
|||
for (int i = 0; i < this.Count / 2; i++) |
|||
{ |
|||
ulong s = Unsafe.Add(ref sBase, i); |
|||
uint lo = (uint)s; |
|||
uint hi = (uint)(s >> 32); |
|||
|
|||
uint tmp1 = lo & 0xFF00FF00; |
|||
uint tmp4 = hi & 0xFF00FF00; |
|||
|
|||
uint tmp2 = lo & 0x00FF00FF; |
|||
uint tmp5 = hi & 0x00FF00FF; |
|||
|
|||
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); |
|||
uint tmp6 = (tmp5 << 16) | (tmp5 >> 16); |
|||
|
|||
lo = tmp1 + tmp3; |
|||
hi = tmp4 + tmp6; |
|||
|
|||
s = (ulong)(hi << 32) | lo; |
|||
|
|||
Unsafe.Add(ref dBase, i) = s; |
|||
} |
|||
} |
|||
|
|||
public static class FromRgba32 |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Argb32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToArgb32(uint packedRgba) |
|||
{ |
|||
// packedRgba = [aa bb gg rr]
|
|||
// ROL(8, packedRgba) = [bb gg rr aa]
|
|||
return (packedRgba << 8) | (packedRgba >> 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a packed <see cref="Rgba32"/> to <see cref="Bgra32"/>.
|
|||
/// </summary>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static uint ToBgra32(uint packedRgba) |
|||
{ |
|||
// packedRgba = [aa bb gg rr]
|
|||
// tmp1 = [aa 00 gg 00]
|
|||
// tmp2 = [00 bb 00 rr]
|
|||
// tmp3=ROL(16, tmp2) = [00 rr 00 bb]
|
|||
// tmp1 + tmp3 = [aa rr gg bb]
|
|||
uint tmp1 = packedRgba & 0xFF00FF00; |
|||
uint tmp2 = packedRgba & 0x00FF00FF; |
|||
uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); |
|||
return tmp1 + tmp3; |
|||
} |
|||
} |
|||
|
|||
|
|||
// RESULTS:
|
|||
// Method | Count | Mean | Error | StdDev | Scaled | ScaledSD |
|
|||
// -------------------- |------ |---------:|----------:|----------:|-------:|---------:|
|
|||
// Default | 64 | 82.67 ns | 0.6737 ns | 0.5625 ns | 1.00 | 0.00 |
|
|||
// Default_Generic | 64 | 88.73 ns | 1.7959 ns | 1.7638 ns | 1.07 | 0.02 |
|
|||
// Default_Group2 | 64 | 91.03 ns | 1.5237 ns | 1.3508 ns | 1.10 | 0.02 |
|
|||
// Default_Group4 | 64 | 86.62 ns | 1.5737 ns | 1.4720 ns | 1.05 | 0.02 |
|
|||
// BitOps | 64 | 57.45 ns | 0.6067 ns | 0.5066 ns | 0.69 | 0.01 |
|
|||
// Bitops_Tuple | 64 | 75.47 ns | 1.1824 ns | 1.1060 ns | 0.91 | 0.01 |
|
|||
// BitOps_GroupAsULong | 64 | 65.42 ns | 0.7157 ns | 0.6695 ns | 0.79 | 0.01 |
|
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
public class GraphicsOptionsTests |
|||
{ |
|||
[Fact] |
|||
public void IsOpaqueColor() |
|||
{ |
|||
Assert.True(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Red)); |
|||
Assert.False(new GraphicsOptions(true, 0.5f).IsOpaqueColorWithoutBlending(Rgba32.Red)); |
|||
Assert.False(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Transparent)); |
|||
Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Lighten, 1).IsOpaqueColorWithoutBlending(Rgba32.Red)); |
|||
Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.DestOver, 1).IsOpaqueColorWithoutBlending(Rgba32.Red)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats |
|||
{ |
|||
public abstract class PixelConverterTests |
|||
{ |
|||
public static readonly TheoryData<byte, byte, byte, byte> RgbaData = |
|||
new TheoryData<byte, byte, byte, byte> |
|||
{ |
|||
{ 0, 0, 0, 0 }, |
|||
{ 0, 0, 0, 255 }, |
|||
{ 0, 0, 255, 0 }, |
|||
{ 0, 255, 0, 0 }, |
|||
{ 255, 0, 0, 0 }, |
|||
{ 255, 255, 255, 255 }, |
|||
{ 0, 0, 0, 1 }, |
|||
{ 0, 0, 1, 0 }, |
|||
{ 0, 1, 0, 0 }, |
|||
{ 1, 0, 0, 0 }, |
|||
{ 3, 5, 7, 11 }, |
|||
{ 67, 71, 101, 109 } |
|||
}; |
|||
|
|||
public class FromRgba32 : PixelConverterTests |
|||
{ |
|||
[Theory] |
|||
[MemberData(nameof(RgbaData))] |
|||
public void ToArgb32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Rgba32 s = ReferenceImplementations.MakeRgba32(r, g, b, a); |
|||
|
|||
// Act:
|
|||
uint actualPacked = PixelConverter.FromRgba32.ToArgb32(s.PackedValue); |
|||
|
|||
// Assert:
|
|||
uint expectedPacked = ReferenceImplementations.MakeArgb32(r, g, b, a).PackedValue; |
|||
|
|||
Assert.Equal(expectedPacked, actualPacked); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(RgbaData))] |
|||
public void ToBgra32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Rgba32 s = ReferenceImplementations.MakeRgba32(r, g, b, a); |
|||
|
|||
// Act:
|
|||
uint actualPacked = PixelConverter.FromRgba32.ToBgra32(s.PackedValue); |
|||
|
|||
// Assert:
|
|||
uint expectedPacked = ReferenceImplementations.MakeBgra32(r, g, b, a).PackedValue; |
|||
|
|||
Assert.Equal(expectedPacked, actualPacked); |
|||
} |
|||
} |
|||
|
|||
public class FromArgb32 : PixelConverterTests |
|||
{ |
|||
[Theory] |
|||
[MemberData(nameof(RgbaData))] |
|||
public void ToRgba32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Argb32 s = ReferenceImplementations.MakeArgb32(r, g, b, a); |
|||
|
|||
// Act:
|
|||
uint actualPacked = PixelConverter.FromArgb32.ToRgba32(s.PackedValue); |
|||
|
|||
// Assert:
|
|||
uint expectedPacked = ReferenceImplementations.MakeRgba32(r, g, b, a).PackedValue; |
|||
|
|||
Assert.Equal(expectedPacked, actualPacked); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(RgbaData))] |
|||
public void ToBgra32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Argb32 s = ReferenceImplementations.MakeArgb32(r, g, b, a); |
|||
|
|||
// Act:
|
|||
uint actualPacked = PixelConverter.FromArgb32.ToBgra32(s.PackedValue); |
|||
|
|||
// Assert:
|
|||
uint expectedPacked = ReferenceImplementations.MakeBgra32(r, g, b, a).PackedValue; |
|||
|
|||
Assert.Equal(expectedPacked, actualPacked); |
|||
} |
|||
} |
|||
|
|||
public class FromBgra32 : PixelConverterTests |
|||
{ |
|||
[Theory] |
|||
[MemberData(nameof(RgbaData))] |
|||
public void ToArgb32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Bgra32 s = ReferenceImplementations.MakeBgra32(r, g, b, a); |
|||
|
|||
// Act:
|
|||
uint actualPacked = PixelConverter.FromBgra32.ToArgb32(s.PackedValue); |
|||
|
|||
// Assert:
|
|||
uint expectedPacked = ReferenceImplementations.MakeArgb32(r, g, b, a).PackedValue; |
|||
|
|||
Assert.Equal(expectedPacked, actualPacked); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(RgbaData))] |
|||
public void ToRgba32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Bgra32 s = ReferenceImplementations.MakeBgra32(r, g, b, a); |
|||
|
|||
// Act:
|
|||
uint actualPacked = PixelConverter.FromBgra32.ToRgba32(s.PackedValue); |
|||
|
|||
// Assert:
|
|||
uint expectedPacked = ReferenceImplementations.MakeRgba32(r, g, b, a).PackedValue; |
|||
|
|||
Assert.Equal(expectedPacked, actualPacked); |
|||
} |
|||
} |
|||
|
|||
|
|||
private static class ReferenceImplementations |
|||
{ |
|||
public static Rgba32 MakeRgba32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Rgba32 d = default; |
|||
d.R = r; |
|||
d.G = g; |
|||
d.B = b; |
|||
d.A = a; |
|||
return d; |
|||
} |
|||
|
|||
public static Argb32 MakeArgb32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Argb32 d = default; |
|||
d.R = r; |
|||
d.G = g; |
|||
d.B = b; |
|||
d.A = a; |
|||
return d; |
|||
} |
|||
|
|||
public static Bgra32 MakeBgra32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Bgra32 d = default; |
|||
d.R = r; |
|||
d.G = g; |
|||
d.B = b; |
|||
d.A = a; |
|||
return d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Argb32OperationsTests : PixelOperationsTests<Argb32> |
|||
{ |
|||
|
|||
public Argb32OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Argb32.PixelOperations>(PixelOperations<Argb32>.Instance); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Bgr24OperationsTests : PixelOperationsTests<Bgr24> |
|||
{ |
|||
public Bgr24OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Bgr24.PixelOperations>(PixelOperations<Bgr24>.Instance); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Bgra32OperationsTests : PixelOperationsTests<Bgra32> |
|||
{ |
|||
public Bgra32OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Bgra32.PixelOperations>(PixelOperations<Bgra32>.Instance); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Gray16OperationsTests : PixelOperationsTests<Gray16> |
|||
{ |
|||
public Gray16OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Gray16.PixelOperations>(PixelOperations<Gray16>.Instance); |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void FromGray8Bytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count); |
|||
var expected = new Gray16[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
expected[i].FromGray8(new Gray8(source[i])); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void ToGray8Bytes(int count) |
|||
{ |
|||
Gray16[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count]; |
|||
var gray = default(Gray8); |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
gray.FromScaledVector4(source[i].ToScaledVector4()); |
|||
expected[i] = gray.PackedValue; |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void FromGray16Bytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count * 2); |
|||
Span<byte> sourceSpan = source.AsSpan(); |
|||
var expected = new Gray16[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i2 = i * 2; |
|||
expected[i].FromGray16(MemoryMarshal.Cast<byte, Gray16>(sourceSpan.Slice(i2, 2))[0]); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void ToGray16Bytes(int count) |
|||
{ |
|||
Gray16[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count * 2]; |
|||
Gray16 gray = default; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i2 = i * 2; |
|||
gray.FromScaledVector4(source[i].ToScaledVector4()); |
|||
OctetBytes bytes = Unsafe.As<Gray16, OctetBytes>(ref gray); |
|||
expected[i2] = bytes[0]; |
|||
expected[i2 + 1] = bytes[1]; |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Gray8OperationsTests : PixelOperationsTests<Gray8> |
|||
{ |
|||
public Gray8OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Gray8.PixelOperations>(PixelOperations<Gray8>.Instance); |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void FromGray8Bytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count); |
|||
var expected = new Gray8[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
expected[i].FromGray8(new Gray8(source[i])); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void ToGray8Bytes(int count) |
|||
{ |
|||
Gray8[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count]; |
|||
var gray = default(Gray8); |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
gray.FromScaledVector4(source[i].ToScaledVector4()); |
|||
expected[i] = gray.PackedValue; |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void FromGray16Bytes(int count) |
|||
{ |
|||
byte[] source = CreateByteTestData(count * 2); |
|||
Span<byte> sourceSpan = source.AsSpan(); |
|||
var expected = new Gray8[count]; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i2 = i * 2; |
|||
expected[i].FromGray16(MemoryMarshal.Cast<byte, Gray16>(sourceSpan.Slice(i2, 2))[0]); |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ArraySizesData))] |
|||
public void ToGray16Bytes(int count) |
|||
{ |
|||
Gray8[] source = CreatePixelTestData(count); |
|||
byte[] expected = new byte[count * 2]; |
|||
Gray16 gray = default; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
int i2 = i * 2; |
|||
gray.FromScaledVector4(source[i].ToScaledVector4()); |
|||
OctetBytes bytes = Unsafe.As<Gray16, OctetBytes>(ref gray); |
|||
expected[i2] = bytes[0]; |
|||
expected[i2 + 1] = bytes[1]; |
|||
} |
|||
|
|||
TestOperation( |
|||
source, |
|||
expected, |
|||
(s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Rgb48OperationsTests : PixelOperationsTests<Rgb48> |
|||
{ |
|||
public Rgb48OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Rgb48.PixelOperations>(PixelOperations<Rgb48>.Instance); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Rgba32OperationsTests : PixelOperationsTests<Rgba32> |
|||
{ |
|||
public Rgba32OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Rgba32.PixelOperations>(PixelOperations<Rgba32>.Instance); |
|||
|
|||
[Fact(Skip = SkipProfilingBenchmarks)] |
|||
public void Benchmark_ToVector4() |
|||
{ |
|||
const int times = 200000; |
|||
const int count = 1024; |
|||
|
|||
using (IMemoryOwner<Rgba32> source = Configuration.Default.MemoryAllocator.Allocate<Rgba32>(count)) |
|||
using (IMemoryOwner<Vector4> dest = Configuration.Default.MemoryAllocator.Allocate<Vector4>(count)) |
|||
{ |
|||
this.Measure( |
|||
times, |
|||
() => PixelOperations<Rgba32>.Instance.ToVector4( |
|||
this.Configuration, |
|||
source.GetSpan(), |
|||
dest.GetSpan())); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Rgba64OperationsTests : PixelOperationsTests<Rgba64> |
|||
{ |
|||
public Rgba64OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<Rgba64.PixelOperations>(PixelOperations<Rgba64>.Instance); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class RgbaVectorOperationsTests : PixelOperationsTests<RgbaVector> |
|||
{ |
|||
public RgbaVectorOperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsSpecialImplementation() => Assert.IsType<RgbaVector.PixelOperations>(PixelOperations<RgbaVector>.Instance); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue