Browse Source

Read all pixels then write all pixels to avoid virtual call overhead (#14111)

* Read all pixels then write all pixels to avoid virual call overhead

* Use unmanaged buffer

* Make UnmanagedBlob IDisposable
Benedikt Stebner 1 year ago
parent
commit
3657128cd2

+ 71 - 2
src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs

@@ -223,7 +223,6 @@ internal static unsafe class PixelFormatReader
         public void Reset(IntPtr address) => _address = (byte*)address;
     }
 
-
     public unsafe struct Rgba64PixelFormatReader : IPixelFormatReader
     {
         private Rgba64Pixel* _address;
@@ -382,5 +381,75 @@ internal static unsafe class PixelFormatReader
             default:
                 return false;
         }
-    } 
+    }
+
+    private static void Read<T>(Span<Rgba8888Pixel> pixels, IntPtr source, PixelSize size, int stride) where T : struct, IPixelFormatReader
+    {
+        var reader = new T();
+
+        var w = size.Width;
+        var h = size.Height;
+        var count = 0;
+
+        for (var y = 0; y < h; y++)
+        {
+            reader.Reset(source + stride * y);
+
+            for (var x = 0; x < w; x++)
+            {
+                pixels[count++] = reader.ReadNext();
+            }
+        }
+    }
+
+    public static void Read(Span<Rgba8888Pixel> pixels, IntPtr source, PixelSize size, int stride, PixelFormat format)
+    {
+        switch (format.FormatEnum)
+        {
+            case PixelFormatEnum.Rgb565:
+                Read<Bgr565PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Rgba8888:
+                Read<Rgba8888PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Bgra8888:
+               Read<Bgra8888PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.BlackWhite:
+                Read<BlackWhitePixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Gray2:
+                Read<Gray2PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Gray4:
+                Read<Gray4PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Gray8:
+                Read<Gray8PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Gray16:
+                Read<Gray16PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Gray32Float:
+                Read<Gray32FloatPixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Rgba64:
+                Read<Rgba64PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Rgb24:
+                Read<Rgb24PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Bgr24:
+                Read<Bgr24PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Bgr555:
+                Read<Bgr555PixelFormatReader>(pixels, source, size, stride);
+                break;
+            case PixelFormatEnum.Bgr565:
+                Read<Bgr565PixelFormatReader>(pixels, source, size, stride);
+                break;
+            default:
+                throw new NotSupportedException($"Pixel format {format} is not supported");
+        }
+    }
 }

+ 9 - 133
src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs

@@ -1,5 +1,7 @@
 using System;
+using System.Runtime.InteropServices;
 using Avalonia.Platform;
+using Avalonia.Platform.Internal;
 namespace Avalonia.Media.Imaging;
 
 internal static unsafe class PixelFormatTranscoder
