Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A 3D-Enabled View Base Class for SDI Direct3D Development

0.00/5 (No votes)
6 Feb 2005 6  
Definition of the CDSSD3DView8 class, a CView-derived class to facilitate Direct3D development with SDI.

Contents

Introduction

This article presents the CDSSD3DView8 class, a CView-derived class that provides Direct3D support for use in MFC Single Document Interface (SDI) applications. The CDSSD3DView8 class is designed to be used instead of CView as the base class for a developer's primary SDI View class.

The goal of this class is to provide functionality similar to the familiar Direct3D SDK CD3DApplication class, but still allow the user the ease-of-use provided by a Visual C++ AppWizard-generated SDI application. The class is also designed so that users don't have to "tack on" Direct3D support in their SDI applications. This support is built right into the View class itself.

Searching the internet, I've found a few examples describing how to use OpenGL with SDI or how to mingle Direct3D with MFC, but nothing providing a generic CView-derived class that can serve as a launching point for developing Direct3D applications using Visual Studio's Single Document Interface functionality. So I thought I'd present a class I've been using for my own development.

Pros and Cons

The pros and cons are a direct result of using SDI to build a Direct3D application. 3D rendering will be slightly slower than a "normal" Direct3D SDK application. There is no full-screen support, since SDI applications don't run full-screen. There's also no enumeration of adapters and display modes. But since an SDI application built with the CDSSD3DView8 class will always use the desktop as its target, there's no need for such support.

On the positive side, by developing an SDI application, you have available all of the AppWizard and ClassWizard support for ease of development. Although MFC/SDI may not be desired for game development, it makes development of tools for those games much easier. For example, I use the CDSSD3DView8 class as the base class for my main View for a DirectX Mesh editor that I'm diddling around with. It's far easier to use Visual C++ to create the supporting dialogs, menus, toolbars, message handlers, etc. etc. than it would be if using the DirectX SDK interface. I shudder to think of coding these resources by hand...

What's Included

The CDSSD3DView8 class is defined in files DSSD3DView8.h and DSSD3DView8.cpp.

Also included is a sample program that renders a simple cube (the "Hello, world" of 3D development) under a flickering light to show how the CDSS3DView8 class can be used as a base class (instead of CView) for an SDI application. All source code for this sample program has been included.

This class and the included sample program use DirectX 8.0. The program was written using Visual C++ 6.0, and tested on Windows XP platform with an NVIDIA GeForce4 MX440-SE video card. The class isn't very complex, so it can easily be adapted for later versions of DirectX. In addition, the MX440-SE is a bit dated, so there should be no trouble running the sample on a more advanced card.

Requirements

This article assumes that the reader has a basic understanding of Direct3D and how it is accessed using the MSSDK DirectX toolkit. This isn't meant as a Direct3D primer, since there are mountains of articles and books available on the subject.

As mentioned, the CDSSD3DView8 class uses DirectX 8.0. So, you'll obviously need to have the DirectX 8.0 SDK toolkit and a compatible 3D video card installed. In addition, since the application was built using Visual C++ 6.0, I'd recommend that as the development platform. I haven't tested this in any other Visual Studio environment.

Setting Up the SDI Project

To create your own SDI application using the CDSSD3DView8 class, just run through the standard AppWizard pages to generate your SDI application's source code. When selecting the base View class, you'll have to select CView. All other SDI options are entirely up to the user.

Once the application code has been created, the user will have to copy the CDSSD3DView8 header and source files to the application directory and make a few simple manual changes to the generated View Header and Source files to support CDSSD3DView8 as the base class. In addition, a few changes to the Project Settings is required.

Setting Up the View Header File

At the start of the AppWizard-generated View header file, you'll have to add the reference to CDSSD3DView8's header file:

#include "DSSD3DView8.h"

Next, change the View class' declaration so that it derives from CDSSD3DView8 rather than CView. CDSSD3DView8 derives directly from CView, so all CView methods and data will still be available to the user. The example program's view class is shown here:

class CD3D8SDIView : public CDSSD3DView8

Finally, you'll want to delete the declaration for OnDraw that was created by AppWizard. A full implementation of OnDraw is contained in the CDSSD3DView8 class. Details of this implementation are discussed below. Suffice to say, however, that derived View classes won't need their own implementation of OnDraw.

Setting Up the View Source File

A few more manual changes are required in the generated .cpp file. The bulk of these are simple search-and-replace operations.

