WPF 및 Direct3D9 상호 운용성
Direct3D9 콘텐츠를 WPF(Windows Presentation Foundation) 응용 프로그램에 포함할 수 있습니다. 이 항목에서는 WPF와 효율적으로 상호 운용될 수 있도록 Direct3D9 콘텐츠를 만드는 방법에 대해 설명합니다.
![]() |
---|
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);
Direct3DCreate9Ex 메서드는 WDDM(Windows Display Driver Model)을 사용하도록 구성된 디스플레이를 사용하는 Windows Vista에서 사용하고 Direct3DCreate9 메서드는 그 이외의 모든 플랫폼에서 사용합니다.
Direct3DCreate9Ex 메서드의 가용성
Direct3DCreate9Ex 메서드는 Windows Vista의 d3d9.dll에만 있습니다. 이 기능을 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가 필요합니다. 대개는 Direct3D9에 사용할 더미 HWND를 만듭니다. 다음 코드 예제에서는 더미 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;
}
Present 매개 변수
장치를 만들려면 D3DPRESENT_PARAMETERS 구조체도 필요한데 그 중 몇 가지 매개 변수가 중요합니다. 이러한 매개 변수는 메모리 공간을 최소화하기 위해 선택합니다.
BackBufferHeight 및 BackBufferWidth 필드를 1로 설정합니다. 값을 0으로 설정하면 HWND의 크기로 설정됩니다.
Direct3D9에서 사용하는 메모리의 손상을 방지하고 Direct3D9로 인해 FPU 설정이 변경되지 않도록 D3DCREATE_MULTITHREADED 및 D3DCREATE_FPU_PRESERVE 플래그를 항상 설정해야 합니다.
다음 코드 예제에서는 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;
}
백 버퍼 렌더링 대상 만들기
Direct3D9 콘텐츠를 D3DImage에 표시하려면 Direct3D9 화면을 만든 후 SetBackBuffer 메서드를 호출하여 할당합니다.
어댑터 지원 확인
화면을 만들기 전에, 필요한 모든 화면 속성이 모든 어댑터에서 지원되는지 확인합니다. 어댑터 하나에만 렌더링하더라도 WPF 창은 시스템에 있는 모든 어댑터에 표시될 수 있습니다. WPF는 사용 가능한 어댑터 간에 화면을 이동할 수 있기 때문에 항상 다중 어댑터 구성을 처리할 수 있도록 Direct3D9 코드를 작성하고 모든 어댑터에서 지원되는지 여부를 확인해야 합니다.
다음 코드 예제에서는 시스템의 모든 어댑터에서 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 장치의 경우 TestCooperativeLevel 메서드가 더 이상 사용되지 않고 항상 장치가 유효하다고 반환하기 때문에 CheckDeviceState 메서드를 호출합니다.
장치가 유효하면 원래 화면에 대해 SetBackBuffer 메서드를 다시 호출합니다.
장치가 유효하지 않으면 장치를 다시 설정하고 리소스를 다시 만들어야 합니다. 유효하지 않은 장치의 화면을 사용하여 SetBackBuffer 메서드를 호출하면 예외가 발생합니다.
다중 어댑터 지원을 구현한 경우에 한해 Reset 메서드를 호출하여 유효하지 않은 장치를 복구합니다. 그 이외의 경우에는 모든 Direct3D9 인터페이스를 해제하고 전체적으로 다시 만듭니다. 어댑터 레이아웃이 변경된 경우 변경 전에 만들어진 Direct3D9 개체는 업데이트되지 않습니다.
크기 조정 처리
D3DImage를 원래 크기가 아닌 다른 해상도로 표시하면 Fant가 Bilinear로 대체된다는 점을 제외하고는 현재 BitmapScalingMode에 맞게 배율이 조정됩니다.
정확도를 높이려면 D3DImage 컨테이너의 크기가 변경될 때 새 화면을 만들어야 합니다.
크기 조정은 다음과 같은 세 가지 방법으로 처리할 수 있습니다.
크기가 변경되면 레이아웃 시스템에 참여하고 새 화면을 만듭니다. 비디오 메모리가 모두 사용되거나 조각화될 수 있으므로 화면을 너무 많이 만들면 안 됩니다.
크기 조정 이벤트가 발생하지 않은 상태로 일정 시간이 경과한 이후에 새 화면을 만듭니다.
컨테이너 크기를 1초에 여러 번 확인하는 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 컨테이너의 크기나 위치가 변경되면 모니터를 업데이트하거나, 1초당 여러 번 업데이트하는 DispatcherTimer를 사용하여 모니터를 업데이트합니다.
WPF 소프트웨어 렌더링
다음과 같은 경우 WPF는 소프트웨어의 UI 스레드에서 동기적으로 렌더링합니다.
이러한 상황이 발생하면 렌더링 시스템은 CopyBackBuffer 메서드를 호출하여 하드웨어 버퍼를 소프트웨어에 복사합니다. 기본 구현을 사용하면 화면과 함께 GetRenderTargetData 메서드가 호출됩니다. 이 호출은 Lock/Unlock 패턴 외부에서 발생하기 때문에 실패할 수 있습니다. 이 경우 CopyBackBuffer 메서드가 null을 반환하고 이미지가 표시되지 않습니다.
CopyBackBuffer 메서드를 재정의하고 기본 구현을 호출할 수 있습니다. 이 경우 null이 반환되면 자리 표시자 BitmapSource를 반환할 수 있습니다.
기본 구현을 호출하는 대신 고유한 소프트웨어 렌더링을 구현할 수도 있습니다.
![]() |
---|
WPF가 소프트웨어에서만 렌더링되면 WPF에 프런트 버퍼가 없기 때문에 D3DImage가 표시되지 않습니다. |
참고 항목
작업
연습: WPF에서 호스팅할 Direct3D9 콘텐츠 만들기