Part B - Direct3D

Background Image

Display a static image using Direct3D
Add rendering to the event iteration


Sample | Framework | Coordinator | Design | Object
Display | Graphic | Texture | APIBase | Exercises

The third stage in building a digital game, after determining the user's selected configuration and setting up the main application window, is drawing some image in that window. 

Once the framework has processed all of the messages in the message queue, it is free to draws image.  We call each rendering of some images on the screen a frame.  That is, a frame is what appears on the screen at a particular instant.  We use sprites and textures in rendering a frame.  A sprite is a two-dimensional graphic representation on the frame.  A texture is the version of an image stored directly on video memory. 

This chapter introduces the frame rendering using a single static image.  The image fills the entire client area of the application window.  Since the image is static, we only redraw it after some change to the client area.  For efficient re-drawing, we store a copy as a texture on video memory and use a sprite to transfer the texture from video memory to the screen.


Background Sample

The image for our background sample is a photograph of Stonehenge.  The user can run this sample in either window or full screen mode.  If the user chooses to run in a window and resizes that window, the application stretches the image to fit the resized window. 

stonehenge


Framework

Three framework components are upgraded for this sample:

  • Coordinator - manages the rendering of each frame
  • Design - defines the texture, the sprite that will draw the texture, and the object that ties them together
  • Display - sets up and manages the display device

There are three new components:

  • Object - represents the drawable image with all of its properties
  • Graphic - manages the sprite that draws the texture
  • Texture - represents a copy of the image on video memory

The Graphic and Texture components are vertical components that span the Model and Translation Layers, with separate classes in each layer.  The Translation class has the name of the component with the prefix API.  It is a wrapper on the API calls.  The Model class has the same name as that of its component and is a wrapper on the Translation class.  The Object component belongs entirely to the Model Layer and combines Graphic and Texture to create drawable entities, which the Coordinator object manages.

Background Image Components

The Display component now consists of the APIDisplaySet class and the new APIDisplay class.

The different classes in the Translation Layer share access to common interfaces to the supporting COM objects.  This represents a form of connectivity across the layer.  The shared variables include singleton and interface addresses as well as configuration identifiers.  The framework implements this connectivity using protected class variables within a base class from which the other classes derive.  We call this class APIBase and describe it in detail at the end of this chapter. 

Topics

The three topics introduced with this sample are:

  • buffering
  • texturing
  • colour description
  • connectivity

Buffering

Rendering a frame involves two (or more) buffers on the graphics hardware: the primary buffer and one or more backbuffers.  The primary (or front) buffer contains the pixel data being displayed on the screen.  The backbuffer contains the pixel data that will be displayed at the next frame.  Direct3D draws to the backbuffer.  When the framework has finished drawing a frame to the backbuffer, it uses Direct3D to present the backbuffer to the primary buffer.  Direct3D swaps the addresses of the two buffer: the backbuffer becomes the primary buffer and the primary buffer becomes the new backbuffer. 

Textures

Transferring an image from a file to the backbuffer directly would take considerable time.  For rapid drawing, we first store all images as textures in video memory alongside the frame buffers.  Transferring the textures to the backbuffer only involves the graphics hardware and is an extremely fast operation.  We call this local high-speed operation blitting

In creating textures from image source files, Direct3D accepts a variety of formats, including .bmp, .jpeg, .png, .gif, and .tga.

Colour

There are several ways of representing colour.  Visible light itself is a continuous spectrum of colours.  Direct3D uses the augmented RGB (red, green, blue) additive colour model, which includes a transparency-opacity component.  This model defines colour in terms of four distinct components:

  • R - red (~428 Tera Hertz)
  • G - green (~566 Tera Hertz)
  • B - blue (~638 Tera Hertz)
  • A - alpha - degree of opacity

Hertz is a unit of frequency, which stands for cycles per seconds.  To describe a particular colour, we specify these four values.  A complete specification occupies a 32-bit unsigned integer, with 8 bits allocated for each component.  The range of each component is [0,255][0] stands for the absence of the component.  [255] stands for its full saturation.

