建立 WPF 中可以裝載的 Direct3D9 內容
更新: 2008 年 7 月
您可以將 Direct3D9 內容包含在 Windows Presentation Foundation (WPF) 應用程式中。這個主題會說明如何建立 Direct3D9 內容,以能有效率地和 WPF 交互操作。
![]() |
---|
在 WPF 中使用 Direct3D9 內容時,您也需要考慮效能。如需如何最佳化效能的詳細資訊,請參閱 Direct3D9 和 WPF 互通性的效能考量。 |
顯示緩衝區
D3DImage 類別管理兩個顯示緩衝區,分別稱為「背景緩衝區」(Back Buffer) 和「前景緩衝區」(Front Buffer)。背景緩衝區是您的 Direct3D9 介面。當您呼叫 Unlock 方法時,對背景緩衝區的變更會複製到前景緩衝區。
下圖顯示了背景緩衝區和前景緩衝區之間的關聯性 (Relationship)。
建立 Direct3D9 裝置
若要呈現 Direct3D9 內容,您必須建立 Direct3D9 裝置。有兩種 Direct3D9 物件可以用於建立裝置,分別是 IDirect3D9 和 IDirect3D9Ex。請使用這些物件分別建立 IDirect3DDevice9 和 IDirect3DDevice9Ex 裝置。
透過呼叫下列方法之一建立裝置。
IDirect3D9 * Direct3DCreate9(UINT SDKVersion);
HRESULT Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D);
對於顯示設定為使用 Windows 顯示驅動程式模型 (WDDM) 的 Windows Vista,請使用 Direct3DCreate9Ex 方法。對於其他的任何平台,請使用 Direct3DCreate9 方法。
Direct3DCreate9Ex 方法的可用性
只有 Windows Vista 上的 d3d9.dll 具有 Direct3DCreate9Ex 方法。如果您在 Windows XP 上直接連結此函式,您的應用程式將無法載入。若要判斷是否支援 Direct3DCreate9Ex 方法,請載入 DLL 並尋找程序位址。下列程式碼顯示如何測試 Direct3DCreate9Ex 方法。如需完整的程式碼範例,請參閱逐步解說:建立裝載於 WPF 中的 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 方法,以對 Direct3D9 介面進行指派。
驗證配接器支援
在建立介面前,驗證是否所有的配接器都支援您需要的介面屬性。就算您僅呈現至一種配接器,在系統中的任何一個配接器上都可能顯示此 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 上使用這個方法,因為您無法建立可鎖定的呈現目標紋理,效能將因此降低。
處理裝置狀態
當 IsFrontBufferAvailable 屬性由 true 轉換至 false,WPF 不會顯示 D3DImage,也不會將您的背景緩衝區複製到前景緩衝區。這通常表示裝置已遺失。
當裝置遺失時,您的程式碼應該停止轉譯,並等待 IsFrontBufferAvailable 屬性轉換為 true。處理 IsFrontBufferAvailableChanged 事件,以取得此轉換的通知。
當 IsFrontBufferAvailable 屬性由 false 轉換為 true,您應該檢查裝置,判斷是否有效。
Direct3D9 裝置,請呼叫 TestCooperativeLevel 方法;Direct3D9Ex 裝置請呼叫 CheckDeviceState 方法,因為 TestCooperativeLevel 方法已被取代,且永遠傳回成功。
如果裝置有效,請以原本的介面再次呼叫 SetBackBuffer 方法。
如果裝置無效,您必須重設裝置,並重新建立資源。透過無效的裝置中的介面呼叫 SetBackBuffer 方法,會引發例外狀況。
只有當您實作多配接器支援時,才呼叫 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 方法。由於這個呼叫發生於 Lock/Unlock 模式外,因此可能會發生失敗。在這個情況中,CopyBackBuffer 方法會傳回 null 且不會顯示任何影像。
您可以覆寫 CopyBackBuffer 方法,呼叫基底實作,如果傳回 null,您可以傳回預留位置 BitmapSource。
您也可以實作自己的軟體呈現,而不要呼叫基底實作。
![]() |
---|
如果 WPF 在軟體中完整呈現,因為 WPF 沒有前景緩衝區,就不會顯示 D3DImage。 |
請參閱
工作
逐步解說:建立裝載於 WPF 中的 Direct3D9 內容
概念
參考
變更記錄
日期 |
記錄 |
原因 |
---|---|---|
2008 年 7 月 |
新增主題。 |
SP1 功能變更。 |