diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 0ac4c29eae..3d9552e423 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -94,6 +94,14 @@ namespace SixLabors.ImageSharp } } + /// + /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible. + /// + /// + /// Contiguous allocations are not possible, if the image needs a buffer larger than . + /// + public bool PreferContiguousImageBuffers { get; set; } + /// /// Gets a set of properties for the Configuration. /// diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index b29608f62a..949b8999a5 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -58,7 +58,11 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers, + AllocationOptions.Clean); } /// @@ -87,7 +91,10 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers); this.Clear(backgroundColor); } @@ -131,7 +138,10 @@ namespace SixLabors.ImageSharp Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + source.PixelBuffer.Width, + source.PixelBuffer.Height, + configuration.PreferContiguousImageBuffers); source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 9cf1af659f..abcf078ac7 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -20,20 +20,68 @@ namespace SixLabors.ImageSharp.Memory /// The memory allocator. /// The buffer width. /// The buffer height. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . /// The allocation options. /// The . public static Buffer2D Allocate2D( this MemoryAllocator memoryAllocator, int width, int height, + bool preferContiguosImageBuffers, AllocationOptions options = AllocationOptions.None) where T : struct { long groupLength = (long)width * height; - MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + MemoryGroup memoryGroup; + if (preferContiguosImageBuffers && groupLength < int.MaxValue) + { + IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options); + memoryGroup = MemoryGroup.CreateContiguous(buffer, false); + } + else + { + memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + } + return new Buffer2D(memoryGroup, width, height); } + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of x elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer width. + /// The buffer height. + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + int width, + int height, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, width, height, false, options); + + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of width x height elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer size. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + Size size, + bool preferContiguosImageBuffers, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options); + /// /// Allocates a buffer of value type objects interpreted as a 2D region /// of width x height elements. @@ -48,7 +96,7 @@ namespace SixLabors.ImageSharp.Memory Size size, AllocationOptions options = AllocationOptions.None) where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, options); + Allocate2D(memoryAllocator, size.Width, size.Height, false, options); internal static Buffer2D Allocate2DOveraligned( this MemoryAllocator memoryAllocator, diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index dd8275ee8e..50b21ba8d6 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -2,10 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -339,6 +340,26 @@ namespace SixLabors.ImageSharp.Tests Assert.False(this.Image.Frames.Contains(frame)); } + [Fact] + public void PreferContiguousImageBuffers_True_AppliedToAllFrames() + { + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 100, 100); + image.Frames.CreateFrame(); + image.Frames.InsertFrame(0, image.Frames[0]); + image.Frames.CreateFrame(Color.Red); + + Assert.Equal(4, image.Frames.Count); + IEnumerable> frames = image.Frames; + foreach (ImageFrame frame in frames) + { + Assert.True(frame.TryGetSinglePixelSpan(out Span span)); + } + } + [Fact] public void DisposeCall_NoThrowIfCalledMultiple() { diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 09ed2afa52..f6656ec5fc 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -22,17 +22,17 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public unsafe void Set_MaximumPoolSizeMegabytes_CreateImage_MaximumPoolSizeMegabytes() + public void PreferContiguousImageBuffers_CreateImage_MaximumPoolSizeMegabytes() { + // Run remotely to avoid large allocation in the test process: RemoteExecutor.Invoke(RunTest).Dispose(); static void RunTest() { - Configuration.Default.MemoryAllocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() - { - MinimumContiguousBlockSizeBytes = sizeof(Rgba32) * 8192 * 4096 - }); - using Image image = new Image(8192, 4096); + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 8192, 4096); Assert.True(image.TryGetSinglePixelSpan(out Span span)); Assert.Equal(8192 * 4096, span.Length); } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 845ea9ca40..04abc6585e 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -68,6 +68,25 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload) + { + this.MemoryAllocator.BufferCapacityInBytes = 10_000; + + using Buffer2D buffer = useSizeOverload ? + this.MemoryAllocator.Allocate2D( + new Size(200, 200), + preferContiguosImageBuffers: true) : + this.MemoryAllocator.Allocate2D( + 200, + 200, + preferContiguosImageBuffers: true); + Assert.Equal(1, buffer.FastMemoryGroup.Count); + Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength); + } + [Theory] [InlineData(50, 10, 20, 4)] public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier)