Step by Step: Migrating a Windows Mobile Application from GAPI to DirectX

 

Microsoft Corporation

December 2005

Applies to:
   Microsoft DirectX version 9.0
   Microsoft Visual Studio 2005
   Windows Mobile version 5.0 software for Pocket PCs
   Windows Mobile version 5.0 software for Smartphones

Summary: Learn about the new Windows Mobile 5.0 features of DirectX in this self-paced hands-on lab. You will explore the various drawing capabilities provided by DirectX such as drawing primitives, lighting and depth. You will also learn how to identify the DirectX capabilities of a particular device. For developers familiar with GAPI, each section contrasts the DirectX capabilities with the corresponding features of GAPI. (54 printed pages)

Download the printable version of this hands on lab MED308_MSDN_Migrating_WM_GAPI_App_DirectX.doc from the Microsoft Download Center.

Contents

Introduction
Lab 1: Creating the Direct3D Mobile Device
Lab 1 Summary
Lab 2: Perspective View and Lighting in Three Dimensions
Lab 2 Summary
Lab 3: Direct Draw Functionality-The 'CAPS' Bit
Lab 3 Summary
Lab 4: Direct Draw Game Design-Donuts
Appendix A: Terminating an Application Running on a Device or Emulator
Lab 4 Summary

To complete this exercise you will need:

  • Windows XP Professional.
    This lab requires Windows XP Professional.
  • Visual Studio 2005.
    This lab requires Visual Studio 2005 Standard, Professional, or Team System Editions. It will not work with any of the Express Editions. If you do not have the correct edition of Visual Studio 2005, find out how you can acquire it from the Visual Studio 2005 Developer Center.
  • ActiveSync 4.0.
    ActiveSync 4.0 allows for connectivity between a Windows Mobile–based device and your computer.
  • Windows Mobile 5.0 SDKs.
    The Windows Mobile 5.0 SDKs for Pocket PC and Smartphone enable development for Windows Mobile–based devices in Visual Studio 2005:
    • Download and install Windows Mobile 5.0 SDK for Pocket PC.

    • Download and install Windows Mobile 5.0 SDK for Smartphone.

      Important Install lab files. This lab requires pre-existing Visual Studio 2005 project, code, and data files. After installing the Windows Mobile 5.0 SDKs, you can locate the sample files here: C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples.

      Note If you used an emulator in a previous hands-on lab, you should do a hard reset of the emulator before starting this lab. On the emulator, click File, point to Reset, and then click Hard.

If you receive an error during deployment that indicates that the process or file is in use, this means that the program is still running on the emulator and must be exited before a new copy can be deployed and run. This error may appear anytime in the lab that you deploy the emulator. See the Appendix in this lab for instructions for exiting a running application.

Introduction

With the release of Windows Mobile 5.0, there are a number of new technologies designed to aid application development and create a wide variety of content for the entire spectrum of devices. In particular, there are two new technologies that have been added to the Windows Mobile 5.0 application suite—Direct3D Mobile and Direct Draw. Each of these graphics technologies have been designed to provide the application developer with a powerful, fully featured alternative to the current high performance graphics API—the Windows Mobile Game API (GAPI).

Although GAPI addresses a number of areas of development (graphics and input), the introduction of Microsoft DirectX technologies to Windows Mobile is only designed to provide alternatives to the graphics implementations. By providing Direct3D Mobile and Direct Draw to the Windows Mobile suite of development technologies, the application developer is provided a powerful set of tools that spans high-level scene generation to low-level hardware access.

This lab hopes to introduce the GAPI developer to the world of DirectX graphics development, with emphasis on the APIs and technology, in addition to the theory behind some of the more advanced concepts—in particular that of three-dimensional development.

This lab is designed to cover the native aspect of development with the DirectX graphics APIs.

It should be noted that while GAPI is a powerful API, as you transition your development away from a basic frame buffer design to that of a complex two- and three-dimensional API, there are a number of theories as well as practical implementation considerations to be aware of. I encourage you, should you decide to make this transition, to refer to the desktop and general graphics literature to help supplement your experience.

Lab 1: Creating the Direct3D Mobile Device

Direct3D Mobile is a powerful new graphics API design provided in Windows Mobile 5.0 to replace other graphics development technologies (in particular the Windows Mobile Game API or GAPI). Direct3D Mobile provides much more than basic frame buffer access as GAPI did; rather, Direct3D Mobile provides an entire graphics API to develop both two- and three-dimensional applications.

Direct3D Mobile and corresponding driver support will be included on all Windows Mobile devices, creating a powerful platform in which to target a wide variety of devices. These devices include a wide variety of resolutions and architectures—some even providing two- and three-dimensional graphics hardware that can be leveraged by the application without the need to write to the low-level hardware directly.

In this lab, we will explore how to create a Direct3D Mobile device that is extensible to target a number of different platforms. In particular, the code designed here today will be able to detect and function on both software drivers that are included with Windows Mobile hardware devices and the native Direct3D Mobile reference driver that can be used through the Windows Mobile Device Emulator. For beginning the labs and comparing performance changes, the device emulator is a good starting point.

While GAPI incorporates a rather rudimentary method of initializing and targeting the frame buffer of the target device, Direct3D Mobile requires the inclusion of a specific driver attached to the Direct3D Mobile device. The driver and corresponding runtime will be responsible for all graphics processing and eventual processing to the display system.

The objective of this lab will be to create a Direct3D Mobile device that is capable of utilizing the Direct3D Mobile reference driver that can be used in the Windows Mobile 5.0 Device Emulator. The code could also utilize the software drivers that are included with Windows Mobile hardware devices.

  • Include necessary files to create a Direct3D Mobile application
  • Construct a Direct3D Mobile object that will access the runtime for an emulated device

Exercise 1: Direct3D Mobile Device Creation

In this exercise, you will build a project that will include Direct3D Mobile and be capable of running on both a physical device as well as the Windows Mobile 5.0 Pocket PC Emulator.

We will be using the tutorials that exist as a part of the Windows Mobile 5.0 Pocket PC SDK (see the earlier Lab Requirements section).

To open the project

  1. After installing the Windows Mobile 2005 Pocket PC SDK, from your computer, open the Direct3D Mobile Pocket PC tutorial from the following location:

    C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Directx\d3dm\tutorials\tut01_createdevice\tut01_createdevice.sln

    This should open Visual Studio 2005 and the Direct3D Mobile Tutorial 1 project. By default, this will be designed to include the Pocket PC device emulator solution. As you move ahead, you may change the project and observe it with an actual device if you have one available.

  2. From the Visual Studio Solution Explorer pane, expand the Sources node and open the file createdevice.cpp.

  3. You can observe the entire project from this view, but for the purposes of the first tutorial, highlight and delete the source that is included in this source file.

