Using DirectDraw in Document/View Architecture



Click here for a larger image.

Environment: VC 6, DirectX 7.0 or above, Windows 2000/XP

Preface

I know there are a bunch of classes and related articles written in this DirectX section of www.codeguru.com. So this article is a simple tutorial for people who just started programming in DirectDraw. I hope you guys will enjoy this tutorial.

I was working on some design for Galloping Ghost Productions when I discovered there is a way to integrate a DirectDraw interface into Document/View architecture. People might ask, why add an DirectDraw interface into the view object when a full-screen DirectDraw application can be done? Yes, doing a windowed DirectDraw application is not as cool as doing a full-screen hardcore DirectDraw application. However, you have some advantages when designing a windowed application:

  • It is easier for debugging.
  • It can create an interface for using DirectShow interfaces.
  • It can be used to design windowed games using DirectDraw and Direct3D. KOEI's "Romance of Three Kingdoms" used windowed applications.
  • The concept of this tutorial can integrate into other concepts in www.codeguru.com. For example, there is a small class that can be used for designing screen savers. You can integrate the stuff in this tutorial into that to create DirectDraw-based screen savers.
  • ......

Anyway, let's start the tutorial.

Setup

We all know the CView class is a derivative of the CWnd class. Thus it has all the physical attributes of a window. So it is really not hard at all to add a DirectDraw interface into the view class derived from CView. I find it is much easier to program than doing full-screen DirectDraw. I used to spend hours correcting problems with SetDisplayMode() or SetCooperationLevel(), or problems with off-screen surfaces. All these problems are due to the problem of debugging. It is really hard to debug under full-screen mode. This problem can be eliminated by using windowed mode DirectDraw and with the debugging tools provided by MFC classes. I am wasting time here; sorry.

  1. First, you have to make some slight modifications to Visual C++ 6.0. I am certain that when you installed DirectX 7.0 SDK or above, the environmental variables in Visual C++ are automatically set up correctly. If, after you download my code and compile it and there are errors during linking or compiling, you need to do some tuning:
    1. Go to Tools, Options.
    2. Select the Directories tab.
    3. Select "Include files" in the "Show directories for" list box.
    4. Add the INCLUDE directory of DirectX on top of all directories. For example, if you installed DirectX 7.0 in C:\DXSDK, there is a directory inside called "include", which is C:\DXSDK\INCLUDE; put this directory on top of all other directories shown in the list box. See the following figure:



    5. Do the same thing with the LIB directory by selecting "Library files" in "Show directories for"; then find the LIB directory in your DirectX SDK directory, For example, if you installed DirectX 7.0 in C:\DXSDK, then there is a directory inside called "include", which is C:\DXSDK\LIB, put this directory on top of all other directories shown in the list box. See the following figure:



      Then, click OK.
  2. If there still are problems in linking, add some more things to the project settings:
    1. Go to Project, Settings.
    2. Select the Link tab.
    3. In "Object/library modules", add "ddraw.lib" and "dxguid.lib". See the following figure:



      Click here for a larger image.

      Then click OK.
  3. Now you can download my code, compile it, and run.

My Code

