// * This file is part of the COLOBOT source code // * Copyright (C) 2001-2008, Daniel ROUX & EPSITEC SA, www.epsitec.ch // * // * This program is free software: you can redistribute it and/or modify // * it under the terms of the GNU General Public License as published by // * the Free Software Foundation, either version 3 of the License, or // * (at your option) any later version. // * // * This program is distributed in the hope that it will be useful, // * but WITHOUT ANY WARRANTY; without even the implied warranty of // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // * GNU General Public License for more details. // * // * You should have received a copy of the GNU General Public License // * along with this program. If not, see http://www.gnu.org/licenses/. //----------------------------------------------------------------------------- // File: D3DFrame.cpp // // Desc: Class functions to implement a Direct3D app framework. // // Copyright (c) 1995-1999 by Microsoft, all rights reserved //----------------------------------------------------------------------------- #define STRICT #include #include #include #include "graphics/d3d/d3dframe.h" #include "graphics/d3d/d3dutil.h" //----------------------------------------------------------------------------- // Name: CD3DFramework7() // Desc: The constructor. Clears static variables //----------------------------------------------------------------------------- CD3DFramework7::CD3DFramework7() { m_hWnd = NULL; m_bIsFullscreen = FALSE; m_bIsStereo = FALSE; m_dwRenderWidth = 0L; m_dwRenderHeight = 0L; m_pddsFrontBuffer = NULL; m_pddsBackBuffer = NULL; m_pddsBackBufferLeft = NULL; m_pddsZBuffer = NULL; m_pd3dDevice = NULL; m_pDD = NULL; m_pD3D = NULL; m_dwDeviceMemType = NULL; } //----------------------------------------------------------------------------- // Name: ~CD3DFramework7() // Desc: The destructor. Deletes all objects //----------------------------------------------------------------------------- CD3DFramework7::~CD3DFramework7() { DestroyObjects(); } //----------------------------------------------------------------------------- // Name: DestroyObjects() // Desc: Cleans everything up upon deletion. This code returns an error // if any of the objects have remaining reference counts. //----------------------------------------------------------------------------- HRESULT CD3DFramework7::DestroyObjects() { LONG nDD = 0L; // Number of outstanding DDraw references LONG nD3D = 0L; // Number of outstanding D3DDevice references if( m_pDD ) { HRESULT err = m_pDD->SetCooperativeLevel( m_hWnd, DDSCL_NORMAL ); char s[100]; sprintf(s, "SetCooperativeLevel error=%d\n", err); OutputDebugString(s); } // Do a safe check for releasing the D3DDEVICE. RefCount must be zero. if( m_pd3dDevice ) if( 0 < ( nD3D = m_pd3dDevice->Release() ) ) DEBUG_MSG( _T("Error: D3DDevice object is still referenced!") ); m_pd3dDevice = NULL; SAFE_RELEASE( m_pddsBackBuffer ); SAFE_RELEASE( m_pddsBackBufferLeft ); SAFE_RELEASE( m_pddsZBuffer ); SAFE_RELEASE( m_pddsFrontBuffer ); SAFE_RELEASE( m_pD3D ); if( m_pDD ) { // Do a safe check for releasing DDRAW. RefCount must be zero. if( 0 < ( nDD = m_pDD->Release() ) ) DEBUG_MSG( _T("Error: DDraw object is still referenced!") ); } m_pDD = NULL; // Return successful, unless there are outstanding DD or D3DDevice refs. return ( nDD==0 && nD3D==0 ) ? S_OK : D3DFWERR_NONZEROREFCOUNT; } //----------------------------------------------------------------------------- // Name: Initialize() // Desc: Creates the internal objects for the framework //----------------------------------------------------------------------------- HRESULT CD3DFramework7::Initialize( HWND hWnd, GUID* pDriverGUID, GUID* pDeviceGUID, DDSURFACEDESC2* pMode, DWORD dwFlags ) { HRESULT hr; // Check params. Note: A NULL mode is valid for windowed modes only. if( ( NULL==hWnd ) || ( NULL==pDeviceGUID ) || ( NULL==pMode && (dwFlags&D3DFW_FULLSCREEN) ) ) return E_INVALIDARG; // Setup state for windowed/fullscreen mode m_hWnd = hWnd; m_bIsStereo = FALSE; m_bIsFullscreen = ( dwFlags & D3DFW_FULLSCREEN ) ? TRUE : FALSE; // Support stereoscopic viewing for fullscreen modes which support it if( ( dwFlags & D3DFW_STEREO ) && ( dwFlags & D3DFW_FULLSCREEN ) ) if( pMode->ddsCaps.dwCaps2 & DDSCAPS2_STEREOSURFACELEFT ) m_bIsStereo = TRUE; // Create the D3D rendering environment (surfaces, device, viewport, etc.) if( FAILED( hr = CreateEnvironment( pDriverGUID, pDeviceGUID, pMode, dwFlags ) ) ) { DestroyObjects(); return hr; } return S_OK; } //----------------------------------------------------------------------------- // Name: CreateEnvironment() // Desc: Creates the internal objects for the framework //----------------------------------------------------------------------------- HRESULT CD3DFramework7::CreateEnvironment( GUID* pDriverGUID, GUID* pDeviceGUID, DDSURFACEDESC2* pMode, DWORD dwFlags ) { HRESULT hr; // Select the default memory type, for whether the device is HW or SW if( IsEqualIID( *pDeviceGUID, IID_IDirect3DHALDevice) ) m_dwDeviceMemType = DDSCAPS_VIDEOMEMORY; else if( IsEqualIID( *pDeviceGUID, IID_IDirect3DTnLHalDevice) ) m_dwDeviceMemType = DDSCAPS_VIDEOMEMORY; else m_dwDeviceMemType = DDSCAPS_SYSTEMMEMORY; // Create the DDraw object hr = CreateDirectDraw( pDriverGUID, dwFlags ); if( FAILED( hr ) ) return hr; // Create the front and back buffers, and attach a clipper if( dwFlags & D3DFW_FULLSCREEN ) hr = CreateFullscreenBuffers( pMode ); else hr = CreateWindowedBuffers(); if( FAILED( hr ) ) return hr; // Create the Direct3D object and the Direct3DDevice object hr = CreateDirect3D( pDeviceGUID ); if( FAILED( hr ) ) return hr; // Create and attach the zbuffer if( dwFlags & D3DFW_ZBUFFER ) hr = CreateZBuffer( pDeviceGUID ); if( FAILED( hr ) ) return hr; return S_OK; } //----------------------------------------------------------------------------- // Name: EnumZBufferFormatsCallback() // Desc: Simply returns the first matching enumerated z-buffer format //----------------------------------------------------------------------------- static HRESULT WINAPI EnumZBufferFormatsCallback( DDPIXELFORMAT* pddpf, VOID* pContext ) { DDPIXELFORMAT* pddpfOut = (DDPIXELFORMAT*)pContext; if( pddpfOut->dwRGBBitCount == pddpf->dwRGBBitCount ) { (*pddpfOut) = (*pddpf); return D3DENUMRET_CANCEL; } return D3DENUMRET_OK; } //----------------------------------------------------------------------------- // Name: CreateDirectDraw() // Desc: Create the DirectDraw interface //----------------------------------------------------------------------------- HRESULT CD3DFramework7::CreateDirectDraw( GUID* pDriverGUID, DWORD dwFlags ) { // Create the DirectDraw interface, and query for the DD7 interface if( FAILED( DirectDrawCreateEx( pDriverGUID, (VOID**)&m_pDD, IID_IDirectDraw7, NULL ) ) ) { DEBUG_MSG( _T("Could not create DirectDraw") ); return D3DFWERR_NODIRECTDRAW; } // Set the Windows cooperative level DWORD dwCoopFlags = DDSCL_NORMAL; if( m_bIsFullscreen ) dwCoopFlags = DDSCL_ALLOWREBOOT|DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN; // By defualt, set the flag to allow D3D to optimize floating point calcs if( 0L == ( dwFlags & D3DFW_NO_FPUSETUP ) ) dwCoopFlags |= DDSCL_FPUSETUP; if( FAILED( m_pDD->SetCooperativeLevel( m_hWnd, dwCoopFlags ) ) ) { DEBUG_MSG( _T("Couldn't set coop level") ); return D3DFWERR_COULDNTSETCOOPLEVEL; } // Check that we are NOT in a palettized display. That case will fail, // since the framework doesn't use palettes. DDSURFACEDESC2 ddsd; ddsd.dwSize = sizeof(ddsd); m_pDD->GetDisplayMode( &ddsd ); if( ddsd.ddpfPixelFormat.dwRGBBitCount <= 8 ) return D3DFWERR_INVALIDMODE; return S_OK; } //----------------------------------------------------------------------------- // Name: CreateFullscreenBuffers() // Desc: Creates the primary and (optional) backbuffer for rendering. // Windowed mode and fullscreen mode are handled differently. //----------------------------------------------------------------------------- HRESULT CD3DFramework7::CreateFullscreenBuffers( DDSURFACEDESC2* pddsd ) { HRESULT hr; // Get the dimensions of the screen bounds // Store the rectangle which contains the renderer SetRect( &m_rcScreenRect, 0, 0, pddsd->dwWidth, pddsd->dwHeight ); m_dwRenderWidth = m_rcScreenRect.right - m_rcScreenRect.left; m_dwRenderHeight = m_rcScreenRect.bottom - m_rcScreenRect.top; // Set the display mode to the requested dimensions. Check for // 320x200x8 modes, and set flag to avoid using ModeX DWORD dwModeFlags = 0; if( (320==m_dwRenderWidth) && (200==m_dwRenderHeight) && (8==pddsd->ddpfPixelFormat.dwRGBBitCount) ) dwModeFlags |= DDSDM_STANDARDVGAMODE; if( FAILED( m_pDD->SetDisplayMode( m_dwRenderWidth, m_dwRenderHeight, pddsd->ddpfPixelFormat.dwRGBBitCount, pddsd->dwRefreshRate, dwModeFlags ) ) ) { DEBUG_MSG( _T("Can't set display mode") ); return D3DFWERR_BADDISPLAYMODE; } // Setup to create the primary surface w/backbuffer DDSURFACEDESC2 ddsd; ZeroMemory( &ddsd, sizeof(ddsd) ); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; // Support for stereoscopic viewing if( m_bIsStereo ) { ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY; ddsd.ddsCaps.dwCaps2 |= DDSCAPS2_STEREOSURFACELEFT; } // Create the primary surface if( FAILED( hr = m_pDD->CreateSurface( &ddsd, &m_pddsFrontBuffer, NULL ) ) ) { DEBUG_MSG( _T("Error: Can't create primary surface") ); if( hr != DDERR_OUTOFVIDEOMEMORY ) return D3DFWERR_NOPRIMARY; DEBUG_MSG( _T("Error: Out of video memory") ); return DDERR_OUTOFVIDEOMEMORY; } // Get the backbuffer, which was created along with the primary. DDSCAPS2 ddscaps = { DDSCAPS_BACKBUFFER, 0, 0, 0 }; if( FAILED( hr = m_pddsFrontBuffer->GetAttachedSurface( &ddscaps, &m_pddsBackBuffer ) ) ) { DEBUG_ERR( hr, _T("Error: Can't get the backbuffer") ); return D3DFWERR_NOBACKBUFFER; } // Increment the backbuffer count (for consistency with windowed mode) m_pddsBackBuffer->AddRef(); // Support for stereoscopic viewing if( m_bIsStereo ) { // Get the left backbuffer, which was created along with the primary. DDSCAPS2 ddscaps = { 0, DDSCAPS2_STEREOSURFACELEFT, 0, 0 }; if( FAILED( hr = m_pddsBackBuffer->GetAttachedSurface( &ddscaps, &m_pddsBackBufferLeft ) ) ) { DEBUG_ERR( hr, _T("Error: Can't get the left backbuffer") ); return D3DFWERR_NOBACKBUFFER; } m_pddsBackBufferLeft->AddRef(); } return S_OK; } //----------------------------------------------------------------------------- // Name: CreateWindowedBuffers() // Desc: Creates the primary and (optional) backbuffer for rendering. // Windowed mode and fullscreen mode are handled differently. //----------------------------------------------------------------------------- HRESULT CD3DFramework7::CreateWindowedBuffers() { HRESULT hr; // Get the dimensions of the viewport and screen bounds GetClientRect( m_hWnd, &m_rcScreenRect ); ClientToScreen( m_hWnd, (POINT*)&m_rcScreenRect.left ); ClientToScreen( m_hWnd, (POINT*)&m_rcScreenRect.right ); m_dwRenderWidth = m_rcScreenRect.right - m_rcScreenRect.left; m_dwRenderHeight = m_rcScreenRect.bottom - m_rcScreenRect.top; // Create the primary surface 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 ) ) ) { DEBUG_MSG( _T("Error: Can't create primary surface") ); if( hr != DDERR_OUTOFVIDEOMEMORY ) return D3DFWERR_NOPRIMARY; DEBUG_MSG( _T("Error: Out of video memory") ); return DDERR_OUTOFVIDEOMEMORY; } // If in windowed-mode, create a clipper object LPDIRECTDRAWCLIPPER pcClipper; if( FAILED( hr = m_pDD->CreateClipper( 0, &pcClipper, NULL ) ) ) { DEBUG_MSG( _T("Error: Couldn't create clipper") ); return D3DFWERR_NOCLIPPER; } // Associate the clipper with the window pcClipper->SetHWnd( 0, m_hWnd ); m_pddsFrontBuffer->SetClipper( pcClipper ); SAFE_RELEASE( pcClipper ); // Create a backbuffer ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; ddsd.dwWidth = m_dwRenderWidth; ddsd.dwHeight = m_dwRenderHeight; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE; if( FAILED( hr = m_pDD->CreateSurface( &ddsd, &m_pddsBackBuffer, NULL ) ) ) { DEBUG_ERR( hr, _T("Error: Couldn't create the backbuffer") ); if( hr != DDERR_OUTOFVIDEOMEMORY ) return D3DFWERR_NOBACKBUFFER; DEBUG_MSG( _T("Error: Out of video memory") ); return DDERR_OUTOFVIDEOMEMORY; } return S_OK; } //----------------------------------------------------------------------------- // Name: CreateDirect3D() // Desc: Create the Direct3D interface //----------------------------------------------------------------------------- HRESULT CD3DFramework7::CreateDirect3D( GUID* pDeviceGUID ) { // Query DirectDraw for access to Direct3D if( FAILED( m_pDD->QueryInterface( IID_IDirect3D7, (VOID**)&m_pD3D ) ) ) { DEBUG_MSG( _T("Couldn't get the Direct3D interface") ); return D3DFWERR_NODIRECT3D; } // Create the device if( FAILED( m_pD3D->CreateDevice( *pDeviceGUID, m_pddsBackBuffer, &m_pd3dDevice) ) ) { DEBUG_MSG( _T("Couldn't create the D3DDevice") ); return D3DFWERR_NO3DDEVICE; } // Finally, set the viewport for the newly created device D3DVIEWPORT7 vp = { 0, 0, m_dwRenderWidth, m_dwRenderHeight, 0.0f, 1.0f }; if( FAILED( m_pd3dDevice->SetViewport( &vp ) ) ) { DEBUG_MSG( _T("Error: Couldn't set current viewport to device") ); return D3DFWERR_NOVIEWPORT; } return S_OK; } //----------------------------------------------------------------------------- // Name: CreateZBuffer() // Desc: Internal function called by Create() to make and attach a zbuffer // to the renderer //----------------------------------------------------------------------------- HRESULT CD3DFramework7::CreateZBuffer( GUID* pDeviceGUID ) { HRESULT hr; // Check if the device supports z-bufferless hidden surface removal. If so, // we don't really need a z-buffer D3DDEVICEDESC7 ddDesc; m_pd3dDevice->GetCaps( &ddDesc ); if( ddDesc.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ZBUFFERLESSHSR ) return S_OK; // Get z-buffer dimensions from the render target DDSURFACEDESC2 ddsd; ddsd.dwSize = sizeof(ddsd); m_pddsBackBuffer->GetSurfaceDesc( &ddsd ); // Setup the surface desc for the z-buffer. ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_PIXELFORMAT; ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | m_dwDeviceMemType; ddsd.ddpfPixelFormat.dwSize = 0; // Tag the pixel format as unitialized // Get an appropiate pixel format from enumeration of the formats. On the // first pass, we look for a zbuffer dpeth which is equal to the frame // buffer depth (as some cards unfornately require this). m_pD3D->EnumZBufferFormats( *pDeviceGUID, EnumZBufferFormatsCallback, (VOID*)&ddsd.ddpfPixelFormat ); if( 0 == ddsd.ddpfPixelFormat.dwSize ) { // Try again, just accepting any 16-bit zbuffer ddsd.ddpfPixelFormat.dwRGBBitCount = 16; m_pD3D->EnumZBufferFormats( *pDeviceGUID, EnumZBufferFormatsCallback, (VOID*)&ddsd.ddpfPixelFormat ); if( 0 == ddsd.ddpfPixelFormat.dwSize ) { DEBUG_MSG( _T("Device doesn't support requested zbuffer format") ); return D3DFWERR_NOZBUFFER; } } // Create and attach a z-buffer if( FAILED( hr = m_pDD->CreateSurface( &ddsd, &m_pddsZBuffer, NULL ) ) ) { DEBUG_MSG( _T("Error: Couldn't create a ZBuffer surface") ); if( hr != DDERR_OUTOFVIDEOMEMORY ) return D3DFWERR_NOZBUFFER; DEBUG_MSG( _T("Error: Out of video memory") ); return DDERR_OUTOFVIDEOMEMORY; } if( FAILED( m_pddsBackBuffer->AddAttachedSurface( m_pddsZBuffer ) ) ) { DEBUG_MSG( _T("Error: Couldn't attach zbuffer to render surface") ); return D3DFWERR_NOZBUFFER; } // For stereoscopic viewing, attach zbuffer to left surface as well if( m_bIsStereo ) { if( FAILED( m_pddsBackBufferLeft->AddAttachedSurface( m_pddsZBuffer ) ) ) { DEBUG_MSG( _T("Error: Couldn't attach zbuffer to left render surface") ); return D3DFWERR_NOZBUFFER; } } // Finally, this call rebuilds internal structures if( FAILED( m_pd3dDevice->SetRenderTarget( m_pddsBackBuffer, 0L ) ) ) { DEBUG_MSG( _T("Error: SetRenderTarget() failed after attaching zbuffer!") ); return D3DFWERR_NOZBUFFER; } return S_OK; } //----------------------------------------------------------------------------- // Name: RestoreSurfaces() // Desc: Checks for lost surfaces and restores them if lost. Note: Don't // restore render surface, since it's just a duplicate ptr. //----------------------------------------------------------------------------- HRESULT CD3DFramework7::RestoreSurfaces() { // Restore all surfaces (including video memory vertex buffers) m_pDD->RestoreAllSurfaces(); return S_OK; } //----------------------------------------------------------------------------- // Name: Move() // Desc: Moves the screen rect for windowed renderers //----------------------------------------------------------------------------- VOID CD3DFramework7::Move( INT x, INT y ) { if( TRUE == m_bIsFullscreen ) return; SetRect( &m_rcScreenRect, x, y, x + m_dwRenderWidth, y + m_dwRenderHeight ); } //----------------------------------------------------------------------------- // Name: FlipToGDISurface() // Desc: Puts the GDI surface in front of the primary, so that dialog // boxes and other windows drawing funcs may happen. //----------------------------------------------------------------------------- HRESULT CD3DFramework7::FlipToGDISurface( BOOL bDrawFrame ) { if( m_pDD && m_bIsFullscreen ) { m_pDD->FlipToGDISurface(); if( bDrawFrame ) { DrawMenuBar( m_hWnd ); RedrawWindow( m_hWnd, NULL, NULL, RDW_FRAME ); } } return S_OK; } //----------------------------------------------------------------------------- // Name: ShowFrame() // Desc: Show the frame on the primary surface, via a blt or a flip. //----------------------------------------------------------------------------- HRESULT CD3DFramework7::ShowFrame() { if( NULL == m_pddsFrontBuffer ) return D3DFWERR_NOTINITIALIZED; if( m_bIsFullscreen ) { // We are in fullscreen mode, so perform a flip. if( m_bIsStereo ) return m_pddsFrontBuffer->Flip( NULL, DDFLIP_WAIT | DDFLIP_STEREO ); else return m_pddsFrontBuffer->Flip( NULL, DDFLIP_WAIT ); } else { // We are in windowed mode, so perform a blit. return m_pddsFrontBuffer->Blt( &m_rcScreenRect, m_pddsBackBuffer, NULL, DDBLT_WAIT, NULL ); } }