.netwpfmanaged-directx

Quick code to get a WPF Visual to DirectX Texture (performance wise)


Well, the problem is simple, I have something rendered on a WPF Visual. I want to get it to on a DirectX Texture. At present, I using the following code to get the job done.

var bmp = new System.Windows.Media.Imaging.RenderTargetBitmap(bound.Width, bound.Height, 96, 96, System.Windows.Media.PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmp));
Texture texture = new Texture(dxDevice, bound.Width, bound.Height, 1, Usage.RenderTarget, Format.A8R8G8B8, Pool.Default);
using (MemoryStream ms = new MemoryStream())
{
    encoder.Save(ms);
    ms.Seek(0, SeekOrigin.Begin);
    Surface privateSurface = texture.GetSurfaceLevel(0);
    SurfaceLoader.FromStream(privateSurface, ms, Filter.None, Color.MediumAquamarine.ToArgb());
    ms.Close();
}
return texture; // our prepared texture

But it's somewhat slow, in an obvious manner. Can you guys suggest something which could possibly make it quicker?

[EDIT] I tried to load the data using CopyPixels(), but it throws D3DXERR_INVALIDDATA exception.

RenderTargetBitmap bmp = GetBitmap();
// copy into a byte array
int stride = bmp.PixelWidth * 4; 
byte[] data = new byte[bmp.PixelHeight * stride]; 
bmp.CopyPixels(data, stride, 0); 

// create texture
using (MemoryStream ms = new MemoryStream(data))
{
    ms.Seek(0, SeekOrigin.Begin);

    if (dxDevice != null)
    {
        // next line throws D3DXERR_INVALIDDATA exception
        texture = TextureLoader.FromStream(dxDevice, ms);
    }
}

[EDIT] Ok, so here's how I managed to do it.

RenderTargetBitmap bmp = GetBitmap();
// copy into a byte array
int stride = bmp.PixelWidth * 4; 
byte[] data = new byte[bmp.PixelHeight * stride]; 
bmp.CopyPixels(data, stride, 0); 

// create texture
Texture texture = new Texture(dxDevice, bound.Width, bound.Height, 1, Usage.SoftwareProcessing, Format.A8R8G8B8, Pool.Managed);
Surface privateSurface = texture.GetSurfaceLevel(0);
var graphicsStream = privateSurface.LockRectangle(LockFlags.None);
graphicsStream.Write(data);
privateSurface.UnlockRectangle();

Solution

  • Yes. You can still use RenderTargetBitmap. But instead of encoding it to PNG and reading in Direct3D, you can call CopyPixels on your RenderTargetBitmap and then write the pixels directly to your texture. Should be much faster!

    // render the visual, just like you did
    var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default, null);
    bmp.Render(visual);
    
    // get the pixels
    var pixels = new int[width * height];
    bmp.CopyPixels(pixels, 4 * width, 0); // each line consists of 4*width bytes.
    

    And now, depending on the exact API you use (SlimDX? Something else?) you need to lock your texture, and write the data in the pixels array to the texture.