Direct3D provides macros for specifying colour for various arrangements of the R, G, B, and A components. 

Translation Settings

The default setting for the background colour in this sample is medium grey:

 // Translation.h

 #define BGROUND_R 200
 #define BGROUND_G 200
 #define BGROUND_B 200

This background colour only appears if the image does not cover the entire client area.  (You can observe this by commenting out the statement that draws the texture in the Object::render() method.) 


Coordinator

The Coordinator component includes management of the display device and housekeeping of the design elements through all possible events.  The Coordinator object creates the The APIDisplay object.  The Design class described below creates the design elements. 

The iCoordinator interface exposes the initialize() method to the Design class:

 // iCoordinator.h

 class iCoordinator {
     virtual void initialize() = 0;
     friend class Design;
     // ...
 };

The Coordinator class includes four new instance pointers and defines the default initialization as a stub:

  • a pointer to the new APIDisplay object
  • a pointer to the Object object
  • a pointer to the Graphic object
  • a pointer to the Texture object
 // Coordinator.h

 class Coordinator : public iCoordinator {

     iAPIDisplay* display; // points to the APIDisplay object
     // ...
   protected:
     iGraphic*    graphic; // points to the sprite manager
     iTexture*    texture; // points to the texture
     iObject*     object;  // points to the object
     void initialize() { }
     // ...
 };

Implementation

Construct

The constructor creates the APIDisplay object and initializes the other pointers:

 Coordinator::Coordinator(void* hinst, int show) {
     coordinator = this;
     window      = CreateAPIWindow(hinst, show);
     userInput   = CreateAPIUserInput(hinst);
     display     = CreateAPIDisplay();
     graphic     = nullptr;
     texture     = nullptr;
     object      = nullptr;
 }

Get Configuration

The getConfiguration() method retrieves the user's selected configuration, releases the current one, re-configures the APIWindow object, sets it up, and finally sets up the APIDisplay object: 

 bool Coordinator::getConfiguration() {
     bool rc = false;
     if (userInput->getConfiguration()) {
         release();
         userInput->configure();
         window->configure(userInput->getWindowMode(),
          userInput->getWindowWidth(), userInput->getWindowHeight());
         if (window->setup()) {
             if (display->setup())
                 rc = true;
             else
                 window->release();
         }
     }
     return rc;
 }

Run

The run() method retrieves the initial configuration, creates the game design, processes the messages in the message queue, and draws the background image:

 int Coordinator::run() {
    int  rc = 0;
    bool keepgoing, draw = true;

    if (keepgoing = getConfiguration())
        initialize();
    while (keepgoing) {
        if (window->processMessage(rc, keepgoing))
            draw = true;
        else if (!active)
            window->wait();
        else if (draw) {
            display->beginDrawFrame();
            object->render();
            display->endDrawFrame();
            draw = false;
        }
    }
    return rc;
}

Note that the rendering of the background object is bounded by calls to start and end the drawing of the frame on the APIDisplay object.

Suspend, Restore, Release

The suspend() method prepares the graphic and texture objects for a loss of connection to the display device:

 void Coordinator::suspend() {
     if (texture) texture->suspend();
     if (graphic) graphic->suspend();
     active = false;
 }

The restore() method resets the connection to the display device:

 void Coordinator::restore() {
     display->restore();
     active = true;
 }

The release() method releases the graphic and texture objects along with the display device:

 void Coordinator::release() {
     if (texture) texture->release();
     if (graphic) graphic->release();
     display->release();
     window->release();
 }

Destroy

The destructor deletes all of the design elements and the display object: 

 Coordinator::~Coordinator() {
     if (object) object->Delete();
     if (texture) texture->Delete();
     if (graphic) graphic->Delete();
     display->Delete();
     userDialog->Delete();
     window->Delete();
     coordinator = nullptr;
 }

Design

The Design component creates the three design elements used in the rendering of the background image. 

The Design class overrides the initialize() method of its Coordinator base class:

 // Design.h

 class Design : public Coordinator {
     // ...
   public:
     Design(void*, int);
     void initialize();
 };

