Click here to Skip to main content
15,860,861 members
Articles / Desktop Programming / WPF
Article

Introduction to D3DImage

Rate me:
Please Sign up or sign in to vote.
5.00/5 (77 votes)
11 Aug 2008CPOL24 min read 413.3K   18.4K   219   59
.NET 3.5 SP1 is here! It's time to break out your DirectX skills. This article provides the information necessary to get started using a new DirectX interop feature in WPF called D3DImage.

Introduction

Don't let the name fool you. The newest release of Windows Presentation Foundation (WPF), contained within Service Pack 1 of the Microsoft .NET Framework, version 3.5, is much more than just a service release. It contains several completely new and highly requested features, including things like hardware accelerated bitmap effects and a much better DirectX interop story. Most of the new features were described last May in Tim Sneath's original announcement for the beta release of SP1.

The purpose of this article is to introduce one particular new feature called D3DImage. D3DImage is a new ImageSource object that enables a whole new level of interoperability between WPF and DirectX by allowing a custom Direct3D (D3D) surface to be blended with WPF's native D3D surface. Although touted in Tim's earlier announcement, D3DImage was not actually available in the beta release of SP1. It is, however, available in the golden release that has just gone public.

We will look at a very simple example of D3D interop in this article. Although our sample demonstrates a custom 3D scene, keep in mind that D3D is not just for rendering 3D content. Beginning with DirectX version 8, D3D has subsumed DirectDraw (the former 2D DirectX graphics technology). As such, D3D is now responsible for rendering both 2D and 3D graphics.

Scope of this Article

The primary goal of this article is to present developers with the information that is needed to get started using D3DImage in their WPF applications. To compile the included sample, a version of Visual Studio that is configured to support both C# and C++ projects is required. Service Pack 1 for Visual Studio 2008 is also recommended; however, it is possible to build the sample with only Service Pack 1 of the Microsoft .NET Framework, version 3.5. If you encounter problems building the sample, please see the section entitled "Tips for Building the D3DImage Sample" at the end of this article.

In an effort to stay focused and keep the sample uncluttered, I have intentionally selected a very simple 3D scene. In fact, I have simply borrowed a scene from the sample in the DirectX SDK, entitled "Tutorial 3: Using Matrices". It contains nothing more than a multicolored triangle spinning about the Y axis. This was about the simplest D3D sample I could find, and it has been reduced even further for the purposes of this article. To be clear, this is not intended to be an example of quality D3D code. The unmanaged code is intentionally minimalistic. I have reduced it to a single code file.

In this article, we will focus more on the usage of the D3DImage object from the WPF side. We will definitely look at the D3D requirements (as far as creating the right type of device and surface), but we will not spend any time explaining pure D3D concepts, which would be far beyond the scope of this intro to D3DImage. Those who are new to DirectX development and planning to use D3DImage to implement an interop solution should definitely plan on getting a good book on D3D.

What is an ImageSource?

First things first... What, exactly, is an ImageSource in WPF? Well, as the name implies, an ImageSource is a MIL object* that can serve as the source of a rendered image. The most obvious usage scenario for an ImageSource involves setting the Source property of the Image element, itself. An ImageSource is also used to specify the source of other MIL objects like ImageBrush and ImageDrawing.

* "MIL object" refers to a managed object that is accessed directly by WPF's Media Integration Layer (MIL) via specific MIL interfaces (like DUCE.IResource). The MIL is a largely unmanaged portion of WPF responsible for graphics and media rendering. It is built on top of DirectX. Until now, we have not been able to directly integrate a custom DirectX surface with the MIL's DirectX surface, but D3DImage now gives us this ability.

The fact that we can create a Brush using an ImageSource is very important because it allows an image to be rendered anywhere that a Brush is used. In WPF, a Brush can be used to render the background of many visual elements, as well as the foreground of text elements, and the stroke and fill of shapes. By leveraging an ImageBrush, all such visual elements can be rendered using an image.

This allows us to make a couple of brushes out of images like the following:

Image 1 Image 2

And, then do silly things like this:

Image 3

XAML

XML
<Grid Background="{StaticResource IceBrush}">
  <TextBlock Foreground="{StaticResource FireBrush}"
      Text="Fire &amp; Ice" />
