提问人:Nicke Manarin 提问时间:11/6/2023 最后编辑:Nicke Manarin 更新时间:11/7/2023 访问量:34
如何共享 WriteableBitmap 后台缓冲区以与 Direct2D 呈现一起使用?
How can share a WriteableBitmap back buffer to use alongside Direct2D rendering?
问:
我正在使用 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));
}
}
我想知道是否可以直接绘制到该后台缓冲区而无需执行如此多的复制操作,因为现在我必须执行两个副本。
答:
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/...) 将为您提供后台缓冲区。
评论
RenderControl