The first two changes are to the ClassWizard macros. Note that once these two changes are made, any handlers added via ClassWizard will automatically include a call to the corresponding member function in CDSSD3DView8.

IMPLEMENT_DYNCREATE(CD3D8SDIView, CDSSD3DView8)
BEGIN_MESSAGE_MAP(CD3D8SDIView, CDSSD3DView8)

Next, you'll want to delete the implementation of OnDraw. As mentioned above, View classes derived from CDSSD3DView8 will not need to provide their own OnDraw implementation.

Finally, you'll want to search for all instances of "CView::" base class calls and replace them with "CDSSD3DView8::". This redirects the AppWizard-generated handlers to call the CDSSD3DView8 base class instead of CView.

The Project Settings

Finally, you may have to make a few changes to your Project->Settings. Under the C++ tab, check the Preprocessor category. If you haven't already specified the paths to the DirectX 8.0 SDK under Tool->Options, then you'll want to add those paths here.

Next, under Project->Settings, select the Link tab and add "d3dx8.lib d3dxof.lib d3d8.lib dxerr8.lib winmm.lib dxguid.lib" to the list of Object/Library modules.

That should take care of setting up your SDI project to include the CDSSD3DView8 class. You're ready to start coding! If this is a new application, build and run it. You'll be presented with the usual SDI frame, menus, toolbar, and status bar. The client area, however, should be pitch black. That's the underlying CDSS3DView8 class clearing the backbuffer and presenting the scene to the client area.

These next few sections go into more details on how to use the CDSSD3DView8 class' simple API for your own application development.

The Code

This section discusses various aspects of the CDSSD3DView8 interface that is exposed to the application programmer as well as some of the internal private data and methods that provide the underlying Direct3D support. First, we list the API that is exposed for use by derived classes. This API is pretty simple, and many parts will be familiar to users of the Direct3D SDK's CD3DApplication class. After that, the "guts" of the class are touched upon... those data members and methods that provide the underlying 3D support.

The API

Here we discuss the simpler aspects of the CDSSD3DView8 interface. The various prototypes defined in the header file are covered here, and any additional notes of clarification are included.

The Direct3D API Methods

The core developer API methods follow the familiar Direct3D SDK prototypes. Like their counterparts in the SDK's CD3DApplication class, these base class implementations do very little.

  virtual HRESULT InitDeviceObjects()       { return S_OK; }
  virtual HRESULT InvalidateDeviceObjects() { return S_OK; }
  virtual HRESULT RestoreDeviceObjects()    { return S_OK; }
  virtual HRESULT DeleteDeviceObjects()     { return S_OK; }
  virtual HRESULT Render()                  { return S_OK; }
  virtual HRESULT FrameMove();

InitDeviceObjects is called once, just after the Direct3D Device is obtained. Its counterpart, DeleteDeviceObjects is called once, just before the Direct3D Device is released when the view is destroyed.

InvalidateDeviceObjects is called when the device is lost or when the view is resized. RestoreDeviceObject is called once the device has been restored, and after internal data has been recalculated due to the View being resized.

Render is called from the CDSSD3DView8 class' OnDraw method once OnDraw has determined that the 3D device is stable. Render differs slightly from the SDK version. OnDraw brackets the call to the derived class' Render method with Direct 3D's BeginScene/EndScene pair. This is followed immediately by the Present call to present the backbuffer to the View. This relieves the derived class' Render from worrying about these details. Rendering will be discussed in more detail later.

Finally, there's FrameMove. This is the only method of the bunch that doesn't have a one-line implementation. FrameMove is presented as an inline at the end of the Header file. FrameMove operates a bit differently than the CD3DApplication implementation, and is also covered in more detail later.

For the most part, the application won't have to call these base class versions. The only possible exception is FrameMove, but its implementation is so simple that it can be included in the derived class' FrameMove methods.

GDI Support

According to the documentation, it's unsafe to call Windows GDI functions between the BeginScene/EndScene pair. To allow for GDI support, two helper virtuals have been included, both of which receive the DeviceContext passed to OnDraw:

  virtual void PreRender(CDC *)     { }
  virtual void PostRender(CDC *)    { }

PreRender is called by the OnDraw method prior to clearing the 3D backbuffer and prior to the call to BeginScene. This allows the user to perform any necessary pre-Render tasks. An example may include establishing one or more transform matrices. Actually, although this method is listed under "GDI Support", it wouldn't be wise to perform any GDI rendering here since the client area will be occupied by the backbuffer very soon.