To set up the application

  1. In the createdevice.cpp file, include the following header files and pragmas.

    #pragma comment(linker, "/nodefaultlib:oldnames.lib")
    
    #include <windows.h>
    #include <d3dm.h>
    #include <aygshell.h>
    
  2. Add the following global variables.

    //-----------------------------------------------------------------------------
    // Global variables
    //-----------------------------------------------------------------------------
    LPDIRECT3DMOBILE        g_pD3DM       = NULL;  // Used to create the   // D3DMDevice
    LPDIRECT3DMOBILEDEVICE  g_pd3dmDevice = NULL;  // Our rendering   // device
    HMODULE                 g_hRefDLL     = NULL;  // DLL handle for   // d3dmref.dll
    bool                    g_bUseRef     = false; // Flag denoting use   // of d3dmref
    
  3. For Windows Mobile 5.0 devices, dynamic rotation support must be detected by the application and addressed. To help aid in this detection, include the following function.

    BOOL IsScreenRotated()
    {
        DEVMODE devMode  = {0};
        devMode.dmSize   = sizeof(DEVMODE);
        devMode.dmFields = DM_DISPLAYORIENTATION;
    
        ChangeDisplaySettingsEx(NULL, &devMode, NULL, CDS_TEST, NULL);
    
        if (devMode.dmDisplayOrientation != DMDO_0)
        {
            MessageBox(
                NULL, 
                TEXT("This D3DM sample will not work on a rotated screen.\nThe application will now exit."),
                TEXT("Notice"),
                MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND
                );
    
            return TRUE;
        }
    
        return FALSE;
    }
    
  4. Initialize Direct3D Mobile.

    HRESULT InitD3DM( HWND hWnd )
    {
        // Create the D3DM object, which is needed to create the     // D3DMDevice.
        if( NULL == ( g_pD3DM = Direct3DMobileCreate( D3DM_SDK_VERSION ) ) )
            return E_FAIL;
    
        // Set up the structure used to create the D3DMDevice. Most     // parameters are zeroed out. We set Windowed to TRUE, since we want     // to do D3DM in a window, and then set the SwapEffect to     // "discard", which is the most efficient method of     // presenting the back buffer to the display. And we request a back     // buffer format that matches the current desktop display 
        // format.
        D3DMPRESENT_PARAMETERS d3dmpp; 
        memset( &d3dmpp, 0, sizeof(d3dmpp) );
        d3dmpp.Windowed = TRUE;
        d3dmpp.SwapEffect = D3DMSWAPEFFECT_DISCARD;
        d3dmpp.BackBufferFormat = D3DMFMT_UNKNOWN;
    
        // Create the Direct3D Mobile device.
        UINT uAdapter;
    
    
        if (g_bUseRef)
        {
            // Load the D3DM reference driver DLL
            g_hRefDLL = (HMODULE)LoadLibrary(TEXT("d3dmref.dll"));
            if (NULL == g_hRefDLL)
            {
                OutputDebugString(TEXT("Unable to load D3DM reference driver DLL.\n"));
                return E_FAIL;
            }
    
            // Get the reference driver's entry point
            void* pfnD3DMInit = GetProcAddress(g_hRefDLL, TEXT("D3DM_Initialize"));   
            if( NULL == pfnD3DMInit )
            {
                OutputDebugString(TEXT("Unable to retrieve D3DM reference driver entry point.\n"));
                return E_FAIL;
            }
    
            // Register the software device
            if( FAILED( g_pD3DM->RegisterSoftwareDevice(pfnD3DMInit) ) )
            {
                OutputDebugString(TEXT("Unable to register D3DM reference driver.\n"));
                return E_FAIL;
            }
    
            uAdapter = D3DMADAPTER_REGISTERED_DEVICE;
        }
        else
        {
            // Use the default system D3DM driver    
            uAdapter = D3DMADAPTER_DEFAULT;
        }
    
        if( FAILED( g_pD3DM->CreateDevice( uAdapter, 
                                        D3DMDEVTYPE_DEFAULT,
                                        hWnd, 0,
                                        &d3dmpp, &g_pd3dmDevice ) ) )
        {
            OutputDebugString(TEXT("Unable to create a D3DM device.\n"));
            return E_FAIL;
        }
    
        // Device state would normally be set here
    
        return S_OK;
    }
    
  5. Create the cleanup routine to address creations and allocations by adding the following.

    VOID Cleanup()
    {
        if( g_pd3dmDevice != NULL) 
            g_pd3dmDevice->Release();
    
        if( g_pD3DM != NULL)
        {
            if (g_hRefDLL)
            {
                g_pD3DM->RegisterSoftwareDevice(NULL);
                FreeLibrary(g_hRefDLL);
            }
    
            g_pD3DM->Release();
        }
    }
    
  6. Add the following application render loop. This will create a Blue background in the scene.

    VOID Render()
    {
        if( NULL == g_pd3dmDevice )
            return;
    
        // Clear the backbuffer to a blue color
        g_pd3dmDevice->Clear( 0, NULL, D3DMCLEAR_TARGET, D3DMCOLOR_XRGB(0,0,255),
     1.0f, 0 );
    
        // Begin the scene
        if( SUCCEEDED( g_pd3dmDevice->BeginScene() ) )
        {
            // Rendering of scene objects can happen here
    
            // End the scene
            g_pd3dmDevice->EndScene();
        }
    
        // Present the backbuffer contents to the display
        g_pd3dmDevice->Present( NULL, NULL, NULL, NULL );
    }
    
  7. Include the main message procedure for the application.

    LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
    {
        switch( msg )
        {
        case WM_LBUTTONUP: 
            PostMessage(hWnd, WM_CLOSE, 0, 0);
            break;
        case WM_KEYDOWN:
            if (VK_ESCAPE == wParam)
            {
                PostMessage(hWnd, WM_CLOSE, 0, 0);
            }
            break;
    
        case WM_CLOSE:
            Cleanup();
            break;
    
        case WM_DESTROY:
            PostQuitMessage( 0 );
            return 0;
    
        case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hDC = BeginPaint(hWnd, &ps);
                Render();
                EndPaint(hWnd, &ps);
                return 0;
            }
    
        case WM_SETTINGCHANGE:
            //make sure the screen is not rotated - if it is then abort         //the app
            if (IsScreenRotated())
            {
                PostMessage(hWnd, WM_CLOSE, 0, 0);
            }
            break;
        }
    
        return DefWindowProc( hWnd, msg, wParam, lParam );
    }
    
  8. Add the main Windows function.

    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPTSTR szCmd, INT )
    {
        // Parse command line to determine if user wants to use
        // the D3DM reference driver instead of the default system     // driver
        if (0 == lstrcmp(szCmd, TEXT("-ref")))
            g_bUseRef = true;
    
    
        //it doesn't support screen rotation (as of yet)
        if (IsScreenRotated())
        {
            return 0;
        }
    
        // Register the window class
        WNDCLASS wc = { 0L, MsgProc, 0L, 0L, 
                          hInst, NULL, NULL, NULL, NULL,
                          TEXT("D3DM Tutorial") };
        RegisterClass( &wc );
    
        int iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
        int iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    
        // Create the application's window
        HWND hWnd = CreateWindow(TEXT("D3DM Tutorial"), 
                                  TEXT("D3DM Tutorial 01: CreateDevice"), 
                                  WS_VISIBLE, 
                                  0, 0, iScreenWidth, iScreenHeight,
                                  NULL, NULL, wc.hInstance, NULL );
    
        SHFullScreen(hWnd, SHFS_HIDESIPBUTTON | SHFS_HIDETASKBAR);
    
        // Initialize Direct3D Mobile
        if( SUCCEEDED( InitD3DM( hWnd ) ) )
        { 
            // Show the window
            ShowWindow( hWnd, SW_SHOWNORMAL );
            UpdateWindow( hWnd );
    
            // Enter the message loop
            MSG msg; 
            while( GetMessage( &msg, NULL, 0, 0 ) )
            {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
            }
        }
    
        return 0;
    }
    

