//----------------------------------------------------------------------------- // File: Multimon.cpp // // Desc: This sample demonstrates the following programming concepts: // Writing code for a multi-monitor program that works on both Windows 95 // (which does not support multiple monitors) and later Windows versions // (which do support it). // Using DirectDrawEnumerateEx to enumerate displays. // Working with separate device and focus windows. // Creating a video-memory sprite that spans multiple screens using multiple // copies of the image data. // Creating a system-memory sprite that spans multiple screens using a shared // copy of the image data. // // Copyright (c) 1998-2001 Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- // Multimon.h implements support of the multimon APIs for when your program // runs on Windows 95 systems. You can #include multimon.h in all your source // files that use multimon APIs, and you should #define COMPILE_MULTIMON_STUBS // in only one of your source files. #define COMPILE_MULTIMON_STUBS #include #include #include #include "resource.h" //----------------------------------------------------------------------------- // Defines, constants, and global variables //----------------------------------------------------------------------------- #define NAME TEXT("MultiMon DirectDraw Sample") #define TITLE TEXT("MultiMon DirectDraw Sample") // An EnumInfo contains extra enumeration information that is passed // into the the DDEnumCallbackEx function. struct EnumInfo { BOOL bMultimonSupported; HRESULT hr; }; // A Screen represents one display that images can be drawn to. struct Screen { GUID guid; TCHAR szDesc[200]; HMONITOR hmon; LPDIRECTDRAW7 pDD; LPDIRECTDRAWSURFACE7 pDDSFront; LPDIRECTDRAWSURFACE7 pDDSBack; Screen* pScreenNext; // For linked list }; // A ScreenSurface holds a DirectDrawSurface that can be used on a // particular screen. struct ScreenSurface { Screen* pScreen; LPDIRECTDRAWSURFACE7 pDDS; ScreenSurface* pScreenSurfaceNext; // For linked list // Could add a "last used time" field, which could be used to // determine whether this ScreenSurface should be // removed to free up video memory for another surface }; // A Sprite holds generic information about a drawable image, and // a linked list of ScreenSurfaces (one per screen). struct Sprite { TCHAR szName[64]; // Name of this Sprite BOOL bForceSystem; // If TRUE, don't try to create video memory surfaces RECT rcSrc; // Dimensions of the image RECT rcDest; // Destination rectangle to draw image into LONG xVel; // X-Velocity for animation LONG yVel; // Y-Velocity for animation HBITMAP hbmImage; // Loaded bitmap image BYTE* pImageData; // Sharable pointer to DD surface data DDSURFACEDESC2 ddsd; // Holds pitch and pixel format of pImageData ScreenSurface* pScreenSurfaceFirst; // Linked list of ScreenSurfaces }; Screen* g_pScreenFirst = NULL; // Linked list of Screens Sprite* g_pSprite1 = NULL; // A sprite that uses video memory where possible Sprite* g_pSprite2 = NULL; // A sprite that always uses system memory HWND g_hWnd = NULL; // Main app focus HWND BOOL g_bActive = TRUE; // Whether app is actively drawing //----------------------------------------------------------------------------- // Function prototypes //----------------------------------------------------------------------------- INT APIENTRY WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, INT nCmdShow ); HRESULT CreateFocusWindow( HINSTANCE hInstance ); HRESULT EnumerateScreens( VOID ); BOOL WINAPI DDEnumCallback( GUID* pGuid, LPTSTR pszDesc, LPTSTR pszDriverName, VOID* pContext ); BOOL WINAPI DDEnumCallbackEx( GUID* pGuid, LPTSTR pszDesc, LPTSTR pszDriverName, VOID* pContext, HMONITOR hmon ); HRESULT InitScreens( VOID ); HRESULT InitSprites( VOID ); HRESULT MainLoop( VOID ); LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); VOID UpdateFrame( VOID ); BOOL RectIntersectsMonitor( RECT* prcSrc, HMONITOR hmon, RECT* prcScreen ); HRESULT FindOrBuildScreenSurface( Sprite* pSprite, Screen* pScreen, ScreenSurface** ppScreenSurface ); HRESULT SetupScreenSurfaceDDS( Sprite* pSprite, ScreenSurface* pScreenSurface ); VOID DestroyScreenSurfaces( Sprite* pSprite ); VOID Cleanup( VOID ); //----------------------------------------------------------------------------- // Name: WinMain() // Desc: Initializes the application, then starts the main application loop. //----------------------------------------------------------------------------- INT APIENTRY WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, INT nCmdShow ) { HRESULT hr; if( FAILED( hr = CreateFocusWindow(hInst) ) ) return 0; if( FAILED( hr = EnumerateScreens() ) ) return 0; if( FAILED( hr = InitScreens() ) ) return 0; if( FAILED( hr = InitSprites() ) ) return 0; if( FAILED( hr = MainLoop() ) ) return 0; return 0; } //----------------------------------------------------------------------------- // Name: CreateFocusWindow() // Desc: Creates the focus window, which is the window which will receive user // input. //----------------------------------------------------------------------------- HRESULT CreateFocusWindow( HINSTANCE hInstance ) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_ICON1) ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; wc.lpszClassName = NAME; RegisterClass( &wc ); // Window style and dimensions are not important, // since they will be adjusted elsewhere. g_hWnd = CreateWindowEx( 0, NAME, TITLE, 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL ); // It is important to call ShowWindow on this window to ensure // that any activity from other windows is obscured. ShowWindow( g_hWnd, SW_SHOW ); if( g_hWnd == NULL ) return E_FAIL; return S_OK; } //----------------------------------------------------------------------------- // Name: EnumerateScreens() // Desc: Creates a Screen structure for every appropriate screen in the user's // computer. //----------------------------------------------------------------------------- HRESULT EnumerateScreens() { HRESULT hr; EnumInfo enumInfo; ZeroMemory( &enumInfo, sizeof(enumInfo) ); enumInfo.bMultimonSupported = TRUE; if( FAILED( hr = DirectDrawEnumerateEx( DDEnumCallbackEx, &enumInfo, DDENUM_ATTACHEDSECONDARYDEVICES ) ) ) return hr; // If something failed inside the enumeration, be sure to return that HRESULT if( FAILED(enumInfo.hr) ) return enumInfo.hr; return S_OK; } //----------------------------------------------------------------------------- // Name: DDEnumCallbackEx() // Desc: This callback function is called by DirectDraw once for each // available DirectDraw device. In this implementation, it saves the // GUID, device description, and hmon in a Screen structure for later use. //----------------------------------------------------------------------------- BOOL WINAPI DDEnumCallbackEx( GUID* pGuid, LPTSTR pszDesc, LPTSTR pszDriverName, VOID* pContext, HMONITOR hmon ) { Screen* pScreenNew; EnumInfo* pEnumInfo = (EnumInfo*)pContext; GUID guidNull; ZeroMemory( &guidNull, sizeof(GUID) ); if( g_pScreenFirst != NULL && g_pScreenFirst->guid == guidNull ) { // We must be running on a multimon system, so get rid of the // guidNull Screen -- we want Screens with specific GUIDs. delete g_pScreenFirst; g_pScreenFirst = NULL; } // Store all the info in a Screen structure pScreenNew = new Screen; if( pScreenNew == NULL ) { pEnumInfo->hr = E_OUTOFMEMORY; return FALSE; // fatal error, stop enumerating } ZeroMemory( pScreenNew, sizeof(Screen) ); if( pGuid == NULL ) pScreenNew->guid = guidNull; else pScreenNew->guid = *pGuid; lstrcpy( pScreenNew->szDesc, pszDesc ); pScreenNew->hmon = hmon; // Insert Screen into global linked list if( g_pScreenFirst == NULL ) { g_pScreenFirst = pScreenNew; } else { // Insert at end of list Screen* pScreen = g_pScreenFirst; while( pScreen->pScreenNext != NULL ) pScreen = pScreen->pScreenNext; pScreen->pScreenNext = pScreenNew; } return TRUE; // Keep enumerating } //----------------------------------------------------------------------------- // Name: InitScreens() // Desc: For each Screen, this function initializes DirectDraw and sets up the // front buffer, back buffer, and a clipper. Many fullscreen DirectDraw // programs don't need to create clippers, but this one does so to allow // the sprites to be automatically clipped to each display. //----------------------------------------------------------------------------- HRESULT InitScreens( VOID ) { HRESULT hr; Screen* pScreen = NULL; GUID* pGuid = NULL; DWORD dwFlags; DDSURFACEDESC2 ddsd; LPDIRECTDRAWCLIPPER pClip = NULL; DDSCAPS2 ddsCaps; RECT rc; HRGN hrgn; BYTE rgnDataBuffer[1024]; GUID guidNull; ZeroMemory( &guidNull, sizeof(GUID) ); for( pScreen = g_pScreenFirst; pScreen != NULL; pScreen = pScreen->pScreenNext ) { if( pScreen->guid == guidNull ) pGuid = NULL; else pGuid = &pScreen->guid; if( FAILED(hr = DirectDrawCreateEx( pGuid, (VOID**) &(pScreen->pDD), IID_IDirectDraw7, NULL ) ) ) return hr; if( pScreen == g_pScreenFirst) { dwFlags = DDSCL_SETFOCUSWINDOW; if( FAILED( hr = pScreen->pDD->SetCooperativeLevel( g_hWnd, dwFlags ) ) ) return hr; dwFlags = DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN; if( FAILED( hr = pScreen->pDD->SetCooperativeLevel(g_hWnd, dwFlags ) ) ) return hr; } else { dwFlags = DDSCL_SETFOCUSWINDOW | DDSCL_CREATEDEVICEWINDOW | DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN; if( FAILED( hr = pScreen->pDD->SetCooperativeLevel(g_hWnd, dwFlags ) ) ) return hr; } if( FAILED( hr = pScreen->pDD->SetDisplayMode( 640, 480, 16, 0, 0 ) ) ) return hr; } // Note: It is recommended that programs call SetDisplayMode on all screens // before creating/acquiring any DirectDrawSurfaces. for( pScreen = g_pScreenFirst; pScreen != NULL; pScreen = pScreen->pScreenNext ) { ZeroMemory( &ddsd, sizeof(ddsd) ); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS; ddsd.dwBackBufferCount = 1; ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE; if( FAILED( hr = pScreen->pDD->CreateSurface( &ddsd, &pScreen->pDDSFront, NULL ) ) ) { return hr; } ZeroMemory( &ddsCaps, sizeof(ddsCaps) ); ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; if( FAILED( hr = pScreen->pDDSFront->GetAttachedSurface( &ddsCaps, &pScreen->pDDSBack ) ) ) return hr; ZeroMemory( &ddsd, sizeof(ddsd) ); ddsd.dwSize = sizeof(ddsd); if( FAILED( hr = pScreen->pDDSFront->GetSurfaceDesc( &ddsd ) ) ) return hr; SetRect( &rc, 0, 0, ddsd.dwWidth, ddsd.dwHeight ); hrgn = CreateRectRgn( 0, 0, ddsd.dwWidth, ddsd.dwHeight ); GetRegionData( hrgn, sizeof(rgnDataBuffer), (RGNDATA*)rgnDataBuffer ); DeleteObject( hrgn ); if( FAILED( hr = pScreen->pDD->CreateClipper( 0, &pClip, 0 ) ) ) return hr; if( FAILED( hr = pClip->SetClipList( (RGNDATA*)rgnDataBuffer, 0 ) ) ) { pClip->Release(); return hr; } if( FAILED( hr = pScreen->pDDSFront->SetClipper( pClip ) ) ) { pClip->Release(); return hr; } if( FAILED( hr = pScreen->pDDSBack->SetClipper( pClip ) ) ) { pClip->Release(); return hr; } pClip->Release(); } return S_OK; } //----------------------------------------------------------------------------- // Name: InitSprites() // Desc: Initializes the objects that will be drawn and animated. Note that // the ScreenSurfaces are created when they are first needed, not here. //----------------------------------------------------------------------------- HRESULT InitSprites( VOID ) { BITMAP bm; // Initialize the first Sprite. This sprite will try to use video memory // on each Screen. g_pSprite1 = new Sprite; if( g_pSprite1 == NULL ) return E_OUTOFMEMORY; ZeroMemory( g_pSprite1, sizeof(Sprite) ); lstrcpy( g_pSprite1->szName, TEXT("Sprite 1") ); g_pSprite1->bForceSystem = FALSE; // This sprite will try to use video memory g_pSprite1->hbmImage = (HBITMAP) LoadImage( GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP1), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION ); if( g_pSprite1->hbmImage == NULL ) return E_FAIL; GetObject( g_pSprite1->hbmImage, sizeof(bm), &bm ); // get size of bitmap SetRect( &g_pSprite1->rcSrc, 0, 0, bm.bmWidth, bm.bmHeight ); g_pSprite1->rcDest = g_pSprite1->rcSrc; g_pSprite1->xVel = 2; // Animation velocity g_pSprite1->yVel = 1; // Initialize the second Sprite. This sprite will use system memory (and // share that memory between ScreenSurfaces whenever possible). g_pSprite2 = new Sprite; if( g_pSprite2 == NULL ) return E_OUTOFMEMORY; ZeroMemory( g_pSprite2, sizeof(Sprite) ); lstrcpy(g_pSprite2->szName, TEXT("Sprite 2")); g_pSprite2->bForceSystem = TRUE; // This sprite will always use system memory g_pSprite2->hbmImage = (HBITMAP) LoadImage( GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP2), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION ); if( g_pSprite2->hbmImage == NULL ) return E_FAIL; GetObject( g_pSprite2->hbmImage, sizeof(bm), &bm ); // get size of bitmap SetRect( &g_pSprite2->rcSrc, 0, 0, bm.bmWidth, bm.bmHeight ); g_pSprite2->rcDest = g_pSprite1->rcSrc; g_pSprite2->xVel = -1; // Animation velocity g_pSprite2->yVel = -2; return S_OK; } //----------------------------------------------------------------------------- // Name: MainLoop() // Desc: The main window message pump. When the application is active (not // minimized), it uses PeekMessage so that UpdateFrame can be called // frequently once all window messages are handled. When it is not // active, GetMessage is used instead to give more processing time to // other running programs. //----------------------------------------------------------------------------- HRESULT MainLoop( VOID ) { MSG msg; BOOL bGotMsg; while( TRUE ) { if( g_bActive) bGotMsg = PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ); else bGotMsg = GetMessage( &msg, NULL, 0, 0); if( msg.message == WM_QUIT) return S_OK; if( bGotMsg) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else if( g_bActive) { UpdateFrame(); } } } //----------------------------------------------------------------------------- // Name: WindowProc() // Desc: Handler for window messages. //----------------------------------------------------------------------------- LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch(uMsg) { case WM_SIZE: if( SIZE_MAXHIDE == wParam || SIZE_MINIMIZED == wParam ) { g_bActive = FALSE; // Give window an icon and system menu on the taskbar when minimized SetWindowLong(hWnd, GWL_STYLE, WS_SYSMENU); } else { g_bActive = TRUE; // Remove any window "decoration" when fullscreen SetWindowLong(hWnd, GWL_STYLE, WS_POPUP); } return DefWindowProc( hWnd, uMsg, wParam, lParam ); case WM_CLOSE: return DefWindowProc( hWnd, uMsg, wParam, lParam ); case WM_CHAR: DestroyWindow( hWnd ); return 0; case WM_DESTROY: Cleanup(); PostQuitMessage( 0 ); return 0; default: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } } //----------------------------------------------------------------------------- // Name: UpdateFrame() // Desc: Renders one frame of animation, then updates the sprites for the next // frame. //----------------------------------------------------------------------------- VOID UpdateFrame( VOID ) { HRESULT hr; Screen* pScreen; RECT rcScreen; ScreenSurface* pScreenSurface; DDBLTFX ddbltfx; for( pScreen = g_pScreenFirst; pScreen != NULL; pScreen = pScreen->pScreenNext ) { // Handle lost surfaces if( pScreen->pDDSFront->IsLost() == DDERR_SURFACELOST ) { // Though surface memory can be reaquired via RestoreAllSurfaces, the // image contents will be undefined. So we destroy all the ScreenSurfaces // and let them be recreated as needed. Calling RestoreAllSurfaces // takes care of the remaining surfaces (the front and back buffers). DestroyScreenSurfaces( g_pSprite1 ); DestroyScreenSurfaces( g_pSprite2 ); hr = pScreen->pDD->RestoreAllSurfaces(); } // Clear the back buffer for this Screen ZeroMemory( &ddbltfx, sizeof(ddbltfx) ); ddbltfx.dwSize = sizeof(ddbltfx); ddbltfx.dwFillColor = 0; // Black hr = pScreen->pDDSBack->Blt( NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx ); // Draw first sprite if( RectIntersectsMonitor( &g_pSprite1->rcDest, pScreen->hmon, &rcScreen ) ) { hr = FindOrBuildScreenSurface( g_pSprite1, pScreen, &pScreenSurface ); if( SUCCEEDED(hr) ) { hr = pScreen->pDDSBack->Blt( &rcScreen, pScreenSurface->pDDS, &g_pSprite1->rcSrc, DDBLT_WAIT, NULL ); } } // Draw second sprite if( RectIntersectsMonitor( &g_pSprite2->rcDest, pScreen->hmon, &rcScreen ) ) { hr = FindOrBuildScreenSurface( g_pSprite2, pScreen, &pScreenSurface ); if( SUCCEEDED( hr ) ) { hr = pScreen->pDDSBack->Blt( &rcScreen, pScreenSurface->pDDS, &g_pSprite2->rcSrc, DDBLT_WAIT, NULL ); } } } // Flip all screens. This is done in a separate loop to make the flips happen // as close together in time as possible for( pScreen = g_pScreenFirst; pScreen != NULL; pScreen = pScreen->pScreenNext ) { hr = pScreen->pDDSFront->Flip( NULL, DDFLIP_WAIT ); } // Animate Sprites for the next frame. The sprites are bounced against the // virtual desktop, which may cause them to partially or totally disappear // if your screens are set up in a way that is non-rectangular. Since the // animation is not the purpose of this demo, this simplified approach is used. RECT rcDesktop; rcDesktop.left = GetSystemMetrics(SM_XVIRTUALSCREEN); rcDesktop.right = rcDesktop.left + GetSystemMetrics(SM_CXVIRTUALSCREEN); rcDesktop.top = GetSystemMetrics(SM_YVIRTUALSCREEN); rcDesktop.bottom = rcDesktop.top + GetSystemMetrics(SM_CYVIRTUALSCREEN); // Animate first sprite OffsetRect( &g_pSprite1->rcDest, g_pSprite1->xVel, g_pSprite1->yVel ); if( ( g_pSprite1->rcDest.right > rcDesktop.right && g_pSprite1->xVel > 0 ) || ( g_pSprite1->rcDest.left < rcDesktop.left && g_pSprite1->xVel < 0 ) ) { g_pSprite1->xVel = -g_pSprite1->xVel; } if( ( g_pSprite1->rcDest.bottom > rcDesktop.bottom && g_pSprite1->yVel > 0 ) || ( g_pSprite1->rcDest.top < rcDesktop.top && g_pSprite1->yVel < 0 ) ) { g_pSprite1->yVel = -g_pSprite1->yVel; } // Animate second sprite OffsetRect( &g_pSprite2->rcDest, g_pSprite2->xVel, g_pSprite2->yVel ); if( ( g_pSprite2->rcDest.right > rcDesktop.right && g_pSprite2->xVel > 0 ) || ( g_pSprite2->rcDest.left < rcDesktop.left && g_pSprite2->xVel < 0 ) ) { g_pSprite2->xVel = -g_pSprite2->xVel; } if( ( g_pSprite2->rcDest.bottom > rcDesktop.bottom && g_pSprite2->yVel > 0 ) || ( g_pSprite2->rcDest.top < rcDesktop.top && g_pSprite2->yVel < 0 ) ) { g_pSprite2->yVel = -g_pSprite2->yVel; } } //----------------------------------------------------------------------------- // Name: RectIntersectsMonitor() // Desc: Returns TRUE if prcSrc intersects the monitor hmon, and uses prcScreen // to store prcSrc in that monitor's local coordinate system. //----------------------------------------------------------------------------- BOOL RectIntersectsMonitor( RECT* prcSrc, HMONITOR hmon, RECT* prcScreen ) { MONITORINFO mi; RECT rcIntersection; BOOL bIntersects; ZeroMemory( &mi, sizeof(mi) ); mi.cbSize = sizeof(mi); if( hmon == NULL ) { SetRect( &mi.rcMonitor, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) ); } else { GetMonitorInfo(hmon, &mi); } bIntersects = IntersectRect( &rcIntersection, prcSrc, &mi.rcMonitor ); if( !bIntersects) return FALSE; *prcScreen = *prcSrc; OffsetRect( prcScreen, -mi.rcMonitor.left, -mi.rcMonitor.top ); return TRUE; } //----------------------------------------------------------------------------- // Name: FindOrBuildScreenSurface() // Desc: This is called when UpdateFrame needs to draw the image of a Sprite // onto a particular Screen. If a ScreenSurface already exists for this // Screen and Sprite, a pointer to it is returned. Otherwise, a new // ScreenSurface is created (and stored for future reuse). //----------------------------------------------------------------------------- HRESULT FindOrBuildScreenSurface( Sprite* pSprite, Screen* pScreen, ScreenSurface** ppScreenSurface ) { HRESULT hr; ScreenSurface* pScreenSurface; for( pScreenSurface = pSprite->pScreenSurfaceFirst; pScreenSurface != NULL; pScreenSurface = pScreenSurface->pScreenSurfaceNext ) { if( pScreenSurface->pScreen == pScreen ) { // ScreenSurface exists for this Screen, so return a pointer to it *ppScreenSurface = pScreenSurface; return S_OK; } } // No ScreenSurface for this Screen exists yet, so build one. pScreenSurface = new ScreenSurface; if( pScreenSurface == NULL ) return E_OUTOFMEMORY; ZeroMemory( pScreenSurface, sizeof(ScreenSurface) ); pScreenSurface->pScreen = pScreen; if( FAILED( hr = SetupScreenSurfaceDDS(pSprite, pScreenSurface ) ) ) { delete pScreenSurface; return hr; } // Insert this new ScreenSurface in the Sprite's list: pScreenSurface->pScreenSurfaceNext = pSprite->pScreenSurfaceFirst; pSprite->pScreenSurfaceFirst = pScreenSurface; *ppScreenSurface = pScreenSurface; return S_OK; } //----------------------------------------------------------------------------- // Name: SetupScreenSurfaceDDS() // Desc: Generates the DirectDrawSurface for a new ScreenSurface, and draws // the appropriate image into it. //----------------------------------------------------------------------------- HRESULT SetupScreenSurfaceDDS( Sprite* pSprite, ScreenSurface* pScreenSurface ) { HRESULT hr; DDSURFACEDESC2 ddsd; TCHAR sz[200]; Screen* pScreen = pScreenSurface->pScreen; ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; ddsd.dwWidth = pSprite->rcSrc.right - pSprite->rcSrc.left; ddsd.dwHeight = pSprite->rcSrc.bottom - pSprite->rcSrc.top; ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY; // Try to create the surface in video memory, unless the bForceSystem flag // is set on the Sprite. if( pSprite->bForceSystem || FAILED( hr = pScreen->pDD->CreateSurface( &ddsd, &pScreenSurface->pDDS, NULL ) ) ) { // Either this sprite has the bForceSystem flag, or creation in video // memory failed, so try to create the surface in system memory. ddsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY; if( FAILED( hr = pScreen->pDD->CreateSurface( &ddsd, &pScreenSurface->pDDS, NULL ) ) ) return hr; } if( ddsd.ddsCaps.dwCaps == DDSCAPS_SYSTEMMEMORY && pSprite->pImageData != NULL ) { // See if we can reuse the image data that is stored in the Sprite. // As long as the pixel formats match, the image is reusable. ZeroMemory( &ddsd, sizeof(ddsd) ); ddsd.dwSize = sizeof(ddsd); if( FAILED( hr = pScreenSurface->pDDS->GetSurfaceDesc( &ddsd ) ) ) return hr; if( ddsd.ddpfPixelFormat.dwRGBBitCount == pSprite->ddsd.ddpfPixelFormat.dwRGBBitCount && ddsd.ddpfPixelFormat.dwRBitMask == pSprite->ddsd.ddpfPixelFormat.dwRBitMask && ddsd.ddpfPixelFormat.dwGBitMask == pSprite->ddsd.ddpfPixelFormat.dwGBitMask && ddsd.ddpfPixelFormat.dwBBitMask == pSprite->ddsd.ddpfPixelFormat.dwBBitMask ) { // Make the DDS use the Sprite's pImageData for its surface contents if( FAILED( hr = pScreenSurface->pDDS->SetSurfaceDesc( &pSprite->ddsd, 0 ) ) ) return hr; return S_OK; // All done! This DDS is ready to use. } // Otherwise, we can't share image data, and this system memory surface // will be for this Screen only. } // Copy image data from the Sprite to this ScreenSurface: HDC hdc; if( FAILED( hr = pScreenSurface->pDDS->GetDC( &hdc ) ) ) return hr; HDC hdcImage; HGDIOBJ hgdiobjOld; DWORD dwWidth = pSprite->rcSrc.right - pSprite->rcSrc.left; DWORD dwHeight = pSprite->rcSrc.bottom - pSprite->rcSrc.top; hdcImage = CreateCompatibleDC(NULL); hgdiobjOld = SelectObject(hdcImage, pSprite->hbmImage); StretchBlt( hdc, 0, 0, dwWidth, dwHeight, hdcImage, 0, 0, dwWidth, dwHeight, SRCCOPY ); SelectObject( hdcImage, hgdiobjOld ); // restore previously selected object DeleteDC( hdcImage ); TextOut( hdc, 0, 0, pSprite->szName, lstrlen(pSprite->szName) ); pScreenSurface->pDDS->ReleaseDC(hdc); if( ddsd.ddsCaps.dwCaps == DDSCAPS_VIDEOMEMORY ) { if( FAILED( hr = pScreenSurface->pDDS->GetDC( &hdc ) ) ) return hr; wsprintf( sz, TEXT("Video memory copy") ); TextOut( hdc, 0, 20, sz, lstrlen(sz) ); wsprintf( sz, TEXT("for %s"), pScreen->szDesc ); TextOut( hdc, 0, 40, sz, lstrlen(sz) ); pScreenSurface->pDDS->ReleaseDC(hdc); } else if( pSprite->pImageData == NULL ) { // No shared copy exists yet, so create one using data in this // system memory surface. if( FAILED( hr = pScreenSurface->pDDS->GetDC( &hdc ) ) ) return hr; wsprintf(sz, TEXT("Shared System memory copy") ); TextOut(hdc, 0, 20, sz, lstrlen(sz) ); pScreenSurface->pDDS->ReleaseDC( hdc ); // Copy image to pImageData so it can be shared among ScreenSurfaces: if( SUCCEEDED( hr = pScreenSurface->pDDS->Lock( NULL, &ddsd, DDLOCK_READONLY | DDLOCK_WAIT, NULL ) ) ) { pSprite->pImageData = new BYTE[ ddsd.lPitch * ddsd.dwHeight ]; if( pSprite->pImageData != NULL ) { // Store size, pitch, pixel format, and surface pointer info in Sprite pSprite->ddsd = ddsd; pSprite->ddsd.lpSurface = pSprite->pImageData; pSprite->ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH | DDSD_PIXELFORMAT | DDSD_LPSURFACE; // Copy image data from DDS's surface memory to Sprite's buffer CopyMemory( pSprite->pImageData, ddsd.lpSurface, ddsd.lPitch * ddsd.dwHeight ); } pScreenSurface->pDDS->Unlock(NULL); if( pSprite->pImageData != NULL ) { // May as well make this ScreenSurface use the sharable copy too: if( FAILED( hr = pScreenSurface->pDDS->SetSurfaceDesc( &pSprite->ddsd, 0 ) ) ) return hr; } } } else { // Shared copy exists, but attempt to use it failed (probably due to // mismatched pixel format), so indicate that this as a non-shared sysmem copy: if( FAILED( hr = pScreenSurface->pDDS->GetDC( &hdc ) ) ) return hr; wsprintf( sz, TEXT("System memory copy") ); TextOut( hdc, 0, 20, sz, lstrlen(sz) ); wsprintf( sz, TEXT("for %s"), pScreen->szDesc ); TextOut( hdc, 0, 40, sz, lstrlen(sz) ); pScreenSurface->pDDS->ReleaseDC( hdc ); } return S_OK; } //----------------------------------------------------------------------------- // Name: DestroyScreenSurfaces() // Desc: Destroys all ScreenSurfaces attached to the given Sprite. This is // called after restoring all surfaces (since image data may be lost) and // when preparing to exit the program. //----------------------------------------------------------------------------- VOID DestroyScreenSurfaces( Sprite* pSprite ) { ScreenSurface* pScreenSurface; ScreenSurface* pScreenSurfaceNext; pScreenSurface = pSprite->pScreenSurfaceFirst; pSprite->pScreenSurfaceFirst = NULL; while( pScreenSurface != NULL ) { pScreenSurfaceNext = pScreenSurface->pScreenSurfaceNext; pScreenSurface->pDDS->Release(); delete pScreenSurface; pScreenSurface = pScreenSurfaceNext; } if( pSprite->pImageData != NULL ) { delete pSprite->pImageData; pSprite->pImageData = NULL; } ZeroMemory( &pSprite->ddsd, sizeof(pSprite->ddsd) ); } //----------------------------------------------------------------------------- // Name: Cleanup() // Desc: Releases all resources allocated during this program. //----------------------------------------------------------------------------- VOID Cleanup( VOID ) { if( g_pSprite1 != NULL ) { DestroyScreenSurfaces(g_pSprite1); delete g_pSprite1; g_pSprite1 = NULL; } if( g_pSprite2 != NULL ) { DestroyScreenSurfaces(g_pSprite2); delete g_pSprite2; g_pSprite2 = NULL; } Screen* pScreen; Screen* pScreenNext; pScreen = g_pScreenFirst; g_pScreenFirst = NULL; while( pScreen != NULL ) { pScreenNext = pScreen->pScreenNext; pScreen->pDDSBack->Release(); pScreen->pDDSFront->Release(); pScreen->pDD->RestoreDisplayMode(); pScreen->pDD->SetCooperativeLevel(g_hWnd, DDSCL_NORMAL); pScreen->pDD->Release(); delete pScreen; pScreen = pScreenNext; } }