PostRender is called by OnDraw after it has called the Direct3D interface's EndScene and Present functions. It is here that any GDI function can be called to add any additional sprites and goodies to the scene. When PostRender is called, the backbuffer has already been moved to the View and Direct3D no longer "owns" the client area.

3D Data Access

All but one data member are kept private. This ensures that a sloppy developer won't accidentally stomp on the master Direct3D Device interface or any other internal data. Access to the device and related data are through "getter" methods. The comments for these methods are included as well for basic explanation. Further details are discussed below.

  public:

  // 3D DEVICE ACCESS

  //

  // Get3DDevice is called to return the IDirect3DDevice8 interface

  //    pointer. This will be NULL if the device hasn't been created

  //    or if Open3D failed creating a 3D device.

  //

  // GetDeviceState returns the current 3D device state. This will

  //    be one of the following values (as returned by

  //    TestCooperativeLevel):

  //

  //    - S_OK if everything is running smoothly

  //    - D3DERR_DEVICELOST if the device is in a lost state

  //    - D3DERR_DEVICENOTRESET if device focus has been regained but

  //      hasn't been reset yet.

  //

  //    The error cases are handled internally, so callers don't have

  //    to take any specific action.

  //

  LPDIRECT3DDEVICE8 Get3DDevice() const     { return m_p3DDevice; }
  HRESULT GetDeviceState() const            { return m_hDeviceState; }

  // MEMBER DATA ACCESS

  //

  // GetBackBuffer returns the backbuffer width and height as a 2D

  //    vector.

  //

  // GetHalfBackBuffer returns the half-size backbuffer coordinate pair.

  //

  // GetViewport returns the current Viewport.

  //

  // GetHALCaps returns the HAL capability set for the current 3D

  //    adapter.

  //

  // GetREFCaps returns the REF capability set for the current 3D

  //    adapter.

  //

  // GetCurrentCaps returns a pointer to either the HAL or REF

  //    capabilities, depending on which was selected when the 3D

  //    Device was created. This will be NULL if the 3D device hasn't

  //    been obtained yet, or if Open3D method failed to create the

  //    device.

  //

  const D3DXVECTOR2 & GetBackBuffer() const     { return m_ptBackBuffer; }
  const D3DXVECTOR2 & GetHalfBackBuffer() const { return m_ptHalfBackBuffer; }
  const D3DVIEWPORT8 & GetViewport() const      { return m_Viewport; }

  const D3DCAPS8 & GetHALCaps() const            { return m_capsHAL; }
  const D3DCAPS8 & GetREFCaps() const            { return m_capsREF; }
  const D3DCAPS8 * GetCurrentCaps() const        { return m_pCaps; }

  protected:

  // m_cClearColor is the background color we'll apply when we

  //    clear the backbuffer. This can be set by derived classes

  //    to their own color. The default is black.

  //

  D3DCOLOR m_cClearColor;

Get3DDevice is how derived classes gain access to the Direct3D interface. Unlike the CD3DApplication class, the Direct3D interface is not exposed. As mentioned above, the device pointer is kept private for protection.

Note that GetDeviceState is included primarily for reference. The user doesn't have to take any action if the 3D Device isn't in the S_OK state. This is handled automatically by the CDSSD3DView8 class as it periodically attempts to regain a lost 3D Device.

The GetHalfBackBuffer function may seem odd. But I use it when converting a mouse point to a normalized (e.g., -1.0f to 1.0f) screen coordinate as the first stage of a 3D hit test.

m_cClearColor is the only 3D-related data member that is accessible to derived classes. This establishes the background color when OnDraw clears the backbuffer. The sample application makes use of this at the start of the program to set a dim gray background. The default m_cClearColor is black.

Debugging Helper Methods

Two more methods are included to assist in application debugging:

  // LogDXDebug is called to log some application debugging message

  //    to the TRACE0 output. The function works just like the

  //    printf() statement, allowing variable argument lists.

  //

  // LogDXError is called to log an error message based on the

  //    specified HRESULT value. The message logged will be of

  //    the format: <FN>:  returned 0x<RESULT> (<STR>).

  //    <FN>, , and <RESULT> are provided by the caller.

  //    <STR> will be filled in by the DX utility method.

  //

  protected:
  void LogDXDebug( const char * szSpec, ... );
  void LogDXError( const char * szFn,
                   const char * szAction,
                   HRESULT hResult );