</Grid>

Alright... not that compelling... I guess that's why I'll never be a designer. But in the hands of a talented designer, an ImageBrush is a mighty powerful tool. (I guess you'll just have to take my word on that!)

D3DImage is an ImageSource

As mentioned previously, D3DImage is an ImageSource. It, therefore, allows a 3D scene to be used as the source of an Image, or more importantly, of an ImageBrush. This means that the D3D surface can be brushed onto any WPF element that is rendered via a Brush. So now, with .NET 3.5 SP1, an ImageBrush is potentially an even more powerful tool, assuming you have a developer on staff with some D3D skills.

Enter D3DImage... Exit Airspace Restrictions

The D3DImage object is first and foremost an interop object. It accepts a D3D surface that has been created and rendered using pure unmanaged D3D code, and hands that surface to the MIL so that it can be blended with the rendered WPF scene. If you are familiar with the D3D interop support that has existed up until now, you know that this new approach is a huge step forward.

Until now, if we needed to host an unmanaged D3D surface within our WPF application, we have been limited to a solution that involves hosting an HWND. This means that we must work around certain "airspace" issues, as described in this MSDN topic. In short, using the pre-SP1 bits, we are forced to give up the region of our window where the DirectX surface exists, since that region is owned by the HWND that is hosting the surface. So, we cannot do things like presenting WPF-based textual and graphical annotations alpha blended over the top of our hosted D3D scene.

These types of airspace restrictions represent a huge limitation in a framework, like WPF, where element composition is used to create very rich user experiences. With a D3DImage solution, these restrictions are no longer present!

Composing a Custom D3D Scene with the WPF Scene

The code sample included with this article demonstrates how to blend a custom D3D render target with the render target of a WPF application. Let's begin by running through the managed side of the sample (the WPF code) at a high level, and then, we'll take a closer look at some of the details.

The WPF app begins by creating a D3DImage and then setting that as the source of an ImageBrush. The ImageBrush is then stored as a resource on the application's Window to make it available when parsing the XAML.

All of the code to do this is contained within the Window's constructor, shown here:

C#
public Window1()
{
    // create a D3DImage to host the scene and
    // monitor it for changes in front buffer availability
    _di = new D3DImage();
    _di.IsFrontBufferAvailableChanged += OnIsFrontBufferAvailableChanged;
 
    // make a brush of the scene available as a resource on the window
    Resources["RotatingTriangleScene"] = new ImageBrush(_di);
 
    // begin rendering the custom D3D scene into the D3DImage
    BeginRenderingScene();
 
    // parse the XAML
    InitializeComponent();
}

We will examine the finer details of this routine momentarily. First, let's look at the markup for the WPF scene, which is contained within Window1.xaml and shown here:

XML
<Window x:Class="D3DImageSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Name="RootWindow" Title="Window1" 
    Height="300" Width="300">
  <Window.Background>
    <ImageBrush ImageSource="Forest.jpg" />
  </Window.Background>
  <Grid Background="{StaticResource RotatingTriangleScene}" />
</Window>

This XAML consists of nothing more than a Window containing a Grid. The background of the Window is painted with an ImageBrush of a forest. This is simply to create an interesting background with which to compose our custom D3D scene. The Grid's background is brushed with our D3D scene, which again, is just a rotating triangle.

When you build and run the app, you see something like the following (although these images do not do justice to the fidelity of the actual scene):

Image 4 Image 5

Thanks to WPF's support for layered windows, we could even take this a step further and get rid of the window chrome entirely. This would allow us to render the custom D3D content directly to the screen, as shown here:

Image 6

Okay... okay... arguably, you would never opt to host a D3D scene in WPF unless you needed to blend it with WPF content... but, it's just cool that you can do this!

Creating the D3DImage

Let's take a closer look at the D3DImage object, itself, which we created and initialized with the following lines of code in Window1's constructor:

C#
// create a D3DImage to host the scene and
// monitor it for changes in front buffer availability
_di = new D3DImage();
_di.IsFrontBufferAvailableChanged += OnIsFrontBufferAvailableChanged;

// make a brush of the scene available as a resource on the window
Resources["RotatingTriangleScene"] = new ImageBrush(_di);

// begin rendering the custom D3D scene into the D3DImage
BeginRenderingScene();

After creating the object, we immediately establish a handler for the IsFrontBufferAvailableChanged event. This is very important because this event will let us know when there is actually a valid WPF D3D surface with which we can compose our custom D3D surface. (After all, if there is no WPF render surface, there really is no reason for us to waste cycles updating our scene because we have nowhere to render it.) More on this shortly...

After creating the D3DImage, we use it to create an ImageBrush and store it in the Resources collection of the Window under the name "RotatingTriangleScene". Finally, we call a routine called BeginRenderingScene() to start what is essentially a render pump for our custom D3D scene.

Knowing When to Update the Scene

As alluded to above, there are actually times when it makes sense to render our custom scene, and other times when it does not. Clearly, it does not make sense to update our scene more often than the WPF scene, itself, is updated. That would just be a waste of cycles. So at most, we should only update our scene during a render pass.

Seasoned WPF developers know that the static Rendering event of the CompositionTarget class can be leveraged to hook into WPF's rendering engine. The Rendering event will fire every time WPF's D3D surface is rendered. So, this provides an ideal time for us to update our custom surface before handing it to WPF for composition with the framework's render target. Our BeginRenderingScene() method is used to hook up a handler for the CompositionTarget.Rendering event, as shown here:

C#
private void BeginRenderingScene()
{
    if (_di.IsFrontBufferAvailable)
    {
        // create a custom D3D scene and get a pointer to its surface
        // (this is a call into our custom unmanaged library)
        _scene = InitializeScene();
        
        // set the back buffer using the new scene
        _di.Lock();
        _di.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _scene);
        _di.Unlock();

        // leverage the Rendering event of WPF's composition target to
        // update the custom D3D scene
        CompositionTarget.Rendering += OnRendering;
    }
}