To build and run the project

  1. In Visual Studio 2005, click Build | Build tut01_createdevice option. This should build the project and issue no warning or errors. If warnings or errors occur, verify against the above code.

  2. On the Visual Studio 2005 toolbar, verify that Windows Mobile 5.0 Pocket PC Emulator is selected in the Target Device drop-down list box, as shown in the following figure.

    Figure 1. Target Device drop-down list box

  3. Run the application by clicking Debug | Start Debugging in Visual Studio 2005. This should first launch the Pocket PC device emulator and load the Direct3D Mobile reference driver. If this is the first time you have deployed to the Pocket PC device emulator, it may take a few minutes.

  4. You should find that the application will create a Direct3D Mobile device that occupies the entire screen and fills it will Blue (RGB 0,0,255) as shown in the following figure.

    Figure 2. A Direct3D Mobile device is created with a blue screen

  5. Stop the running project (Debug | Stop Debugging).

  6. Find the Render() loop in the source code. In particular, find the code that clears the background.

    // Clear the backbuffer to a blue color
        g_pd3dmDevice->Clear( 0, NULL, D3DMCLEAR_TARGET, D3DMCOLOR_XRGB(0,0,255),
     1.0f, 0 );
    
  7. Note that one of the key parameters is a D3DMCOLOR value, in this case an RGB based value (ranging from 0 to 255) that determines what color the framebuffer should be filled with when the clear command is called. Change the RGB value to Red (255,0,0), and then rebuild, deploy, and run the project.

    Note   The background color is now red. When applying this background color, Direct3D automatically selects whether to perform the action using hardware acceleration or, when no hardware acceleration is available, using software. This is a distinct improvement over GAPI which puts the burden on the application to detect and determine whether to use hardware or software to perform the action.

  8. Change the RGB background color to an arbitrary RGB value.

  9. Rebuild and deploy the project. Were the results as expected?

Exercise 2: Creating Triangles and Shading

In this exercise, you will modify the first example created to include a shaded triangle on the device. The triangle is the fundamental unit of three-dimensional graphic designs. While GAPI provides low level access to the framebuffer, the creation of triangles in Direct3D Mobile utilizes inherent constructs of the API. Triangles and other primitive types are native additions to the Direct3D Mobile technology and provide high-level graphics tools that will be new to the GAPI developer. These primitive types can be leveraged for both 2-D as well as 3-D scene generation.

To create a triangle

  1. Working from the functional project as outlined above, add the following global parameters near the top of the createdevice.cpp file, directly below the existing global variable declarations.

    LPDIRECT3DMOBILEVERTEXBUFFER g_pVB    = NULL; // Buffer to hold  // vertices
    
    // A structure for our custom vertex type
    struct CUSTOMVERTEX
    {
        FLOAT x, y, z, rhw; // The transformed position for the vertex
        DWORD color;        // The vertex color
    };
    
    // Our custom FVF, which describes our custom vertex structure
    #define D3DMFVF_CUSTOMVERTEX (D3DMFVF_XYZRHW_FLOAT | D3DMFVF_DIFFUSE)
    
  2. Now add a Vertex Buffer (VB) to hold the vertices that this example will use for its triangle creation.

    //-----------------------------------------------------------------------------
    // Name: InitVB()
    // Desc: Creates a vertex buffer and fills it with our vertices. The // vertex buffer is basically just a chuck of memory that holds // vertices. After creating it, we must Lock()/Unlock() it to // fill it. For indices, D3DM also uses index buffers. 
    //-----------------------------------------------------------------------------
    HRESULT InitVB()
    {
        // Initialize three vertices for rendering a triangle
        CUSTOMVERTEX vertices[] =
        {
            { 150.0f,  50.0f, 0.5f, 1.0f, 0x00000000 }, // x, y, z, rhw, color
            { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00 },
            {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff }
        };
    
        // Determine if the device can create vertex buffers in video     // memory by testing the device caps bits.
        D3DMCAPS caps;
        if( FAILED( g_pd3dmDevice->GetDeviceCaps(&caps) ) )
        {
            return E_FAIL;
        }
        D3DMPOOL pool;
        if (caps.SurfaceCaps & D3DMSURFCAPS_VIDVERTEXBUFFER)
        {
            pool = D3DMPOOL_VIDEOMEM;
        }
        else
        {
            pool = D3DMPOOL_SYSTEMMEM;
        }        
        // Create the vertex buffer. Here we are allocating enough     // memory (from the default pool) to hold all our 3 custom vertices.     // We also specify the FVF, so the vertex buffer knows what     // data it contains.
        if( FAILED( g_pd3dmDevice->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                                                      0, D3DMFVF_CUSTOMVERTEX,
                                                      pool, &g_pVB ) ) )
        {
            return E_FAIL;
        }
    
        // Now we fill the vertex buffer. To do this, we need to Lock()    // the VB to gain access to the vertices. This     // mechanism is required becuase vertex buffers may be in device     // memory.
        void* pVertices;
        if( FAILED( g_pVB->Lock( 0, sizeof(vertices), &pVertices, 0 ) ) )
            return E_FAIL;
        memcpy( pVertices, vertices, sizeof(vertices) );
        g_pVB->Unlock();
    
        return S_OK;
    }
    
  3. From the above, you should observe the methodology of populating the vertex buffer. Like with many graphics implementations (such as GAPI) there is a discrete concept of Lock and Unlock to flag the buffer as being written into. If this approach were not used, this could result in race conditions during memory modification and could ultimately lead to memory corruption.

  4. Having added a new variable type (g_pVB) to the file, we need to ensure that we are properly freeing its resources. In the Cleanup() function add the following.

    if( g_pVB != NULL )        
            g_pVB->Release();
    
  5. We now need to modify the Render loop so that it will not only include the background as it did in the first example, but also the triangle that we are creating. Modify the code in your render loop so that it looks like the following.

    VOID Render()
    {
        if( NULL == g_pd3dmDevice )
            return;
    
        // Clear the backbuffer to a blue color
        g_pd3dmDevice->Clear( 0, NULL, D3DMCLEAR_TARGET, D3DMCOLOR_XRGB(0,0,255), 
              1.0f, 0 );
    
        // Begin the scene
        if( SUCCEEDED( g_pd3dmDevice->BeginScene() ) )
        {
            // Draw the triangles in the vertex buffer. This is broken         // into a few steps. We are passing the vertices down a         // "stream", so first we need to specify the source of         // that stream, which is our vertex buffer. 
           // Then, we call DrawPrimitive() which does the actual         // rendering of our geometry (in this case, just one triangle).
            g_pd3dmDevice->SetStreamSource( 0, g_pVB, sizeof(CUSTOMVERTEX) );
            g_pd3dmDevice->DrawPrimitive( D3DMPT_TRIANGLELIST, 0, 1 );
    
            // End the scene
            g_pd3dmDevice->EndScene();
        }
    
        // Present the backbuffer contents to the display
        g_pd3dmDevice->Present( NULL, NULL, NULL, NULL );
    }
    
  6. From the above example, note the BeginScene() and EndScene() methods are used. This is designed to create and write a frame that will ultimately be rendered to the device (initiated by the call to the Present()). In migrating from the GAPI development model, this is analogous to the GXBeginDraw() and GXEndDraw() functions.

  7. We must now also modify the WinMain function so that it will create the Vertex Buffer and launch the new logic of the program.

    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPTSTR szCmd, INT )
    {
        // Parse command line to determine if user wants to use
        // the D3DM reference driver instead of the default system     // driver
        if (0 == lstrcmp(szCmd, TEXT("-ref")))
            g_bUseRef = true;
    
        //we don't support a rotated screen
        if (IsScreenRotated())
        {
            return 0;
        }
    
        // Register the window class
        WNDCLASS wc = { 0L, MsgProc, 0L, 0L, 
                          hInst, NULL, NULL, NULL, NULL,
                          TEXT("D3DM Tutorial") };
        RegisterClass( &wc );
    
        int iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
        int iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    
        // Create the application's window
        HWND hWnd = CreateWindow( TEXT("D3DM Tutorial"), 
                                  TEXT("D3DM Tutorial 02: Vertices"), 
                                  WS_VISIBLE, 
                                  0, 0, iScreenWidth, iScreenHeight,
                                  NULL, NULL, wc.hInstance, NULL );
    
        SHFullScreen(hWnd, SHFS_HIDESIPBUTTON | SHFS_HIDETASKBAR);
    
        // Initialize Direct3D Mobile
        if( SUCCEEDED( InitD3DM( hWnd ) ) )
        { 
            // Create the vertex buffer
            if( SUCCEEDED( InitVB() ) )
            {
                // Show the window
                ShowWindow( hWnd, SW_SHOWNORMAL );
                UpdateWindow( hWnd );
    
                // Enter the message loop
                MSG msg;
                memset( &msg, 0, sizeof(msg) );
                while( msg.message!=WM_QUIT )
                {
                    if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
                    {
                        TranslateMessage( &msg );
                        DispatchMessage( &msg );
                    }
                    else
                        Render();
                }
            }
        }
    
        return 0;
    }
    
  8. Once these steps have been completed, rebuild and run the (Debug | Start Debugging). Because this particular example illustrates a static image, the performance of the three-dimensional pipeline (in particular the rasterization component which will be the dominant factor) should not be a factor, as shown in the following figure.

    Figure 3. Adding a triangle