I wrote this CDDrawSystem class (located in DDrawSystem.h and DDrawSystem.cpp files) to display graphics using DirectDraw. Basically it has:

  1. Constructor, Destructor.
  2. Init() function that creates the IDirectDraw7 interface, a primary surface buffer, a back surface buffer, and a clipper for the primary surface buffer. It is self explanatory. I grabbed the code from DDUTIL.H and DDUTIL.CPP, which are from Microsoft, along with the rest of the DirectX SDK.
  3. Terminate() function that terminates all objects using the COM method release().
  4. Clear() function that clears the primary surface and the back surface buffer by blit color 0 (black) to these two surfaces.
  5. Display() function will blit the back surface buffer to the primary surface and thus the graphic will be displayed. The problem with Display is that you cannot blit using absolute coordinates. You have to know exactly where your view (which is the client window) is, and blit the graphics to this view area. So this is how I designed the function:
  6. void CDDrawSystem::Display()
    {
      HRESULT hRet;
    
      RECT rt;
      POINT p = {0, 0};
    
      ClientToScreen(hWnd, &p);
    // get the client area on
    // the desktop by using this line.
      rt.left = 0 + p.x;
      rt.top = 0 + p.y;
      rt.right = 800 + p.x;
      rt.bottom = 600 + p.y;
    
    // to ensure the drawing is complete, we use loops to continue
    // the drawing until the blitting returns DD_OK or it is not
    // DDERR_WASSTILLDRAWING
    
      while( 1 )
        {
        hRet = m_pddsFrontBuffer->Blt(&rt,
        m_pddsStoreBuffer,
        NULL,
        DDBLT_WAIT,
        NULL);
    
        if (hRet == DD_OK)
          break;
        else if(hRet == DDERR_SURFACELOST)
        {
          m_pddsFrontBuffer->Restore();
          m_pddsStoreBuffer->Restore();
        }
        else if(hRet != DDERR_WASSTILLDRAWING)
        return;
      }
    }
    
  7. In addition to these functions, you can add new functions such as load bitmap files, bit blit bitmaps from off-screen surfaces, and draw text, primitive geometric shapes, and lock surface and do crazy stuff.

I wrote this TestDraw() function just to show you how to do primitive drawings on back screen surfaces and blit them to the primary surface. It will display "This is a stinky App" at coordinate (20, 20). Then, if you click anywhere in the client area, it will draw a big white circular dot (about 50 pixels in radius).

Now, the Complete Code

// DDrawSystem.h: interface for the CDDrawSystem class.
//
////////////////////////////////////////////////////////////////

#if !defined(AFX_DDRAWSYSTEM_H__1E152EB4_ED1D_4079_BDD4_773383DD98C8
    __INCLUDED_)
#define AFX_DDRAWSYSTEM_H__1E152EB4_ED1D_4079_BDD4_773383DD98C8
    __INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <ddraw.h>

#define _CHARACTORBUILDER_
#include "../GameLib/Image.h"

class CDDrawSystem
{
public:
  CDDrawSystem();
  virtual ~CDDrawSystem();

  BOOL Init(HWND hWnd);
  void Terminate();
  void Clear();
  void TestDraw(int x, int y);
  void Display();

protected:
  LPDIRECTDRAW7 m_pDD;
  LPDIRECTDRAWSURFACE7 m_pddsFrontBuffer;
  LPDIRECTDRAWSURFACE7 m_pddsStoreBuffer;
  LPDIRECTDRAWCLIPPER pcClipper;

  HWND hWnd;
};

#endif

//!defined(AFX_DDRAWSYSTEM_H__1E152EB4_ED1D_4079_BDD4_
773383DD98C8__INCLUDED_)
// DDrawSystem.cpp: implementation of the CDDrawSystem class.
//
////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DDrawSystem.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


////////////////////////////////////////////////////////////////
// Construction/Destruction
////////////////////////////////////////////////////////////////

CDDrawSystem::CDDrawSystem()
{
  m_pDD = NULL;
  m_pddsFrontBuffer = NULL;
  m_pddsStoreBuffer = NULL;
  pcClipper = NULL;
}

CDDrawSystem::~CDDrawSystem()
{
  Terminate();
}