You probably noticed that this call is wrapped within a conditional if statement that checks the IsFrontBufferAvailable property on our D3DImage. Most of the time, the front buffer will be immediately available after creating the object. When that is the case, it makes sense for us to set the back buffer on the D3DImage so that it can be transferred to the front buffer. (We will look at how this is done momentarily.)

But, if WPF loses its D3D device for any reason, then IsFrontBufferAvailable will become false. WPF is nice enough to fire the IsFrontBufferAvailableChanged event when this happens so that we can stop updating the back buffer of the D3DImage. Recall that we are already monitoring this event via a handler called OnIsFrontBufferAvailableChanged(). That handler looks like this:

C#
private void OnIsFrontBufferAvailableChanged(object sender, 
        DependencyPropertyChangedEventArgs e)
{
    // if the front buffer is available, then WPF has just created a new
    // D3D device, so we need to start rendering our custom scene
    if (_di.IsFrontBufferAvailable)
    {
        BeginRenderingScene();
    }
    else
    {
        // If the front buffer is no longer available, then WPF has lost 
        // its D3D device so there is no reason to waste cycles rendering
        // our custom scene until a new device is created.
        StopRenderingScene();
    }
}

The routine to stop rendering the scene is fairly straightforward. It is basically the inverse of the earlier BeginRenderingScene() routine:

C#
private void StopRenderingScene()
{
    // This method is called when WPF loses its D3D device.
    // In such a circumstance, it is very likely that we have lost 
    // our custom D3D device also, so we should just release the scene.
    // We will create a new scene when a D3D device becomes 
    // available again.
    CompositionTarget.Rendering -= OnRendering;
 
    // release the scene 
    // (this is a call into our custom unmanaged library)
    ReleaseScene();
    _scene = IntPtr.Zero;
}

WPF will automatically create a new D3D device when the Operating System signals that it is once again possible. At that point, IsFrontBufferAvailableChanged will fire again as the property becomes true. We, too, will then create a new D3D device and resume rendering our scene.

Side-note: If you're not a DirectX developer, you may be wondering why WPF would ever lose its D3D device. This is actually a pretty common occurrence, and can happen if the user performs a system operation that affects the video driver's resolution or color settings; or if the user presses Ctrl+Alt+Delete to invoke the WinLogon desktop; or if another application locks the screen; or if the user launches a full-screen D3D app; etc.