The initialize() method creates the texture, the graphic representation, and the object that ties them together: 

 void Design::initialize() {
     texture = CreateTexture(L"stonehenge.bmp");
     graphic = CreateGraphic();
     object  = CreateObject(graphic);
     object->attach(texture);
 }

Note how the object integrates the texture with the graphic.  The graphic for any object is specified at construction, while the texture may change.  Defining graphics and textures as distinct design elements enables shared use by several objects and thereby minimizes their consumption of video memory.


Object

The Object component describes the structure of a drawable entity.  This component belongs wholly to the Model Layer.  Each instance of the Object class is the drawable entity that the game designer creates, manipulates, and destroys.  Each instance of the Object class uses a Graphic object for its visual representation, which it may share with other instances, and optionally a Texture object that it superimposes on the representation, which it may also share with other instances.  Neither Graphic nor Texture objects are standalone drawable entities in the framework.

The iObject interface exposes three methods to the Coordinator and Design objects:

  • attach() - attaches a texture to the object
  • render() - draws the object
  • Delete() - destroys the object
 // iObject.h

 class iObject {
     virtual void attach(iTexture*) = 0;
     virtual void render()          = 0;
     virtual void Delete() const    = 0;
     friend class Coordinator;
     friend class Design;
 };
 iObject* CreateObject(iGraphic* v);

The Object class contains two instance pointers:

  • a pointer to the Graphic object that holds its graphic representation
  • a pointer to the Texture object that holds the image used, if any
 // Object.h

 class Object : public iObject  {
     iGraphic* graphic; // points to the graphic representation
     iTexture* texture; // points to the attached texture, if any
     virtual   ~Object();
   public:
     Object(iGraphic*);
     Object(const Object&);
     void attach(iTexture*);
     void render();
     void Delete() const { delete this; }
 };

Implementation

Construct

The constructor stores the address of the Graphic representation to be used in the rendering and initializes the texture's address. 

Object::Object(iGraphic* v) : graphic(v), texture(nullptr) { }

Render

The render() method attaches the texture to the drawing pipeline, draws the graphic representation and detaches the texture:

 void Object::render() {
     if (graphic && texture) {
         texture->attach();
         graphic->beginDraw();
         graphic->render();
         graphic->endDraw();
         texture->detach();
     }
 }

Note that the rendering is bound by beginDraw() and endDraw() methods on the graphic representation.

Attach

The attach() method stores the address of the texture that the object will use in drawing itself:

 void Object::attach(iTexture* t) { texture = t; }

Destroy

The destructor does nothing here:

 Object::~Object() { }

Display

The Display component manages the connections to the display devices, which now includes the connection to the selected one at the selected resolution.  The new APIDisplay class augments the APIDisplaySet class which interrogates the host for the configuration options.  The APIDisplay object manages drawing on the selected configuration. 

Display Component

The APIDisplay object accesses the graphics hardware through two COM interfaces:

  • IDirect3D9 - to the Direct3D COM object
  • IDirect3DDevice9 - to the Direct3DDevice COM object

The IDirect3D9 interface provides general support including retrieval of capabilities, enumeration functionality, and creation of a display device.  The IDirect3DDevice9 interface provides HAL access to the display device at a specified resolution mode. 

The iAPIDisplay interface exposes seven methods to the Coordinator and APIUserInput objects:

  • configure() - configures the APIDisplay object
  • setup() - retrieves an interface to the Direct3DDevice COM object
  • beginDrawFrame() - starts the drawing of a frame
  • endDrawFrame() - finishes the drawing of a frame
  • restore() - resets the connection to the Direct3DDevice COM object
  • release() - releases the interface to the Direct3DDevice COM object
  • Delete() - destroys the APIDisplay object
 // iAPIDisplay.h

 class iAPIDisplay {
     virtual void configure(int, int, int) = 0;
     virtual bool setup()                  = 0;
     virtual void beginDrawFrame()         = 0;
     virtual void endDrawFrame()           = 0;
     virtual bool restore()                = 0;
     virtual void release()                = 0;
     virtual void Delete()                 = 0;
     friend class Coordinator;
     friend class APIUserInput;
 };
 iAPIDisplay* CreateAPIDisplay(); 

