Notes On Software

DirectXTest Application

Initializing DirectX 9 Drawing in WPF

| 1 Comment

In this article I’ll construct a very simple demo appication that hosts a D3DImage control whose buffer is directly manipulated using DirectX. I won’t be able to describe all of the code, but will try to hit the important points. Please download the code to see the full working application.

Download the code here: DirectX 9 Sample Code

Here’s a quick preview of what it will look like (click the picture to see it bigger):

DirectXTest Application

First credit where credit is due: The first article I read on integrating DirectX with WPF was Dr. Wpf’s D3DImage article on CodeProject. I ported some of the C++ code in this application from his code.

The goal of this project is to demonstrate:

  • How to set up a Visual Studio C++/Cli project to use the DirectX 9 libraries
  • How to initialize a drawing surface using DirectX 9
  • How to integrate the DirectX drawing surface into a C# WPF application

Solution Overview:

Solution Explorer

DIrectXTest Solution Explorer


The Visual Studio solution consists of a managed C++ Library (DirectX9Library) and a C# WPF Application (DirectX9LibraryClient).

The DirectX9Library interacts with native DirectX libraries and provides the code necessary to initialize a DirectX surface.

The DirectX9LibraryClient provides a simple user interface with a D3DImage control whose back buffer is generated using DirectX.





Setting up a Visual Studio C++/Cli project to use the DirectX 9 libraries:

The June 2010 DirectX SDK typically installs to “C:\Program Files\Microsoft DirectX SDK (June 2010)\”. If you installed the DirectX SDK after you installed Visual Studio, it will add a macro $(DXSDK_DIR) to your C++ environment. If not, substitute the path to your SDK install directory for $(DXSDK_DIR) below.

In order to utilize the DirectX libraries from a managed C++ project, right-click on the Project in Solution Explorer and choose Properties. Towards the top of the Property Pages, change the Configuration setting to “All Configurations” to ensure your project will build in Debug and Release modes.

Under Configuration Properties, select C/C++ and then General. In Additional Include Directories, add $(DXSDK_DIR)\include.

C/C++ General Properties


Now select Linker and then General. In Additional Library Directories, add $(DXSDK_DIR)\lib\x86.

Linker General Settings


Finally, under Linker and Input, set Additional Dependencies to: d3dx9.lib;d3d9.lib

Linker Input Settings

Initializing a Drawing Surface using DirectX 9:

The WPF application interacts with the DirectX surface via the DirectXManager class in the DirectX9Library project. The Initialize function calls CreateD3dObjects, where the main DirectX objects are created and the DirectX capabilities of the system are determined.

These are declared in the DirectXManager header file:

IDirect3D9 *_direct3DObject;
IDirect3D9Ex *_direct3DExObject;

Here is the CreateD3dObjects function in its entirety:

HRESULT DirectXManager::CreateD3dObjects(void)
{
    HRESULT hr = S_OK;
    HMODULE hD3D = NULL;

    // first load the d3d library
    hD3D = LoadLibrary(TEXT("d3d9.dll"));

    if (hD3D)
    {
        // see if the function to create a d3dEx object is there
        DIRECT3DCREATE9EXFUNCTION pfnCreate9Ex = (DIRECT3DCREATE9EXFUNCTION)GetProcAddress(hD3D, "Direct3DCreate9Ex");

        if (pfnCreate9Ex)
        {
            _supportsD3dEx = true;
            pin_ptr<IDirect3D9Ex*> pinnedD3dEx = &_direct3DExObject;
            // the function exists, so create a d3dEx object
            hr = (*pfnCreate9Ex)(D3D_SDK_VERSION, pinnedD3dEx);
            if (hr == S_OK)
            {
                pin_ptr<IDirect3D9*> pinnedD3d = &_direct3DObject;
                hr = _direct3DExObject->QueryInterface(__uuidof(IDirect3D9), reinterpret_cast<void **>(pinnedD3d));
            }
        }
        else
        {
            _supportsD3dEx = false;
            // the function was not found, so just create a d3d object
            _direct3DObject = Direct3DCreate9(D3D_SDK_VERSION);
           if (!_direct3DObject)
           {
                hr = E_FAIL;
           }
        }

        D3DCAPS9 caps;

        hr = _direct3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
        if ((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == D3DDEVCAPS_HWTRANSFORMANDLIGHT)
            _supportsHardwareVertexProcessing = true;
        else
            _supportsHardwareVertexProcessing = false;

        FreeLibrary(hD3D);
    }
    else
        hr = E_FAIL;

    return hr;
}

Because this code is utilized in a managed library, pin_ptr types are used to ensure that the garbage collector does not move things around while the DirectX objects are being initialized.

If the host operating system is running Windows Vista or later, the so called “Ex” functions and interfaces should be used for the best performance. These functions are supported by WDDM drivers, and are not available on Windows XP.

In order to determine whether the extended functionality of the IDirect3D9Ex interface can be used, the code has to determine whether the DirectX version on the system supports it. This is done by loading the d3d9 library and trying to get the address of the Direct3DCreate9Ex function. If the address is not found, then an object supporting the IDirect3D9 interface is created, otherwise an object that supports IDirect3D9Ex is created.

Once the d3dObjects are set up, the code checks to see if the graphics adapter supports hardware acceleration by checking for the D3DDEVCAPS_HWTRANSFORMANDLIGHT capability. The result of this inquiry is stored in the _supportsHardwareVertexProcessing variable for use when creating the surface later on. Finally, the d3d9 library is unloaded.

Once the DirectX objects are initialized, the WPF application needs to get an IntPtr to a surface that DirectX is going to draw on. This is accomplished in the DirectXManager GetImageBackBuffer function. The first step is to get a device object initialized. We’ll see where this is called from in a minute, but take a quick look at the GetDevice function:

bool DirectXManager::GetDevice(D3DPRESENT_PARAMETERS presentParameters)
{
    HRESULT hr;
    bool result = false;

    // determine what type of vertex processing to use based on the device capabilities
    DWORD vertexProcessing;

    if (_supportsHardwareVertexProcessing)
        vertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
    else
        vertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;;

    if (_direct3DExObject)
    {
        pin_ptr<IDirect3DDevice9Ex*> pinnedDeviceEx = &_direct3DDeviceEx;

        // create the D3D device using the Ex function
        hr = _direct3DExObject->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _windowHandle,
			vertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
			&presentParameters, NULL, pinnedDeviceEx);

        if (hr == S_OK)
        {
            pin_ptr<IDirect3DDevice9*> pinnedDevice = &_direct3DDevice;

            // obtain the standard D3D device interface
            hr = _direct3DDeviceEx->QueryInterface(__uuidof(IDirect3DDevice9), reinterpret_cast<void **>(pinnedDevice));
        }
    }
    else
    {
        pin_ptr<IDirect3DDevice9*> pinnedDevice = &_direct3DDevice;

        // create the D3D device
        hr = _direct3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _windowHandle,
            vertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
            &presentParameters, pinnedDevice);
    }

    if (hr == S_OK)
        result = true;

    return result;
}

If the extended interfaces were found during initialization, a device that supports the IDirect3DDevice9Ex interface is created; otherwise a device that supports the IDirect3DDevice9 interface is created. Note that pin_ptr is used to prevent garbage collection from moving things around and that the hardware acceleration support and a window handle are used to create the device. The hardware acceleration support was determined during initialization and the window handle comes from the main window of the WPF application.

The last major function in the DirectXLibrary returns an IntPtr pointing to the surface that the WPF D3DImage interacts with. Here’s the code for the DirectXManager GetImageBackBuffer function:

IntPtr DirectXManager::GetImageBackBuffer(unsigned int height, unsigned int width, IntPtr windowHandle)
{
    if (!_isInitialized)
        throw gcnew InvalidOperationException("Cannot call GetImageBackBuffer without initializing first");

    _windowHandle = (HWND)windowHandle.ToPointer();
    IntPtr backBuffer = IntPtr::Zero;

    _width = width;
    _height = height;

    // present parameters to create the D3DDevice
    D3DPRESENT_PARAMETERS presentParameters;

    ZeroMemory(&presentParameters, sizeof(presentParameters));

    presentParameters.Windowed = TRUE;
    presentParameters.BackBufferHeight = 1;
    presentParameters.BackBufferWidth = 1;
    presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
    presentParameters.BackBufferFormat = D3DFMT_UNKNOWN;

    // 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.

    bool deviceResult = GetDevice(presentParameters);

    if (deviceResult)
    {
        HRESULT hr;
        pin_ptr<IDirect3DSurface9*> pinnedSurface = &_direct3DSurface;

        // create and set the render target surface
        // it should be lockable on XP and nonlockable on Vista
        hr = _direct3DDevice->CreateRenderTarget(width, height,
            D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
            !_supportsD3dEx, // lockable
            pinnedSurface, NULL);

        if (hr == S_OK)
        {
            // set the device to render to the surface
            hr = _direct3DDevice->SetRenderTarget(0, _direct3DSurface);

            if (hr == S_OK)
            {
                // turn off culling
                hr = _direct3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

                if (hr == S_OK)
                {
                    // turn off D3D lighting
                    hr = _direct3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

                    if (hr == S_OK)
                        backBuffer = IntPtr(_direct3DSurface);
                 }
            }
        }
    }

    return backBuffer;
}