Exercise 3: Properties of Triangles

In this exercise, you will modify the fundamental aspects of a triangle in three-dimensional space to change not only its physical characteristics as presented on screen, but also the color associated with it.

To modify color

  1. Find the Vertex Buffer as created above in the InitVB() function. It should look like the following.

    CUSTOMVERTEX vertices[] =
        {
            { 150.0f,  50.0f, 0.5f, 1.0f, 0x00000000 }, // x, y, z, rhw, color
            { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00 },
            {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff }
        };
    
  2. As you can see in the above code, each element of the array has five values, however only four of those values are of interest at this point. The first three values are the X, Y and Z coordinates which represent the position of each vertex in 3-D space. .The last parameter represents color. The fourth parameter, RHW, is outside the scope of this lab. Each element in the above array represents a single vertex with color value, that when used together create the triangle you see on screen.

  3. First, the color parameter determines the color at each vertex point and will be used in the particular blending for this example. Modify the first vertex color parameter to be 0xffffffff.

  4. Rebuild the application and run.

  5. Note the result now of the first vertex and the impact on the entire triangle, as shown in the following figure.

    Figure 4. Modifying the color of one of the triangle's vertices

  6. Now, modify all colors to hex values of your choice.

  7. Rebuild the application and deploy.

    Note the results of the colors.

  8. Revert the colors to the original values as above.

  9. Modify the colors so that they are all the identical for each vertex (perhaps 0xff00ff00).

  10. Rebuild and deploy the project.

    Note the color behavior of the triangle when all of the colors at each vertex point are the same. Unlike the earlier models in which color blend is interpolated across the distance from each vertex point and its corresponding color, identical colors do not exhibit such behavior, as shown in the following figure.

    Figure 5. All vertices' colors are the same

  11. Revert the changes to as they were for the original project.

  12. Rebuild the project to ensure there are no errors.

To modify shape

  1. Again, locate the Vertex Buffer creation in the InitVB() function (which should look as the original).

    CUSTOMVERTEX vertices[] =
        {
            { 150.0f,  50.0f, 0.5f, 1.0f, 0x00000000 }, // x, y, z, rhw, color
            { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00 },
            {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff }
        };
    

    Note the three coordinate values that are defined as the first parameters of each vertex. In particular, note the third parameter for each vertex (0.5f) representing the Z value. In three-dimensional programming, the Z coordinate represents the depth, and since all values are equal in this example, the depth at each point causes the triangle to appear on a flat surface perpendicular to the user.

  2. For one of the vertices in the triangle above, modify the first two coordinates to values close to the overall range of the others. Try to predict the outcome of the resulting triangle.

  3. Rebuild and redeploy the project.

  4. Note the changes to the triangle, as shown in the following figure.

    Figure 6. Changing each of the third vertex's first two coordinates (X & Y) to 200.0f

To modify type

  1. For this portion of the lab we are going to view the primitive types associated with Direct3D Mobile development. Unlike GAPI which handles drawing by placing pixels into the framebuffer, Direct3D Mobile provides basic primitive drawing types. Direct3D Mobile is the only graphics API that includes a number of basic primitive types and representation methods for use in. Having performed the above exercise, you are already familiar with the Triangle primitive.

    Examine the vertex list from the above.

    CUSTOMVERTEX vertices[] =
        {
            { 150.0f,  50.0f, 0.5f, 1.0f, 0x00000000 }, // x, y, z, rhw, color
            { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00 },
            {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff }
        };
    
  2. Find the following code in the Render() loop.

      g_pd3dmDevice->SetStreamSource( 0, g_pVB, sizeof(CUSTOMVERTEX) );
      g_pd3dmDevice->DrawPrimitive( D3DMPT_TRIANGLELIST, 0, 1 );
    

    Note   The SetStreamSource function determines what vertices will be included for processing in the scene. In this case we are passing in the vertex buffer outlined as above. For the DrawPrimitive function, this will determine what we are drawing with those vertices. From the above example, you can find that we are drawing a TriangleList—creating one from the range.

  3. Modify the code such that we are using Point types rather than Triangles. Change the primitive type in the call above from D3DMPT_TRIANGLELIST to D3DMPT_POINTLIST as follows.

      g_pd3dmDevice->DrawPrimitive( D3DMPT_POINTLIST, 0, 1 );
    
  4. Rebuild and deploy the project.

  5. While it might take some hard looking, you should see a single point on the display in the color of the first vertex described above. Note that the red outline DOES NOT APPEAR on the device but is used here to help you locate the single point on the device display, as shown in the following figure.

    Figure 7. Changing the primitive type in the call

    Note   You might want to modify the background and vertex colors to make them easier to view—perhaps a Blue background and Red pixel values.

  6. You will note that while there are three vertex values included in the vertex buffer that is being passed for processing, only one is displayed on the screen.

  7. Modify the vertex range used by the DrawPrimitive function (from the remaining two values) from 0, 1 to 0, 3 as follows.

      g_pd3dmDevice->DrawPrimitive( D3DMPT_POINTLIST, 0, 3 );
    
  8. Rebuild and deploy the project.

    How many pixels do you now see on the screen? At this point, you should only see two, as shown in the following figure. The reason? The same as should be observed from the original triangle example above. The second of the three vertices is located off of screen space and not displayed.

    Figure 8. Two points are displayed

  9. Modify the second vertex so that its X and Y values are within the bounds of the display. For example, you might want to change these values to 200 and 200.

        { 200.0f, 200.0f, 0.5f, 1.0f, 0xff00ff00 },
    
  10. Rebuild and deploy the project.

    All three coordinates can be seen on screen, as shown in the following figure.

    Figure 9. All three coordinates on the screen