@@ -15,140 +17,14 @@ internal static unsafe class PixelFormatTranscoder
         PixelFormat destFormat,
         AlphaFormat destAlphaFormat)
     {
-        var reader = GetReader(srcFormat);
-        var writer = GetWriter(destFormat);
+        var pixelCount = srcSize.Width * srcSize.Height;
+        var bufferSize = pixelCount * Marshal.SizeOf<Rgba8888Pixel>();
+        using var blob = new UnmanagedBlob(bufferSize);
+      
+        var pixels = new Span<Rgba8888Pixel>((void*)blob.Address, pixelCount);
 
-        var w = srcSize.Width;
-        var h = srcSize.Height;
+        PixelFormatReader.Read(pixels, source, srcSize, sourceStride, srcFormat);
 
-        for (var y = 0; y < h; y++)
-        {
-            reader.Reset(source + sourceStride * y);
-
-            writer.Reset(dest + destStride * y);
-
-            for (var x = 0; x < w; x++)
-            {
-                writer.WriteNext(GetConvertedPixel(reader.ReadNext(), srcAlphaFormat, destAlphaFormat));
-            }
-        }
-    }
-
-    private static Rgba8888Pixel GetConvertedPixel(Rgba8888Pixel pixel, AlphaFormat sourceAlpha, AlphaFormat destAlpha)
-    {
-        if (sourceAlpha != destAlpha)
-        {
-            if (sourceAlpha == AlphaFormat.Premul && destAlpha != AlphaFormat.Premul)
-            {
-                return ConvertFromPremultiplied(pixel);
-            }
-
-            if (sourceAlpha != AlphaFormat.Premul && destAlpha == AlphaFormat.Premul)
-            {
-                return ConvertToPremultiplied(pixel);
-            }
-        }
-
-        return pixel;
-    }
-
-    private static Rgba8888Pixel ConvertToPremultiplied(Rgba8888Pixel pixel)
-    {
-        var factor = pixel.A / 255F;
-
-        return new Rgba8888Pixel
-        {
-            R = (byte)(pixel.R * factor),
-            G = (byte)(pixel.G * factor),
-            B = (byte)(pixel.B * factor),
-            A = pixel.A
-        };
-    }
-
-    private static Rgba8888Pixel ConvertFromPremultiplied(Rgba8888Pixel pixel)
-    {
-        var factor = 1F / (pixel.A / 255F);
-
-        return new Rgba8888Pixel
-        {
-            R = (byte)(pixel.R * factor),
-            G = (byte)(pixel.G * factor),
-            B = (byte)(pixel.B * factor),
-            A = pixel.A
-        };
-    }
-
-    private static IPixelFormatReader GetReader(PixelFormat format)
-    {
-        switch (format.FormatEnum)
-        {
-            case PixelFormatEnum.Rgb565:
-                return new PixelFormatReader.Bgr565PixelFormatReader();
-            case PixelFormatEnum.Rgba8888:
-                return new PixelFormatReader.Rgba8888PixelFormatReader();
-            case PixelFormatEnum.Bgra8888:
-                return new PixelFormatReader.Bgra8888PixelFormatReader();
-            case PixelFormatEnum.BlackWhite:
-                return new PixelFormatReader.BlackWhitePixelFormatReader();
-            case PixelFormatEnum.Gray2:
-                return new PixelFormatReader.Gray2PixelFormatReader();
-            case PixelFormatEnum.Gray4:
-                return new PixelFormatReader.Gray4PixelFormatReader();
-            case PixelFormatEnum.Gray8:
-                return new PixelFormatReader.Gray8PixelFormatReader();
-            case PixelFormatEnum.Gray16:
-                return new PixelFormatReader.Gray16PixelFormatReader();
-            case PixelFormatEnum.Gray32Float:
-                return new PixelFormatReader.Gray32FloatPixelFormatReader();
-            case PixelFormatEnum.Rgba64:
-                return new PixelFormatReader.Rgba64PixelFormatReader();
-            case PixelFormatEnum.Rgb24:
-                return new PixelFormatReader.Rgb24PixelFormatReader();
-            case PixelFormatEnum.Bgr24:
-                return new PixelFormatReader.Bgr24PixelFormatReader();
-            case PixelFormatEnum.Bgr555:
-                return new PixelFormatReader.Bgr555PixelFormatReader();
-            case PixelFormatEnum.Bgr565:
-                return new PixelFormatReader.Bgr565PixelFormatReader();
-            default:
-                throw new NotSupportedException($"Pixel format {format} is not supported");
-        }
-    }
-
-    private static IPixelFormatWriter GetWriter(PixelFormat format)
-    {
-        switch (format.FormatEnum)
-        {
-            case PixelFormatEnum.Rgb565:
-                return new PixelFormatWriter.Bgr565PixelFormatWriter();
-            case PixelFormatEnum.Rgba8888:
-                return new PixelFormatWriter.Rgba8888PixelFormatWriter();
-            case PixelFormatEnum.Bgra8888:
-                return new PixelFormatWriter.Bgra8888PixelFormatWriter();
-            case PixelFormatEnum.BlackWhite:
-                return new PixelFormatWriter.BlackWhitePixelFormatWriter();
-            case PixelFormatEnum.Gray2:
-                return new PixelFormatWriter.Gray2PixelFormatWriter();
-            case PixelFormatEnum.Gray4:
-                return new PixelFormatWriter.Gray4PixelFormatWriter();
-            case PixelFormatEnum.Gray8:
-                return new PixelFormatWriter.Gray8PixelFormatWriter();
-            case PixelFormatEnum.Gray16:
-                return new PixelFormatWriter.Gray16PixelFormatWriter();
-            case PixelFormatEnum.Gray32Float:
-                return new PixelFormatWriter.Gray32FloatPixelFormatWriter();
-            case PixelFormatEnum.Rgba64:
-                return new PixelFormatWriter.Rgba64PixelFormatWriter();
-            case PixelFormatEnum.Rgb24:
-                return new PixelFormatWriter.Rgb24PixelFormatWriter();
-            case PixelFormatEnum.Bgr24:
-                return new PixelFormatWriter.Bgr24PixelFormatWriter();
-            case PixelFormatEnum.Bgr555:
-                return new PixelFormatWriter.Bgr555PixelFormatWriter();
-            case PixelFormatEnum.Bgr565:
-                return new PixelFormatWriter.Bgr565PixelFormatWriter();
-            default:
-                throw new NotSupportedException($"Pixel format {format} is not supported");
-        }
+        PixelFormatWriter.Write(pixels, dest, srcSize, destStride, destFormat, destAlphaFormat, srcAlphaFormat);
     }
 }

+ 129 - 1
src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs

@@ -1,4 +1,5 @@
 using System;
+using Avalonia.Platform;
 namespace Avalonia.Media.Imaging;
 
 internal interface IPixelFormatWriter
@@ -7,7 +8,7 @@ internal interface IPixelFormatWriter
     void Reset(IntPtr address);
 }
 
-internal static class PixelFormatWriter
+internal static unsafe class PixelFormatWriter
 {
     public unsafe struct Rgb24PixelFormatWriter : IPixelFormatWriter
     {
@@ -319,6 +320,133 @@ internal static class PixelFormatWriter
 
         public void Reset(IntPtr address) => _address = (ushort*)address;
     }
+
+    private static void Write<T>(
+        ReadOnlySpan<Rgba8888Pixel> pixels,
+        IntPtr dest,
+        PixelSize size,
+        int stride,
+        AlphaFormat alphaFormat,
+        AlphaFormat srcAlphaFormat) where T : struct, IPixelFormatWriter
+    {
+        var writer = new T();
+
+        var w = size.Width;
+        var h = size.Height;
+        var count = 0;
+
+        for (var y = 0; y < h; y++)
+        {
+            writer.Reset(dest + stride * y);
+
+            for (var x = 0; x < w; x++)
+            {
+                writer.WriteNext(GetConvertedPixel(pixels[count++], srcAlphaFormat, alphaFormat));
+            }
+        }
+    }
+
+    private static Rgba8888Pixel GetConvertedPixel(Rgba8888Pixel pixel, AlphaFormat sourceAlpha, AlphaFormat destAlpha)
+    {
+        if (sourceAlpha != destAlpha)
+        {
+            if (sourceAlpha == AlphaFormat.Premul && destAlpha != AlphaFormat.Premul)
+            {
+                return ConvertFromPremultiplied(pixel);
+            }
+
+            if (sourceAlpha != AlphaFormat.Premul && destAlpha == AlphaFormat.Premul)
+            {
+                return ConvertToPremultiplied(pixel);
+            }
+        }
+
+        return pixel;
+    }
+
+    private static Rgba8888Pixel ConvertToPremultiplied(Rgba8888Pixel pixel)
+    {
+        var factor = pixel.A / 255F;
+
+        return new Rgba8888Pixel
+        {
+            R = (byte)(pixel.R * factor),
+            G = (byte)(pixel.G * factor),
+            B = (byte)(pixel.B * factor),
+            A = pixel.A
+        };
+    }
+
+    private static Rgba8888Pixel ConvertFromPremultiplied(Rgba8888Pixel pixel)
+    {
+        var factor = 1F / (pixel.A / 255F);
+
+        return new Rgba8888Pixel
+        {
+            R = (byte)(pixel.R * factor),
+            G = (byte)(pixel.G * factor),
+            B = (byte)(pixel.B * factor),
+            A = pixel.A
+        };
+    }
+
+    public static void Write(
+        ReadOnlySpan<Rgba8888Pixel> pixels,
+        IntPtr dest,
+        PixelSize size,
+        int stride,
+        PixelFormat format,
+        AlphaFormat alphaFormat,
+        AlphaFormat srcAlphaFormat)
+    {
+        switch (format.FormatEnum)
+        {
+            case PixelFormatEnum.Rgb565:
+                Write<Bgr565PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Rgba8888:
+                Write<Rgba8888PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Bgra8888:
+                Write<Bgra8888PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.BlackWhite:
+                Write<BlackWhitePixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Gray2:
+                Write<Gray2PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Gray4:
+                Write<Gray4PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Gray8:
+                Write<Gray8PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Gray16:
+                Write<Gray16PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Gray32Float:
+                Write<Gray32FloatPixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Rgba64:
+                Write<Rgba64PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Rgb24:
+                Write<Rgb24PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Bgr24:
+                Write<Bgr24PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Bgr555:
+                Write<Bgr555PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            case PixelFormatEnum.Bgr565:
+                Write<Bgr565PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
+                break;
+            default:
+                throw new NotSupportedException($"Pixel format {format} is not supported");
+        }
+    }
 }
 
 