Updating the Custom Scene

If our custom D3D scene involves animations, we will likely want to update it as part of a WPF render pass. Then, any changes made to our 3D surface can be integrated with WPF's surface. For this reason, we have established an OnRendering() method to handle the CompositionTarget's Rendering event. That method is shown here:

C#
private void OnRendering(object sender, EventArgs e)
{
    // when WPF's composition target is about to render, we update our 
    // custom render target so that it can be blended with the WPF target
    UpdateScene();
}

In our example, we will update the scene on every render pass. Note, however, that if you wanted to throttle the rate at which the custom scene is updated, you could certainly insert that logic into this OnRendering() method.

To update our scene, we must perform the following four steps:

  1. Lock the D3DImage
  2. Update the custom D3D surface (this involves a call into unmanaged code)
  3. Invalidate the affected rectangular regions on the D3DImage by marking them as dirty
  4. Unlock the D3DImage

These steps can be seen in our UpdateScene() routine here:

C#
private void UpdateScene()
{
    if (_di.IsFrontBufferAvailable && _scene != IntPtr.Zero)
    {
        // lock the D3DImage
        _di.Lock();
 
        // update the scene 
        // (this is a call into our custom unmanaged library)
        SIZE size = new SIZE();
        RenderScene(size);
 
        // invalidate the updated rect of the D3DImage (in this case, the 
        // whole image)
        _di.AddDirtyRect(new Int32Rect(0, 0, size.Width, size.Height));
 
        // unlock the D3DImage
        _di.Unlock();
    }
}

When the scene is rendered, WPF will check the D3DImage for dirty regions. If dirty rects are found, those portions of the custom D3D surface will be recomposed with WPF's own D3D surface before it is rendered.

Side-note: It should be noted that to support composition with the WPF scene, a flush of the D3D device is required. This will certainly have an impact on performance. The degree of that impact depends on both the scene being rendered and the video driver's cost for such a flush operation. As such, performance may vary on different hardware or even under different driver versions.

A Brief Look at D3D Interop Requirements

We have now covered the managed side of the equation. Hopefully, it all makes pretty good sense. Refer to Window1.xaml.cs for the full code file, including the interop (P/Invoke) declarations that are necessary to call into the unmanaged library.

Before we move on to the unmanaged side of things, let's take a moment to look at some of the requirements for a well performing D3D interop scenario...

D3DImage can be used on both Windows XP and Windows Vista to compose a D3D scene with a WPF scene. However, there are definitely differences in how the DirectX device and surface should be created depending on the underlying Operating System. Some of these are based on gaining optimal performance. Others are pure requirements of D3DImage, which simply won't work if you use an improper pixel format, for example.

It should be noted up front that a general requirement, in case it is not immediately obvious, is that any application using D3DImage for DirectX interop must also run with full trust. The D3D surface is passed between managed and unmanaged code via an IntPtr. Anytime you're passing a raw pointer between managed and unmanaged code, there can be no guarantees regarding safety.

Below are the specific requirements for a performant solution on a particular Operating System:

Vista Requirements for a Performant D3DImage Solution

  • WDDM Driver
  • D3D 9Ex Device
  • 32-bit RGB or 32-bit ARGB surface (preferably non-lockable)

XP Requirements for a Performant D3DImage Solution

  • Lockable 32-bit RGB surface

- OR -

  • Lockable 32-bit ARGB surface
  • plus SP3 (or SP2 with this patch)

On Vista, D3DImage will perform best with a WDDM video driver. Performance will likely be quite poor if you are using an XDDM driver on Vista, since a software copy of the surface will be required.

On either Operating System, the pixel format of the created surface must be either 32-bit RGB (D3DFMT_X8R8G8B8) or 32-bit ARGB (D3DFMT_A8R8G8B8). Since the latter format supports alpha, it is clearly more appealing. However, if you wish to use the ARGB format on XP, you will need to either install SP3, or the layered windows patch on SP2. Also, note that the ARGB surface is assumed to be pre-multiplied (sometimes referred to as PARGB).

For hardware acceleration on XP, the surface must be created as lockable. On Vista, however, a non-lockable surface is preferable (although not required), as it is generally faster.

