I'll try to answer the question as best I can based on WPF 4.5 Unleashed Chapter 19. If you want to look it up, you can find all information there in the sub-section "Mixing DirectX Content with WPF Content".
Your C++ DLL should have 3 exposed methods Initialize(), Cleanup() and Render().
The interesting methods are Initialize() and InitD3D(), which is called by Initialize():
extern "C" __declspec(dllexport) IDirect3DSurface9* WINAPI Initialize(HWND hwnd, int width, int height)
{
// Initialize Direct3D
if( SUCCEEDED( InitD3D( hwnd ) ) )
{
// Create the scene geometry
if( SUCCEEDED( InitGeometry() ) )
{
if (FAILED(g_pd3dDevice->CreateRenderTarget(width, height,
D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
true, // lockable (true for compatibility with Windows XP. False is preferred for Windows Vista or later)
&g_pd3dSurface, NULL)))
{
MessageBox(NULL, L"NULL!", L"Missing File", 0);
return NULL;
}
g_pd3dDevice->SetRenderTarget(0, g_pd3dSurface);
}
}
return g_pd3dSurface;
}
HRESULT InitD3D( HWND hWnd )
{
// For Windows Vista or later, this would be better if it used Direct3DCreate9Ex:
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
// Set up the structure used to create the D3DDevice. Since we are now
// using more complex geometry, we will create a device with a zbuffer.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// Create the D3DDevice
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
// Turn on the zbuffer
g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
// Turn on ambient lighting
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
return S_OK;
}
Lets move on to the XAML code:
xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
<Button.Background>
<ImageBrush>
<ImageBrush.ImageSource>
<interop:D3DImage x:Name="d3dImage" />
</ImageBrush.ImageSource>
</ImageBrush>
</Button.Background>
I've set it as background of a button here, using an ImageBrush. I believe adding it as background is a good way to display the DirectX content. However, you can use the image in any way you like.
To initialize the rendering acquire a handle to the current window and call the Initialize() method of the DLL with it:
private void initialize()
{
IntPtr surface = DLL.Initialize(new WindowInteropHelper(this).Handle,
(int)button.ActualWidth, (int)button.ActualHeight);
if (surface != IntPtr.Zero)
{
d3dImage.Lock();
d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
d3dImage.Unlock();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
}
The CompositionTarget.Rendering event is fired just before the UI is rendered. You should render your DirectX content in there:
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (d3dImage.IsFrontBufferAvailable)
{
d3dImage.Lock();
DLL.Render();
// Invalidate the whole area:
d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
d3dImage.Unlock();
}
}
That was basically it, I hope it helps. Now just a few important sidenotes:
- Always lock your image, to avoid that WPF draws frames partially
- Dont call Present on the Direct 3D device. WPF presents its own backbuffer, based on the surface you passed to d3dImage.SetBackBuffer().
The event IsFrontBufferAvailableChanged should be handled because sometimes the frontbuffer can become unavailable (for example when the user enters the lock screen). You should free or acquire the resources based on the buffer availability.
private void d3dImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (d3dImage.IsFrontBufferAvailable)
{
initialize();
}
else
{
// Cleanup:
CompositionTarget.Rendering -= CompositionTarget_Rendering;
DLL.Cleanup();
}
}