An important aspect of Direct3D Mobile development that differs from other graphics models is how scenes are generated and ultimately rendered to the display. As 3-D is based on camera view and perspective (and this applies to 2-D-like development as we see in the above example), it is possible to render primitives that are off screen. Determining how to keep pixels on screen and their use in creating more complex designs is important.

Lab 1 Summary

In this lab you performed the following exercises:

  • Created a Direct3D Mobile device
  • Deployed a D3DM application on a Windows Mobile device
  • Created a triangle with Direct3D Mobile
  • Modified color and shading parameters of vertices in a three-dimensional scene
  • Implemented other primitive types for coordinate generation

In this lab, you learned how to create a Direct3D Mobile device for Windows Mobile 2005 that is capable of running both in the device emulator and on an actual device. For the purposes of the above lab, both of the above examples are included in the Windows Mobile Pocket PC SDK for future reference.

Lab 2: Perspective View and Lighting in Three Dimensions

The objective of this lab is to understand the fundamentals of three-dimensional perspectives when displaying objects, as well as the basics of lighting in Direct3D Mobile. In this lab, you will perform the following exercises:

  • Understand and modify perspective on a given object
  • Understand and modify lighting parameters in a Direct3D Mobile scene

Exercise 1: Perspective View in Three-Dimensional Space

In this exercise, you will build a project that includes perspective matrix transformations, and you will come to understand the implications of this design.

Unlike in a GAPI-based application, the creation of three-dimensional objects and their presentation to the viewer are native aspects of the API. Understanding how to modify these will allow you to create three dimensional scenes and effects based on the core of the API.

To open the perspective matrix project

  1. From your computer, open the tutorial project at:

    C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Directx\d3dm\tutorials\Tut03_matrices\tut03_matrices.sln

  2. Build and deploy the project to view the original state of the project (Debug | Start Debugging).

    You will note that in this application there is some form of animation—in this case, by the rotation of a single triangle in three-dimensional space based on the perspective view from the camera, as shown in the following figure. Depending on whether you have a physical device available, now run the project on another platform. Notice the performance differences. While this application is very basic and does not include complex scene elements, you should be able to notice performance differences between the emulator and other platforms. The emulator Direct3D Mobile driver is a non-optimized software solution designed for evaluation, not as a production shipping driver. Depending on the speed of the PC and load of the system, your development on this might be suitable for complex scenes, but ultimately the device will be the best choice to determine end user experience.

    Figure 10. A rotating triangle

  3. Stop the running application (Debug | Stop Debugging).

  4. Open the matrices.cpp source code file from the Solution Explorer pane.

  5. Locate in the source file the SetupMatrices() function. This should include the following.

    VOID SetupMatrices()
    {
        // For our world matrix, we will just rotate the object about     // the y-axis.
        D3DMXMATRIX matWorld;
    
        // Set up the rotation matrix to generate 1 full rotation (2*PI     // radians) every 1000 ms. To avoid the loss of precision     // inherent in very high floating point numbers, the system     // time is modulated by the rotation period before     // conversion to a radian angle.
        UINT  iTime  = GetTickCount() % 1000;
        float fAngle = iTime * (2.0f * D3DMX_PI) / 1000.0f;
        D3DMXMatrixRotationY( &matWorld, fAngle );
        g_pd3dmDevice->SetTransform( D3DMTS_WORLD,  (D3DMMATRIX*)&matWorld,
     D3DMFMT_D3DMVALUE_FLOAT );
    
        // Set up our view matrix. A view matrix can be defined given an     // eye point, a point to lookat, and a direction for which     // way is up. Here, we set the eye five units back along the     // z-axis and up three units, look at the origin, and define     // "up" to be in the y-direction.
        D3DMXVECTOR3 vEyePt( 0.0f, 3.0f, -5.0f );
        D3DMXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
        D3DMXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
        D3DMXMATRIX matView;
        D3DMXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
        g_pd3dmDevice->SetTransform( D3DMTS_VIEW, (D3DMMATRIX*)&matView, 
                D3DMFMT_D3DMVALUE_FLOAT );
    
        // For the projection matrix, we set up a perspective transform     // (which transforms geometry from 3D view space to 2D     // viewport space, with a perspective divide making objects smaller     // in the distance). To build a perpsective transform, we     // need the field of view (1/4 pi is common), the aspect     // ratio, and the near and far clipping planes (which define     // at what distances geometry should be no longer be     // rendered).
        D3DMXMATRIX matProj;
        D3DMXMatrixPerspectiveFovLH( &matProj, D3DMX_PI/4.0f, 1.0f, 1.0f, 100.0f );
        g_pd3dmDevice->SetTransform( D3DMTS_PROJECTION,  (D3DMMATRIX*)&matProj, 
                D3DMFMT_D3DMVALUE_FLOAT );
    }
    

To use the eye point matrix

  1. Locate the following matrix definition.

        D3DMXVECTOR3 vEyePt( 0.0f, 3.0f, -5.0f );
    
  2. The above code determines the viewing position to the triangle in the scene in three dimensional coordinate space (X, Y, Z). Modify the Z value (the third component) to a greater negative value than what is presented (as an example -25.0f).

  3. Rebuild and deploy the project.

  4. Note difference in the view of the triangle and the behavior, as shown in the following figure. By moving the viewing position farther back, the triangle appears further in the distance. In addition, note the performance difference of the spinning triangle. This will be most prevalent on the device emulator, and perhaps not discernable on a physical device. If you have a Windows Mobile 5.0 device available, try running the program on both the emulator and the actual device to determine performance differences.

    Figure 11. The triangle appears further in the distance, and rotation is performed better

  5. Now, modify the value in the opposite direction—perhaps to -2.0f.

  6. Rebuild and deploy the project.

  7. Note the change not only in view of the triangle, but also the performance, as shown in the following figure.

    Figure 12. The triangle appears closer and rotates more slowly

  8. Return the values to the original coordinates as defined above.

To use the look point matrix

  1. Locate the look-at matrix, as defined as the following.

        D3DMXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
    
  2. The above coordinate values (X, Y, Z) start with the view matrix above and "looks" in the direction defined above, in this case the origin. Modify any one of the parameters a slight degree (Ex: 2 or 3).

  3. Rebuild and deploy the project.

  4. Note the difference in view from the original project design. Based on the offset of where the camera is looking and the fixed position of the triangle, it will appear to the end user as if it is in a different position, as shown in the following figure.

    Figure 13. The X coordinate of the Look-At Point has been changed to 2.0f

  5. If desired, return the values to their original coordinates at the origin and modify several of the coordinates.

  6. Rebuild and deploy the project.

    Does the behavior match with what you expected?

You should now have an understanding of eye and look matrices for objects in three-dimensional space.

Exercise 2: Lighting

In this exercise, you will exercise and modify the basic lighting elements in a three-dimensional scene. Unlike GAPI, Direct3D Mobile includes native support for lighting three-dimensional objects through a variety of methods. Rather than creating custom light tables or coloring for specific polygons as would be undertaken in GAPI, Direct3D Mobile can be used to light scenes as incorporated in the API.

To open the lighting project

  1. From the system, open the tutorial project at:

    C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Directx\d3dm\tutorials\Tut04_lights\tut04_lights.sln

  2. Build and deploy the project to view the original state of the project (Debug | Start Debugging).

  3. For this project, if possible, build and run this application on both the device emulator, as well as on a production device with a Direct3D Mobile driver. As this scene includes both animation of primitives and lighting calculations, the difference in performance should increase considerably.

    Figure 14. Rotating, lighted cylinder

  4. Close the application (Debug | Stop Debugging).

  5. Open the lights.cpp source code file from the Solution Explorer pane.

To add lighting parameters

  1. From the above project source file, find the SetupLights() function. It should consist of the following.

    VOID SetupLights()
    {
        // Set up a material. The material here just has the diffuse and     // ambient colors set to yellow. Note that only one material     // can be used at a time.
        D3DMMATERIAL mtrl;
        memset( &mtrl, 0, sizeof(D3DMMATERIAL) );
        float f = 1.0f;
        mtrl.Diffuse.r = mtrl.Ambient.r = *(D3DMVALUE*)&f; // 1.0f;
        mtrl.Diffuse.g = mtrl.Ambient.g = *(D3DMVALUE*)&f; // 1.0f;
        mtrl.Diffuse.b = mtrl.Ambient.b = 0;               // 0.0f
        mtrl.Diffuse.a = mtrl.Ambient.a = *(D3DMVALUE*)&f; // 1.0f;
        g_pd3dmDevice->SetMaterial( &mtrl, D3DMFMT_D3DMVALUE_FLOAT );
    
        // Set up a white, directional light, with an oscillating     // direction. Note that many lights may be active at a time (but     // each one slows down the rendering of our scene). However,     // here we are just using one. Also, we need to set the     // D3DMRS_LIGHTING renderstate to enable lighting
        D3DMXVECTOR3 vecDir;
        D3DMLIGHT light;
        memset( &light, 0, sizeof(D3DMLIGHT) );
        light.Type       = D3DMLIGHT_DIRECTIONAL;
        light.Diffuse.r  = *(D3DMVALUE*)&f; // 1.0f;
        light.Diffuse.g  = *(D3DMVALUE*)&f; // 1.0f;
        light.Diffuse.b  = *(D3DMVALUE*)&f; // 1.0f;
        vecDir = D3DMXVECTOR3((float)cos(GetTickCount()/350.0f),
                             1.0f,
                             (float)sin(GetTickCount()/350.0f) );
        D3DMXVec3Normalize( (D3DMXVECTOR3*)&light.Direction, &vecDir );
        light.Range       = 1000.0f;
        g_pd3dmDevice->SetLight( 0, &light, D3DMFMT_D3DMVALUE_FLOAT );
        g_pd3dmDevice->LightEnable( 0, TRUE );
        g_pd3dmDevice->SetRenderState( D3DMRS_LIGHTING, TRUE );
    
        // Finally, turn on some ambient light.
        g_pd3dmDevice->SetRenderState( D3DMRS_AMBIENT, 0x00202020 );
    }
    
  2. Find the lighting render state, which indicates whether lighting should be included in the scene or not. This can be found in the following call.

    g_pd3dmDevice->SetRenderState( D3DMRS_LIGHTING, TRUE );
    
  3. Change the lighting render state value to FALSE as follows.

    g_pd3dmDevice->SetRenderState( D3DMRS_LIGHTING, FALSE );
    
  4. Rebuild the project and deploy.

    Note   Take notice of the change in appearance from the original scene, as shown in the following figure. It should be observed that while lighting can be enabled for a given scene, if it is not enabled, only the material characteristics as defined earlier in this function will be exhibited. Lighting for a specific light is toggled from the LightEnable call, such as exhibited above:

        g_pd3dmDevice->LightEnable( 0, TRUE );
    

    Figure 15. Lighting is not included in the scene

  5. Return the lighting render state to TRUE.

  6. Rebuild and deploy the project to ensure that the lighting is once again as it was during the initial state of the project.

  7. Modify the LightEnable parameter for the first light by changing this to FALSE as follows.

        g_pd3dmDevice->LightEnable( 0, FALSE );
    
  8. Rebuild and deploy the project and observe the behavior, as shown in the following figure.

    Figure 16. The first light is turned off individually

  9. Return the LightEnable state to TRUE.

  10. Rebuild and deploy the project to ensure that the lighting is once again as it was during the initial state of the project.

  11. In this particular scene, we are using a white directional light. Locate the following code in the example.

        light.Diffuse.r  = *(D3DMVALUE*)&f; // 1.0f;
        light.Diffuse.g  = *(D3DMVALUE*)&f; // 1.0f;
        light.Diffuse.b  = *(D3DMVALUE*)&f; // 1.0f;
    
  12. Modify the Red and Blue values to Zero as follows.

        light.Diffuse.r  = 0.0f; // 1.0f;
        light.Diffuse.g  = *(D3DMVALUE*)&f; // 1.0f;
        light.Diffuse.b  = 0.0f; // 1.0f;
    
  13. Rebuild and deploy the application.

    With lighting now enabled, the only diffuse color component for the directional light will be green, which should now be the observed color for the cylinder, as shown in the following figure.

    Figure 17. The light color is green

  14. Now, zero out all of the color components for this light and rebuild the project.

    You will notice that with no color channel components enabled for the light that the resulting view for the cylinder will lack any color components—showing black in the final view, as shown in the following figure.

    Figure 18. No light colors

Lab 2 Summary

In this lab you performed the following exercises:

  • Loaded and modified the Look and View matrices for projections
  • Enabled and disabled lighting
  • Modified lighting parameters and determined their impact

In this lab, you have learned the impact of projection matrices in creating a three-dimensional scene. Also, you have learned how to toggle light implementations and the impact of various parameters.

Lab 3: Direct Draw Functionality-The 'CAPS' Bit

The objective of this lab is to review and understand the way in which platform functionality (both hardware and software based) is exposed with DirectX—in particular with Direct Draw. While Direct Draw is a powerful two-dimensional API, it most closely related to the GAPI graphics implementation when dealing with surfaces presentation.

In this lab, you will learn the basis in which DirectX functionality is exposed and how to view these parameters both programmatically and through the Visual Studio 2005 debugger. The foundation of the functionality is the DirectX Capability Bit (also known as the CAPS Bit).

In this lab you will perform the following exercises:

  • Learn about DirectX Capability Bits
  • Determine how to obtain CAPS bits for programmatic use
  • Review caps bits values through the system debugger

Exercise 1: Direct Draw Capability Bits

