-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
More convenient ways to work with MemoryManager<T>? #44412
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Just for reference, regarding the original issue, just to add a couple more notes:
|
Sorry - not quite sure I follow. There's not really a bulletproof way to remove the lifetime concerns whenever One way to mitigate this would be never to expose a public interface IThirdPartyExtension
{
void ProcessData(Span<byte> imageData, ImageMetadata anyAdditionalMetadataYouNeed, CancellationToken cancellationToken);
} The caller creates a span around the native buffer then passes that span through this interface. Because the data is provided as a span, there's no way (short of using unsafe code) for the callee to hold on to the span after the method returns. Patterns like this provide a pretty solid guarantee that third-party extensions aren't still holding on to the buffer at the time the caller discards it. |
@GrabYourPitchforks Anton's concern regarding lifetime stemmed from this example I provided in the linked issue, which is the solution I wrote to help out that UWP developer that was trying to use public static unsafe void ProcessFrame(ProcessVideoFrameContext context)
{
using BitmapBuffer sourceBuffer = context.InputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Read);
using BitmapBuffer targetBuffer = context.OutputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Write);
using IMemoryBufferReference sourceReference = sourceBuffer.CreateReference();
using IMemoryBufferReference targetReference = targetBuffer.CreateReference();
((IMemoryBufferByteAccess)sourceReference).GetBuffer(out byte* sourcePtr, out uint sourceCapacity);
((IMemoryBufferByteAccess)targetReference).GetBuffer(out byte* targetPtr, out _);
BitmapPlaneDescription bufferLayout = sourceBuffer.GetPlaneDescription(0);
int length = bufferLayout.Height * bufferLayout.Width;
var memoryManager = new UnmanagedMemoryManager<Rgba32>((Rgba32*)sourcePtr, length);
using (Image<Rgba32> image = Image.WrapMemory(memoryManager.Memory, bufferLayout.Width, bufferLayout.Height))
{
image.Mutate(x => x.DetectEdges());
}
Buffer.MemoryCopy(sourcePtr, targetPtr, sourceCapacity, sourceCapacity);
} The issue is that here I'm essentially (ab)using My idea here was that in general you'd expect developers working with code like this to be comfortable enough with manually managing lifetime of buffers and whatnot, but I can indeed agree that the API needs extreme care to avoid nasty errors, so I can understand how they're being very careful about integrating that into Though now that you mention it, @antonfirsov That suggestion from Levi though gave me a potentially related idea, what if we completely flipped this proposal on its head and came up with something completely static instead, taking a Just a random idea, something like (plus additional overloads): public static unsafe void Mutate<TPixel>(
Span<TPixel> span,
int width,
int height,
Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (TPixel* p = span)
{
using var manager = new UnmanagedMemoryManager<TPixel>(p, span.Length);
using var image = Image.WrapMemory(manager.Memory, width, height);
image.Mutate(operation);
}
} This would be a safer and flexible way for developers to statically use Otherwise, I'm not sure I see another solution outside of either this static approach, or the one proposed in #1419. |
IMHO that's a legitimate use case. We have an internal type |
@GrabYourPitchforks if it's legitimate, it should be made more accessible to developers. Currently it's very complicated to use
A |
@antonfirsov Just to make sure I understand, here you're talking about two separate issues, right? As in, even if .NET 6 added some new By that I mean, regardless of when/if the BCL will get built-in support for this new constructor in the future, if @GrabYourPitchforks says using that workaround is a legitimate usage of the Of course, with the remaining point of future consideration being to eventually swap our internal manager to create the |
@antonfirsov We make pretty strong guarantees that creating a |
@GrabYourPitchforks I'm closing this, since my original question about I'm still unhappy though that |
More a discussion than anything else.
Update:
Might make sense to repurpose this discussion around accessibility of non-array backed
Memory<T>
. See #44412 (comment)In ImageSharp we faced the following situation:
Image.WrapMemory<TPixel>(Memory<TPixel>)
andImage.WrapMemory<TPixel>(IMemoryOwner<TPixel>)
to enable users going high-perf by wrapping existing memory buffers asImage<TPixel>
so they can for example run edge detection on a video buffer, copy-freeMemoryManager<T>
around the buffers held bySoftwareBitmap
(understandably IMHO)LockBuffer
and the release ofBitmapBuffer
within it's pin/unpin conceptImage.WrapMemory<TPixel>(void* ptr, int width, int height)
overload , using aMemoryManager
implementation that - in my opinion - goes against best practices by pushing the lifecycle management concerns out of scope.I really don't know what is the right resolution of this situation.
/cc @GrabYourPitchforks
EDIT:
It's important to note that the issue is not specific to UWP interop. Applies to other imaging API's having a lock/release concept, for example:
System.Drawing.Bitmap.LockBits
, and I assume other areas outside of imaging.The text was updated successfully, but these errors were encountered: