根据《WPF 4.5 Unleashed》第19章的内容,我会尽力回答这个问题。如果您想查找它,请在“将DirectX内容与WPF内容混合使用”子部分中找到所有信息。
您的C++ DLL应该有3个公开方法:Initialize()、Cleanup()和Render()。其中有趣的方法是Initialize()和InitD3D(),后者由Initialize()调用。
extern "C" __declspec(dllexport) IDirect3DSurface9* WINAPI Initialize(HWND hwnd, int width, int height)
{
if( SUCCEEDED( InitD3D( hwnd ) ) )
{
if( SUCCEEDED( InitGeometry() ) )
{
if (FAILED(g_pd3dDevice->CreateRenderTarget(width, height,
D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
true,
&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 )
{
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
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;
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
return S_OK;
}
让我们开始看XAML代码:
xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
<Button.Background>
<ImageBrush>
<ImageBrush.ImageSource>
<interop:D3DImage x:Name="d3dImage" />
</ImageBrush.ImageSource>
</ImageBrush>
</Button.Background>
我在这里使用了一个ImageBrush将其设置为按钮的背景。我相信将其添加为背景是展示DirectX内容的好方法。然而,您可以按任何方式使用该图像。
要初始化渲染,请获取当前窗口的句柄,并调用DLL的Initialize()方法:
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;
}
}
CompositionTarget.Rendering事件在UI呈现之前被触发。您应该在其中呈现DirectX内容:
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (d3dImage.IsFrontBufferAvailable)
{
d3dImage.Lock();
DLL.Render();
d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
d3dImage.Unlock();
}
}
那基本就是这样,希望有所帮助。现在只有几个重要的侧记:
- 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
{
CompositionTarget.Rendering -= CompositionTarget_Rendering;
DLL.Cleanup();
}
}