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

64 views Asked by At

I'm using a WriteableBitmap as a fast way to render and display content in a WPF window and than later export as media (reusing the same rendering pipeline).

I can draw images, basic shapes, do common operations manipulating the image bytes (resize, crop, flip, change angle, opacity, etc). Unrelated to this issue.

My rendering pipeline simply gets the WriteableBitmap back buffer (nint/IntPtr) and some of its parameters (size, scale, offset, etc.) and uses SharpDX to draw text like so:

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));
    }
}

I wonder if it's possible to directly draw into that back buffer without having to do so many copy operations, because right now I have to do two copies.

1

There are 1 answers

4
Simon Mourier On

WPF's WriteableBitmap already wraps a WIC Bitmap undercovers, so performance of your code can be improved maybe more than you think but it's not exposed publicly unfortunately.

You can get to it with a code like this:

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
}

Note 1: WPF is quite frozen now, so I don't think it's a big risk to use this internal structure but it's up to you obviously.

Note 2: working on a WIC bitmap is working on the CPU. If you want even better performance you can use a Direct2D bitmap which resides on the GPU and can take advantage of GPU power. Requires probably important changes.