rendering to multiple views with DirectX :
CreateAdditionalSwapChain
(level : medium)


Contents

Introduction
Architecture
IRenderer / CRenderer
CSwapChain
CSwapChainDX8
CRendererDX8
Use
Reference
Update : windows resizing
Update : DirectX 9


Introduction

The goal of this article is to render a scene seen from several different cameras in 4 independent views, like on this picture :

This is of course useful when programming tools : editor for levels, special effects, animations, etc...
With DirectX 8, once the main object has been created by a call to Direct3DCreate8 (see the SDK documentation and samples if you don't know about it), it would be possible to ask for a new device for each view during its initialization. This option has the following drawbacks :

- the documentation says that DirectX is optimized for the rendering with a single device (case of games, and the most frequent), and not with 4 of them.
- to be able to draw the desired scene, each device has to own the corresponding objects (index and vertex buffers), textures and materials. Assuming it would be possible (I didn't check that) to share these resources between the devices, it would also be necessary to ensure the render states are the same, that is to say to keep them synchronized. A lot of work you wish to avoid if possible !

The "good" method consists in using the CreateAdditionalSwapChain function, like indicated in the documentation (see Using Direct3D / Rendering / Presenting a scene / Multiple views in windowed mode). The latter is unfortunately not very wordy on the subject, and the only sample (DxTex) using this function at the time being only draws a single quad already projected in screen space, with no zbuffer. I didn't find a lot of information on the internet either, except for the page cited in the reference ; hence this article and the sample (DirectX 8 version, 329 Kb - DirectX 9 version, 412 Kb) that comes with it.


Architecture

The given example is a very simple MFC application (an MDI to be precise, Multiple Document Interface), but this is not important with regard to the problem we're interested in. The important files are located in the \rendering folder, which owns a \dx8 subdirectory. The first one contains the base classes that don't depend on the API used for rendering (= they would be the same with OpenGL), the second one contains the derived classes specific to DirectX 8.

These classes are the following :
- IRenderer << CRenderer << CRendererDX8
- CSwapChain << CSwapChainDX8

IRenderer and its derived classes deal with the application's rendering. CSwapChain and its derived class manage the different "swap chains" ; but what is a swap chain in DirectX ? This term represents a group (circular chain) of buffers, made of 1 or several back buffers, and 0 or 1 zbuffer (and/or eventually the stencil buffer that goes with it, as depth and stencil buffers share the same space on current cards). The scene is drawn in each back buffer in turn (always using the same zbuffer), then copied to the front buffer to become visible on screen.

By default the device already owns 1 swap chain, created during the call to CreateDevice, which allows it to render full screen or in one window. The principle of rendering to several views is to create an additional swap chain for each view with CreateAdditionalSwapChain, to select the appropriate chain for display when a view needs to be redrawn, and to copy the result to the corresponding window. These operations are going to be covered later on.

NB : the device already owning a swap chain, it is not necessary to create one for the first view. This is however what I do, to simplify and unify the swap chains management. In order not to waste memory uselessly, the size of my swap chain associated with the device is only 16*16 pixels, see the renderer's creation in MultiDx.cpp.


IRenderer / CRenderer

For the sake of this article, I have reduced the renderer to its minimum in order not to complicate the understanding of the source code ; that's why there is for example no textures management. Starting from this base, I've made the following modifications :

- in the IRenderer virtual class, addition of the methods :
    virtual u32               CreateSwapChain     (const HWND hWnd,const u32 u32Flags=_WINDOWED_|_ZBUFFER_,
                                                   const u32 u32Width=0,const u32 u32Height=0) = 0;
    virtual void              ReleaseSwapChain    (const u32 u32Chain) = 0;
    virtual bool              SelectSwapChain     (const u32 u32Chain) = 0;
    virtual bool              AddSwapChain        (CSwapChain* pSwapChain) = 0;
    virtual bool              RemoveSwapChain     (CSwapChain* pSwapChain) = 0;

    virtual CSwapChain*       GetSwapChain        (const u32 u32Chain) = 0;
    virtual CSwapChain*       GetCurrentChain     (void) const = 0;

- in the CRenderer class, addition of the methods :
    virtual bool              AddSwapChain        (CSwapChain* pSwapChain);
    virtual bool              RemoveSwapChain     (CSwapChain* pSwapChain);

    virtual CSwapChain*       GetSwapChain        (const u32 u32Chain);
    virtual CSwapChain*       GetCurrentChain     (void) const;
and the members :
  // typedefs

  public:

    typedef std::list< CSwapChain* >              listChain;
    typedef listChain::iterator                   iterChain;

  // protected data

  protected:

    listChain                 m_SwapChains;
    u32                       m_u32CurrentChain;
    CSwapChain*               m_pCurrentChain;

One can see that :

- CRenderer stores and manages the device's swap chains in an STL list. It also has the notion of "current chain".
- several methods are not implemented in CRenderer : CreateSwapChain, ReleaseSwapChain, SelectSwapChain. Their code depends on the API used for graphics, and so is located in CRendererDX8.


CSwapChain

The aim of this class is only to keep a swap chain's characteristics : handle, windowed mode or not, has a zbuffer or not, handle of the associated window, dimensions, clear color (not essential). Its header is the following :

//--------------------------------------------------------------------------------------------------------------------//
//                                                SWAP CHAIN CLASS                                                    //
//                                                (MULTIPLE VIEWS)                                                    //
// 22/05/02, Mythos                                                                                                   //
//--------------------------------------------------------------------------------------------------------------------//

#ifndef   _MYTHOS_SWAPCHAIN_H_
#define   _MYTHOS_SWAPCHAIN_H_
#pragma    once

//----------------------------------------------- INCLUDES -----------------------------------------------------------//

#include  "Maths/VectMat.h"

#include  "Global/Defines.h"
#include  "Memory/SmartPtr.h"

//----------------------------------------------- CLASSES ------------------------------------------------------------//

//--------------------------------------------------------------------------------------------------------------------//
//                                                CSwapChain                                                          //
//--------------------------------------------------------------------------------------------------------------------//

namespace Mythos
{
  class CSwapChain
  {
  // public methods

  public:

    // constructors & destructor

                              CSwapChain          (u32 u32Handle,bool boWindowed,bool boZBuffer,HWND hWnd,u32 u32Width,u32 u32Height);
    virtual                  ~CSwapChain          (void);

    // get/set

    static u32                GetFreeHandle       (void);
    u32                       GetHandle           (void) const;

    bool                      IsWindowed          (void) const;
    bool                      HasZBuffer          (void) const;

    HWND                      GetHwnd             (void) const;
    u32                       GetWidth            (void) const;
    u32                       GetHeight           (void) const;

    CVect3D                   GetClearColor       (void) const;
    void                      SetClearColor       (const CVect3D& v3Color);

  // protected data

  protected:

    static u32                m_u32NextHandle;
    u32                       m_u32Handle;

    bool                      m_boWindowed;
    bool                      m_boZBuffer;

    HWND                      m_hWnd;
    u32                       m_u32Width,m_u32Height;

    CVect3D                   m_v3ClearColor;
  };

  // smart ptr

  _SMART_POINTER_(CSwapChain)
}

//----------------------------------------------- INLINES ------------------------------------------------------------//

#ifndef   _DEBUG
#include  "SwapChain.inl"
#endif

//--------------------------------------------------------------------------------------------------------------------//

#endif // _MYTHOS_SWAPCHAIN_H_

NB : the GetHandle function returns the handle of the swap chain, the static GetFreeHandle method returns the next free number and increments it, see CSwapChain.inl.


CSwapChainDX8

The CSwapChainDX8 class is very simple too : it adds the following methods and members, that will allow to handle a swap chain created with DirectX 8 :

    // get/set

    IDirect3DSwapChain8*      GetInterface        (void) const;
    void                      SetInterface        (IDirect3DSwapChain8* pInterface);
    IDirect3DSurface8*        GetBackBuffer       (void) const;
    void                      SetBackBuffer       (IDirect3DSurface8* pBackBuffer);
    IDirect3DSurface8*        GetZStencil         (void) const;
    void                      SetZStencil         (IDirect3DSurface8* pZStencil);

  // protected data

  protected:

    IDirect3DSwapChain8*      m_pInterface;
    IDirect3DSurface8*        m_pBackBuffer;
    IDirect3DSurface8*        m_pZStencil;

m_pInterface points to the DX8 swap chain, m_pBackBuffer points to its back buffer (in my application each swap chain has only one back buffer, triple buffering is not interesting in windowed mode and uses a lot of video memory), m_pZStencil points to the potential zbuffer (and/or its stencil) or is NULL. So some views that wouldn't need it (example : textures browser) can have no zbuffer at all, which saves some video memory.


CRendererDX8

What changes in the DirectX renderer, with respect to the "standard" version (with a single view) ? not a lot of things actually, 3 new functions appear and 2 are modified (3 if you count Clear whose color depends on the swap chain being used).

- CreateSwapChain : this method is by far the longest, it's the one that creates a new swap chain for the device. The chain contains 1 back buffer, and a zbuffer or not according to the flags. The format is hardcoded (D3DFMT_D16), the code will have to be modified if a stencil buffer becomes necessary.

//----------------------------------------------- CreateSwapChain ----------------------------------------------------//
// create additional swap chain
// 21/05/02, Mythos
// in : window handle,flags(=_WINDOWED_|_ZBUFFER_),width(=0) & height(=0)
// out: chain handle, 0==error
// rem: if width==height==0 the client area of the window is used
//--------------------------------------------------------------------------------------------------------------------//

u32 Mythos::CRendererDX8::CreateSwapChain(const HWND hWnd,const u32 u32Flags,const u32 u32Width,const u32 u32Height)
  {
  bool boWindowed =(u32Flags & _WINDOWED_) != 0;
  bool boZBuffer  =(u32Flags & _ZBUFFER_ ) != 0;
  u32  u32Width2  = u32Width;
  u32  u32Height2 = u32Height;

  if(boWindowed)
    {
    RECT rect;
    if(!GetClientRect(hWnd,&rect)) return 0;
    if(!u32Width)  u32Width2  = rect.right;
    if(!u32Height) u32Height2 = rect.bottom;
    }

  if(!u32Width2 || !u32Height2) return 0;

  // back buffer

  if(!m_pD3D | !m_pDevice) return 0;

  D3DDISPLAYMODE d3ddm;
  if(FAILED(m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddm))) return 0;

  D3DPRESENT_PARAMETERS d3dpp; 
  ZeroMemory(&d3dpp,sizeof(d3dpp));
  d3dpp.BackBufferWidth  = u32Width2;
  d3dpp.BackBufferHeight = u32Height2;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.BackBufferCount  = 1;
  d3dpp.MultiSampleType  = D3DMULTISAMPLE_NONE;
  d3dpp.SwapEffect       = D3DSWAPEFFECT_DISCARD;
  d3dpp.hDeviceWindow    = hWnd;
  d3dpp.Windowed         = boWindowed;
  d3dpp.EnableAutoDepthStencil = false;
  d3dpp.Flags            = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;

  IDirect3DSwapChain8* pInterface;
  HRESULT hrErr = m_pDevice->CreateAdditionalSwapChain(&d3dpp,&pInterface);
  if(FAILED(hrErr))
    {
    TCHAR szError[1024];
    D3DXGetErrorString(hrErr,szError,1024);
    _ASSERT_(false,"CreateAdditionalSwapChain failed");
    _ASSERT_(false,szError);
    return 0;
    }

  // Z & stencil

  IDirect3DSurface8* pZStencil = NULL;

  if(boZBuffer)
    {
    hrErr = m_pDevice->CreateDepthStencilSurface(u32Width2,u32Height2,D3DFMT_D16,D3DMULTISAMPLE_NONE,&pZStencil);
    if(FAILED(hrErr))
      {
      TCHAR szError[1024];
      D3DXGetErrorString(hrErr,szError,1024);
      _ASSERT_(false,"CreateDepthStencilSurface failed");
      _ASSERT_(false,szError);
      pInterface->Release();
      return 0;
      }
    }

  //

  CSwapChainDX8* pSwapChain = new CSwapChainDX8(CSwapChain::GetFreeHandle(),boWindowed,boZBuffer,hWnd,u32Width2,u32Height2);
  if(!pSwapChain)
    {
    pInterface->Release();
    if(pZStencil) pZStencil->Release();
    return 0;
    }

  IDirect3DSurface8* pBackBuffer;
  pInterface->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);

  AddSwapChain(pSwapChain);
  pSwapChain->SetInterface (pInterface);
  pSwapChain->SetBackBuffer(pBackBuffer);
  pSwapChain->SetZStencil  (pZStencil);

  return pSwapChain->GetHandle();
  }

