연습: WPF에서 Direct3D9 콘텐츠 호스팅
업데이트: 2008년 7월
이 연습에서는 WPF(Windows Presentation Foundation) 응용 프로그램에서 Direct3D9 콘텐츠를 호스팅하는 방법을 보여 줍니다.
이 연습에서는 다음 작업을 수행합니다.
Direct3D9 콘텐츠를 호스팅할 WPF 프로젝트를 만듭니다.
Direct3D9 콘텐츠를 가져옵니다.
D3DImage 클래스를 사용하여 Direct3D9 콘텐츠를 표시합니다.
작업을 마치면 WPF 응용 프로그램에서 Direct3D9 콘텐츠를 호스팅하는 방법을 알 수 있습니다.
참고
실제 설정이나 버전에 따라서 화면에 나타나는 대화 상자와 메뉴 명령이 도움말의 설명과 다를 수 있습니다. 설정을 변경하려면 도구 메뉴에서 설정 가져오기 및 내보내기를 선택합니다. 자세한 내용은 Visual Studio 설정을 참조하십시오.
사전 요구 사항
이 연습을 완료하려면 다음 구성 요소가 필요합니다.
Visual Studio 2008 SP1
DirectX SDK 9 이상
WPF 호환 형식의 Direct3D9 콘텐츠가 포함된 DLL. 자세한 내용은 연습: WPF에서 호스팅할 Direct3D9 콘텐츠 만들기를 참조하십시오.
WPF 프로젝트 만들기
첫 번째 단계는 WPF 응용 프로그램의 프로젝트를 만드는 것입니다.
WPF 프로젝트를 만들려면
Visual C#에서 D3DHost라는 새 WPF 응용 프로그램 프로젝트를 만듭니다. 자세한 내용은 방법: 새 WPF 응용 프로그램 프로젝트 만들기를 참조하십시오.
WPF Designer에 Window1.xaml이 열립니다.
Direct3D9 콘텐츠 가져오기
DllImport 특성을 사용하여 관리되지 않는 DLL에서 Direct3D9 콘텐츠를 가져옵니다.
Direct3D9 콘텐츠를 가져오려면
코드 편집기에서 Window1.xaml.cs를 엽니다.
자동으로 생성된 코드를 다음 코드로 바꿉니다.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; using System.Runtime.InteropServices; using System.Security.Permissions; namespace D3DHost { public partial class Window1 : Window { public Window1() { InitializeComponent(); // Set up the initial state for the D3DImage. HRESULT.Check(SetSize(512, 512)); HRESULT.Check(SetAlpha(false)); HRESULT.Check(SetNumDesiredSamples(4)); // // Optional: Subscribing to the IsFrontBufferAvailableChanged event. // // If you don't render every frame (e.g. you only render in // reaction to a button click), you should subscribe to the // IsFrontBufferAvailableChanged event to be notified when rendered content // is no longer being displayed. This event also notifies you when // the D3DImage is capable of being displayed again. // For example, in the button click case, if you don't render again when // the IsFrontBufferAvailable property is set to true, your // D3DImage won't display anything until the next button click. // // Because this application renders every frame, there is no need to // handle the IsFrontBufferAvailableChanged event. // CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); // // Optional: Multi-adapter optimization // // The surface is created initially on a particular adapter. // If the WPF window is dragged to another adapter, WPF // ensures that the D3DImage still shows up on the new // adapter. // // This process is slow on Windows XP. // // Performance is better on Vista with a 9Ex device. It's only // slow when the D3DImage crosses a video-card boundary. // // To work around this issue, you can move your surface when // the D3DImage is displayed on another adapter. To // determine when that is the case, transform a point on the // D3DImage into screen space and find out which adapter // contains that screen space point. // // When your D3DImage straddles two adapters, nothing // can be done, because one will be updating slowly. // _adapterTimer = new DispatcherTimer(); _adapterTimer.Tick += new EventHandler(AdapterTimer_Tick); _adapterTimer.Interval = new TimeSpan(0, 0, 0, 0, 500); _adapterTimer.Start(); // // Optional: Surface resizing // // The D3DImage is scaled when WPF renders it at a size // different from the natural size of the surface. If the // D3DImage is scaled up significantly, image quality // degrades. // // To avoid this, you can either create a very large // texture initially, or you can create new surfaces as // the size changes. Below is a very simple example of // how to do the latter. // // By creating a timer at Render priority, you are guaranteed // that new surfaces are created while the element // is still being arranged. A 200 ms interval gives // a good balance between image quality and performance. // You must be careful not to create new surfaces too // frequently. Frequently allocating a new surface may // fragment or exhaust video memory. This issue is more // significant on XDDM than it is on WDDM, because WDDM // can page out video memory. // // Another approach is deriving from the Image class, // participating in layout by overriding the ArrangeOverride method, and // updating size in the overriden method. Performance will degrade // if you resize too frequently. // // Blurry D3DImages can still occur due to subpixel // alignments. // _sizeTimer = new DispatcherTimer(DispatcherPriority.Render); _sizeTimer.Tick += new EventHandler(SizeTimer_Tick); _sizeTimer.Interval = new TimeSpan(0, 0, 0, 0, 200); _sizeTimer.Start(); } ~Window1() { Destroy(); } void AdapterTimer_Tick(object sender, EventArgs e) { POINT p = new POINT(imgelt.PointToScreen(new Point(0, 0))); HRESULT.Check(SetAdapter(p)); } void SizeTimer_Tick(object sender, EventArgs e) { // The following code does not account for RenderTransforms. // To handle that case, you must transform up to the root and // check the size there. // Given that the D3DImage is at 96.0 DPI, its Width and Height // properties will always be integers. ActualWidth/Height // may not be integers, so they are cast to integers. uint actualWidth = (uint)imgelt.ActualWidth; uint actualHeight = (uint)imgelt.ActualHeight; if ((actualWidth > 0 && actualHeight > 0) && (actualWidth != (uint)d3dimg.Width || actualHeight != (uint)d3dimg.Height)) { HRESULT.Check(SetSize(actualWidth, actualHeight)); } } void CompositionTarget_Rendering(object sender, EventArgs e) { RenderingEventArgs args = (RenderingEventArgs)e; // It's possible for Rendering to call back twice in the same frame // so only render when we haven't already rendered in this frame. if (d3dimg.IsFrontBufferAvailable && _lastRender != args.RenderingTime) { IntPtr pSurface = IntPtr.Zero; HRESULT.Check(GetBackBufferNoRef(out pSurface)); if (pSurface != IntPtr.Zero) { d3dimg.Lock(); // Repeatedly calling SetBackBuffer with the same IntPtr is // a no-op. There is no performance penalty. d3dimg.SetBackBuffer(D3DResourceType.IDirect3DSurface9, pSurface); HRESULT.Check(Render()); d3dimg.AddDirtyRect(new Int32Rect(0, 0, d3dimg.PixelWidth, d3dimg.PixelHeight)); d3dimg.Unlock(); _lastRender = args.RenderingTime; } } } DispatcherTimer _sizeTimer; DispatcherTimer _adapterTimer; TimeSpan _lastRender; // Import the methods exported by the unmanaged Direct3D content. [DllImport("D3DCode.dll")] static extern int GetBackBufferNoRef(out IntPtr pSurface); [DllImport("D3DCode.dll")] static extern int SetSize(uint width, uint height); [DllImport("D3DCode.dll")] static extern int SetAlpha(bool useAlpha); [DllImport("D3DCode.dll")] static extern int SetNumDesiredSamples(uint numSamples); [StructLayout(LayoutKind.Sequential)] struct POINT { public POINT(Point p) { x = (int)p.X; y = (int)p.Y; } public int x; public int y; } [DllImport("D3DCode.dll")] static extern int SetAdapter(POINT screenSpacePoint); [DllImport("D3DCode.dll")] static extern int Render(); [DllImport("D3DCode.dll")] static extern void Destroy(); } public static class HRESULT { [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static void Check(int hr) { Marshal.ThrowExceptionForHR(hr); } } }
Direct3D9 콘텐츠 호스팅
마지막으로 D3DImage 클래스를 사용하여 Direct3D9 콘텐츠를 호스팅합니다.
Direct3D9 콘텐츠를 호스팅하려면
Window1.xaml에서 자동으로 생성된 XAML을 다음 XAML로 바꿉니다.
<Window x:Class="D3DHost.Window1" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interop;assembly=PresentationCore" Title="Window1" Height="300" Width="300" Background="PaleGoldenrod"> <Grid> <Image x:Name="imgelt"> <Image.Source> <i:D3DImage x:Name="d3dimg" /> </Image.Source> </Image> </Grid> </Window>
프로젝트를 빌드합니다.
Direct3D9 콘텐츠가 포함된 DLL을 bin/Debug 폴더에 복사합니다.
F5 키를 눌러 프로젝트를 실행합니다.
WPF 응용 프로그램에 Direct3D9 콘텐츠가 나타납니다.
참고 항목
개념
Direct3D9 및 WPF 상호 운용성을 위한 성능 고려 사항
참조
변경 기록
날짜 |
변경 내용 |
이유 |
---|---|---|
2008년 7월 |
새 항목이 추가되었습니다. |
SP1 기능 변경 |