演练:在 WPF 中承载 Direct3D9 内容

本演练演示如何在 Windows Presentation Foundation (WPF) 应用程序中托管 Direct3D9 内容。

在本演练中,您将执行下列任务:

  • 创建一个 WPF 项目来托管 Direct3D9 内容。

  • 导入 Direct3D9 内容。

  • 使用 D3DImage 类显示 Direct3D9 内容。

完成后,你将了解如何在 WPF 应用程序中托管 Direct3D9 内容。

先决条件

你需要满足以下条件才能完成本演练:

创建 WPF 项目

第一步是为 WPF 应用程序创建项目。

创建 WPF 项目

在 Visual C# 中创建一个名为 D3DHost 的 WPF 应用程序项目。 有关详细信息,请参阅演练:我的第一个 WPF 桌面应用程序

MainWindow.xaml 在 WPF 设计器中随即打开。

导入 Direct3D9 内容

使用 DllImport 属性从非托管 DLL 导入 Direct3D9 内容。

导入 Direct3D9 内容

  1. 在代码编辑器中打开 MainWindow.xaml.cs。

  2. 使用以下代码替换自动生成的代码。

    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 MainWindow : Window
        {
            public MainWindow()
            {
                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();
            }
    
            ~MainWindow()
            {
                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
        {
            public static void Check(int hr)
            {
                Marshal.ThrowExceptionForHR(hr);
            }
        }
    }
    

托管 Direct3D9 内容

最后,使用 D3DImage 类来托管 Direct3D9 内容。

托管 Direct3D9 内容

  1. 在 MainWindow.xaml 中,将自动生成的 XAML 替换为以下 XAML。

        <Window x:Class="D3DHost.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
        Title="MainWindow" Height="300" Width="300" Background="PaleGoldenrod">
        <Grid>
            <Image x:Name="imgelt">
                <Image.Source>
                    <i:D3DImage x:Name="d3dimg" />
                </Image.Source>
            </Image>
        </Grid>
    </Window>
    
  2. 生成项目。

  3. 将包含 Direct3D9 内容的 DLL 复制到 bin/Debug 文件夹。

  4. 按 F5 运行项目。

    Direct3D9 内容显示在 WPF 应用程序中。

另请参阅