// old DirectDraw Initialization stuff.
// Set a window mode DirectDraw Display.
BOOL CDDrawSystem::Init(HWND hWnd)
{
  HRESULT hRet;

  this->hWnd = hWnd;

  hRet = DirectDrawCreateEx(NULL, (VOID**)&m_pDD,
    IID_IDirectDraw7, NULL);

  if(hRet != DD_OK)
  {
    AfxMessageBox("Failed to create directdraw object.");
    return FALSE;
  }

  hRet = m_pDD->SetCooperativeLevel(hWnd, DDSCL_NORMAL);
  if(hRet != DD_OK)
  {
    AfxMessageBox("Failed to set directdraw display behavior.");
    return FALSE;
  }

  HRESULT hr;

  DDSURFACEDESC2 ddsd;
  ZeroMemory( &ddsd, sizeof( ddsd ) );
  ddsd.dwSize = sizeof( ddsd );
  ddsd.dwFlags = DDSD_CAPS;
  ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

  if(FAILED(hr = m_pDD->CreateSurface(&ddsd,
    &m_pddsFrontBuffer, NULL)))
  {
    AfxMessageBox("Failed to create primary surface.");
    return FALSE; 
    }


  // Create the backbuffer surface
  ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; 
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | 
    DDSCAPS_3DDEVICE;
  ddsd.dwWidth = 800;
  ddsd.dwHeight = 600;

  if(FAILED(hr = m_pDD->CreateSurface(&ddsd, 
    &m_pddsStoreBuffer, NULL)))
  {
    AfxMessageBox("Failed to create back buffer surface.");
    return FALSE; 
  }

  if(FAILED(hr = m_pDD->CreateClipper(0, &pcClipper, NULL)))
  {
    AfxMessageBox("Failed to create clipper.");
    return FALSE; 
  }

  if(FAILED(hr = pcClipper->SetHWnd(0, hWnd)))
  {
    pcClipper->Release();
    AfxMessageBox("Failed to create primary surface.");
    return FALSE; 
  }
if(FAILED(hr = m_pddsFrontBuffer->SetClipper(pcClipper))) { pcClipper->Release(); AfxMessageBox("Failed to create primary surface."); return FALSE; } return TRUE; } // make sure all things are terminated and set to NULL // when application ends. void CDDrawSystem::Terminate() { if (m_pDD != NULL) { if (m_pddsFrontBuffer != NULL) { if (m_pddsStoreBuffer != NULL) { m_pddsStoreBuffer->Release(); m_pddsStoreBuffer = NULL; } if (pcClipper != NULL) { pcClipper->Release(); pcClipper = NULL; } m_pddsFrontBuffer->Release(); m_pddsFrontBuffer = NULL; } m_pDD->Release(); m_pDD = NULL; } } // clear both off csreen buffer and primary buffer. void CDDrawSystem::Clear() { HRESULT hRet; DDBLTFX fx; fx.dwSize = sizeof(fx); fx.dwFillColor = 0x000000; while (1) { hRet = m_pddsFrontBuffer->Blt(NULL, NULL, NULL, DDBLT_COLORFILL, &fx); if (hRet == DD_OK) break; else if (hRet == DDERR_SURFACELOST) { m_pddsFrontBuffer->Restore(); } else if (hRet != DDERR_WASSTILLDRAWING) break; } while (1) { hRet = m_pddsStoreBuffer->Blt(NULL, NULL, NULL, DDBLT_COLORFILL, &fx); if (hRet == DD_OK) break; else if (hRet == DDERR_SURFACELOST) { m_pddsStoreBuffer->Restore(); } else if (hRet != DDERR_WASSTILLDRAWING) break; } } // a test: // The conclusion is: Under no circumstance, draw directly to // primary Surface! // It doesn't work that way. // ... // ... // This is just a simple test function. It has shit use in this // project. void CDDrawSystem::TestDraw(int x, int y) { HRESULT hRet; HDC dc; hRet = m_pddsStoreBuffer->GetDC(&dc); if (hRet != DD_OK) return; POINT p = {0 + x, 0 + y}; ClientToScreen(hWnd, &p); SetTextColor(dc, RGB(255, 0, 0)); TextOut(dc, 20, 20, "This is a stinky App", lstrlen("This is a stinky App")); Ellipse(dc, x-50, y-50, x+50,y+50); m_pddsStoreBuffer->ReleaseDC(dc); } // Load images from offscteen buffer to primary buffer // and for display. void CDDrawSystem::Display() { HRESULT hRet; RECT rt; POINT p = {0, 0}; ClientToScreen(hWnd, &p); rt.left = 0 + p.x; rt.top = 0 + p.y; rt.right = 800 + p.x; rt.bottom = 600 + p.y; while(1) { hRet = m_pddsFrontBuffer->Blt(&rt, m_pddsStoreBuffer, NULL, DDBLT_WAIT, NULL); if (hRet == DD_OK) break; else if(hRet == DDERR_SURFACELOST) { m_pddsFrontBuffer->Restore(); m_pddsStoreBuffer->Restore(); } else if(hRet != DDERR_WASSTILLDRAWING) return; } }

Downloads

Download source - 39 Kb