These functions simply log strings to an internal buffer that is then dumped to TRACE0. As a result, these messages will be disabled in Release builds. LogDXDebug works just like a printf() statement, allowing variable argument lists. LogDXError logs a message with a specific format. Both of these methods are used in the base CDSSD3DView8 class as it starts up, and the output they generate are shown here:

For my own use, I have a separate debugging thread that logs debugging and error information to a set of files. But that debugger is too complex for this simple application, and I didn't want to clog up this article with too much fluff. So I opted for this simple TRACE0 approach.

ClassWizard-generated Command Handlers

To allow for proper operation, CDSSD3DView8 handles a small set of Windows messages. To ensure proper operation, it is vital that these base class versions be called if any of these methods are overridden in derived classes. The command handlers are defined here and discussed briefly below.

// Overrides

  public:
    // ClassWizard generated virtual function overrides

    //{{AFX_VIRTUAL(CDSSD3DView8)

    public:
    virtual void OnInitialUpdate();
    protected:
    virtual void OnDraw(CDC* pDC);
    //}}AFX_VIRTUAL


  // Generated message map functions

  protected:
    //{{AFX_MSG(CDSSD3DView8)

    afx_msg void OnDestroy();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnTimer(UINT nIDEvent);
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()

OnInitialUpdate is where the Direct3D interface is established. This implementation calls the internal method Open3D() to initialize the 3D windowed environment. Open3D will be discussed in more detail later.

OnDraw is the primary client paint entry point. This will be discussed in more detail below.

OnDestroy is called in response to the WM_DESTROY command received as the View is being destroyed. This method calls the internal Close3D method, which cleans up the 3D environment. Close3D will make a final call to InvalidateDeviceObjects followed immediately by a call to DeleteDeviceObjects. After that, all timers are shut down and the Direct3D interfaces are released.

OnSize is called in response to a user resize of the view. If a Direct3D device has been established (and if the client area's size is actually changing), OnSize will call the internal Reset3D method to reestablish the 3D environment using the new view size.

OnTimer is called in response to WM_TIMER messages. The CDSSD3DView8 class manages two timers: The FrameMove timer, which is discussed below, and a 3D Device state timer. This second timer is started when the 3D Device has been established but has been determined to be in an invalid state. This timer will fire every half-second, and call the internal TestDeviceState method to see if the Device can be reset. This timer runs until the Device is reestablished or the View is destroyed.

OnEraseBkgnd is a do-nothing stub, as shown in the implementation:

BOOL CDSSD3DView8::OnEraseBkgnd(CDC* pDC) 
{
  return TRUE;
}

Your program won't be able to control all paths that lead to an invalidation of portions of the client area. An example would be opening a context-sensitive popup menu with a right-click of the mouse. When the menu closes, Windows sometimes invalidates your client area, which can cause flicker if the default CView::OnEraseBkgnd is called. All of these uncontrolled client invalidation paths, however, eventually lead to OnDraw, which calls Direct3DDevice's Clear function to set the backbuffer to the current m_ClearColor. So flicker is eliminated by not letting CView handle background erasure.

In Depth

Here we discuss some of the code in a bit more detail. FrameMove is described, as is OnDraw's relationship with Render. Following that, some of the internal 3D control methods are examined.

More on FrameMove

FrameMove is a bit different than the CD3DApplication version. Since this is just a View class, it doesn't control the Run loop in the application. So FrameMove is handled by using an internal Windows Timer.

FrameMove doesn't fire automatically as it does in the SDK's CD3DApplication. It has to be manually configured in derived classes via a call to:

  BOOL StartFrameTimer(DWORD dwTimeoutMS);

The function accepts a millisecond count as the timeout value. It returns TRUE if successful, FALSE if the call to SetTimer fails. Once this call is made, the timer will start running. As expected, FrameMove is called by this class' OnTimer implementation. As such, derived classes that include their own timers should make sure to call CDSSD3DView8::OnTimer to ensure that the Frame timer runs correctly.

FrameMove is implemented as an inline in the header file, as follows:

inline HRESULT CDSSD3DView8::FrameMove()
{
  Invalidate(FALSE);
  return S_OK;
}

Note that FrameMove invalidates the View, which will ultimately lead to a call to OnDraw and, hence, Render. The Invalidate call specifies a bRepaint flag of FALSE. However, since the CDSSD3DView8 class stubs OnEraseBkgnd (c.f. above), you won't see any flicker if you accidentally pass the default TRUE as the bRepaint flag.

The CDSSD3DView8 class also doesn't maintain any internal time counters. A derived class' FrameMove should call:

  FLOAT GetElapsedTime() const;

to get the elapsed time since the last frame. However, GetElapsedTime simply returns the original millisecond count specified in StartFrameTimer divided by 1000 to convert it to a floating-point value with a unit of seconds. This can lead to small timing errors if a derived class relies on precise to-the-millisecond timing.

When the application is done with the Frame timer, a simple call will disable it:

  void StopFrameTimer();

This function is called during cleanup, so the derived class doesn't have to worry about this detail.

The example application makes use of FrameMove and its supporting functions to apply a random flickering Point light to the scene. The light is located at the viewer's Eye Point.

If you'd like a more robust implementation of FrameMove, you can try adding timing support in the main CWinApp-derived application class' OnIdle method.

OnDraw and Render

As mentioned, there is no need for derived classes to provide their own OnDraw implementation. The CDSSD3DView8 class provides this, calling out virtual hooks as it progresses. For ease of explaining where the various virtual hooks are called, the code for OnDraw is presented here:

void CDSSD3DView8::OnDraw(CDC* pDC) 
{
  // Test the device state. If we're not good to go, then we have

  // to exit.

  TestDeviceState();
  if( m_hDeviceState != S_OK ) return;

  // Allow derived class pre-rendering.

  PreRender(pDC);

  // Clear the backbuffer.

  m_p3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                      m_cClearColor, 1.0f, 0 );

  // BeginScene...

  const char * szAction = "BeginScene";
  HRESULT hResult = m_p3DDevice->BeginScene();
  if( hResult == S_OK )
  {
    szAction = "Render";
    hResult = Render();
    m_p3DDevice->EndScene();
    m_p3DDevice->Present(NULL, NULL, NULL, NULL);
  }

  // Log any errors.

  if( hResult != S_OK ) LogDXError("OnDraw", szAction, hResult);

  // And now post-rendering.

  PostRender(pDC);
}