In this exercise, you will learn the basis of Capability Bits in Direct Draw and how to view these parameters both in system code and through the Visual Studio debugger.

To load DDex1

  1. From your computer, locate and load the Visual Studio 2005 project associated with DDEx1. This can be found at the following location within the Windows Mobile 5.0 Pocket PC SDK.

    C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Directx\Ddraw\DDex1\DDex1.sln

  2. Open the DDex1.cpp source code file from the Solution Explorer pane.

    Important   Do not yet build and deploy the project.

For those migrating from the GAPI environment, you should be familiar with the basic methods of determining system characteristics in regards to both graphics and input. While the scope of these methods were rather basic due to the design of the API and supporting platform architectures, the DirectX capability bits are far greater in scope and for Direct Draw designed to expose both software and hardware functionality that does not exist in GAPI.

In migrating from GAPI, the fundamental return characteristics in the GXDisplayProperties structure (from the GXDisplayProperties() functional call) returns a populated structure with rudimentary information regarding surface support types and frame buffer access.

For Direct Draw, a GetCaps() method is called that will return both the software (DDHEL—Hardware Emulation Layer) as well as hardware (DDHAL—Hardware Abstraction Layer, if applicable). While the DDCAPS values returned do include basic information similar to those of the GAPI calls—the Direct Draw CAPS mechanism returns dozens more that can be used to create applications capable of not only targeting a wide variety of platforms, but also providing the best experience on a given device.

To view capability bits

  1. Build and deploy DDex1 (Debug | Start Debugging).

    Important   You should notice that when this application is deployed, if you are using the emulator or a Toshiba E750/Himalaya that this should fail. An error message should be displayed on the screen. This is a result from not running on a device that includes the necessary capability bits. This is expected behavior.

  2. From the source file, find the #include section that includes the <ddraw.h> header file. Right-click this file and open for viewing in Visual Studio 2005.

To use capability bits

  1. In the DDex1.cpp source file, include a breakpoint at line 335. This should include the following.

    if (!(ddCaps.ddsCaps.dwCaps & DDSCAPS_BACKBUFFER)) 
        {
            return InitFail(hWnd, E_FAIL, szNoBackBufferMsg);
        }
    
  2. Run the program with the debugger, which should bring you to the first above breakpoint.

  3. Go to the debugger Auto window and notice the variable listing.

  4. Expand the ddcaps tree and view all of the capability bits exposed in this structure.

    Note   Notice the various capability bits that are exposed from this particular call—in particular those that are listed as 0 (or not supported). Alpha and Color key CAPS are good examples.

  5. From the above call, the failure in question on this platform can be traced back to the DDSCAPS_BACKBUFFER surface CAP. In the debugger auto watch window, expand the ddscaps tree. You should find a value associated with this.

  6. Go to the ddraw.h header file that you have already opened in Visual Studio 2005.

  7. Search for DDSCAPS_BACKBUFFER.

  8. Cycle on this search until you've found the #define section that maps this and other capability bit values for surface caps.

  9. You should find a series of ddscaps definitions that map to values. Find the one that matches the value for your current platform. This is the current surface CAPS defined for this platform.

    Note   Notice the other values and the one associated with the BACKBUFFER caps in addition to other values.

  10. Learning to map these return values is one method of determining the support on a given platform and of creating code that is extensible enough to run on a wide variety of devices while taking advantage of all system characteristics.

To use capability bits across platforms

  1. Take a different platform from what you had used in the above exercise, this could be any other platform (including the emulator), though a device with hardware acceleration would be preferable.

  2. Remove the breakpoints from the code and run DDex1 as you had when you first began this exercise.

    Note   If you are running on a platform that support hardware flipping and multiple backbuffers, the results should be different than the first time you ran this code. If on the other hand, the capability bits for this new platform match those of the earlier platform in this area, the results should be similar.

  3. On this new platform, run the program with the debugger, which should bring you to the same breakpoint as above.

  4. Go to the auto watch window for the debugger and again expand the general capability bits in addition to the surface caps as you had above.

    Note   Look at the values of the various capability bit fields. How do these differ from the platform you had used earlier? Take particular note of the differences on a hardware accelerated platform (such as the Dell Axim).

  5. Close the currently running application (Debug | Stop Debugging).

  6. Insert a breakpoint at line 333.

  7. Rerun the program with the debugger. Note the additional variables in the auto watch window and the capability bits they represent. If you would like, compare these values against various platform targets that are at your discretion.

Lab 3 Summary

In this lab, you performed the following exercises:

  • Observed the behavior of Direct Draw capabilities
  • Learned where to find CAPS information in the header file for review
  • Determined CAPS and code on various platforms

Lab 4: Direct Draw Game Design-Donuts

The objective of this lab is to review and understand the key points of two-dimensional game development using DirectX. As GAPI has traditionally been used in this space, both input and graphics setup will be reviewed in this portion of the lab.

In this lab, you will perform the following exercises:

  • Understand how basic input can be used for game development (as opposed to GAPI)
  • Understand the construction of Direct Draw surfaces and types
  • Basic position setup

Exercise 1: DirectX and Input Options

In this exercise, you will understand how basic input methods can be used for gaming applications that utilize DirectX graphics.

