Chapter 10. Project Setup and Window Initialization

In this chapter, you establish the foundation of your rendering engine. You create the necessary Visual Studio projects, implement a game loop, and present a window to the screen.

A New Beginning

Now that you’ve reached the C++ chapters (the sections of the book that focus on the DirectX API using C++), it’s important to set your expectations about how quickly you can get to rendering something to the screen. Put simply, it’s going take awhile. You need to build quite a bit of scaffolding, but the net result will pay dividends in your capability to reuse the code over multiple projects. If you’d prefer to use the existing codebase from the book’s companion website and skip ahead a couple chapters to where you are actually rendering, you are welcome to do so. But if you’re interested in how the supporting systems are developed, you’ll find the next few chapters particularly interesting.

Also, although these chapters expect that you are already familiar with C++ programming and Visual Studio, they do not expect that you are familiar with writing Windows applications. Thus, this initial material walks you through setting up the projects that you’ll be able to use for all the work going forward.

Project Setup

The rendering engine that’s developed over the next several chapters is split into two Visual Studio projects, a Library project and a Game project. The Library project contains common code that you can use across any number of games or rendering applications. The Game project houses code that is application specific.

Directory Structure

Establishing a directory structure for your projects is a good idea. Table 10.1 presents the structure used for the code base found on the companion website.

Image

Table 10.1 Project Directory Structure

Project Creation

To adopt this directory structure, create a blank Visual Studio solution inside the build directory. Then right-click the solution within the Solution Explorer panel and choose Add, New Project from the context menu to launch the Add New Project Wizard. Choose the Win32 Project template with name and location options, such as those in Figure 10.1.

Image

Figure 10.1 The Visual Studio 2013 Add New Project Wizard.

This selection launches the Win32 Application Wizard. For the Library project, choose Static Library for the application type and Empty Project for the additional options (see Figure 10.2). In this image, precompiled headers are disabled; if you’d like to use precompiled headers (for increased compilation speed), just enable that option.

Image

Figure 10.2 The Visual Studio 2013 Win32 Application Wizard.

Run through these steps a second time to add the Game project, but choose Windows Application for the application type.

Project Build Order

Next, establish the correct build order by right-clicking the solution or one of the projects in the Solution Explorer panel and choosing Project Dependencies from the context menu. Your Game project should depend on your Library project. This configuration builds the Library project first and then builds the Game project (see Figure 10.3).

Image

Figure 10.3 The Visual Studio 2013 Project Dependencies dialog box.


Note

An alternative to using the Project Dependencies dialog box and manually specifying the Library project as input to the Game project (which is done in the upcoming Linking Libraries section) is to employ the Visual Studio 2013 Common Properties, References dialog box. With that system, you specify the Library project as a reference to the Game project. See the online MSDN documentation for more information about project references.


Include Directories

For your projects to find the header files for DirectX, you need to specify a few directory paths within the project configurations. To do this, open the Property Pages for the Library project by right-clicking the project in the Solution Explorer and choosing Properties. Navigate to the section labeled Configuration Properties, C/C++, General, and edit the Additional Include Directories field to match Figure 10.4. Be sure to do this for both debug and release builds.

Image

Figure 10.4 The Visual Studio 2013 Additional Include Directories dialog box.

The $(WindowsSDK_IncludePath) macro is available after you install the Windows SDK. The other two directories refer to the Effects11 and DirectXTK libraries. Extract those packages to the external directory, or update the include directory to match their installed locations.


Note

Recall the discussion of the Effects 11 and DirectX Tool Kit libraries from Chapter 3. Both libraries are distributed in source form and require that you build the packages before you can use them in your projects.

As the text suggests, you should place the built libraries in the external directory, or in some other directory that’s readily accessible by the Game and Library projects.


Duplicate these settings for the Game project, but add the following to the list of directories:

$(SolutionDir)..sourceLibrary

This setting allows source files in your Game project to include header files from the Library project.


caution

The Configuration Properties, C/C++ section will not appear unless the project contains at least one .cpp file. If you are following along with your own projects, your Library project will not contain any .cpp files at this point, and thus the C/C++ section will be hidden. To fix this, add a temporary, empty .cpp file to your Library project. You’ll be able to remove the temporary file when you begin adding code.


Linking Libraries

Next, open the Property Pages for the Game project and navigate to the section labeled Configuration Properties, Linker, Input. Edit the Additional Dependencies field to match the settings in Table 10.2. Note the differences between the debug and release configurations. For the debug configuration, the Effects11 and Library .lib files have a trailing d to denote that they are debug builds. This is a common convention and allows both debug and release libraries to exist in the same output directory. Figure 10.5 shows the associated Visual Studio dialog boxes.