The APIDisplay class derives from this interface and the APIBase class.  It supports four instance variables:

  • the display, mode, and pixel format identifiers
  • the presentation parameters for creating and resetting the connection to the display device
 // APIDisplay.h

 class APIDisplay : public iAPIDisplay, public APIBase {
     int      displayId;          // display adapter identifier
     int      mode;               // resolution mode identifier
     int      pixel;              // pixel format identifier
     D3DPRESENT_PARAMETERS d3dpp; // parameters for creating/restoring D3D
                                  // display device
     APIDisplay(const APIDisplay& d);            // prevents copying
     APIDisplay& operator=(const APIDisplay& d); // prevents assignments
     virtual ~APIDisplay();
   public:
     APIDisplay();
     void configure(int, int, int);
     bool setup();
     void beginDrawFrame();
     void endDrawFrame();
     bool restore();
     void release();
     void Delete() { delete this; }
 };

Implementation

Construct

The constructor initializes the pointers and the default window dimensions, all of which are members of the APIBase class: 

 APIDisplay::APIDisplay()  {
     display = this;
     d3dd    = nullptr;
     width   = WND_WIDTH;
     height  = WND_HEIGHT;
 }

These four variables are shared with other classes in the Translation Layer as protected variables of the APIBase class.

Configure

The configure() method sets the display identifiers to the received values in preparation for setup: 

 void APIDisplay::configure(int a, int m, int p) {
     displayId = a;
     mode      = m;
     pixel     = p;
 }

Setup

The setup() method creates the connection to the selected display device.  The EnumAdapterModes() method retrieves the properties of the selected device at the selected mode by populating a local instance of the D3DDISPLAYMODE struct.  setup() uses these properties to populate the d3dpp instance variable.  Finally, setup() calls the CreateDevice() method on the Direct3D COM object to retrieve an interface to the display device: 

 bool APIDisplay::setup() {
     bool rc = false;
     UIN adapter;
     D3DFORMAT d3dFormat;

     ZeroMemory(&d3dpp, sizeof d3dpp);
     d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
     d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
     d3dpp.BackBufferCount = 1;
     D3DDISPLAYMODE d3ddm;
     if (!runInWindow) {
         D3DFORMAT Format[] = D3D_DOC_FORMATS;
         d3dFormat = Format[pixel];
         if (FAILED(d3d->EnumAdapterModes(display, d3dFormat, mode, &d3ddm))) {
            error(L"APIDisplay::10 Failed to get selected display mode");
            error(L"APIDisplay::11 Defaulting to windowed mode");
            runinwndw = true;
         }
         else {
             adapter   = displayId;
             width     = d3ddm.Width;
             height    = d3ddm.Height;
             d3dFormat = d3ddm.Format;
             d3dpp.FullScreen_RefreshRateInHz = d3ddm.RefreshRate;
         }
     }
     if (runinwndw) {
         adapter = D3DADAPTER_DEFAULT;
         d3d->GetAdapterDisplayMode(adapter, &d3ddm);
         d3dpp.Windowed = TRUE;
         d3dFormat = d3ddm.Format;
     }
     d3dpp.BackBufferWidth  = width;
     d3dpp.BackBufferHeight = height;
     d3dpp.BackBufferFormat = d3dFormat;

     if (d3dd)
         error(L"APIDisplay::11 Pointer to Direct3D interface is not null");
     else if (FAILED(d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, (HWND)hwnd,
      D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd)))
         error(L"APIDisplay::12 Failed to create Direct3D device");
     else
         rc = true;
     return rc;
 }

ZeroMemory() initializes the memory pointed to by the first argument and extending according the second argument.  If the Direct3D object fails to return an interface to the requested display device, setup() reports an error and returns false

Begin Draw Frame