The back buffer's creation is rather similar to the device's one. The zbuffer could be created thanks to the EnableAutoDepthStencil field, but 1) the zbuffer is not always required, 2) its address has to be retrieved and the IDirect3DSwapChain8 interface doesn't have a GetDepthStencilSurface method. That's why the zbuffer is created separately.

If everything went well, an object of type CSwapChainDX8 is created and added to the list of swap chains. Its variables are initialized, and its handle is returned to indicate the operation succeeded.


- ReleaseSwapChain : this method is a lot more simple : it removes the asked swap chain from the list (through the RemoveSwapChain function of CRenderer), and deletes it. This has the effect of freeing the buffers allocated by DirectX, thanks to the Release calls placed in CSwapChainDX8's destructor.

//----------------------------------------------- ReleaseSwapChain ---------------------------------------------------//
// release additional swap chain
// 21/05/02, Mythos
// in : chain handle
// out:
//--------------------------------------------------------------------------------------------------------------------//

void Mythos::CRendererDX8::ReleaseSwapChain(const u32 u32Chain)
  {
  CSwapChainDX8* pSwapChain = reinterpret_cast<CSwapChainDX8*>(GetSwapChain(u32Chain));
  if(!pSwapChain) return;

  RemoveSwapChain(pSwapChain);
  delete pSwapChain;
  }


- SelectSwapChain : this method activates the swap chain given as a parameter, that is to say it directs all the future device's operations to the corresponding back and depth buffers. For that purpose it uses the SetRenderTarget function.

//----------------------------------------------- SelectSwapChain ----------------------------------------------------//
// select as rendering target
// 21/05/02, Mythos
// in : chain handle
// out: OK?
//--------------------------------------------------------------------------------------------------------------------//

bool Mythos::CRendererDX8::SelectSwapChain(const u32 u32Chain)
  {
  if(!m_pDevice) return false;
  if((u32Chain == m_u32CurrentChain) && m_pCurrentChain) return true;

  CSwapChainDX8* pSwapChain = reinterpret_cast<CSwapChainDX8*>(GetSwapChain(u32Chain));
  if(!pSwapChain) return false;

  IDirect3DSurface8* pBackBuffer = pSwapChain->GetBackBuffer();
  IDirect3DSurface8* pZStencil   = pSwapChain->GetZStencil();
  if(!pBackBuffer) return false;

  if(FAILED(m_pDevice->SetRenderTarget(pBackBuffer,pZStencil))) return false;

  m_pCurrentChain   = pSwapChain;
  m_u32CurrentChain = u32Chain;
  return true;
  }


- Create : after creating the device, a few lines are inserted in my Create method to put its initial swap chain in the renderer's list, with handle 0. This swap chain is not used in the sample, but it could be in another application.

  // swap chain 0

  CSwapChainDX8* pSwapChain = new CSwapChainDX8(0,boWindowed,boZBuffer,hWnd,u32Width2,u32Height2);
  if(!pSwapChain) return false;
  AddSwapChain(pSwapChain);

  IDirect3DSurface8* pBackBuffer;
  IDirect3DSurface8* pZStencil;
  m_pDevice->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
  m_pDevice->GetDepthStencilSurface(&pZStencil);

  pSwapChain->SetInterface (NULL);
  pSwapChain->SetBackBuffer(pBackBuffer);
  pSwapChain->SetZStencil  (pZStencil);

  SelectSwapChain(0); 


- Swap : the method that makes the drawn picture visible on screen is modified. Depending on wether you want to copy the buffer of the initial swap chain of the device or of an additional swap chain, you have to call the Present function of the device or of the desired chain. The prototype is the same in both cases, hence the code :

//----------------------------------------------- Swap ---------------------------------------------------------------//
// swap front & back buffers
// 20/05/02, Mythos
// in : source(=NULL) & dest(=NULL) rects,nb vbls(=0) to wait (TBI)
// out: OK?
//--------------------------------------------------------------------------------------------------------------------//

bool Mythos::CRendererDX8::Swap(const RECT* pSrcRect,const RECT* pDestRect,const u32 u32Vbls)
  {
  if(!m_pDevice) return false;
  if(!m_u32CurrentChain) return SUCCEEDED(m_pDevice->Present(pSrcRect,pDestRect,NULL,NULL));

  CSwapChainDX8* pSwapChain = reinterpret_cast<CSwapChainDX8*>(m_pCurrentChain);
  if(!pSwapChain) return false;
  IDirect3DSwapChain8* pInterface = pSwapChain->GetInterface();
  if(!pInterface) return false;
  return SUCCEEDED(pInterface->Present(pSrcRect,pDestRect,NULL,NULL));

  UNREFERENCED_PARAMETER(u32Vbls);
  }