Image

Table 10.2 Game Project Input Libraries

Image

Figure 10.5 The Visual Studio 2013 Additional Dependencies dialog box.

As with the include paths, you must provide search locations for Visual Studio to find the specified libraries. These directories are provided through the Additional Library Directories field under the Configuration Properties, Linker, General section of the Property Pages dialog box. Edit this field to match Figure 10.6. As before, be sure you do this for both debug and release configurations, and note that the DirectXTK library has different directories for debug and release builds.

Image

Figure 10.6 The Visual Studio 2013 Additional Library Directories dialog box.

Final Project Settings

With just a bit more configuration, you’ll be ready to build your projects. Because the Library project is separate from the Game project (to allow for multiple Game projects), you must make the output of the Library build available to a common location. A location common to both projects is the solution directory, and you can write your .lib file to, for example, $(SolutionDir)..lib. You can configure your Library project to output directly to this location, or you can copy the .lib file through a post-build event. A build event is just a set of DOS batch instructions that can be triggered before or after the build. You can find these settings under the section labeled Configuration Properties, Build Events, and you can edit them through the Command Line field for a specific event. Figure 10.7 shows the post-build event used to copy the output .lib file to a directory that the Game project can more readily access.

Image

Figure 10.7 The Visual Studio 2013 Post-Build Event/Command Line dialog boxes.

If you intend to use a different file name for the debug build of your Library project (such as Libraryd.lib), be sure to update the Target Name field under the Configuration Properties, General section of the Property Pages dialog box.

Finally, the code base presented in the coming chapters uses Unicode strings. To match this, update the Character Set field to Use Unicode Character Set. You can find this setting under Configuration Properties, General.


Note

You can simplify this setup by collapsing the Library and Game projects, but you won’t be able to reuse the common Library code as readily. The demos presented over the next several chapters assume the previous configuration, which allows the demos to be presented independently.

For your own work, you are encouraged to modify the configuration to suit your taste.


Application Startup

Your project configuration is now complete, and you can start adding source code. Begin by adding a Program.cpp file to your Game project, which will house the application entry point. Instead of the traditional main()function, Windows programs use WinMain() as the startup function. Listing 10.1 presents the startup code used as the base for all the demos of the upcoming chapters.

Listing 10.1 The Program.cpp File


#include <memory>
#include "Game.h"
#include "GameException.h"

#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif

using namespace Library;

int WINAPI WinMain(HINSTANCE instance, HINSTANCE previousInstance,
LPSTR commandLine, int showCommand)
{
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    std::unique_ptr<Game> game(new Game(instance, L"RenderingClass",
L"Real-Time 3D Rendering", showCommand));

    try
    {
        game->Run();
    }
    catch (GameException ex)
    {
        MessageBox(game->WindowHandle(), ex.whatw().c_str(), game-
>WindowTitle().c_str(), MB_ABORTRETRYIGNORE);
    }

    return 0;
}


The <memory> include allows the use of std::unique_ptr, a smart pointer that destroys the object it maintains when the pointer goes out of scope. The next two includes are for Game and GameException classes you’ll create within the Library project. We discuss the Game class shortly. The GameException class is an extension of std::exception. The code for this class isn’t listed here, but is available on the companion website.

Next, note the conditional includes for the CRT debug library (from crtdbg.h). When this library is enabled, any memory leaks are listed in Visual Studio’s Output panel after the program exits. The call to _CrtSetDbgFlag() enables the library.

Just before the WinMain() declaration, you’ll notice the using namespace directive. All the code for the rendering engine is encapsulated in a C++ namespace called Library (for example, the Game and GameException classes). All the demo-related code is housed in the Game project and contained within the Rendering namespace. I encourage you to make friends with C++ namespaces if you haven’t already; they are part of the light side of the Force.

The parameters to the WinMain() function are mostly related to window initialization, which you’ll handle within the Game class. Thus, these values are passed as arguments to the Game class constructor. We discuss window initialization momentarily. You can use the commandLine parameter to pass information to the startup method from the DOS command line or Windows shortcut.

The body of WinMain() is quite short. It contains the Game class instantiation followed by a call to Game::Run(). This call is wrapped within a try/catch block that presents a message box whenever there’s a problem within Game::Run(). The Run() method encapsulates the application’s game loop.


Note

