Support decoding into an existing Memory<T> #1487
-
The ProblemThere is currently no built-in API that enables a decoder to decode directly into a For additional context, consider the following example: static Texture2D LoadImage<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// Identify the image without loading it so we can create the Texture2D with an appropriate size.
var startPosition = stream.Position;
IImageInfo info = Image.Identify(configuration, stream);
stream.Position = startPosition;
var texture = new Texture2D(info.Width, info.Height, GetTextureFormat<TPixel>(), mipChain: false);
// Unity allows access to the pixel data using GetPixelData<T>, which can
// then be wrapped into a Memory<T> instance using a custom MemoryManager.
NativeArray<TPixel> pixels = texture.GetPixelData<TPixel>(mipLevel: 0);
// As there is no API to either re-use an existing Image or decode directly
// into a Memory<T>, we are required to allocate an entirely new Image and
// copy it into another Image that wraps our memory, which is expensive.
using var srcImage = Image.Load<TPixel>(configuration, stream);
using var dstImage = Image.WrapMemory(configuration, pixels.AsMemory(), info.Width, info.Height, info.Metadata);
if (srcImage.TryGetSinglePixelSpan(out Span<TPixel> src) &&
dstImage.TryGetSinglePixelSpan(out Span<TPixel> dst))
{
src.CopyTo(dst);
texture.Apply();
}
return texture;
} The third party API offers no control over the memory that is allocated for the pixel data, which forces the code to incur the cost of a copy operation. The ProposalA possible solution for this could be to expose an namespace SixLabors.ImageSharp.Formats
{
public interface ITargetImageDecoder
{
void Decode<TPixel>(Stream stream, Image<TPixel> image, Rectangle? region)
where TPixel : unmanaged, IPixel<TPixel>;
Task DecodeAsync<TPixel>(Stream stream, Image<TPixel> image, Rectangle? region, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
}
}
namespace SixLabors.ImageSharp.Advanced
{
public partial static class AdvancedImageExtensions
{
public static void Load<TPixel>(this Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
public static void Load<TPixel>(this Image<TPixel> image, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>;
public static void Load<TPixel>(this Image<TPixel> image, Stream stream, ITargetImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>;
public static Task LoadAsync<TPixel>(this Image<TPixel> image, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>;
public static Task LoadAsync<TPixel>(this Image<TPixel> image, Stream stream, ITargetImageDecoder decoder, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>;
public static Task<IImageFormat> LoadWithFormatAsync(this Image<TPixel> image, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>;
}
} Open Questions
|
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 40 replies
-
I don't like this. Basically the premise is backwards. If you want a texture you should be creating that from the decoded image not the other way around. You have access to the decoded image pixel buffer so why can you not use that? |
Beta Was this translation helpful? Give feedback.
-
@JimBobSquarePants I may not have been clear enough with my description in the OP. This is primarily about avoiding the need to perform a copy when I could just decode directly into the pixel buffer that's been allocated by the third party API. |
Beta Was this translation helpful? Give feedback.
-
I was actually just about to open this same exact discussion, then I saw this 😄 The way I see it, there are two main reasons why such an API should be public:
I have this issue in ComputeSharp, but really the same applies to many scenarios. In my case, since I'm working with DirectX 12 buffer types, I have the following scenario when loading from disk to a GPU resource, or when going back from GPU resource to disk:
As you can see, I have just one copy when reading back, as I can do
This would completely skip one copy of the entire image, as ImageSharp would be effectively decoding the image directly over the temporary GPU buffer (since we can use All in all, this would be a greatly useful API for consumers working with 3rd party APIs and interested in high performance, that would fix a problem that cannot be otherwise fixed with the currently available APIs. Furthermore, it also feels like a missing API anyway, due to the fact that its symmetric equivalent |
Beta Was this translation helpful? Give feedback.
-
@antonfirsov Following up there from your comments on the PR 🙂
Not sure on this point, I could be convinced either way. The main reasons why I went for static methods was:
We were discussing this in the Discord server as well, and point number 2 would actually be pretty handy especially when working with GPU textures. The main issue is that those are row-aligned (both in DX12 and Vulkan), so the memory isn't guaranteed to be contiguous. This means you'd necessarily have to have basically 3 copies to load an image and put it into a 2D texture:
If we went with "The user wants to decode to a sub rectangle of the target image" instead, it would be possible to do:
This means we'd now have:
So effectively removing one copy operation entirely. Thoughts? 🙂 |
Beta Was this translation helpful? Give feedback.
-
I noticed that a while back, the pull request associated with this (#1490) was closed. Has this been implemented elsewhere or was it rejected in the end? Assuming it's still an accepted proposal, I'd be willing to contribute the changes for the feature once I have time to (in the event that nobody else takes it up). |
Beta Was this translation helpful? Give feedback.
I was actually just about to open this same exact discussion, then I saw this 😄
@JimBobSquarePants I think we absolutely should expose APIs for this, and the solution found by @DaZombieKiller doesn't actually solvee the issue at all. The code in the last comment is still performing an unnecessary copy, which can (and should) be avoided.
The way I see it, there are two main reasons why such an API should be public:
Image.WrapMemory
. We exposed that, so it just seems logical to expose this too.I have this issue in ComputeSharp, but really the same applie…