The beginDrawFrame() method clears the backbuffer and starts the drawing process:

 void APIDisplay::beginDrawFrame() {
     if (d3dd) {
         d3dd->Clear(0, nullptr, D3DCLEAR_TARGET,
          D3DCOLOR_XRGB(BGROUND_R, BGROUND_G, BGROUND_B), 1.0, 0);
         d3dd->BeginScene();
     }
 }

The Clear() method on the device fills the backbuffer with pixels of the specified color.  The D3DXCOLOR_XRGB() macro defines the background color from a BGROUND_R red, a BGROUND_G green, and a BGROUND_B blue component.  The range of each component is [0,255], where [0] represents perfect absence and [255] represents full saturation.  The X in the macro identifier denotes the absence of an alpha component in the description.  The BeginScene() method on the display device starts drawing operations to the backbuffer.  We call this method before sending any further information to the backbuffer. 

End Draw Frame

The endDrawFrame() method concludes the drawing process and presents the backbuffer to the primary (front) buffer:

 void APIDisplay::endDrawFrame() {
     if (d3dd) {
         d3dd->EndScene();
         if (FAILED(d3dd->Present(nullptr, nullptr, nullptr, nullptr)))
             error(L"APIDisplay::40 Failed to flip backbuffer");
     }
 }

The EndScene() method on the display device informs the device that the drawing process has finished.  The Present() method on the display device flips the backbuffer with the primary buffer causing the new frame to appear on the screen.

Restore

The restore() method resets the connection to the selected display device.  This method checks the device's cooperative level to see if it needs resetting and if so resets the connection using the Reset() method on the device and the presentation parameters stored in the instance variable:

 bool APIDisplay::restore() {
     bool rc = false;

     if (d3dd) {
         HRESULT hr = d3dd->TestCooperativeLevel();
         if (hr == S_OK) rc = true;
         else {
             if (hr == D3DERR_DEVICELOST)
                 while (hr != D3DERR_DEVICENOTRESET)
                     hr = d3dd->TestCooperativeLevel()();
             hr = d3dd->Reset(&d3dpp);
             rc = SUCCEEDED(hr);
         }
     }
     return rc;
 }

The TestCooperativeLevel() method on display device reports its cooperative level.  If the device is lost and can be restored, this method returns D3DERR_DEVICENOTRESET.  In that case, we try to reset the device. 

Release

The release() method disengages the interface to the display device and resets the pointer value:

 void APIDisplay::release() {
     if (d3dd) {
         d3dd->Release();
         d3dd = nullptr;
     }
 }

Graphic

The Graphic component manages the graphical representations used by the drawable objects.  This component consists of the Graphic class in the Model Layer and the APIGraphic class in the Translation Layer.  The Design component refers to graphic representations through Graphic objects alone.  These objects communicate with the supporting API through the underlying APIGraphic objects. 

iDisplay Interface

The APIGraphic objects accesses the graphics hardware through interfaces to three COM objects:

  • IDirect3DDevice9 - to the Direct3D display object
  • ID3DXSprite - to the Direct3D sprite object
  • IDirect3DTexture9 - to the Direct3D texture object

The D3DXSprite object draws 2D images to the backbuffer.

The iGraphic interface exposes six methods to the Design, Coordinator, and Object classes:

  • beginDraw() - begins drawing the graphic representation
  • render() - draws the graphic representation
  • endDraw() - ends the drawing of the graphic representation
  • suspend() - suspends the connection to the display device
  • release() - releases the connection to the graphics hardware
  • Delete() - destroys the graphic representation
 // iGraphic.h

 class iGraphic {
     virtual void beginDraw()           = 0;
     virtual void render()              = 0;
     virtual void endDraw()             = 0;
     virtual void suspend()             = 0;
     virtual void release()             = 0;
     virtual void Delete() const        = 0;
     friend class Coordinator;
     friend class Design;
     friend class Object;
 };
 iGraphic* CreateGraphic();

The iAPIGraphic interface exposes seven methods to the Graphic class:

  • clone() - creates a clone of the graphic translation
  • beginDraw() - begins drawing the graphic representation
  • render() - draws the graphic representation
  • endDraw() - ends the drawing of the graphic representation
  • suspend() - suspends the connection to the display device
  • release() - releases the connection to the graphics hardware
  • Delete() - destroys the graphic representation
 // iAPIGraphic.h

 class iAPIGraphic {
     virtual iAPIGraphic* clone() const = 0;
     virtual void beginDraw()           = 0;
     virtual void render()              = 0;
     virtual void endDraw()             = 0;
     virtual void suspend()             = 0;
     virtual void release()             = 0;
     virtual void Delete() const        = 0;
     friend class Graphic;
 };
 iAPIGraphic* CreateAPIGraphic();

The APIGraphic class derives from this interface and the APIBase class and contains two instance variables:

  • a pointer to the ID3DXSprite interface
  • a flag reporting the state of readiness for rendering
 // APIGraphic.h

 class APIGraphic : public iAPIGraphic, public APIBase {
     ID3DXSprite* sprite;  // points to the interface to sprite COM object
     bool         ready;   // sprite is ready to display an image?
     virtual ~APIGraphic();
     void    setup();
 public:
     APIGraphic();
     APIGraphic(const APIGraphic&);
     APIGraphic& operator=(const APIGraphic&);
     iAPIGraphic* clone() const { return new APIGraphic(*this); }
     void beginDraw();
     void render();
     void endDraw();
     void suspend();
     void release();
     void Delete() const { delete this; }
 };

Construct

The constructor initializes the instance variables: 

 APIGraphic::APIGraphic() : sprite(nullptr), ready(false) { }

Setup

The setup() method retrieves an interface to the D3DXSprite COM object if one doesn't exist, but if one exists simply resets its connection to the display device:

 void APIGraphic::setup() {
      if (!sprite) {
          if (FAILED(D3DXCreateSprite(d3dd, &sprite)))
              error(L"APIGraphic::10 Failed to create sprite COM object");
          else
              ready = true;
      }
      else {
          sprite->OnResetDevice();
          ready = true;
      }
 }

D3DXCreateSprite() retrieves the interface to the D3DXSprite COM object on the device identified by the first argument.  The OnResetDevice() method on the interface re-acquires the connection to the display device.  We call his method every time that we have reset the display device. 

Begin Draw

The beginDraw() method prepares the D3DXSprite COM object for the drawing process:

 void APIGraphic::beginDraw() {
     if (!ready) setup();
     if (d3dd && sprite) sprite->Begin(D3DXSPRITE_ALPHABLEND);
 }

The Begin() method initailizes the D3DXSprite COM object for drawing on the display device.  We call this method inside the BeginScene() - EndScene() pair on the display device.  The argument accommodates the drawing of translucent images.

Render

The render() method uses the D3DXSprite COM object to draw the image pointed to by texture:

 void APIGraphic::render() {
     if (!ready) setup();
     if (d3dd && sprite) {
         D3DXVECTOR3 topLeft(0, 0, 1.f);
         sprite->Draw(texture, nullptr, nullptr, &topLeft,
          D3DCOLOR_RGBA(255, 255, 255, '\xFF'));
     }
 }

The Draw() method on the D3DXSprite COM object takes as its first argument the address of the texture stored on video memory, as its fourth argument the screen coordinates of the image's top left corner, and as its fifth argument the colour to be used in modulating the texture colour.  The arguments to the D3DCOLOR_RGBA() macro preserve the texture colouring without modulation. 

End Draw

The endDraw() method informs the D3DXSprite COM object that the drawing process has finished: 

 void APIGraphic::endDraw() { if (sprite) sprite->End(); }

We call the [BeginDraw(), EndDraw()] pair on the D3DXSprite COM object inside the [BeginScene(), EndScene()] pair on the Direct3DDevice COM object.

Suspend and Release

The suspend() method informs the D3DXSprite COM object that it is about to lose its connection to the display device:

 void APIGraphic::suspend() {
     if (sprite) sprite->OnLostDevice();
     ready = false;
 }

The OnLostDevice() method on the D3DXSprite COM object frees video memory resources for use by another application.  We call this method whenever a display device is about to be lost and before its connection is reset.

The release() method releases the interface to the D3DXSprite COM object:

 void APIGraphic::release() {
      suspend();
      if (sprite) {
          sprite->Release();
          sprite = nullptr;
      }
 }

Destroy

The destructor releases the APIGraphic object itself:

 APIGraphic::~APIGraphic() { release(); }

Texture

The Texture component manages the images on video memory.  This component consists of the Texture class in the Model Layer and the APITexture class in the Translation Layer.  The Design component refers to textures through Texture objects alone.  These objects communicate with the supporting API through the underlying APITexture objects. 

Texture Component

The APITexture object accesses interfaces to two distinct COM objects:

  • IDirect3DDevice9 - to the Direct3DDevice COM object
  • IDirect3DTexture9 - to the Direct3DTexture COM object

The iTexture interface exposes five methods to the to the Design, Coordinator, and Object classes:

 // iTexture.h

 class iTexture {
     virtual void attach()              = 0;
     virtual void detach()              = 0;
     virtual void suspend()             = 0;
     virtual void release()             = 0;
     virtual void Delete() const        = 0;
     friend class Coordinator;
     friend class Design;
     friend class Object;
 };
 iTexture* CreateTexture(const wchar_t*);

The iAPITexture interface exposes six methods to the Texture class:

 // iAPITexture.h

 class iAPITexture {
     virtual iAPITexture* clone() const = 0;
     virtual void attach()              = 0;
     virtual void detach()              = 0;
     virtual void suspend()             = 0;
     virtual void release()             = 0;
     virtual void Delete() const        = 0;
     friend class Texture;
 };
 iAPITexture* CreateAPITexture(const wchar_t*);

The APITexture class derives from this interface and the APIBase class and contains two instance pointers:

  • a pointer to the name of the file that contains the image to be copied
  • a pointer to the IDirect3DTexture9 interface that accesses the texture on video memory
 // APITexture.h

 class APITexture : public iAPITexture, public APIBase {
     wchar_t*           file; // points to file with texture image
     IDirect3DTexture9* tex;  // interface to the texture COM object
     void    setup();
     virtual ~APITexture();
   public:
     APITexture(const wchar_t*);
     APITexture(const APITexture&);
     iAPITexture& operator=(const APITexture&);
     iAPITexture* clone() const { return new APITexture(*this); }
     void attach();
     void detach();
     void suspend();
     void release();
     void Delete() const { delete this; }
 };

Construct

The constructor stores the name of the file that holds the image and initializes the pointer to the texture interface.  The copy constructor calls the assignment operator.  The assignment operator suspends the connection and performs a deep copy on the file name:

 APITexture::APITexture(const wchar_t* file) {
     if (file) {
         int len = strlen(file);
         this->file = new wchar_t[len + 1];
         strcpy(this->file, file, len);
     }
     else
         this->file = nullptr;
     tex = nullptr;
 }

 APITexture::APITexture(const APITexture& src) {
     file  = nullptr;
     tex   = nullptr;
     *this = src;
 }

 iAPITexture& APITexture::operator=(const APITexture& src) {
     if (this != &src) {
         suspend();
         delete [] file;
         if (src.file) {
             int len = strlen(src.file);
             file    = new wchar_t[len + 1];
             strcpy(file, src.file, len);
         }
         else
             file = nullptr;
     }
     return *this;
 }

Setup

The setup() method creates a texture on video memory:

 void APITexture::setup() {
     if (file && FAILED(D3DXCreateTextureFromFileEx(d3dd, file,
      width, height, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
      D3DX_DEFAULT, D3DX_DEFAULT, 0, nullptr, nullptr, &tex))) {
         error(L"APITexture::11 Failed to create texture COM object from file");
         tex = nullptr;
     }
 }

D3DXCreateTextureFromFile() retrieves an interface to the Direct3DTexture COM object, copies the image stored in file to video memory, and stretches the image to fit the width and height dimensions stored in the APIBase class.  This function supports the following file formats: .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga. 

