mirror of https://github.com/SixLabors/ImageSharp
18 changed files with 302 additions and 166 deletions
@ -1,11 +1,10 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
|
||||
using System.Buffers.Binary; |
using System.Buffers.Binary; |
||||
using System.Runtime.CompilerServices; |
using System.Runtime.CompilerServices; |
||||
|
|
||||
namespace SixLabors.ImageSharp.PixelFormats |
namespace SixLabors.ImageSharp.PixelFormats.Utils |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Contains optimized implementations for conversion between pixel formats.
|
/// Contains optimized implementations for conversion between pixel formats.
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.PixelFormats |
namespace SixLabors.ImageSharp.PixelFormats.Utils |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Low-performance extension methods to help conversion syntax, suitable for testing purposes.
|
/// Low-performance extension methods to help conversion syntax, suitable for testing purposes.
|
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue