From c9634f39133849fc2e2d9ef764c1da00a7841c83 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 23 Feb 2021 01:38:53 +0100 Subject: [PATCH] Optimized Textures and Sampler --- .../Rendering/Textures/PixelTexture.cs | 57 ++++++++++++--- .../Textures/Sampler/AverageColorSampler.cs | 34 +++++++++ .../Textures/Sampler/AverageSampler.cs | 38 ---------- .../Rendering/Textures/Sampler/ISampler.cs | 6 +- .../Rendering/Textures/Sampler/SamplerInfo.cs | 26 +++++++ .../RGB.NET.Presets.csproj.DotSettings | 3 +- RGB.NET.Presets/Textures/BytePixelTexture.cs | 72 +++++++++++++++++++ RGB.NET.Presets/Textures/Enums/ColorFormat.cs | 8 +++ RGB.NET.Presets/Textures/FloatPixelTexture.cs | 72 +++++++++++++++++++ .../Textures/Sampler/AverageByteSampler.cs | 31 ++++++++ .../Textures/Sampler/AverageFloatSampler.cs | 30 ++++++++ 11 files changed, 325 insertions(+), 52 deletions(-) create mode 100644 RGB.NET.Core/Rendering/Textures/Sampler/AverageColorSampler.cs delete mode 100644 RGB.NET.Core/Rendering/Textures/Sampler/AverageSampler.cs create mode 100644 RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs create mode 100644 RGB.NET.Presets/Textures/BytePixelTexture.cs create mode 100644 RGB.NET.Presets/Textures/Enums/ColorFormat.cs create mode 100644 RGB.NET.Presets/Textures/FloatPixelTexture.cs create mode 100644 RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs create mode 100644 RGB.NET.Presets/Textures/Sampler/AverageFloatSampler.cs diff --git a/RGB.NET.Core/Rendering/Textures/PixelTexture.cs b/RGB.NET.Core/Rendering/Textures/PixelTexture.cs index d5b6bab..7193e52 100644 --- a/RGB.NET.Core/Rendering/Textures/PixelTexture.cs +++ b/RGB.NET.Core/Rendering/Textures/PixelTexture.cs @@ -1,18 +1,28 @@ using System; +using System.Buffers; using System.Runtime.CompilerServices; namespace RGB.NET.Core { public abstract class PixelTexture : ITexture + where T : unmanaged { + #region Constants + + private const int STACK_ALLOC_LIMIT = 1024; + + #endregion + #region Properties & Fields - protected ISampler Sampler { get; set; } + private readonly int _dataPerColor; + + protected ISampler Sampler { get; set; } protected T[] Data { get; set; } public Size Size { get; } - public Color this[in Point point] + public virtual Color this[in Point point] { get { @@ -24,7 +34,7 @@ namespace RGB.NET.Core } } - public Color this[in Rectangle rectangle] + public virtual Color this[in Rectangle rectangle] { get { @@ -35,7 +45,23 @@ namespace RGB.NET.Core int width = (int)Math.Round(Size.Width * rectangle.Size.Width.Clamp(0, 1)); int height = (int)Math.Round(Size.Height * rectangle.Size.Height.Clamp(0, 1)); - return Sampler.SampleColor(x, y, width, height, GetColor); + int bufferSize = width * height * _dataPerColor; + if (bufferSize <= STACK_ALLOC_LIMIT) + { + Span buffer = stackalloc T[bufferSize]; + GetRegionData(x, y, width, height, buffer); + return Sampler.SampleColor(new SamplerInfo(width, height, buffer)); + } + else + { + T[] rent = ArrayPool.Shared.Rent(bufferSize); + Span buffer = new Span(rent).Slice(0, bufferSize); + GetRegionData(x, y, width, height, buffer); + Color color = Sampler.SampleColor(new SamplerInfo(width, height, buffer)); + ArrayPool.Shared.Return(rent); + + return color; + } } } @@ -43,9 +69,10 @@ namespace RGB.NET.Core #region Constructors - public PixelTexture(int with, int height, T[] data, ISampler sampler) + public PixelTexture(int with, int height, T[] data, int dataPerColor, ISampler sampler) { this.Data = data; + this._dataPerColor = dataPerColor; this.Sampler = sampler; Size = new Size(with, height); @@ -55,8 +82,8 @@ namespace RGB.NET.Core #region Methods - [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract Color GetColor(int x, int y); + protected abstract void GetRegionData(int x, int y, int width, int height, in Span buffer); #endregion } @@ -72,11 +99,11 @@ namespace RGB.NET.Core #region Constructors public PixelTexture(int with, int height, Color[] data) - : this(with, height, data, new AverageSampler()) + : this(with, height, data, new AverageColorSampler()) { } - public PixelTexture(int with, int height, Color[] data, ISampler sampler) - : base(with, height, data, sampler) + public PixelTexture(int with, int height, Color[] data, ISampler sampler) + : base(with, height, data, 1, sampler) { this._stride = with; @@ -87,8 +114,20 @@ namespace RGB.NET.Core #region Methods + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Color GetColor(int x, int y) => Data[(y * _stride) + x]; + protected override void GetRegionData(int x, int y, int width, int height, in Span buffer) + { + Span data = Data.AsSpan(); + for (int i = 0; i < height; i++) + { + Span dataSlice = data.Slice(((y + i) * _stride) + x, width); + Span destination = buffer.Slice(i * width, width); + dataSlice.CopyTo(destination); + } + } + #endregion } } diff --git a/RGB.NET.Core/Rendering/Textures/Sampler/AverageColorSampler.cs b/RGB.NET.Core/Rendering/Textures/Sampler/AverageColorSampler.cs new file mode 100644 index 0000000..5492f90 --- /dev/null +++ b/RGB.NET.Core/Rendering/Textures/Sampler/AverageColorSampler.cs @@ -0,0 +1,34 @@ +namespace RGB.NET.Core +{ + public class AverageColorSampler : ISampler + { + #region Properties & Fields + + public bool SampleAlpha { get; set; } + + #endregion + + #region Methods + + public Color SampleColor(SamplerInfo info) + { + int count = info.Width * info.Height; + if (count == 0) return Color.Transparent; + + float a = 0, r = 0, g = 0, b = 0; + foreach (Color color in info.Data) + { + a += color.A; + r += color.R; + g += color.G; + b += color.B; + } + + return SampleAlpha + ? new Color(a / count, r / count, g / count, b / count) + : new Color(r / count, g / count, b / count); + } + + #endregion + } +} diff --git a/RGB.NET.Core/Rendering/Textures/Sampler/AverageSampler.cs b/RGB.NET.Core/Rendering/Textures/Sampler/AverageSampler.cs deleted file mode 100644 index c9e95ce..0000000 --- a/RGB.NET.Core/Rendering/Textures/Sampler/AverageSampler.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace RGB.NET.Core -{ - public class AverageSampler : ISampler - { - #region Properties & Fields - - public bool SampleAlpha { get; set; } - - #endregion - - #region Methods - - public Color SampleColor(int x, int y, int width, int height, GetColor getColor) - { - int maxX = x + width; - int maxY = y + height; - int count = width * height; - if (count == 0) return Color.Transparent; - - float a = 0, r = 0, g = 0, b = 0; - for (int yPos = y; yPos < maxY; yPos++) - for (int xPos = x; xPos < maxX; xPos++) - { - Color color = getColor(x, y); - a += color.A; - r += color.R; - g += color.G; - b += color.B; - } - - return SampleAlpha - ? new Color(a / count, r / count, g / count, b / count) - : new Color(r / count, g / count, b / count); - } - - #endregion - } -} diff --git a/RGB.NET.Core/Rendering/Textures/Sampler/ISampler.cs b/RGB.NET.Core/Rendering/Textures/Sampler/ISampler.cs index 2d9fced..9bfd812 100644 --- a/RGB.NET.Core/Rendering/Textures/Sampler/ISampler.cs +++ b/RGB.NET.Core/Rendering/Textures/Sampler/ISampler.cs @@ -1,9 +1,7 @@ namespace RGB.NET.Core { - public delegate Color GetColor(int x, int y); - - public interface ISampler + public interface ISampler { - Color SampleColor(int x, int y, int width, int height, GetColor getColorFunc); + Color SampleColor(SamplerInfo info); } } diff --git a/RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs b/RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs new file mode 100644 index 0000000..a544381 --- /dev/null +++ b/RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs @@ -0,0 +1,26 @@ +using System; + +namespace RGB.NET.Core +{ + public readonly ref struct SamplerInfo + { + #region Properties & Fields + + public int Width { get; } + public int Height { get; } + public ReadOnlySpan Data { get; } + + #endregion + + #region Constructors + + public SamplerInfo(int width, int height, ReadOnlySpan data) + { + this.Width = width; + this.Height = height; + this.Data = data; + } + + #endregion + } +} diff --git a/RGB.NET.Presets/RGB.NET.Presets.csproj.DotSettings b/RGB.NET.Presets/RGB.NET.Presets.csproj.DotSettings index 5cecdd9..3af07cd 100644 --- a/RGB.NET.Presets/RGB.NET.Presets.csproj.DotSettings +++ b/RGB.NET.Presets/RGB.NET.Presets.csproj.DotSettings @@ -1,4 +1,5 @@  False False - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/RGB.NET.Presets/Textures/BytePixelTexture.cs b/RGB.NET.Presets/Textures/BytePixelTexture.cs new file mode 100644 index 0000000..d83398a --- /dev/null +++ b/RGB.NET.Presets/Textures/BytePixelTexture.cs @@ -0,0 +1,72 @@ +using System; +using RGB.NET.Core; +using RGB.NET.Presets.Textures.Sampler; + +namespace RGB.NET.Presets.Textures +{ + public sealed class BytePixelTexture : PixelTexture + { + #region Properties & Fields + + private readonly int _stride; + + public ColorFormat ColorFormat { get; } + + public override Color this[in Rectangle rectangle] + { + get + { + Color color = base[rectangle]; + if (ColorFormat == ColorFormat.BGR) + return new Color(color.A, color.B, color.G, color.R); + + return color; + } + } + + #endregion + + #region Constructors + + public BytePixelTexture(int with, int height, byte[] data, ColorFormat colorFormat = ColorFormat.RGB) + : this(with, height, data, new AverageByteSampler(), colorFormat) + { } + + public BytePixelTexture(int with, int height, byte[] data, ISampler sampler, ColorFormat colorFormat = ColorFormat.RGB) + : base(with, height, data, 3, sampler) + { + this._stride = with; + this.ColorFormat = colorFormat; + + if (Data.Length != ((with * height) * 3)) throw new ArgumentException($"Data-Length {Data.Length} differs from the given size {with}x{height} * 3 bytes ({with * height * 3})."); + } + + #endregion + + #region Methods + + protected override Color GetColor(int x, int y) + { + int offset = ((y * _stride) + x) * 3; + + if (ColorFormat == ColorFormat.BGR) + return new Color(Data[offset + 2], Data[offset + 1], Data[offset + 1]); + + return new Color(Data[offset], Data[offset + 1], Data[offset + 2]); + } + + protected override void GetRegionData(int x, int y, int width, int height, in Span buffer) + { + int width3 = width * 3; + Span data = Data.AsSpan(); + for (int i = 0; i < height; i++) + { + Span dataSlice = data.Slice((((y + i) * _stride) + x) * 3, width3); + Span destination = buffer.Slice(i * width3, width3); + dataSlice.CopyTo(destination); + } + } + + #endregion + } +} diff --git a/RGB.NET.Presets/Textures/Enums/ColorFormat.cs b/RGB.NET.Presets/Textures/Enums/ColorFormat.cs new file mode 100644 index 0000000..a098ff5 --- /dev/null +++ b/RGB.NET.Presets/Textures/Enums/ColorFormat.cs @@ -0,0 +1,8 @@ +namespace RGB.NET.Presets.Textures +{ + public enum ColorFormat + { + RGB, + BGR + } +} diff --git a/RGB.NET.Presets/Textures/FloatPixelTexture.cs b/RGB.NET.Presets/Textures/FloatPixelTexture.cs new file mode 100644 index 0000000..410f9c9 --- /dev/null +++ b/RGB.NET.Presets/Textures/FloatPixelTexture.cs @@ -0,0 +1,72 @@ +using System; +using RGB.NET.Core; +using RGB.NET.Presets.Textures.Sampler; + +namespace RGB.NET.Presets.Textures +{ + public sealed class FloatPixelTexture : PixelTexture + { + #region Properties & Fields + + private readonly int _stride; + + public ColorFormat ColorFormat { get; } + + public override Color this[in Rectangle rectangle] + { + get + { + Color color = base[rectangle]; + if (ColorFormat == ColorFormat.BGR) + return new Color(color.A, color.B, color.G, color.R); + + return color; + } + } + + #endregion + + #region Constructors + + public FloatPixelTexture(int with, int height, float[] data, ColorFormat colorFormat = ColorFormat.RGB) + : this(with, height, data, new AverageFloatSampler(), colorFormat) + { } + + public FloatPixelTexture(int with, int height, float[] data, ISampler sampler, ColorFormat colorFormat = ColorFormat.RGB) + : base(with, height, data, 3, sampler) + { + this._stride = with; + this.ColorFormat = colorFormat; + + if (Data.Length != ((with * height) * 3)) throw new ArgumentException($"Data-Length {Data.Length} differs from the given size {with}x{height} * 3 bytes ({with * height * 3})."); + } + + #endregion + + #region Methods + + protected override Color GetColor(int x, int y) + { + int offset = ((y * _stride) + x) * 3; + + if (ColorFormat == ColorFormat.BGR) + return new Color(Data[offset + 2], Data[offset + 1], Data[offset + 1]); + + return new Color(Data[offset], Data[offset + 1], Data[offset + 2]); + } + + protected override void GetRegionData(int x, int y, int width, int height, in Span buffer) + { + int width3 = width * 3; + Span data = Data.AsSpan(); + for (int i = 0; i < height; i++) + { + Span dataSlice = data.Slice((((y + i) * _stride) + x) * 3, width3); + Span destination = buffer.Slice(i * width3, width3); + dataSlice.CopyTo(destination); + } + } + + #endregion + } +} diff --git a/RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs b/RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs new file mode 100644 index 0000000..694dbe6 --- /dev/null +++ b/RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs @@ -0,0 +1,31 @@ +using System; +using RGB.NET.Core; + +namespace RGB.NET.Presets.Textures.Sampler +{ + public class AverageByteSampler : ISampler + { + #region Methods + + public Color SampleColor(SamplerInfo info) + { + int count = info.Width * info.Height; + if (count == 0) return Color.Transparent; + + ReadOnlySpan data = info.Data; + + uint r = 0, g = 0, b = 0; + for (int i = 0; i < data.Length; i += 3) + { + r += data[i]; + g += data[i + 1]; + b += data[i + 2]; + } + + float divisor = count * byte.MaxValue; + return new Color(r / divisor, g / divisor, b / divisor); + } + + #endregion + } +} diff --git a/RGB.NET.Presets/Textures/Sampler/AverageFloatSampler.cs b/RGB.NET.Presets/Textures/Sampler/AverageFloatSampler.cs new file mode 100644 index 0000000..59e1987 --- /dev/null +++ b/RGB.NET.Presets/Textures/Sampler/AverageFloatSampler.cs @@ -0,0 +1,30 @@ +using System; +using RGB.NET.Core; + +namespace RGB.NET.Presets.Textures.Sampler +{ + public class AverageFloatSampler : ISampler + { + #region Methods + + public Color SampleColor(SamplerInfo info) + { + int count = info.Width * info.Height; + if (count == 0) return Color.Transparent; + + ReadOnlySpan data = info.Data; + + float r = 0, g = 0, b = 0; + for (int i = 0; i < data.Length; i += 3) + { + r += data[i]; + g += data[i + 1]; + b += data[i + 2]; + } + + return new Color(r / count, g / count, b / count); + } + + #endregion + } +}