To load donuts

  1. From the local machine, locate and load the Visual Studio 2005 project associated with Donuts2. This can be found at the following location within the Windows Mobile 5.0 Pocket PC SDK:

    C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Pocket PC SDK\Samples\CPP\Win32\Directx\Ddraw\Donuts2\Donuts2.sln

  2. Build and deploy the project (Debug | Start Debugging). The Donuts game will load on the emulator, as shown in the following figure.

    Figure 19. Donuts game

  3. Observe the basic game play (particularly movement of the ship's orientation based on input). You need to press enter on the device pad to start game play.

  4. Close the running application (Debug | Stop Debugging).

To input mapping

  1. Open the donuts.c source code file from the Solution Explorer pane.

  2. Locate the MainWndproc function. In that code, find the following case statements.

    case WM_KEYDOWN:
          switch (wParam) {
          case VK_NUMPAD8:
          case VK_UP:
            GlobalInput |= KEY_UP;
            break;
          case VK_NUMPAD2:
          case VK_DOWN:
            GlobalInput |= KEY_DOWN;
            break;
          case VK_NUMPAD4:
          case VK_LEFT:
            GlobalInput |= KEY_LEFT;
            break;
          case VK_NUMPAD6:
          case VK_RIGHT:
            GlobalInput |= KEY_RIGHT;
            break;
          case VK_ACTION:
            GlobalInput |= KEY_FIRE;
            break;
          case VK_NUMPAD5:
          case VK_CLEAR:
          case VK_APP1:
            GlobalInput |= KEY_STOP;
            break;
          case VK_NUMPAD7:
          case VK_HOME:
          case VK_APP2:
            GlobalInput |= KEY_SHIELD;
            break;
          }
          break;
    
        case WM_KEYUP:
          switch (wParam) {
          case VK_NUMPAD8:
          case VK_UP:
            GlobalInput &= ~KEY_UP;
            break;
          case VK_NUMPAD2:
          case VK_DOWN:
            GlobalInput &= ~KEY_DOWN;
            break;
          case VK_NUMPAD4:
          case VK_LEFT:
            GlobalInput &= ~KEY_LEFT;
            break;
          case VK_NUMPAD6:
          case VK_RIGHT:
            GlobalInput &= ~KEY_RIGHT;
            break;
          case VK_ACTION:
            GlobalInput &= ~KEY_FIRE;
            break;
          case VK_NUMPAD5:
          case VK_CLEAR:
          case VK_APP1:
            GlobalInput &= ~KEY_STOP;
            break;
          case VK_NUMPAD7:
          case VK_HOME:
          case VK_APP2:
            GlobalInput &= ~KEY_SHIELD;
            break;
          }
          break;
    

    Note   Notice the mapping of the WM_KEYDOWN and WM_KEYUP case statements.

  3. For the GlobalInput keyup and keydown cases, switch these mappings with each other.

  4. Rebuild and deploy the project.

  5. Accelerate the ship and note the input pattern differences. This should have the effect of remapping input.

    In this example, input is based on the basic message pump and can be used in such applications.

  6. Return the input states to as they were in the original project.

Exercise 2: Surface Setup and Meaning

In this exercise, you will walk through the various presentation types and their relevance to Direct Draw application development. You will also take basic coordinate space as how this is similar to GAPI design.

To create a surface

  1. Locate the InitializeGame(…) function in the donuts.c file.

  2. Locate the following code within this function.

    if (!(ddCaps.ddsCaps.dwCaps & DDSCAPS_BACKBUFFER) || !(ddCaps.ddsCaps.dwCaps & 
            DDSCAPS_FLIP)) 
        {
            bSingleBuffer = TRUE;
        }
    

    Note   Look at the capability bit selection from the above code, similar to the platform issue examined in the earlier lab session. Capability bits in DirectX determine the functionality exposed on a given platform and help to abstract the underlying architecture from the application developer. As GAPI does not have a method to expose hardware functionality and other advanced features of modern platforms, DirectX is required to include this is the driver model so you don't have to write to specific graphics hardware.

  3. From the above code example, note the check for whether the device is capable of "Flipping" surfaces rather than BLT'ing them to display memory. For the vast majority of emerging Windows Mobile platforms, hardware flipping is not available.

  4. For the above code, modify the single buffer status by changing this Boolean assignment to FALSE.

    bSingleBuffer = TRUE;
    
  5. Build and deploy the application (Debug | Start Debugging).

    Note   See the behavior as to what happens if an application attempts to run on a platform that does not support the required functionality. When proper support for technology is not available on a given platform, it is up to the application to either account for this or fail gracefully. Failing with at least some error message to the user (as seen in DDex1) is far better than failing silently.

  6. Now that you have noted the behavior on the emulator, if you would like to attempt the same exercise by deploying to a physical device, you may do so. Note the behavior and how this changes on a device that exposes some form of hardware graphics acceleration.

    It should be noted that while platforms are increasing in functionality, not all features are exposed on all platforms. While GAPI has a rather limited set of possible functionality that is exposed from the display controller, there are a wide variety of capability bits (CAPS) that are exposed in both Direct Draw and Direct3D Mobile. For reference, refer to the capability bits for DirectX in the supporting development documentation.

  7. To provide further context to the above, note the logic that impacts the above change.

    if (bSingleBuffer)
        {
            ddsd.dwFlags = DDSD_CAPS;
            ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
        }
        else 
        {
            ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
            ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP;
            ddsd.dwBackBufferCount = 1;
        }
    
  8. Revert the above change and rebuild the project. Redeploy the project to ensure that the project is operating as it was originally.

  9. For a brief view as to the coordinate system that can be used in Direct Draw, locate the initShip() function in the donuts.c file.

    void initShip( BOOL delay )
    {
        DL.posx = (double)(ScreenX/2-16);       // center the ship
        DL.posy = (double)(ScreenY/2-16);
        DL.frame = 0.0;
        if( bTest )
        {
            DL.velx = 0.25;
            DL.vely = 0.5;
        }
        else
        {
            DL.velx = DL.vely = 0.0;        // not moving
        }
        if( !bTest && delay ) {
            showDelay = DEF_SHOW_DELAY;
        }
        else {
            UpdateEngineIdle();
            SndObjPlay(hsoEngineIdle);
        }
    }
    

    Note   Notice the code fragment that determines whether the ship is centered or not.

    DL.posx = (double)(ScreenX/2-16);       // center the ship
    DL.posy = (double)(ScreenY/2-16);
    
  10. Modify the computation above (perhaps by removing the 16 pixel offsets) to determine the correlation to screen space for the ship sprite layout.

  11. Rebuild the project and deploy.

  12. Observe the behavior of the ship when the application begins.

  13. Restore the coordinates to the original position as outlined above.

  14. Modify the starting coordinates by making the offset greater than the current resolution (Ex: -100).

  15. Rebuild and deploy the application.

    The ship is no longer centered, as shown in the following figure.

    Figure 20. The ship is no longer centered

Appendix A: Terminating an Application Running on a Device or Emulator

This task describes how to terminate an application that is running on a device or emulator. This is useful for cases where an application has been started without having the debugger attached and needs to be terminated so that a new copy of the application can be deployed. You will terminate the application by using the Remote Process Viewer remote tool in Visual Studio.

Before you can terminate a process, you need to know the name of the executable file. In most cases, this is simply the same name as the Visual Studio project. If you are uncertain of the name of the executable file, you can find it in the project properties.

  1. From Visual Studio, select Project, and then xxx Properties, where xxx represents the name of the current project.

  2. Note the value in the Assembly Name field. This is the name that the executable file will be running as on the device or emulator.

  3. Close the Properties dialog box.

    Now, you can terminate the process.

  4. From the Start menu, click Start > Microsoft Visual Studio 2005 > Visual Studio Remote Tools > Remote Process Viewer.

  5. When prompted by the Select a Windows CE Device dialog box, select the emulator or device where the application is running, as shown in the following figure, and then click OK.

    Figure 21. Select a Windows CE Device dialog box

  6. After the connection to the emulator or device completes, locate the application you want to terminate in the top pane of the Remote Process Viewer, as shown in the following figure.

    Figure 22. Selecting the application to terminate. Click the thumbnail for a larger image.

    You may need to widen the Process column (leftmost column) to fully view the process names.

  7. Click the process name to select the process.

  8. To terminate the process, select File > Terminate Process from the Remote Process Viewer menu.

    Note   Be certain that the correct process is selected before clicking Terminate Process. Terminating the incorrect process may render the device or emulator unusable, requiring it to be reset before you can use it again.

  9. Verify that the process is terminated by selecting Target > Refresh on the Remote Process Viewer menu, and scrolling through the top pane again. If the application name still appears, the process was not terminated, and you need to repeat these steps.

    Note   Most processes will terminate in just one try; however, depending on the state of the application, it does occasionally take two attempts.

Lab 4 Summary

In this lab you performed the following exercises:

  • Observed a complete Direct Draw-enabled game title
  • View and modify basic input for the application
  • Understand the fundamental aspects of capability bits and the impact on application design
  • Interact with basic coordinate computations for Direct Draw and the impact to the application

In this lab, you observed a completed DirectX based game and how factors other than graphics (such as input and sound) can be used. In particular, you had the opportunity to evaluate input, surface creation, and the impact of DirectX on two-dimensional sprite based game design.

Congratulations! You've completed this hands-on lab.