First the GetDevice function is called to create the appropriate device configuration, then a surface is created using the device CreateRenderTarget method. Note that for performance reasons the surface should be lockable on Windows XP and non-lockable on Vista or later. Once the surface is created, it is set as the device render target, configured, and a pointer to it is returned to the application.

Integrating the DirectX surface into the WPF application:

The DirectXLibraryClient application interacts with DirectX by using a D3DImage control on the main window. It is implemented using the MVVM pattern, so a reference to the D3DImage control and the window handle are passed in to the ViewModel constructor.

/// <summary>
/// constructor
/// </summary>
/// <param name="height">the height of the directX window</param>
/// <param name="width">the width of the directX window</param>
/// <param name="d3DImage">a reference to the d3dImage control use with DirectX</param>
/// <param name="windowHandle">the main window handle</param>
public ViewModel(uint height, uint width, D3DImage d3DImage, IntPtr windowHandle)
{
    // validate inputs
    if (height <= 0)
        throw new ArgumentOutOfRangeException("height", "height must be > 0");
    if (width <= 0)
        throw new ArgumentOutOfRangeException("width", "width must be > 0");
    if (d3DImage == null)
        throw new ArgumentNullException("d3DImage", "d3DImage cannot be null");
    if (windowHandle == IntPtr.Zero)
        throw new ArgumentException("windowHandle cannot be zero", "windowHandle");

    // set local references
    _height = height;
    _width = width;
    _d3DImage = d3DImage;
    _windowHandle = windowHandle;

    // set up directx
    InitializeDirectX();

    // respond to IsFrontBufferAvailableChanged event from d3DImage
    _d3DImage.IsFrontBufferAvailableChanged += IsFrontBufferAvailableChanged;
}

The main thing to note here is that the ViewModel saves a reference to the window handle and the D3DImage control and subscribes to the IsFrontBufferAvailableChanged event on the D3DImage control. This event is used to Initialize and Uninitialize DirectX and is detailed in the CodeProject article mentioned at the beginning of this page.

The InitializeDirectX method is shown here:

private void InitializeDirectX()
{
    _directXManager = new DirectXManager();

    _directXManager.Initialize();
    _backBuffer = _directXManager.GetImageBackBuffer(_width, _height, _windowHandle);

    if (_backBuffer != IntPtr.Zero)
    {
        _d3DImage.Lock();
        _d3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _backBuffer);
        _d3DImage.Unlock();
    }
}

As expected, the InitializeDirectX method creates an object of type DirectXManager from the DirectXLibrary, initializes it, and then gets a pointer to the surface DirectX will draw on. That surface is then passed to the D3DImage control via the SetBackBuffer method.

Now the DirectX surface is seamlessly integrated into the WPF application as the back buffer of the D3DImage. Whenever the surface image has changed, it can be rendered using the following snippet:

_d3DImage.Lock();
// make call to DirectXLibrary to redraw the DirectX surface
_d3DImage.AddDirtyRect(new Int32Rect(0, 0, (int)_width, (int)_height));
_d3DImage.Unlock();

In order to demonstrate the interaction between the DirectX surface and the application, I implemented a combobox from which the user can select a surface background color. When a new selection is made, the DirectX surface is cleared to the selected color. I’ll leave the details of the user interface and how everything is hooked for to you to explore in the code download. Enjoy!

One Comment

  1. Pingback: Wpf and DirectX using D3dImage | NotesOnSoftware

Leave a Reply

Required fields are marked *.

*