On XP, the Direct3DCreate9() function must be used to create the D3D device. On Vista, however, you should use the Direct3DCreate9Ex() function to create a D3D 9Ex device. By creating a 9Ex device, WPF will be able to use your device to create its own temporary surface that is shared with the MIL's surface. Then, it can copy your surface to its shared surface via a highly performant GPU routine.

It is also worth noting that D3DImage only supports multi-sample anti-aliasing (MSAA) when using a 9Ex device on Vista.

Keeping the above requirements in mind, let's now look at the native (unmanaged) code that is used to create our very simple D3D scene.

Dynamically Creating a Simple D3D Scene

You may have noticed that our managed application calls into three routines within our unmanaged library: InitializeScene(), RenderScene(), and ReleaseScene(). Starting with the latter two, the RenderScene() method simply contains the D3D code to render the scene, and the ReleaseScene() method simply releases the interfaces obtained within an earlier call to InitializeScene(). We won't look at these latter functions any further, since they contain nothing more than pure D3D code.

Only the InitializeScene() method contains anything of real interest to our usage of D3DImage, since that is where we create the scene. It is important that we dynamically create a device and surface that is appropriate for the application's runtime environment. More specifically, we need code that creates a device and surface that meet the requirements outlined in the previous section.

Creating the Device

One of our requirements is that when running on Vista, we should create a D3D device that is capable of creating a shared surface. This can only be done using a D3D 9Ex device. So, the first order of business is to determine if the 9Ex functions are available. If so, we should initialize D3D using the Ex functions; otherwise, we can initialize D3D using the non-Ex functions. This is done in the InitializeScene() method, with the following lines:

C++
// Vista requires the D3D "Ex" functions for optimal performance.
// The Ex functions are only supported with WDDM drivers, so they 
// will not be available on XP. As such, we must use the D3D9Ex 
// functions on Vista and the D3D9 functions on XP.
 
// Rather than query the OS version, we can simply check for the
// 9Ex functions, since this is ultimately what we care about.
HMODULE hD3D9 = LoadLibrary(TEXT("d3d9.dll"));
g_pfnCreate9Ex = (DIRECT3DCREATE9EX)GetProcAddress(hD3D9, 
    "Direct3DCreate9Ex");
g_is9Ex = (g_pfnCreate9Ex != NULL);
FreeLibrary(hD3D9);
 
if (g_is9Ex)
{
    InitializeD3DEx(hWnd, d3dpp);
}
else
{
    InitializeD3D(hWnd, d3dpp);
}

For simplicity, we have created two separate functions for initializing the D3D library and creating a device: InitializeD3D() and InitializeD3DEx(). These functions do essentially the same thing in that they obtain the available interfaces for the D3D library. InitializeD3D() does this by creating a D3D 9 object directly and then using that to create a D3D 9 device. The InitializeD3DEx() routine instead creates a D3D 9Ex object and uses that to create a D3D 9Ex device.

It is worth noting that the function to create the 9Ex device is not even available within the D3D9 library on Windows XP. For this reason, the unmanaged code within our sample application does not contain a direct call to Direct3DCreate9Ex(). Instead, it determines availability, and then accesses this function by loading the D3D9 library and looking up the function by name via GetProcAddress(). This allows our library to run equally well on Vista and XP.

It should also be noted that in addition to obtaining the 9Ex interfaces, the InitializeD3DEx() routine also obtains the standard D3D object and device interfaces. This is done only to simplify the library by allowing the standard interfaces to be used throughout the library (during initialization and rendering). Otherwise, we would constantly need to check the g_is9Ex flag and then use one interface or the other.

What's up with the HWND?

Getting back to the InitializeScene() routine... You may have noticed that we registered a window class and created an instance of it prior to initializing D3D. This allows us to supply a required Win32 window handle (or HWND) when creating our device. Although we create this window, we never actually show it, nor is it used by D3D in our scenario.

For completeness, it should be noted that there is a very small performance cost associated with this HWND. A few cycles are required to create it, and then it consumes a small amount of memory. Another option would be to use the handle of our WPF application's window to create the D3D device. The window handle could easily be supplied to the unmanaged library via an IntPtr parameter to InitializeScene(). In WPF, you can obtain the HWND for a Window instance using the following call (where this represents the Window or a Visual within it):