Attach and Detach

The attach() method associates the texture with the drawing pipeline, making it the one to be used in subsequent drawing:

 void APITexture::attach() {
     if (!tex) setup();
     if (tex) texture = tex;
 }

The detach() method dissociates the texture from the drawing pipeline:

 void APITexture::detach() { texture = nullptr; }

Suspend and Release

The suspend() method releases the interface to the texture COM object just before loss of focus:

 void APITexture::suspend() {
     if (tex) {
         tex->Release();
         tex = nullptr;
     }
 }

The release() method suspends the APITexture object:

 void APITexture::release() { suspend(); }

Destroy

The destructor releases the APITexture object:

 APITexture::~APITexture() { release(); }

APIBase

The APIBase class holds the variables for the Translation Layer that the APIDisplay, APIGraphic, APITexture, APIUserInput, and APIWindow classes share with one another.  The APIBase class embodies the connectivity across the Translation Layer and serves as the base class for its derived classes. 

Translation Layer Connectivity

The APIBase class contains eleven class variables:

  • pointers that holds the addresses of API* objects in the Translation Layer
  • pointers to the application and its window
  • pointers that hold the addresses of interfaces to API COM objects
  • dimensions of the application window and its mode
 // APIBase.h

 class APIBase {
   protected:
     static iAPIDisplaySet*    displaySet;  // the attached displays
     static iAPIDisplay*       display;     // the graphics card object
     static iAPIWindow*        window;      // the application window
     static void*              application; // points to the application
     static void*              hwnd;        // handle to application window
     static IDirect3D9*        d3d;         // points to Direct3D object
     static IDirect3DDevice9*  d3dd;        // points to Direct3D display
     static IDirect3DTexture9* texture;     // points to the drawing texture
     static int                width;       // width of the client area
     static int                height;      // height of the client area
     static bool               runinwndw;   // running in a window?
 public:
     void error(const wchar_t*, const wchar_t* = 0) const;
     void logError(const wchar_t*) const;
 };

Implementation

All class variables are initially zero-valued. 

 // APIBase.cpp

 iAPIDisplaySet*     APIBase::displaySet  = nullptr;
 iAPIDisplay*        APIBase::display     = nullptr;
 iAPIWindow*         APIBase::window      = nullptr;
 void*               APIBase::application = nullptr;
 void*               APIBase::hwnd        = nullptr;
 IDirect3D9*         APIBase::d3d         = nullptr;
 IDirect3DDevice9*   APIBase::d3dd        = nullptr;
 IDirect3DTexture9*  APIBase::texture     = nullptr;
 int                 APIBase::width       = 0;
 int                 APIBase::height      = 0;
 bool                APIBase::runinwndw   = false;

Error

The error() method concatenates the error strings if there are two, pops up a message box with the concatenated string, and logs the error:

 void APIBase::error(const wchar_t* msg, const wchar_t* more) const {

     int len = strlen(msg);
     if (more) len += strlen(more);
     wchar_t* str = new wchar_t[len + 1];
     strcpy(str, msg, len);
     if (more) strcat(str, more, len);
     if (hwnd) MessageBox((HWND)hwnd, str, L"Error", MB_OK);
     logError(str);
     delete [] str;
 }

Log Error

The logError() method appends a record consisting of the error string to the error.log file:

 void APIBase::logError(const wchar_t* msg) const {
     std::wofstream fp("error.log", std::ios::app);
     if (fp) {
          fp << msg << std::endl;
          fp.close();
     }
 }

Exercises

  • Read MSDN about sprites here
  • Read MSDN on IDirect3D9::EnumAdapterModes()
  • Read MSDN on IDirect3D9::CreateDevice()
  • Read MSDN on IDirect3DDevice9::Reset()
  • Read the DirectX documentation on the D3DXSPRITE
  • Change the Design object to display the background image within the top left quadrant of the application window
  • Change the APITexture object to display the background image 20 pixels down and to the right of the top left corner of the application window