Use

The renderer (therefore its device) is created in MultiDx.cpp, during the application's initialization (InitInstance). symmetrically, it is deleted in ExitInstance. All views of all documents (because you can create others with the File\New command, within the limits of the available video memory, and cascade or tile them) use this common renderer, to which they have access through the global variable theApp. When it has some free time (OnIdle function), the application asks all the views of the active document to refresh, and the document to rotate the teapot. When the program doesn't have the focus anymore (m_boActivated is false), this portion of the code is not executed in order to free the CPU for the other processes.

Regarding the swap chains, everything is obviously handled in the CMultiDxView class, from which each view of the given sample is an instance. It has a m_u32Renderer variable containing the handle of the swap chain corresponding to it (or 0 if the chain doesn't exist), and two methods DrawFrame and CreateRenderer.

CreateRenderer is called during the initialization of the view from OnInitialUpdate, as its name more or less indicates it is in charge of asking the renderer to create a new swap chain for the view. This chain will of course be used in DrawFrame, and destroyed by CMultiDxView's destructor.

DrawFrame is called by OnEraseBkgnd (for example if another application is moved and this requires the view to be redrawn), and in OnUpdate by the message sent when the application asks its views to refresh.

Now that we have seen the general working of the sample, we still have to look closer at the code of the two main functions :

void CMultiDxView::DrawFrame()
  {
  if(!m_u32Renderer || !theApp.m_pRenderer) return;

  Mythos::IRenderer* pRenderer = theApp.m_pRenderer;
  if(!pRenderer->SelectSwapChain(m_u32Renderer))
    {
    _ASSERT_(false,"DrawFrame : SelectSwapChain failed");
    return;
    }

  // rotate mesh

  Mythos::CMat4x4 m4Y;
  m4Y.RotationY(GetDocument()->m_fAngleY);
  pRenderer->SetModel2World(m4Y);

  // camera

  Mythos::CMat4x4 m4W2C;
  Mythos::CMat4x4 m4Rot = Mythos::CMat4x4::Identity;
  m4W2C.Translation(Mythos::CVect3D(0.f,0.f,-5.f));

  switch(m_u32Renderer)
    {
    case 1 :
             break;
    case 2 : m4Rot.RotationY(-_PI_/2.f);
             break;
    case 3 : m4Rot.RotationX(-_PI_/2.f);
             break;
    case 4 : m4Rot.RotationXYZ(Mythos::CVect3D(-_PI_/4.f,_PI_/6.f,_PI_/4.f));
             break;
    }

  m4W2C *= m4Rot;
  pRenderer->SetWorld2View(m4W2C);

  // draw

  _ASSERT_(pRenderer->Clear(),     "DrawFrame : Clear failed");
  _ASSERT_(pRenderer->BeginScene(),"DrawFrame : BeginScene failed");

  pRenderer->DrawTeapot();

  _ASSERT_(pRenderer->EndScene(),  "DrawFrame : EndScene failed");
  _ASSERT_(pRenderer->Swap(),      "DrawFrame : Swap failed");
  }

//

bool CMultiDxView::CreateRenderer()
  {
  Mythos::IRenderer* pRenderer = theApp.m_pRenderer;
  if(!pRenderer) return false;

  CFrameWnd* pFrame = GetParentFrame();                     // CChildFrame
  if(!pFrame) return false;
  RECT Rect;
  pFrame->GetClientRect(&Rect);
  Rect.right  /= 2;
  Rect.bottom /= 2,

  m_u32Renderer = pRenderer->CreateSwapChain(m_hWnd,Mythos::IRenderer::_WINDOWED_ | Mythos::IRenderer::_ZBUFFER_,Rect.right,Rect.bottom);
  if(!m_u32Renderer) return false;

  if(!pRenderer->SelectSwapChain(m_u32Renderer)) return false;

  Mythos::CVect3D v3Red   (.5f,0.f,0.f);
  Mythos::CVect3D v3Green (0.f,.5f,0.f);
  Mythos::CVect3D v3Blue  (0.f,0.f,.5f);
  Mythos::CVect3D v3Yellow(.5f,.5f,0.f);
  Mythos::CVect3D v3Color;

  static DWORD dwColor = 0;
  switch(dwColor)
    {
    case 0  : v3Color = v3Red;    break;
    case 1  : v3Color = v3Green;  break;
    case 2  : v3Color = v3Blue;   break;
    case 3  : v3Color = v3Yellow; break;
    default : v3Color.Set(0.f);   break;
    }
  dwColor++;
  dwColor &= 3;

  pRenderer->SetClearColor(v3Color);
  return true;
  }

DrawFrame starts by checking a swap chain exists for the view, and selects it for display. The model->world and world->view matrices are then calculated so that each view has a different camera (see the switch instruction). The rendering itself is not different from what is usually found when having a single view : Clear, BeginScene, drawing, EndScene, Swap. As a sidenote, the teapot is built in CRendererDX8::Create by a call to D3DXCreateTeapot.

CreateRenderer asks the renderer to create a swap chain corresponding to the dimensions of the view (these are the initial dimensions, the resizing of the view is not handled : if you change its size, the rendering is still done in 1/4 of the screen, but stretched during the copy to cover the whole window, because this is defined like that in the Swap function by the parameters sent to the Present method). CreateRenderer then selects the new swap chain, and gives it a color for the clearing according to the "number" of the view.

To sump up : once the swap chain has been created, it suffices to select it at the beginning of DrawFrame, and the whole rendering then takes place as usual.


Reference

The page that was almost my only source of information for this article.
Robert's way of doing things is different from mine : the initial swap chain of his device covers the whole screen, and the zbuffer is shared among all views.
My code can seem quite long compared to his : that's because it fits into the "if possible reusable" classes of a real renderer, which necessarily requires a few more lines. Hoping this will be of any use to you one day :)


Update : windows resizing

The first program (DirectX 8 version) given with this article does not handle windows resizing, which has two major drawbacks :

- the buffers (back buffers and z-buffers) of the 4 swap chains are never resized, they keep their initial dimensions. If a view is enlarged, rendering is so performed in a window which is smaller than the view, and the result is zoomed in by DirectX to make it cover the whole view which leads to texelated and blurred display.

- the projection matrix is never updated, which means that the camera always "sees" the same part of the scene (here the teapot), and displays it on the screen whitout taking the view ratio into account. The initial width/height ratio is 4/3, if it is not maintained when resizing the view the scene is stretched horizontally or vertically.

These two shortcomings are addressed in the DirectX 9 version in the following way :

- to resize the buffers, one only need to recreate the swap chain with new dimensions. This is what the ResizeSwapChain method added to the renderer class does. If it does not succeed in creating the new buffers the old ones are kept. In any case the handle of the swap chain is left unchanged.
This function's code is very similar to the one of CreateSwapChain, ideally duplication should be avoided by creating a new function containing the common lines.
ResizeSwapChain is called from the MFC method CMultiDxView::OnSize of the view.

- the ratio of each view is taken into account in the CMultiDxView::DrawFrame method to calculate a projection matrix which does not stretch the teapot. The code in charge of this task is simply :
  RECT rect;
  GetClientRect(&rect);
  pRenderer->SetPerspective(_DEG2RAD_(60.f),1.f,1000.f,(float)rect.right/rect.bottom);
Notice that the width/height ratio is passed to SetPerspective as the last parameter.
This function preserves the width of the scene, i.e. the teapot always covers the same percentage of the view horizontally. Of course it is possible to maintain height instead by modifying SetPerspective.


Update : DirectX 9

Converting the sample and more specifically the swap chains from DirectX 8 to DirectX 9 does not raise any problem. Here are the modifications :

- the prototypes of some DirectX functions have changed, often to add one or more parameters : GetBackBuffer, D3DXFillTexture (const parameters), CreateDepthStencilSurface, Present (for a swap chain).
- some render states have disappeared, and others are new.
- DXGetErrorString9 replaces D3DXGetErrorString. To use it you need to include the dxerr9 library.
- texture filtering is not managed through SetTextureStageState anymore, but with the new SetSamplerState function.
- finally, SetRenderTarget does not allow to specify the depth/stencil buffer to be used anymore, you have to call SetDepthStencilSurface for that purpose.


back to top