C#
IntPtr hwnd = (PresentationSource.FromVisual(this) as HwndSource).Handle;

Of course, this approach requires that we wait for the HwndSource to be created prior to initializing the D3D scene. This means we cannot initialize the scene within the Window's constructor, as the PresentationSource will still be null at that point. For the sample included with this article, it just felt cleaner and simpler to create a private, non-visible window.

Creating the Render Target Surface

Once we have the appropriate device, we are ready to create a render surface. Recall that our requirement for this surface is that it be lockable on XP, yet we prefer a non-lockable surface on Vista. Fortunately, the g_is9Ex flag now essentially tells us whether we are running on Vista or XP (again, the 9Ex functions are only available on Vista with WDDM drivers), so we can use this flag in our CreateRenderTarget() call, as shown here:

C++
// create and set the render target surface
// it should be lockable on XP and nonlockable on Vista
if (FAILED(g_pd3dDevice->CreateRenderTarget(WIDTH, HEIGHT, 
    D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, 
    !g_is9Ex, // lockable
    &g_pd3dSurface, NULL)))
{
    return NULL;
}
g_pd3dDevice->SetRenderTarget(0, g_pd3dSurface);

Note that in this sample, we are creating a 32-bit ARGB surface. On XP, this will require SP3 (or SP2 with the aforementioned layered windows patch).

That pretty much does it for our initialization of the D3D surface. We now have a pointer to an interface for the D3D surface that can be returned from our InitializeScene() function to the managed app via an IntPtr. The managed app will use this pointer to update the back buffer of the D3DImage element.

The remainder of the InitializeScene() routine performs the necessary operations for setting up the D3D scene (culling, lighting, vertices, etc.). Again, for more information on these types of D3D operations, refer to the DirectX SDK.

Additional D3D Considerations

Below are several additional things to keep in mind when implementing a D3D interop solution.

Present Not Thy Buffer

As mentioned already, the HWND that is supplied when creating the D3D device is not really used in a D3DImage- based scenario. In a pure D3D application, that window would typically be used for two purposes:

  1. monitoring focus changes, and
  2. determining where to render the scene on the display.

For the latter purpose, the HWND only comes into play when the back buffer is transferred to the front buffer. In D3D, this is called "presenting" the scene. It is done by executing the appropriately named Present() (or PresentEx()) method on the D3D device.

One very interesting thing to note is that for a D3DImage scenario, we never actually present our back buffer. Instead, we let WPF copy the contents of our custom surface to its own render target. Then, WPF will present its own back buffer (which now contains the composed scenes).

WPF Maintains a Reference to the D3D Surface

Recall that a D3D surface is supplied to WPF via a call to SetBackBuffer() on a D3DImage instance. When the back buffer is established, WPF adds a reference to that surface interface. It will hold onto that reference until the device is either lost or until another call is made to SetBackBuffer(). We can (and should) instruct WPF to release its reference when we no longer need the surface for rendering. To do this, simply call SetBackBuffer() with IntPtr.Zero, as shown here:

C#
_di.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero);

Pool Considerations for the D3D 9Ex Device

The fact that a 9Ex device is required for a performant solution on Vista actually has a few other implications for our D3D code. The biggest one is that we cannot use managed D3D resources with a 9Ex device. Note that this caveat does not apply to the sample included with this article, since we are not creating any pool resources. But if we were, this restriction basically means when creating pool resources, we would need to use our g_is9Ex flag to determine when to specify D3DPOOL_DEFAULT instead of D3DPOOL_MANAGED (or D3DXMESH_MANAGED, etc.). A typical call might look like this:

C++
D3DXLoadMeshFromXInMemory(meshSrcData, meshSrcDataSize,
    g_is9Ex ? D3DPOOL_DEFAULT : D3DXMESH_MANAGED,
    g_pd3dDevice, NULL, NULL, NULL, NULL, ppMesh); 

A Note about Supporting Multiple Video Adapters