The code in this text is written specifically for the Windows platform, and you’ll find Windows-specific references throughout the code base. You’ll also encounter various Visual Studio language extensions that might not be happy in other environments. I chose this route so as not to get mired in cross-platform complexity, when the focus of the text is on rendering. With a bit of effort, the framework in these chapters will port to Windows RT and Xbox One. With a bit more effort, this work could be independent of the graphics API (that is, it could support either DirectX or OpenGL).


The Game Loop

The game loop is the system that makes calls to update the objects in your scene and draw them to the screen. Typically, the game loop is initialized and executed shortly after program startup and runs continuously until the program is terminated. A variety of game loop designs exist, but Figure 10.8 illustrates the most basic.

Image

Figure 10.8 A basic game loop.

From a code perspective, this game loop looks something like Listing 10.2.

Listing 10.2 A Basic Game Loop in C++


void Game::Run()
{
    Initialize();

    while(isRunning)
    {
        mGameClock.UpdateGameTime(mGameTime);

        Update(mGameTime);
        Draw(mGameTime);
    }

    Shutdown();
}

void Game::Exit()
{
    isRunning = false;
}


The Game::Initialize() method refers to window and Direct3D initialization (which you’ll actually split into two separate functions), along with game-specific initialization steps. The Game::Update() method performs any non-rendering-related operations. For example, the Update() method might process keyboard and mouse input, rotate an object, or update its position. Conversely, the Draw() method handles all graphics-related instructions. These methods accommodate updating and drawing for all the objects in a scene.

Time

Notice the UpdateGameTime() method in Listing 10.2 and the associated mGameTime member that’s passed to the Update() and Draw() methods. These elements are about time, which is important in the context of game and graphics programming. Two pieces of time-related information are of interest: the time elapsed since the clock started and the time elapsed since the last iteration of the game loop. The latter applies to what is commonly known as the game’s frame rate. An iteration of the game loop counts as one frame, and the frame rate is the number of frames executed in a second.

The elapsed-time data and associated functionality are encapsulated within the GameClock and GameTime classes. Figure 10.9 shows class diagrams for these types. Listing 10.3 presents the GameClock header file.

Image

Figure 10.9 Class diagrams for the GameClock and GameTime classes.

Listing 10.3 The GameClock.h Header File


#pragma once

#include <windows.h>
#include <exception>

namespace Library
{
    class GameTime;

    class GameClock
    {
    public:
        GameClock();

        const LARGE_INTEGER& StartTime() const;
        const LARGE_INTEGER& CurrentTime() const;
        const LARGE_INTEGER& LastTime() const;

        void Reset();
        double GetFrequency() const;
        void GetTime(LARGE_INTEGER& time) const;
        void UpdateGameTime(GameTime& gameTime);

    private:
        GameClock(const GameClock& rhs);
        GameClock& operator=(const GameClock& rhs);

        LARGE_INTEGER mStartTime;
        LARGE_INTEGER mCurrentTime;
        LARGE_INTEGER mLastTime;
        double mFrequency;
    };
}


The GameClock class wraps calls to the Windows API high-resolution performance counter, a timer that increments a value at a frequency expressed in counts per second. You determine the frequency through a call to QueryPerformanceFrequency(), which modifies a parameter of type LARGE_INTEGER (a 64-bit integer, or long long). You store the frequency in a class member and use it for subsequent elapsed-time calculations.

You can get the current state of the high-resolution timer through the QueryPerformanceCounter() function. To compute the elapsed time of an iteration of the game loop, you call QueryPerformanceCounter() twice, once at the beginning of the loop (before the Update() call) and once at the end of the loop (after the Draw() call). The elapsed time (in seconds) is calculated as:

timeelapsed = (stopTime − startTime) / frequency

To calculate the total elapsed time since the start of the program, you can either accumulate the elapsed time per frame or save off the result of an initial call to QueryPerformanceCounter() and then use it with the aforementioned equation. Listing 10.4 presents the implementation of the GameClock class.

Listing 10.4 The GameClock.cpp File


#include "GameClock.h"
#include "GameTime.h"

namespace Library
{
    GameClock::GameClock()
        : mStartTime(), mCurrentTime(), mLastTime(), mFrequency()
    {
        mFrequency = GetFrequency();
        Reset();
    }

    const LARGE_INTEGER& GameClock::StartTime() const
    {
        return mStartTime;
    }

    const LARGE_INTEGER& GameClock::CurrentTime() const
    {
        return mCurrentTime;
    }

    const LARGE_INTEGER& GameClock::LastTime() const
    {
        return mLastTime;
    }

