Tutorial Part 1 "Hello World"

In this tutorial you will learn the how to develop Direct3D 11 applications with Tk11. Before you get started, you should follow the build instructions on the Getting Started page.

This part of the tutorial describes how to initialize the Tk11 framework, create a rendering window, run the main loop and register callbacks. The result will be a minimal Tk11 application which opens a window and animates the background color of the screen, which is identical to the empty.cpp example.

Headers and Namespace

All symbols of the Tk11 library are defined in the tk11 namespace. A single header file tk11/tk11.hpp can be included in order to define all symbols. Preprocessor macros are prefixed with TK11_ to keep the global namespace clean. To make all symbols of Tk11 available in the global namespace, you can use

 #include <tk11/tk11.hpp>

 using namespace tk11;

Introduction

Tk11 provides a framework for applications using Direct3D 11. The framework is responsible for all initialization tasks, such as creating a target window and a Direct3D 11 device. The framework also implements a main loop, which handles window messages and presents rendered frames.

Of course, the framework does not know how to render a frame or how to react to user input. This functionality must be implemented in the application. The framework makes sure that this application code is executed at the right time by invoking callbacks that are registered at the framework by the application. Furthermore, the application can provide initialization parameters to the framework, such as the desired screen resolution or window size and whether to use fullscreen or windowed mode.

Initialization Parameters

The first thing an application needs to do is set up a tk11::Window_Parameters structure, which contains initialization parameters and callbacks for the rendering window (for now, we assume there is only one rendering window).

 // Window parameters
 Window_Parameters parameters;
 
 // Set windowed mode with 800x600 resolution
 parameters.x_resolution = 800;
 parameters.y_resolution = 600;
 parameters.fullscreen = false;

Note that it is not necessary to manually initialize all members of the structure, because there are defaults for all members.

Callbacks

Before the framework can be initialized, the application needs to define callback functions and specify these callbacks in the tk11::Window_Parameters structure. The most important callbacks are the render and update callbacks. These callbacks are responsible for updating the scene based on the current elapsed time and for rendering the scene to the back buffer.

Our first application implements these callbacks as follows:

 // Background color.
 // Updated every frame and used for clearing the back buffer.
 static float background_color = 0;

 // Scene update callback.
 // Here we update all variables that change over time.
 void update(const Duration& elapsed_time) {
   // Set clear color
   background_color =
     fmod(time_div_float<float>(elapsed_time, seconds(1)), 1.0f);
 }


 // Scene rendering callback.
 // Here we render the scene to the main render target (the back buffer)
 void render(ID3D11DeviceContext_ptr context,
   ID3D11RenderTargetView_ptr render_target,
   ID3D11DepthStencilView_ptr depth_stencil)
 {
   // Clear render target
   float clear_color_rgba[] = {
     background_color, background_color, background_color, 0
   };
   context->ClearRenderTargetView(render_target.get(), clear_color_rgba);
 }

In this simple example, the update callback uses the current time to compute the background color using a simple periodic function. The current time is available through the elapsed_time parameter. The framework sets this parameter in a way which allows it to be used for computing smooth and animations. For more information about timing, see Time and Clocks .

The render callback uses the background color computed by the update callback to clear the render target. Interfaces to the render target and the device context are provided by the framework as callback parameters, because they are used frequently to initiate rendering operations.

Note that pointers to COM interfaces are passed using types such as tk11::ID3D11DeviceContext_ptr. These types are wrappers for COM pointers which do automatic reference counting. You can use them like plain C++ pointers (e.g. using "->" for calling interface methods). For more information, see COM Pointers.

There are two more important callbacks, which we will implement but not use for now. The init callback is used by applications to create resources such as textures and shaders. The resize callback is invoked at startup and whenever the size of the target window changes. The application can use this callback to create resources which depend on the screen size or the back buffer texture.

 // Resize callback.
 // Here we update all variables which depend on the size of the target window,
 // such as projection matrices and viewport parameters.
 void resize(unsigned int x_resolution, unsigned int y_resolution,
     bool fullscreen, ID3D11Device_ptr device, IDXGISwapChain_ptr swap_chain,
     ID3D11Texture2D_ptr back_buffer)
 {}


 // Scene initialization callback.
 // Here we create resources, such as textures, buffers and shaders.
 void init(ID3D11Device_ptr device) {}

As you can see, the framework again provides interfaces as callback parameters. For example, the ID3D11Device interface is needed for creating resources.

Having defined these callbacks, the application needs to tell the framework to invoke these callbacks at the appropriate time. This is done by setting the callback attributes in the tk11::Window_Parameters structure:

 // Set callbacks
 parameters.resize_callback = &resize;
 parameters.init_callback = &init;
 parameters.update_callback = &update;
 parameters.render_callback = &render;

Note that in this simple example, all callbacks are global functions. This is convenient for simple applications, but not always the best approach. Fortunately, Tk11 does not require you to use global functions. More on this later.

For more information on callbacks in general, see Callbacks.

Creating the Framework

Finally, when all initialization parameters and callbacks have been set, the application can create a tk11::Framework object. This will initialize the framework:

 // Create framework
 Framework framework;

The constructor of tk11::Framework initializes Direct3D 11, but does not create a rendering window yet. If this process fails, then an exception will be thrown, and all created resources will be released again.

Note that the framework can be configured by passing a tk11::Framework_Parameters structure to its constructor. However, most applications do not need to set framework parameters.

Creating the Window

