如何共享 WriteableBitmap 后台缓冲区以与 Direct2D 呈现一起使用?

How can share a WriteableBitmap back buffer to use alongside Direct2D rendering?

提问人:Nicke Manarin 提问时间:11/6/2023 最后编辑:Nicke Manarin 更新时间:11/7/2023 访问量:34

问:

我正在使用 WriteableBitmap 作为在 WPF 窗口中呈现和显示内容的快速方法,然后导出为媒体(重用相同的呈现管道)。

我可以绘制图像、基本形状、执行操作图像字节的常见操作(调整大小、裁剪、翻转、更改角度、不透明度等)。与此问题无关。

我的渲染管线只是获取 WriteableBitmap 后台缓冲区 (nint/IntPtr) 及其一些参数(大小、比例、偏移量等),并使用 SharpDX 绘制文本,如下所示:

public override void RenderAt(nint canvas, int canvasStride, int canvasWidth, int canvasHeight, 
    double horizontalScale, double verticalScale, int leftOffset, int topOffset, 
    int rightOffset, int bottomOffset) 
{
    using (var factory = new SharpDX.Direct2D1.Factory(FactoryType.MultiThreaded))
    using (var writeFactory = new SharpDX.DirectWrite.Factory(SharpDX.DirectWrite.FactoryType.Shared))
    using (var textFormat = new TextFormat(writeFactory, "Segoe UI", 20))
    using (var bitmap = new SharpDX.WIC.Bitmap(new ImagingFactory(), canvasWidth, canvasHeight, SharpDX.WIC.PixelFormat.Format32bppPBGRA, SharpDX.WIC.BitmapCreateCacheOption.CacheOnLoad))
    using (var renderTarget = new WicRenderTarget(factory, bitmap, new RenderTargetProperties()))
    {
        //Create a SharpDX Bitmap as the backbuffer.
        var backBufferBitmap = new SharpDX.Direct2D1.Bitmap(renderTarget, new Size2(canvasWidth, canvasHeight), new SharpDX.Direct2D1.BitmapProperties(renderTarget.PixelFormat));
        backBufferBitmap.CopyFromMemory(canvas, canvasStride);

        renderTarget.BeginDraw();
        renderTarget.Transform = Matrix3x2.Scaling((float)horizontalScale, (float)verticalScale) * Matrix3x2.Translation(leftOffset, topOffset);
        
        //Get the current canvas content, to draw the text later.
        renderTarget.DrawBitmap(backBufferBitmap, 1f, BitmapInterpolationMode.Linear);

        var text = "Example";

        //Backdrop of the text.
        using (var brush = new SharpDX.Direct2D1.SolidColorBrush(renderTarget, new RawColor4(0,0,0,1)))
            renderTarget.FillRectangle(new RawRectangleF(0, 0, canvasWidth, canvasHeight / 4), brush);

        //Text.
        using (var brush = new SharpDX.Direct2D1.SolidColorBrush(renderTarget, new RawColor4(1,1,1,1)))
            renderTarget.DrawText(text, textFormat, new RectangleF(0, 0, Width, Height), brush);

        renderTarget.EndDraw();

        //Copy back the rendered result.
        bitmap.CopyPixels(new RawBox(0, 0, canvasWidth, canvasHeight), canvasStride, new DataPointer(canvas, canvasStride * canvasHeight));
    }
}

我想知道是否可以直接绘制到该后台缓冲区而无需执行如此多的复制操作,因为现在我必须执行两个副本。

C# Direct2D 夏普DX WIC

评论

0赞 JonasH 11/6/2023
有一个大概可以用来直接画的。它是一个 winforms 控件,但可以承载在 WPF 控件中。但是 SharpDX 自 2018 年以来一直没有发布过,您确定要在其上构建应用程序吗?像你提到的基本图像操作可以很容易地直接在 WPF 中完成,大多数操作可能只需操作渲染和/或布局转换即可完成。RenderControl
0赞 Nicke Manarin 11/7/2023
我一直在通过手动操作字节直接执行基本的图像操作。我不得不使用 Direct2D,因为我想添加对文本呈现的支持。
0赞 Nicke Manarin 11/7/2023
我需要一个易于读/写的图像访问,以使用内容来显示和导出为图像/视频/动画。

答:

1赞 Simon Mourier 11/6/2023 #1

WPF 的 WriteableBitmap 已经包装了 WIC 位图,因此代码的性能可以比您想象的要高,但不幸的是,它没有公开。

你可以用这样的代码来获取它:

var writeableBitmap = new WriteableBitmap(200, 200, 96, 96, PixelFormats.Bgr32, null); // create some writeable bitmap

// get its internal COM reference
// PropertyInfo can be cached
var prop = writeableBitmap.GetType().GetProperty("WicSourceHandle", BindingFlags.NonPublic | BindingFlags.Instance);
var handle = (SafeHandleZeroOrMinusOneIsInvalid)prop.GetValue(writeableBitmap, null);

using (var bitmap = new SharpDX.WIC.Bitmap(handle.DangerousGetHandle())) // we're already walking on the dangerous edge anyway
{
    etc...
    // EndDraw does Lock+Unlock
}

注 1:WPF 现在已经完全冻结了,所以我认为使用这种内部结构的风险不大,但显然这取决于你。

注 2:处理 WIC 位图就是在 CPU 上工作。如果需要更好的性能,可以使用驻留在 GPU 上的 Direct2D 位图,并可以利用 GPU 功能。可能需要进行重要的更改。

评论

0赞 Nicke Manarin 11/7/2023
哇,太酷了。我要试试。我是 WIC 的新手,获取 WriteableBitmap.BackBuffer 句柄与获取 WicSourceHandle 有什么区别?
0赞 Nicke Manarin 11/7/2023
对于注 2:是的,现在我正在使用 CPU 进行渲染和导出。我想手动做一些事情来学习,并且只使用一个 WriteableBitmap(实际上是两个,用于双缓冲区)并执行基本的字节操作更容易(也足够快)。
0赞 Nicke Manarin 11/7/2023
现在,我可以在非 OC'd i7 8700k 上以 60fps 的速度渲染 3440x1440p 多层内容。下一阶段,我想切换到 GPU。
0赞 Simon Mourier 11/7/2023
WriteableBitmap.BackBuffer 不是句柄,而是指向位图内存的指针。WicSourceHandle 是指向 IWICBitmap (learn.microsoft.com/en-us/windows/win32/api/wincodec/...) 实现本身的 COM 引用 (指针) 。在 IWICBitmap 中,调用 Lock (learn.microsoft.com/en-us/windows/win32/api/wincodec/...) 将获得 IWICBitmapLock COM 引用。从那里,GetDataPointer (learn.microsoft.com/en-us/windows/win32/api/wincodec/...) 将为您提供后台缓冲区。