+ 1 - 1
src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs

@@ -6,7 +6,7 @@ using System.Threading;
 
 namespace Avalonia.Platform.Internal;
 
-internal class UnmanagedBlob
+internal class UnmanagedBlob : IDisposable
 {
     private IntPtr _address;
     private readonly object _lock = new object();

+ 51 - 0
tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatTranscoderTests.cs

@@ -0,0 +1,51 @@
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests.Media.Imaging
+{
+    public class PixelFormatTranscoderTests
+    {
+        [Fact]
+        public void Should_Transcode()
+        {
+            var sourceMemory = CreateBitmapMemory();
+
+            var destMemory = new BitmapMemory(PixelFormat.Bgra8888, AlphaFormat.Opaque, sourceMemory.Size);
+
+            PixelFormatTranscoder.Transcode(
+                sourceMemory.Address,
+                sourceMemory.Size,
+                sourceMemory.RowBytes,
+                sourceMemory.Format,
+                sourceMemory.AlphaFormat,
+                destMemory.Address,
+                destMemory.RowBytes,
+                destMemory.Format,
+                destMemory.AlphaFormat);
+
+            var reader = new PixelFormatReader.Bgra8888PixelFormatReader();
+
+            reader.Reset(destMemory.Address);
+
+            Assert.Equal(new Rgba8888Pixel(255, 0, 0, 0), reader.ReadNext());
+            Assert.Equal(new Rgba8888Pixel(0, 255, 0, 0), reader.ReadNext());
+            Assert.Equal(new Rgba8888Pixel(0, 0, 255, 0), reader.ReadNext());
+        }
+
+        private BitmapMemory CreateBitmapMemory()
+        {
+            var bitmapMemory = new BitmapMemory(PixelFormat.Rgba8888, AlphaFormat.Opaque, new PixelSize(3, 1));
+
+            var sourceWriter = new PixelFormatWriter.Rgba8888PixelFormatWriter();
+
+            sourceWriter.Reset(bitmapMemory.Address);
+
+            sourceWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+            sourceWriter.WriteNext(new Rgba8888Pixel { G = 255 });
+            sourceWriter.WriteNext(new Rgba8888Pixel { B = 255 });
+
+            return bitmapMemory;
+        }
+    }
+}