Rendering to Multiple Windows

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Written by Robert Dunlop
Microsoft DirectX MVP

Introduction

In DirectX 8, support for rendering to multiple windows is provided through the creation of additional swap chains.  However, there are currently no examples of this in the SDK, and the documentation is a bit vague.  This article is provided to fill the gaps, and will explain the steps you need to take to write an application that will render multiple views in separate windows.

Step 1 - Setting Up The Parent Frame

In an application with multiple views, we start with a top level frame that will contain child windows in its client area to display various views.  Once the parent frame parent frame has been created, we create our Direct3D device interface, specifying windowed mode and setting the top level window handle as the focus window:

g_pD3D=Direct3DCreate8(D3D_SDK_VERSION);
if (!g_pD3D) return -1;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
// Use the current display mode.
D3DDISPLAYMODE mode;
if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT , &mode))) {
    SAFE_RELEASE(g_pD3D);
    return -1;
}
d3dpp.BackBufferFormat = mode.Format;
d3dpp.BackBufferWidth = mode.Width;
d3dpp.BackBufferHeight = mode.Height;
d3dpp.EnableAutoDepthStencil=TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// m_hWnd is handle to top level window
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &g_pd3dDevice) ) ) {
    SAFE_RELEASE(g_pD3D);
    return -1;
}

Note that for simplicity the above code does not test depth format, instead choosing a fixed format.  Your application should determine a compatible depth format for the format of the rendering target.

The device has a frame buffer, which the child views will be rendered into, as well as a depth buffer which will be shared among the views.  The frame buffer and depth buffer are sized to the full screen resolution, to allow for the fact that the window may later be resized.  Otherwise, window size changes would require resetting the device and re-creating the swap chains.

Step 2 - Setting Up View Windows

Now we are ready to create our view windows, and associate them with swap chains that can be rendered to the device.  Once the windows have been created, the following code generates a swap chain for the child window:

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
// Use the current display mode.
D3DDISPLAYMODE mode;
g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT , &mode);
d3dpp.BackBufferFormat = mode.Format;
// m_hWnd contains child window handle
d3dpp.hDeviceWindow=m_hWnd;
// m_pSwapChain is IDirect3DSwapChain *
g_pd3dDevice->CreateAdditionalSwapChain(&d3dpp, &m_pSwapChain);

After executing this code, the m_pSwapChain variable will contain a pointer to an IDirect3DSwapChain interface, which contains a frame buffer corresponding to the client area of the child window.  This process is performed for each view window, so that that there is a swap chain for each view window.

Step 3 - Rendering a View

Prior to rendering each view, we must direct the device to render to the appropriate frame buffer, using the SetRenderTarget() method.  We pass the back buffer from the window's swap chain, while using the depth buffer that was originally created with the device:

LPDIRECT3DSURFACE8 pBack=NULL,pStencil=NULL;
m_pSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBack);
g_pd3dDevice->GetDepthStencilSurface(&pStencil);
g_pd3dDevice->SetRenderTarget(pBack,pStencil);
pBack->Release();
pStencil->Release();

Note that we release the stencil and backbuffer pointers after we use them, because the GetBackBuffer() and GetDepthStencilSurface() functions call AddRef() on these interfaces to increment their reference counters.  Failing to release them would lead to a memory leak.

We are now ready to render the view.  Rendering is performed within a scene in the normal manner, except that we call Present() on the swap chain interface rather than the device interface:

g_pd3dDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0x00000000,1.0,0);
if (SUCCEEDED(g_pd3dDevice->BeginScene())) {
   
    // rendering code goes here

    g_pd3dDevice->EndScene();
}
m_pSwapChain->Present(NULL,NULL,NULL,NULL);

Step 4 - Handling Resize of Child Views

DirectX will automatically deal with changes in the child view by using a stretch blit to present the swap chain if the dimensions have client area is not the same size as the swap chain's frame buffer.  However, this may not be desirable, as it will cause aliasing if the client area is increased in size.

To prevent this, you can write a handler for the WM_SIZE message of the child window.  The handler should release the existing swap chain, and create a new swap chain using the code from Step 2.

Back to The Top

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.