    void GameClock::Reset()
    {
        GetTime(mStartTime);
        mCurrentTime = mStartTime;
        mLastTime = mCurrentTime;
    }

    double GameClock::GetFrequency() const
    {
        LARGE_INTEGER frequency;

        if (QueryPerformanceFrequency(&frequency) == false)
        {
            throw std::exception("QueryPerformanceFrequency()
failed.");
        }

        return (double)frequency.QuadPart;
    }

    void GameClock::GetTime(LARGE_INTEGER& time) const
    {
        QueryPerformanceCounter(&time);
    }

    void GameClock::UpdateGameTime(GameTime& gameTime)
    {
        GetTime(mCurrentTime);
        gameTime.SetTotalGameTime((mCurrentTime.QuadPart - mStartTime.
QuadPart) / mFrequency);
        gameTime.SetElapsedGameTime((mCurrentTime.QuadPart - mLastTime.
QuadPart) / mFrequency);
        mLastTime = mCurrentTime;
    }
}


In this implementation, the mStartTime member stores the counter value for computing the total elapsed time and is assigned in the Reset() method. The members mCurrentTime and mLastTime are used for storing subsequent calls to QueryPerformanceCounter() to compute the elapsed time per frame. The UpdateGameTime() method encapsulates those elapsed-time calculations instead of forcing that code into the Game::Run() method. The frequency is queried on GameClock instantiation. The GameTime class has no special functionality; it simply contains accessors and mutators for its mElapsedGameTime and mTotalElapsedGameTime members. This and all source code is available on the book’s companion website.

Game Loop Initialization

The Game class is the heart of the rendering engine and encapsulates the game loop. The initialization stage of the game loop is executed with calls to three virtual methods of the Game class: InitializeWindow(), InitializeDirectX(), and Inititialize(); respectively, they create a window for your application to draw to, set up DirectX, and perform generic component initialization (discussed shortly). The Game class is intended to be inherited (although it’s not officially an abstract base class because it contains a full implementation), and because all three methods are virtual, they can be customized by the derived class. However, the base Game class provides general-purpose implementations of each of these methods.

Window Initialization

Your DirectX game and graphics applications reside within windows, as do all Microsoft Windows programs, and you must create these containers with calls to the Windows API. Listing 10.5 presents the header file for the Game class, containing just those members necessary for window initialization. In Chapter 11, “Direct3D Initialization,” the Game class will expand to include data and methods for initializing Direct3D.

Listing 10.5 The Game.h Header File


#pragma once

#include <windows.h>
#include <string>
#include "GameClock.h"
#include "GameTime.h"

namespace Library
{
    class Game
    {
    public:
        Game(HINSTANCE instance, const std::wstring& windowClass, const
std::wstring& windowTitle, int showCommand);
        virtual ~Game();

        HINSTANCE Instance() const;
        HWND WindowHandle() const;
        const WNDCLASSEX& Window() const;
        const std::wstring& WindowClass() const;
        const std::wstring& WindowTitle() const;
        int ScreenWidth() const;
        int ScreenHeight() const;

        virtual void Run();
        virtual void Exit();
        virtual void Initialize();
        virtual void Update(const GameTime& gameTime);
        virtual void Draw(const GameTime& gameTime);

    protected:
        virtual void InitializeWindow();
        virtual void Shutdown();

        static const UINT DefaultScreenWidth;
        static const UINT DefaultScreenHeight;

        HINSTANCE mInstance;
        std::wstring mWindowClass;
        std::wstring mWindowTitle;
        int mShowCommand;

        HWND mWindowHandle;
        WNDCLASSEX mWindow;

        UINT mScreenWidth;
        UINT mScreenHeight;

        GameClock mGameClock;
        GameTime mGameTime;

    private:
        Game(const Game& rhs);
        Game& operator=(const Game& rhs);