In our example, we are creating a device for the default adapter by passing D3DADAPTER_DEFAULT to the CreateDevice() or CreateDeviceEx() method. If a surface is created on one adapter and displayed on another adapter, the performance will suffer. This is especially true on XP. If there are multiple adapters, we would instead want to create a device for a specific adapter so that we can create a surface for that adapter.

A truly dynamic approach would be to recreate the surface when the window is moved to a monitor that is driven by a different adapter. This would require additional logic within both the WPF application (to re-initialize the scene) and the unmanaged library (to ensure that the correct adapter is used to create the surface). One approach might be to have the WPF application supply the screen coordinates of the window in its InitializeScene() call. These coordinates could be used to determine the appropriate adapter on which to create the D3D device. In the interest of simplicity, the code sample supplied with this article does not demonstrate this approach.

Tips for Running the D3DImage Sample

The remainder of this article is dedicated to helping you build and/or run the provided sample. Please read through the following tips prior to contacting me with specific questions. I will, of course, do my best to address any outstanding questions that pertain directly to this article. If you encounter bugs or limitations in D3DImage, please take those issues up with Microsoft in the WPF Forum or submit your feedback via the WPF Connect site. For D3D questions, your best bet is the D3D Forum.

I have included a compiled version of the D3DImage sample as part of this article. If you simply want to run this application, then follow the recommendations in this section. However, if you intend to build the sample yourself, please skip to the section entitled "Tips for Building the D3DImage Sample".

Install .NET 3.5 SP1

Make sure that you have installed Service Pack 1 of the Microsoft .NET Framework, version 3.5.

Install the DirectX Redistributable Libraries

You will also need the latest DirectX Redistributable Libraries. Note that the prebuilt sample that I have included consists of 32-bit binaries that were built against the June 2008 release of the DirectX SDK. They are not binary compatible with earlier versions. If you need to run against the March 2008 release, for example, you will need to download and recompile the sample project yourself.

Tips for Building the D3DImage Sample

I have also included the source code for the D3DImage sample as part of this article. Below are some tips for compiling and running the sample:

Install Visual Studio 2008 SP1 - Or - Install .NET 3.5 SP1

This sample requires the new D3DImage feature within SP1 of .NET 3.5. If you are using Visual Studio 2008 for your development environment, the best way to get .NET 3.5 is to install Service Pack 1 for Visual Studio 2008. The other option is to directly install Service Pack 1 of the Microsoft .NET Framework, version 3.5.

Install the DirectX SDK

Make sure that you have installed the latest DirectX SDK.

Configure Visual Studio

If Visual Studio is not configured properly, you may see one of the following messages:

fatal error C1083: Cannot open include file: 'd3dx9.h': No such file or directory
fatal error LNK1104: Cannot open file 'd3dxof.lib'

To resolve this, make sure the paths to the DirectX header and library files are correctly set within your Visual Studio settings. To configure these paths, bring up the "Options..." dialog from the Tools menu. Then, expand "Projects and Solutions" in the tree on the left, and select "VC++ Directories". Next, select "Include files" from the "Show directories for:" drop down list and add an entry pointing to the DirectX header files. The entry should be "$(DXSDK_DIR)include", as shown here:

Image 7

You will also need to select "Library files" from the "Show directories for:" drop down list and add an entry pointing to the DirectX lib files. The entry should be "$(DXSDK_DIR)lib\x86" (or "$(DXSDK_DIR) lib\x64" if you are targeting a 64-bit platform), as shown here:

Image 8

Enable Unmanaged Code Debugging

Note that the D3DImageSample project is already configured for managed code debugging since the target application is a .NET app. However, since the sample project is also comprised of unmanaged code, you may wish to configure Visual Studio for unmanaged code debugging. This will allow you to step directly from managed code into unmanaged code.

To enable unmanaged code debugging, you must first open the property pages for the Visual Studio project. There are several ways to do this, but one easy method is to select the project within Solution Explorer and then select "D3DImageSample Properties..." from the "Project" menu. When the property pages open, select the "Debug" page and then make sure the "Enable unmanaged code debugging" option is selected, as shown below:

Image 9

Save and rebuild the project, and you should be all set for mixed-mode debugging. You can verify this by setting a breakpoint in an unmanaged function in main.cpp. Then, run under the debugger to verify that your breakpoint is hit.