Once the framework has been created, the application can create rendering windows. Most applications will create only a single window. This is done by constructing a tk11::Window object and passing the tk11::Window_Parameters structure to it, which has been prepared earlier.

 // Create window
 Window window(framework, parameters);

The constructor of tk11::Window creates and displays a window and performs additional Direct3D initialization, such as creating a swap chain. Again, an exception will be thrown if initialization of the window fails.

Also note that the constructor of tk11::Window calls the init callback that our application has defined earlier and registered in the tk11::Window_Parameters structure.

The Main Loop

After the framework and the window have been created, the application may invoke the main loop:

 // Main loop
 framework.run();

This method call does not return until the application exits, or a fatal error occurs (e.g. an exception is thrown and not handled within a callback. See Callbacks for more information on exception handling in callbacks).

The application must therefore do all its remaining initialization before calling tk11::Framework::run. Note, however, that Direct3D 11 resources should be created in the init and resize callbacks.

Error Handling

Finally, let us add some error handling to this simple application. For now we will treat all errors as fatal errors and exit the application when an error occurs. The Tk11 framework is fully exception-neutral and exception-save, so any error which occurs as a result of any operation during creation of the framework or in the main loop, will be propagated to the caller, and resources will be released if necessary. In order to get some diagnostic information about unhandled exceptions, we add the following code blocks to the beginning and end of our application:

 int main() {
   try {

     // ...

     return 0;
   }
   catch(const boost::exception& e) {
     // Show an error dialog with diagnostic information about the exception
     // which occurred.
     // (Only for debugging. In a real application, you may want to translate
     // exceptions into more readable error messages).
     show_error_dialog(diagnostic_information(e));
     return 1;
   }
   catch(const std::exception& e) {
     // Show an error dialog with the name of the exception.
     // (Only for debugging. In a real application, you may want to translate
     // exceptions into more readable error messages).
     show_error_dialog(e.what());
     return 1;
   }
   catch(...) {
     // Show an error dialog.
     // (Only for debugging. In a real application, you may want to translate
     // exceptions into more readable error messages).
     show_error_dialog("Unknown exception");
     return 1;
   }
 }

The show_error_dialog function will display a message box with diagnostic information and also write this information to the standard error stream on the console. This is handy for debugging, but by far not sufficient for providing readable error information to a real user (although most modern games and other professional products display the same kind of Gibberish when they crash).

For more information on error handling, see the the documentation of the Boost.Exception library, which is used by Tk11 (http://www.boost.org/doc/libs/1_42_0/libs/exception/doc/boost-exception.html). See the documentation of the class tk11::Exception (inheritance diagram) to get an overview of the possible errors that can occur in the Tk11 framework.

Conclusion

That's it for this part of the tutorial. Here is the full source code of the example application from this section:

//          Copyright Florian Winter 2010 - 2010.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <tk11/tk11.hpp>

using namespace tk11;


// Resize callback.
// Here we update all variables which depend on the size of the target window,
// such as projection matrices and viewport parameters.
void resize(unsigned int x_resolution, unsigned int y_resolution,
    bool fullscreen, ID3D11Device_ptr device, IDXGISwapChain_ptr swap_chain,
    ID3D11Texture2D_ptr back_buffer)
{}


// Scene initialization callback.
// Here we create resources, such as textures, buffers and shaders.
void init(ID3D11Device_ptr device) {}


// Background color.
// Updated every frame and used for clearing the back buffer.
static float background_color = 0;


// Scene update callback.
// Here we update all variables that change over time.
void update(const Duration& elapsed_time) {
  // Set clear color
  background_color =
    fmod(time_div_float<float>(elapsed_time, seconds(1)), 1.0f);
}


// Scene rendering callback.
// Here we render the scene to the main render target (the back buffer)
void render(ID3D11DeviceContext_ptr context,
    ID3D11RenderTargetView_ptr render_target,
    ID3D11DepthStencilView_ptr depth_stencil)
{
  // Clear render target
  float clear_color_rgba[] = {
    background_color, background_color, background_color, 0
  };
  context->ClearRenderTargetView(render_target.get(), clear_color_rgba);
}


int main() {
  try {
    // Window parameters
    Window_Parameters parameters;
    
    // Set windowed mode with 800x600 resolution
    parameters.x_resolution = 800;
    parameters.y_resolution = 600;
    parameters.fullscreen = false;

    // Set callbacks
    parameters.resize_callback = &resize;
    parameters.init_callback = &init;
    parameters.update_callback = &update;
    parameters.render_callback = &render;

    // Create framework
    Framework framework;

    // Create window
    Window window(framework, parameters);

    // Main loop
    framework.run();

    return 0;
  }
  catch(const boost::exception& e) {
    // Show an error dialog with diagnostic information about the exception
    // which occurred.
    // (Only for debugging. In a real application, you may want to translate
    // exceptions into more readable error messages).
    show_error_dialog(diagnostic_information(e));
    return 1;
  }
  catch(const std::exception& e) {
    // Show an error dialog with the name of the exception.
    // (Only for debugging. In a real application, you may want to translate
    // exceptions into more readable error messages).
    show_error_dialog(e.what());
    return 1;
  }
  catch(...) {
    // Show an error dialog.
    // (Only for debugging. In a real application, you may want to translate
    // exceptions into more readable error messages).
    show_error_dialog("Unknown exception");
    return 1;
  }
}
Tk11 Direct3D 11 Toolkit version 0.2 (SourceForge)
Copyright Florian Winter 2010 - 2010. Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Generated on Sun Apr 11 20:22:59 2010 for Tk11 by  doxygen 1.6.3