WPF 和 Direct3D9 互通
您可以在 Windows Presentation Foundation (WPF) 應用程式中加入 Direct3D9 內容。 本主題描述如何建立 Direct3D9 內容,使其與 WPF 有效地交互操作。
注意
在 WPF 中使用 Direct3D9 內容時,您也需要考慮效能。 如需如何優化效能的詳細資訊,請參閱 Direct3D9 和 WPF 交互操作效能考量。
顯示緩衝區
D3DImage 物件類別管理兩個顯示緩衝區,稱為 背景緩衝區 和 前端緩衝區。 背景緩衝區是 Direct3D9 介面。 當您呼叫 Unlock 方法時,會將背景緩衝區的變更複製到前端緩衝區。
下圖示範背景緩衝區與前端緩衝區之間的關聯性。
Direct3D9 裝置建立
若要轉譯 Direct3D9 內容,您必須建立 Direct3D9 裝置。 您可以使用兩個 Direct3D9 物件來建立裝置, IDirect3D9
和 IDirect3D9Ex
。 使用這些物件分別建立 IDirect3DDevice9
和 IDirect3DDevice9Ex
裝置。
呼叫下列其中一種方法來建立裝置。
IDirect3D9 * Direct3DCreate9(UINT SDKVersion);
HRESULT Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D);
在 Windows Vista 或更新版本的作業系統上,使用 Direct3DCreate9Ex
方法搭配設定為使用 Windows 顯示驅動程式模型 (WDDM) 的顯示器。 在任何其他平臺上使用 Direct3DCreate9
方法。
Direct3DCreate9Ex 方法的可用性
只有在 Windows Vista 或更新版本的作業系統上 d3d9.dll 才會有 Direct3DCreate9Ex
方法。 如果您直接連結 Windows XP 上的語言函數,您的應用程式將無法載入。 若要判斷是否支援 Direct3DCreate9Ex
方法,請載入 DLL 並尋找程式位址。 下列程式代碼示範如何測試 Direct3DCreate9Ex
方法。 如需完整的程式代碼範例,請參閱 逐步解說:為 WPF 中的 Hosting 建立 Direct3D9 內容。
HRESULT
CRendererManager::EnsureD3DObjects()
{
HRESULT hr = S_OK;
HMODULE hD3D = NULL;
if (!m_pD3D)
{
hD3D = LoadLibrary(TEXT("d3d9.dll"));
DIRECT3DCREATE9EXFUNCTION pfnCreate9Ex = (DIRECT3DCREATE9EXFUNCTION)GetProcAddress(hD3D, "Direct3DCreate9Ex");
if (pfnCreate9Ex)
{
IFC((*pfnCreate9Ex)(D3D_SDK_VERSION, &m_pD3DEx));
IFC(m_pD3DEx->QueryInterface(__uuidof(IDirect3D9), reinterpret_cast<void **>(&m_pD3D)));
}
else
{
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!m_pD3D)
{
IFC(E_FAIL);
}
}
m_cAdapters = m_pD3D->GetAdapterCount();
}
Cleanup:
if (hD3D)
{
FreeLibrary(hD3D);
}
return hr;
}
HWND 建立
建立裝置需要 HWND。 一般而言,您會建立虛擬 HWND 供 Direct3D9 使用。 下列程式代碼範例示範如何建立虛擬 HWND。
HRESULT
CRendererManager::EnsureHWND()
{
HRESULT hr = S_OK;
if (!m_hwnd)
{
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = DefWindowProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = NULL;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
IFC(E_FAIL);
}
m_hwnd = CreateWindow(szAppName,
TEXT("D3DImageSample"),
WS_OVERLAPPEDWINDOW,
0, // Initial X
0, // Initial Y
0, // Width
0, // Height
NULL,
NULL,
NULL,
NULL);
}
Cleanup:
return hr;
}
展示參數
建立裝置也需要 D3DPRESENT_PARAMETERS
結構,但是重要的只有少數的參數。 系統會選擇這些參數,以將記憶體使用量降到最低。
將 BackBufferHeight
和 BackBufferWidth
欄位設定為 1。 將它們設定為 0 會使它們被設定為 HWND 的維度。
請一律設定 D3DCREATE_MULTITHREADED
和 D3DCREATE_FPU_PRESERVE
旗標,以防止 Direct3D9 所使用的記憶體損毀,以及防止 Direct3D9 變更 FPU 設定。
下列程式代碼示範如何初始化 D3DPRESENT_PARAMETERS
結構。
HRESULT
CRenderer::Init(IDirect3D9 *pD3D, IDirect3D9Ex *pD3DEx, HWND hwnd, UINT uAdapter)
{
HRESULT hr = S_OK;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.BackBufferHeight = 1;
d3dpp.BackBufferWidth = 1;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
D3DCAPS9 caps;
DWORD dwVertexProcessing;
IFC(pD3D->GetDeviceCaps(uAdapter, D3DDEVTYPE_HAL, &caps));
if ((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == D3DDEVCAPS_HWTRANSFORMANDLIGHT)
{
dwVertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else
{
dwVertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
if (pD3DEx)
{
IDirect3DDevice9Ex *pd3dDevice = NULL;
IFC(pD3DEx->CreateDeviceEx(
uAdapter,
D3DDEVTYPE_HAL,
hwnd,
dwVertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
&d3dpp,
NULL,
&m_pd3dDeviceEx
));
IFC(m_pd3dDeviceEx->QueryInterface(__uuidof(IDirect3DDevice9), reinterpret_cast<void**>(&m_pd3dDevice)));
}
else
{
assert(pD3D);
IFC(pD3D->CreateDevice(
uAdapter,
D3DDEVTYPE_HAL,
hwnd,
dwVertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
&d3dpp,
&m_pd3dDevice
));
}
Cleanup:
return hr;
}
建立背景緩衝區轉譯目標
若要在 D3DImage中顯示 Direct3D9 內容,您可以建立 Direct3D9 介面,並透過呼叫 SetBackBuffer 方法來指派它。
驗證介面卡支援
建立介面之前,請確認所有介面卡都支援您需要的介面屬性。 即使您只轉譯到一個介面卡,WPF 視窗也可能顯示在系統中的任何介面卡上。 您應該一律撰寫處理多介面卡設定的 Direct3D9 程式代碼,而且您應該檢查所有介面卡是否支援,因為 WPF 可能在可用的介面卡之間移轉介面。
下列程式代碼範例示範如何檢查系統上的所有介面卡是否支援 Direct3D9。
HRESULT
CRendererManager::TestSurfaceSettings()
{
HRESULT hr = S_OK;
D3DFORMAT fmt = m_fUseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;
//
// We test all adapters because because we potentially use all adapters.
// But even if this sample only rendered to the default adapter, you
// should check all adapters because WPF may move your surface to
// another adapter for you!
//
for (UINT i = 0; i < m_cAdapters; ++i)
{
// Can we get HW rendering?
IFC(m_pD3D->CheckDeviceType(
i,
D3DDEVTYPE_HAL,
D3DFMT_X8R8G8B8,
fmt,
TRUE
));
// Is the format okay?
IFC(m_pD3D->CheckDeviceFormat(
i,
D3DDEVTYPE_HAL,
D3DFMT_X8R8G8B8,
D3DUSAGE_RENDERTARGET | D3DUSAGE_DYNAMIC, // We'll use dynamic when on XP
D3DRTYPE_SURFACE,
fmt
));
// D3DImage only allows multisampling on 9Ex devices. If we can't
// multisample, overwrite the desired number of samples with 0.
if (m_pD3DEx && m_uNumSamples > 1)
{
assert(m_uNumSamples <= 16);
if (FAILED(m_pD3D->CheckDeviceMultiSampleType(
i,
D3DDEVTYPE_HAL,
fmt,
TRUE,
static_cast<D3DMULTISAMPLE_TYPE>(m_uNumSamples),
NULL
)))
{
m_uNumSamples = 0;
}
}
else
{
m_uNumSamples = 0;
}
}
Cleanup:
return hr;
}
建立介面
建立介面之前,請確認裝置功能支援目標作業系統上的良好效能。 如需詳細資訊,請參閱 Direct3D9 和 WPF 交互操作效能考量 。
在驗證過裝置功能後,即可以建立介面。 下列程式代碼範例示範如何建立轉譯目標。
HRESULT
CRenderer::CreateSurface(UINT uWidth, UINT uHeight, bool fUseAlpha, UINT m_uNumSamples)
{
HRESULT hr = S_OK;
SAFE_RELEASE(m_pd3dRTS);
IFC(m_pd3dDevice->CreateRenderTarget(
uWidth,
uHeight,
fUseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8,
static_cast<D3DMULTISAMPLE_TYPE>(m_uNumSamples),
0,
m_pd3dDeviceEx ? FALSE : TRUE, // Lockable RT required for good XP perf
&m_pd3dRTS,
NULL
));
IFC(m_pd3dDevice->SetRenderTarget(0, m_pd3dRTS));
Cleanup:
return hr;
}
WDDM
在設定為使用 WDDM 的 Windows Vista 和更新版本的作業系統上,您可以建立轉譯目標材質,並將等級 0 介面傳送至 SetBackBuffer 方法。 不建議在 Windows XP 上使用此方法,因為您無法建立可鎖定的轉譯目標材質,而且效能將會降低。
處理裝置狀態
D3DImage 物件類別管理兩個顯示緩衝區,稱為 背景緩衝區 和 前端緩衝區。 背景緩衝區是 Direct3D 介面。 當您呼叫 Unlock 方法時,系統會將背景緩衝區的變更複製到前端緩衝區,其會顯示在硬體上。 有時候,前端緩衝區變得無法使用。 這種可用性不足的原因可能是螢幕鎖定、全螢幕排除、應用 Direct3D、使用者切換或其他系統活動所造成。 發生這種情況時,將會透過處理 IsFrontBufferAvailableChanged 事件通知您的 WPF 應用程式。 您的應用程式如何回應無法使用前端緩衝區,取決於 WPF 是否能夠回復到軟體轉譯。 SetBackBuffer 方法具有多載,其採用參數,指定 WPF 是否回復至軟體轉譯。
當您呼叫 SetBackBuffer(D3DResourceType, IntPtr) 多載或呼叫 SetBackBuffer(D3DResourceType, IntPtr, Boolean) 多載,並將 enableSoftwareFallback
參數設定為 false
時,轉譯系統會在前端緩衝區無法使用且未顯示任何內容時,提供參考給背景緩衝區。 當前端緩衝區再次可用時,轉譯系統會發起 IsFrontBufferAvailableChanged 事件來通知您的 WPF 應用程式。 您可以為 IsFrontBufferAvailableChanged 事件建立事件處理常式,以使用有效的 Direct3D 介面重新啟動轉譯。 若要重新啟動轉譯,您必須呼叫 SetBackBuffer。
當您使用設定為 true
的 enableSoftwareFallback
參數呼叫 SetBackBuffer(D3DResourceType, IntPtr, Boolean) 多載時,轉譯系統會在前端緩衝區無法使用時保留參考給背景緩衝區,因此在前端緩衝區再次可用時不需要呼叫 SetBackBuffer。
啟用軟體轉譯時,可能會有使用者’裝置無法使用的情況,但轉譯系統會保留 Direct3D 介面的參考。 若要檢查 Direct3D9 裝置是否無法使用,請呼叫 TestCooperativeLevel
方法。 若要檢查 Direct3D9Ex 裝置,請呼叫 CheckDeviceState
方法,因為 TestCooperativeLevel
方法已被取代,且一律會傳回成功。 如果使用者裝置無法使用,請呼叫 SetBackBuffer 以向背景緩衝區提供 WPF’的參考。 如果您需要重設裝置,請呼叫 SetBackBuffer ,並將 backBuffer
參數設定為 null
,然後再次呼叫 SetBackBuffer ,並將 backBuffer
設定為有效的 Direct3D 介面。
只有在實施多介面卡支援時,才呼叫 Reset
方法來從無效的裝置復原。 否則,請釋放所有 Direct3D9 介面,並整個重新建立。 如果介面卡配置已變更,並不會更新變更之前建立的 Direct3D9 物件。
處理調整大小
如果 D3DImage 顯示非其原生大小的解析度,則會根據目前的 BitmapScalingMode來調整,但 Bilinear 會取代為 Fant。
如果您需要較高的逼真度,當 D3DImage 的容器大小變更時,您必須建立新的介面。
有三種方法可以處理調整大小。
參與版面配置系統,並在變更大小時建立新的介面。 請勿建立太多介面,因為這可能會耗盡或片段圖形記憶體。
等待一段固定時間直到未發生調整大小事件後,以建立新的介面。
建立 DispatcherTimer ,每秒檢查容器維度數次。
多重監視器優化
當轉譯系統將 D3DImage 移到另一個監視器時,效能會大幅降低。
在 WDDM 上,只要監視器位於相同的顯示卡,並且您使用 Direct3DCreate9Ex
,效能就不會降低。 如果監視器位於不同的顯示卡,效能會降低。 在 Windows XP 上,效能一律會降低。
當 D3DImage 移至另一個監視器時,您可以在對應的介面卡上建立新的介面,以還原良好的效能。
若要避免效能降低,請特別針對多重監視器情形撰寫程序代碼。 下列清單顯示撰寫多重監視器程序代碼的方式之一。
使用
Visual.ProjectToScreen
方法,在螢幕空間中尋找 D3DImage 點。使用
MonitorFromPoint
GDI 方法來尋找正顯示該點的監視器。使用
IDirect3D9::GetAdapterMonitor
方法來尋找監視器所在的 Direct3D9 介面卡。如果該介面卡與背景緩衝區的介面卡不同,請在新的監視器上建立新的背景緩衝區,並將它指派給 D3DImage 背景緩衝區。
注意
如果 D3DImage 跨接監視器,則效能會變慢,但相同介面卡上的 WDDM 和 IDirect3D9Ex
除外。 在此情況下,無法改善效能。
下列程式代碼範例示範如何尋找目前的監視器。
void
CRendererManager::SetAdapter(POINT screenSpacePoint)
{
CleanupInvalidDevices();
//
// After CleanupInvalidDevices, we may not have any D3D objects. Rather than
// recreate them here, ignore the adapter update and wait for render to recreate.
//
if (m_pD3D && m_rgRenderers)
{
HMONITOR hMon = MonitorFromPoint(screenSpacePoint, MONITOR_DEFAULTTONULL);
for (UINT i = 0; i < m_cAdapters; ++i)
{
if (hMon == m_pD3D->GetAdapterMonitor(i))
{
m_pCurrentRenderer = m_rgRenderers[i];
break;
}
}
}
}
當 D3DImage 容器的大小或位置變更時,更新監視器,或使用每秒更新數次的 DispatcherTimer
來更新監視器。
WPF 軟體轉譯
在下列情況下,WPF 會在軟體的UI執行緒上同步轉譯。
發生上述其中一種情況時,轉譯系統會呼叫 CopyBackBuffer 方法,將硬體緩衝區複製到軟體上。 使用您的介面預設實作呼叫 GetRenderTargetData
方法。 由於不是在鎖定/解除鎖定模式下呼叫,因此可能會失敗。 在此情況下, CopyBackBuffer
方法會傳回 null
,而且不會顯示任何影像。
您可以覆寫 CopyBackBuffer 方法、呼叫基礎實作,如果傳回 null
,則可以傳回一個預留位置 BitmapSource。
您也可以實作自己的軟體轉譯,而不是呼叫基礎實作。
注意
如果 WPF 完全在軟體中轉譯,則不會顯示 D3DImage ,因為 WPF 沒有前端緩衝區。