From 2bccda8c03ec44261a563b33f1716ee8dda4ec9c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 24 Aug 2021 15:29:08 +0300 Subject: [PATCH] 8x8 matrices small fixes --- .../Formats/Jpeg/Components/Block8x8.cs | 60 ++++++++++++++++++ .../Formats/Jpeg/Components/Block8x8F.cs | 55 +++++++++++++++++ .../Components/Encoder/HuffmanScanEncoder.cs | 61 +------------------ .../Formats/Jpg/HuffmanScanEncoderTests.cs | 10 +-- 4 files changed, 121 insertions(+), 65 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 79b26a042..adfabc13c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using System.Text; namespace SixLabors.ImageSharp.Formats.Jpeg.Components @@ -276,6 +278,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// public override int GetHashCode() => (this[0] * 31) + this[1]; + /// + /// Returns index of the last non-zero element in given matrix. + /// + /// + /// Returns 0 for all-zero matrix by convention. + /// + /// Index of the last non-zero element. + [MethodImpl(InliningOptions.ShortMethod)] + public int GetLastValuableElementIndex() + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + + Vector256 zero8 = Vector256.Zero; + + ref Vector256 mcuStride = ref Unsafe.As>(ref this); + + for (int i = 7; i >= 0; i--) + { + int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Unsafe.Add(ref mcuStride, i).AsInt32(), zero8).AsByte()); + + if (areEqual != equalityMask) + { + // Each 2 bits represents comparison operation for each 2-byte element in input vectors + // LSB represents first element in the stride + // MSB represents last element in the stride + // lzcnt operation would calculate number of zero numbers at the end + + // Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements + // So we need to invert it + int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual); + + // As input number is represented by 2 bits in the mask, we need to divide lzcnt result by 2 + // to get the exact number of zero elements in the stride + int strideRelativeIndex = 7 - (lzcnt / 2); + return (i * 8) + strideRelativeIndex; + } + } + + return 0; + } + else +#endif + { + int index = Size - 1; + ref short elemRef = ref Unsafe.As(ref this); + + while (index > 0 && Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + return index; + } + } + /// /// Calculate the total sum of absolute differences of elements in 'a' and 'b'. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index a11b807bb..b0d7b0876 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -864,5 +864,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return true; } } + + /// + /// Returns index of the last non-zero element in this matrix. + /// + /// Index of the last non-zero element. Returns -1 if all elements are equal to zero. + [MethodImpl(InliningOptions.ShortMethod)] + public int GetLastValuableElementIndex() + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + + Vector256 zero8 = Vector256.Zero; + + ref Vector256 mcuStride = ref Unsafe.As>(ref this); + + for (int i = 7; i >= 0; i--) + { + int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); + + if (areEqual != equalityMask) + { + // Each 4 bits represents comparison operation for each 4-byte element in input vectors + // LSB represents first element in the stride + // MSB represents last element in the stride + // lzcnt operation would calculate number of zero numbers at the end + + // Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements + // So we need to invert it + int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual); + + // As input number is represented by 4 bits in the mask, we need to divide lzcnt result by 4 + // to get the exact number of zero elements in the stride + int strideRelativeIndex = 7 - (lzcnt / 4); + return (i * 8) + strideRelativeIndex; + } + } + + return -1; + } + else +#endif + { + int index = Size - 1; + ref float elemRef = ref Unsafe.As(ref this); + + while (index >= 0 && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + return index; + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 373475f6b..134b4e1cc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; int runLength = 0; - int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2); + int lastValuableIndex = refTemp2.GetLastValuableElementIndex(); for (int zig = 1; zig <= lastValuableIndex; zig++) { int ac = (int)refTemp2[zig]; @@ -458,65 +458,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder #endif } - /// - /// Returns index of the last non-zero element in given matrix. - /// - /// - /// Returns 0 for all-zero matrix by convention. - /// - /// Mcu block. - /// Index of the last non-zero element. - [MethodImpl(InliningOptions.ShortMethod)] - internal static int GetLastValuableElementIndex(ref Block8x8F mcu) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported) - { - const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); - - Vector256 zero8 = Vector256.Zero; - - ref Vector256 mcuStride = ref mcu.V0; - - for (int i = 7; i >= 0; i--) - { - int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); - - if (areEqual != equalityMask) - { - // Each 4 bits represents comparison operation for each 4-byte element in input vectors - // LSB represents first element in the stride - // MSB represents last element in the stride - // lzcnt operation would calculate number of zero numbers at the end - - // Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements - // So we need to invert it - int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual); - - // As input number is represented by 4 bits in the mask, we need to divide lzcnt result by 4 - // to get the exact number of zero elements in the stride - int strideRelativeIndex = 7 - (lzcnt / 4); - return (i * 8) + strideRelativeIndex; - } - } - - return 0; - } - else -#endif - { - int index = Block8x8F.Size - 1; - ref float elemRef = ref Unsafe.As(ref mcu); - - while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) - { - index--; - } - - return index; - } - } - [MethodImpl(InliningOptions.ShortMethod)] private void WriteToStream() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs index b953e80b8..f75b0a0b8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int expectedLessThan = 1; - int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + int actual = data.GetLastValuableElementIndex(); Assert.True(actual < expectedLessThan); } @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int expected = Block8x8F.Size - 1; - int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + int actual = data.GetLastValuableElementIndex(); Assert.Equal(expected, actual); } @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int expected = setIndex; - int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + int actual = data.GetLastValuableElementIndex(); Assert.Equal(expected, actual); } @@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int expected = lastIndex; - int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + int actual = data.GetLastValuableElementIndex(); Assert.Equal(expected, actual); } @@ -226,7 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int expected = lastIndex2; - int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + int actual = data.GetLastValuableElementIndex(); Assert.Equal(expected, actual); }