        POINT CenterWindow(int windowWidth, int windowHeight);
        static LRESULT WINAPI WndProc(HWND windowHandle, UINT message,
WPARAM, LPARAM lParam);
    };
}


As you can see, initializing a window requires a lot of declaration. First, the Game constructor accepts arguments for an instance handle and a window class name. The initial parameter is a handle to the program instance that contains the windows procedure, a call back for all messages passed from the operating system to your window. The second parameter (the window class name) is just a string. Together, these arguments uniquely identify your window among the sea of windows in your operating system. The instance handle is a pass-through parameter that comes from your program’s entry point WinMain() as is the showCommand parameter, which determines how your window will be shown. The windowTitle parameter is the string used for the window’s title bar. All parameters are saved to class members for use in the InitializeWindow() method. Listing 10.6 presents the implementation of the InitializeWindow() method.

Listing 10.6 The Game::InitializeWindow() Method


void Game::InitializeWindow()
{
    ZeroMemory(&mWindow, sizeof(mWindow));
    mWindow.cbSize = sizeof(WNDCLASSEX);
    mWindow.style = CS_CLASSDC;
    mWindow.lpfnWndProc = WndProc;
    mWindow.hInstance = mInstance;
    mWindow.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    mWindow.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
    mWindow.hCursor = LoadCursor(nullptr, IDC_ARROW);
    mWindow.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
    mWindow.lpszClassName = mWindowClass.c_str();

    RECT windowRectangle = { 0, 0, mScreenWidth, mScreenHeight };
    AdjustWindowRect(&windowRectangle, WS_OVERLAPPEDWINDOW, FALSE);

    RegisterClassEx(&mWindow);
    POINT center = CenterWindow(mScreenWidth, mScreenHeight);
    mWindowHandle = CreateWindow(mWindowClass.c_str(), mWindowTitle.c_
str(), WS_OVERLAPPEDWINDOW, center.x, center.y, windowRectangle.right
- windowRectangle.left, windowRectangle.bottom - windowRectangle.top,
nullptr, nullptr, mInstance, nullptr);

    ShowWindow(mWindowHandle, mShowCommand);
    UpdateWindow(mWindowHandle);
}


A window is created with calls to RegisterClassEx() and CreateWindow() and subsequently is displayed with a call to ShowWindow(). The window is defined through the WNDCLASSEX type and has myriad options for customizing its look. The AdjustWindowRect()function creates a client area whose size is specified by the mScreenWidth and mScreenHeight class members. The client area is the inside the window, excluding elements such as the title bar, status bar, or scrollbars.

The CenterWindow() call centers the window within the screen and is not a Windows API function. Listing 10.7 presents the implementation of this method and the WndProc() callback function.

Listing 10.7 Implementations for Game::WndProc() and Game::CenterWindow()


LRESULT WINAPI Game::WndProc(HWND windowHandle, UINT message, WPARAM,
LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }

    return DefWindowProc(windowHandle, message, wParam, lParam);
}

POINT Game::CenterWindow(int windowWidth, int windowHeight)
{
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    POINT center;
    center.x = (screenWidth - windowWidth) / 2;
    center.y = (screenHeight - windowHeight) / 2;

    return center;
}


The CenterWindow() method uses the Windows API GetSystemMetrics() function to determine the screen’s width and height, and returns a point at the center of the screen. The windows procedure is required for all Windows applications. The WndProc() implementation simply passes all messages to the default windows procedure, except for the message to destroy the window after it has been removed from the screen (that is, the application should shut down). In response to this message, the PostQuitMessage() function is called and posts a WM_QUIT message to the Windows message queue. When the application receives the WM_QUIT message, the Windows message loop exits and the application ends.

With this code in place, you can update your Game::Run() method to Listing 10.8.

Listing 10.8 The Game::Run() Method


void Game::Run()
{
    InitializeWindow();

    MSG message;
    ZeroMemory(&message, sizeof(message));

    mGameClock.Reset();

    while(message.message != WM_QUIT)
    {
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&message);
            DispatchMessage(&message);
        }
        else
        {
            mGameClock.UpdateGameTime(mGameTime);
            Update(mGameTime);
            Draw(mGameTime);
        }
    }

    Shut down();
}


This is the game loop, but augmented to accommodate window initialization and messages from the Windows message queue. The Windows function PeekMessage() looks at the queue for new messages and dispatches them with the calls to TranslateMessage() and DispatchMessage(). The WndProc() method processes the resulting messages.

You can find more details in the online documentation on creating windows and the Windows message loop, but this is enough to get a window on the screen. If you build and run your application, you should see the image in Figure 10.10.

Image

Figure 10.10 A blank window created through the demo framework.

This might seem a bit anti-climactic, but you’re much closer to having something rendering to the screen. The next step is to initialize Direct3D.

Summary

In this chapter, you established the foundation of your rendering engine. You set up the appropriate Visual Studio projects, developed a game loop, and initialized a window through the Windows API.

In the next chapter, you extend this work to build your first Direct3D demo.

Exercise

1. From within the debugger, walk through the code used to initialize the game loop and create a blank window. Visit the book’s companion website to find an associated demo application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.167.87