At the outset, OnDraw calls the internal TestDeviceState method. TestDeviceState tests the current state of the 3D device and will take appropriate actions if an unexpected condition develops. The result of this test is stored in the m_hDeviceState member which can be accessed via GetDeviceState(). The implementation of TestDeviceState is pretty straightforward, and can be examined in the source file DSSD3DView8.cpp.

Following TestDeviceState comes the call to PreRender. This method has been mentioned above and needs no further explanation.

After that, OnDraw moves into the 3D rendering section. As mentioned above, note that OnDraw brackets the Render call with the BeginScene/EndScene pair, and follows up with the call to Present. This differs from the Direct3D SDK's CD3DApplication class implementation, where the user's Render method is expected to handle all of these details. If you're uncomfortable about the way Render is handled in CDSSD3DView8, then feel free to remove the calls to BeginScene, EndScene, and Present, and move them to your own Render method. This sequence is included in OnDraw for simplicity.

OnDraw finishes up with a call to PostRender. This has also been described earlier.

3D Device Management

This section defines the internal methods and data used to manage the master Direct3D interface and the Direct3DDevice obtained from there. These methods and data are all private, and are inaccessible to the application programmer. The specific declarations are in CDSSD3DView8.h, and are displayed below. Code comments have been included to provide explanation for most of this information.