Credit Where Credit is Due

I want to sincerely thank Jordan Parker (from the WPF 3D Team) and Dwayne Need at Microsoft for helping me get started and setting me straight on so many things while I was learning the new D3DImage feature!

Go Ye Therefore and Create Coolness!

Now, it's up to all the serious DirectX developers to create some real fire and ice! I certainly look forward to seeing all the coolness and hotness that folks are able to produce in WPF using the new D3DImage feature.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Dr. WPF is a WPF Disciple! Check out the doctor's blog and bio for more information.

Comments and Discussions

 
GeneralGood write Pin
Thomas Pace27-Nov-21 0:19
Thomas Pace27-Nov-21 0:19 
QuestionI've updated the code to work on Windows 10 and Windows SDK, I'd like to repost on github, are you ok with this? Pin
Member 1395160720-Aug-18 5:40
Member 1395160720-Aug-18 5:40 
Questioncompiled, running but no triangle Pin
Member 1207691124-Oct-15 16:09
Member 1207691124-Oct-15 16:09 
AnswerRe: compiled, running but no triangle Pin
bing20082-Sep-21 23:52
bing20082-Sep-21 23:52 
Generalhi Pin
Member 1194317228-Aug-15 7:29
Member 1194317228-Aug-15 7:29 
GeneralMy vote of 5 Pin
Shmuel Zang13-Nov-12 11:38
Shmuel Zang13-Nov-12 11:38 
QuestionExcellent! one question! Pin
ribben198921-Jul-12 16:08
ribben198921-Jul-12 16:08 
QuestionThanks Pin
zhujinlong1984091316-Aug-11 21:05
zhujinlong1984091316-Aug-11 21:05 
General3D CAD imports Pin
DotNET7415-Jun-11 19:14
DotNET7415-Jun-11 19:14 
GeneralCrash On Windows7 / Visual 2010 Pin
Mike2411-Apr-11 15:40
Mike2411-Apr-11 15:40 
GeneralRe: Crash On Windows7 / Visual 2010 Pin
Halloko4-May-11 15:19
Halloko4-May-11 15:19 
GeneralRe: Crash On Windows7 / Visual 2010 Pin
mjhamre1-Jun-12 6:23
mjhamre1-Jun-12 6:23 
NewsSlimDXControl -- with D3D10 support Pin
mpgflx2-Feb-11 10:13
mpgflx2-Feb-11 10:13 
GeneralThanks Pin
zhujinlong198409138-Jul-10 19:57
zhujinlong198409138-Jul-10 19:57 
Generalthanks Pin
zhujinlong1984091327-Aug-09 1:40
zhujinlong1984091327-Aug-09 1:40 
GeneralDont use the OnRendering event Pin
laurent_laurent28-Mar-09 19:26
laurent_laurent28-Mar-09 19:26 
QuestionSweet! But what about DirectX 10? Pin
Member 25905015-Feb-09 6:57
Member 25905015-Feb-09 6:57 
GeneralWorks nicely on Vista; cannot get the rotating triangle to show on XP SP3 Pin
Member 369787426-Jan-09 14:54
Member 369787426-Jan-09 14:54 
GeneralRe: Works nicely on Vista; cannot get the rotating triangle to show on XP SP3 Pin
dudiz24-May-11 8:14
dudiz24-May-11 8:14 
GeneralRe: Works nicely on Vista; cannot get the rotating triangle to show on XP SP3 Pin
Member 369787425-May-11 7:05
Member 369787425-May-11 7:05 
QuestionDepth Buffer? Pin
bchan15-Dec-08 17:42
bchan15-Dec-08 17:42 
GeneralWeird Issue Pin
DrisWorld29-Nov-08 10:17
DrisWorld29-Nov-08 10:17 
GeneralOdd Issue Pin
pjcast23-Nov-08 16:52
pjcast23-Nov-08 16:52 
GeneralThanks! Pin
arahton18-Nov-08 3:54
arahton18-Nov-08 3:54 
QuestionHosting DirectDraw surface [modified] Pin
BassFreak21-Oct-08 8:20
BassFreak21-Oct-08 8:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.