First, we have the data. The comments are descriptive enough so that no further explanation should be needed.

  private:

  /////////////////////////////////////////////////////////////////////

  // 3D DEVICE DATA

  //

  // m_pD3D is the master 3D interface object. This object provides

  //    the entry point into Direct3D services.

  //

  // m_D3DDisplayMode is the current adapter display mode, and is

  //    established as soon as the master 3D interface is created.

  //

  // m_capsHAL is the HAL adapter capabilities, as loaded from the

  //    master 3D object.

  //

  // m_capsREF is the REF adapter capabilities, as loaded from the

  //    master 3D object.

  //

  // m_pCaps will point to the currently active capability set (either

  //    m_capsHAL or m_capsREF). This pointer is established depending

  //    on whether or not the 3D Device was created using a HAL device

  //    (preferable) or a REF device.

  //

  // m_d3dPresentParams is the presentation parameter structure used

  //    to initialize the 3D device. This is maintained in case the

  //    device needs reset (e.g. when the window is resized).

  //

  // m_p3DDevice is the 3D device interface created for this View.

  //

  // m_Viewport is the current viewport. This will be similar to the

  //    local Backbuffer definition, but will be loaded from the 3D

  //    device.

  //

  // m_ptBackBuffer is a 2-D point that defines the width (x) and

  //    height (y) of the backbuffer. This will always reflect the

  //    current width and height of the view's client area.

  //

  // m_ptHalfBackBuffer is a point that simply holds the backbuffer

  //    width/2 in the X field, height/2 in the Y field. This value

  //    is used when converting a mouse point to model space.

  //

  // m_hDeviceState holds the current 3D device state, as returned

  //    by TestCooperativeLevel. If no 3D device exists, then this

  //    will hold D3DERR_INVALIDDEVICE.

  //

  // m_bInitDevicesNeeded is a one-shot flag that indicates whether

  //    or not a call to InitDeviceObjects is needed.

  //

  LPDIRECT3D8 m_pDirect3D;
  D3DDISPLAYMODE m_d3dDisplayMode;
  D3DCAPS8 m_capsHAL;
  D3DCAPS8 m_capsREF;
  D3DCAPS8 * m_pCaps;
  D3DPRESENT_PARAMETERS m_d3dPresentParams;
  LPDIRECT3DDEVICE8 m_p3DDevice;
  D3DVIEWPORT8 m_Viewport;
  D3DXVECTOR2 m_ptBackBuffer;
  D3DXVECTOR2 m_ptHalfBackBuffer;
  HRESULT m_hDeviceState;
  BOOL m_bInitDevicesNeeded;

The member functions that directly manage the 3D environment are also private and inaccessible to the application programmer. They perform all the "behind the scenes" operations to maintain the 3D environment including startup, operations, and shutdown. Since they aren't part of the formal CDSSD3DView8 API exposed to the application developer, it is up to the reader to investigate the implementation of these methods. The header file comments have been included to provide a brief explanation of these methods.

  // Open3D is called to create the 3D device and perform any one-time

  //    initialization. The function returns S_OK if successful, or

  //    some error code if an error occurs. The result of this test

  //    is stored in m_hDeviceState, which can be accessed via the

  //    GetDeviceState() method.

  //

  // Create3DDevice is called to create a 3D device of the requested

  //    type.

  //

  // Close3D is called to shut down all 3D objects and the master

  //    3D device.

  //

  // Reset3D is called whenever the size of the view changes or

  //    whenever it has been determined that the 3D device has been

  //    lost. If the reset fails and the device is lost, the function

  //    will start the 1 second timer to keep polling the 3D Device's

  //    Reset method.

  //

  // TestDeviceState is called internally to test the 3D device state

  //    and either reset the device or start the reset timer if the

  //    device isn't ready. When the function exits, m_hDeviceState

  //    will hold the current state.

  //

  HRESULT Open3D();
  HRESULT Create3DDevice(D3DDEVTYPE d3dDevType);
  void Close3D();
  HRESULT Reset3D();
  void TestDeviceState();


  // StartResetTimer is called whenever a "device lost" state is

  //    detected. If the timer isn't already running, then a new

  //    timer is started. Timeouts are handled in OnTimer, which will

  //    attempt to reset the device if control hasn't been regained.

  //

  // StopResetTimer is called to kill any current reset timer. If it's

  //    not running, then the call is ignored.

  //

  // m_nResetTimerId is the timer Id value created when the Reset

  //    timer is running.

  //

  UINT m_nResetTimerId;
  void StartResetTimer();
  inline void StopResetTimer();

These functions are all driven by various MFC events via the six ClassWizard overrides described above (OnInitialUpdate through OnEraseBkgnd). Thus, it is imperative that the CDSSD3DView8 version of these event handlers be called if a user's View class provides its own override of these On... methods.

What About MDI?

I've tested this class in a sample MDI application and have had no trouble. Using the sample code, I created an MDI application and was able to open multiple simultaneously flickering cube views within the MDI frame. Although this wasn't a full test of the CDSSD3DView8 class in an MDI environment (I was just checking for leaks and whatnot), I'd suspect that the class is quite suitable for MDI applications as well.

Disclaimer and Licensing

The bulk of the classes and files include the "DSS" prefix. This code was developed by me for my one-man company, Donut Shop Software (hence, the DSS prefix). I've removed all copyright notices in the code to release it here for this article. There are no explicit or implicit license issues involved in using this software other than those defined by this website.

Thanks

Thanks to all who took the time to read this article and diddle around with the code. This is my first article, so I'm thankful that you've been able to bear with me to this point. Have fun!

Additional thanks to Obliterator for pointing out a flaw when resizing the View. This has been corrected and the downloads updated.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here