//----------------------------------------------------------------------------- // File: RefreshRate.cpp // // Desc: This example demonstrates basic usage of the IDirectDraw7::StartRefreshRate // and IDirectDraw7::EvaluateMode methods. Together, these methods allow an // application to explore what display modes and refresh rates the monitor // connected to this display device is able to display, through a manual // user-controlled process. The application will present the UI that asks // the user if the current mode under test is being displayed correctly by // the monitor. // // Applications should use these methods when they are interested in using // higher refresh rates. // // The basic idea is that DirectDraw will setup a list of modes to be tested // (based on the list the app passed in), and then sequentially test them // under application control. The app starts the test process, and then // calls IDirectDraw7::EvaluateMode continuously. DirectDraw will take care // of setting the modes. All the app has to do is SetCooperativeLevel // beforehand, and then handle surface loss and drawing the UI that asks the // user if they can see the current mode under test. DirectDraw returns // enough information from IDirectDraw7::EvaluateMode to allow the app to // know when to do these things, and when to stop testing. The app can pass // a flag to IDirectDraw7::EvaluateMode if the user happened to say they // could see the mode corretly, which will cause DirectDraw to mark the mode // as good and move on. DirectDraw may also decide that time as run out and // give up on a certain mode. // // DirectDraw uses information at its disposal from any automated means to // make the testing process as short as possible, and applications only need // to test modes they are interested in. // // Copyright (c) 1999-2001 Microsoft Corp. All rights reserved. //----------------------------------------------------------------------------- #define STRICT #include #include #include #include #include #include "resource.h" //----------------------------------------------------------------------------- // Function-prototypes //----------------------------------------------------------------------------- INT_PTR CALLBACK MainDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ); HRESULT GetDirectDrawDevices(); VOID OnInitDialog(); HRESULT ResetDeviceModes( DWORD dwDeviceIndex ); HRESULT UpdateModesListBox( DWORD dwDeviceIndex ); BOOL WINAPI DDEnumCallbackEx( GUID*, LPSTR, LPSTR, LPVOID, HMONITOR ); HRESULT WINAPI EnumModesCallback( LPDDSURFACEDESC pddsd, LPVOID pContext ); HRESULT WINAPI EnumAllModesCallback( LPDDSURFACEDESC2 pddsd, LPVOID pContext ); HRESULT OnRefreshRate(); HRESULT PerformDirectDrawRefreshRate( LPDIRECTDRAW7 pDD, SIZE* aTestModes, DWORD dwTestModes ); //----------------------------------------------------------------------------- // Defines, constants, and global variables //----------------------------------------------------------------------------- #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } struct DDRAW_DEVICE_STRUCT { GUID guid; CHAR strDescription[256]; CHAR strDriverName[64]; DWORD dwModeCount; SIZE aModeSize[256]; }; HWND g_hDlg = NULL; LPDIRECTDRAW g_pDD = NULL; DDRAW_DEVICE_STRUCT g_aDevices[16]; DWORD g_dwDeviceCount; //----------------------------------------------------------------------------- // Name: WinMain() // Desc: Entry point for the application. Since we use a simple dialog for // user interaction we don't need to pump messages. //----------------------------------------------------------------------------- INT APIENTRY WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, INT nCmdShow ) { if( FAILED( GetDirectDrawDevices() ) ) return FALSE; // Display the main dialog box. DialogBox( hInst, MAKEINTRESOURCE(IDD_MAIN), NULL, MainDlgProc ); return TRUE; } //----------------------------------------------------------------------------- // Name: MainDlgProc() // Desc: Handles dialog messages //----------------------------------------------------------------------------- INT_PTR CALLBACK MainDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_INITDIALOG: // Store HWND in global g_hDlg = hDlg; OnInitDialog(); break; case WM_COMMAND: switch( LOWORD(wParam) ) { case IDCANCEL: EndDialog( hDlg, IDCANCEL ); break; case IDC_TEST: if( FAILED( OnRefreshRate() ) ) { MessageBox( g_hDlg, "Error doing starting mode test. " "Sample will now exit.", "DirectDraw Sample", MB_OK | MB_ICONERROR ); EndDialog( g_hDlg, IDABORT ); } break; case IDC_RESET: HWND hWndDeviceList; DWORD dwDeviceIndex; // Get the currently selected DirectDraw device hWndDeviceList = GetDlgItem( hDlg, IDC_DDRAW_DEVICE_LIST ); dwDeviceIndex = (DWORD)SendMessage( hWndDeviceList, LB_GETCURSEL, 0, 0 ); // Reset the modes for it if( FAILED( ResetDeviceModes( dwDeviceIndex ) ) ) { MessageBox( g_hDlg, "Error reset DirectDraw device. " "Sample will now exit.", "DirectDraw Sample", MB_OK | MB_ICONERROR ); EndDialog( g_hDlg, IDABORT ); } break; case IDC_DDRAW_DEVICE_LIST: switch( HIWORD(wParam) ) { case LBN_SELCHANGE: // Get the currently selected DirectDraw device DWORD dwDeviceIndex; dwDeviceIndex = (DWORD)SendMessage( (HWND) lParam, LB_GETCURSEL, 0, 0 ); // Update the list boxes using it if( FAILED( UpdateModesListBox( dwDeviceIndex ) ) ) { MessageBox( g_hDlg, "Error enumerating DirectDraw modes." "Sample will now exit.", "DirectDraw Sample", MB_OK | MB_ICONERROR ); EndDialog( g_hDlg, IDABORT ); } break; default: return FALSE; } break; default: return FALSE; // Didn't handle message } break; default: return FALSE; // Didn't handle message } return TRUE; // Handled message } //----------------------------------------------------------------------------- // Name: GetDirectDrawDevices() // Desc: Retrieves all available DirectDraw devices and stores the information // in g_aDevices[] //----------------------------------------------------------------------------- HRESULT GetDirectDrawDevices() { return DirectDrawEnumerateEx( DDEnumCallbackEx, NULL, DDENUM_ATTACHEDSECONDARYDEVICES | DDENUM_DETACHEDSECONDARYDEVICES | DDENUM_NONDISPLAYDEVICES ); } //----------------------------------------------------------------------------- // Name: DDEnumCallbackEx() // Desc: Enumerates all available DirectDraw devices //----------------------------------------------------------------------------- BOOL WINAPI DDEnumCallbackEx( GUID* pGUID, LPSTR strDriverDescription, LPSTR strDriverName, LPVOID pContext, HMONITOR hm ) { HRESULT hr; LPDIRECTDRAW pDD = NULL; // Create a DirectDraw device using the enumerated guid hr = DirectDrawCreateEx( pGUID, (VOID**)&pDD, IID_IDirectDraw7, NULL ); if( SUCCEEDED(hr) ) { if( pGUID ) { // Add it to the global storage structure g_aDevices[ g_dwDeviceCount ].guid = *pGUID; } else { // Clear the guid from the global storage structure ZeroMemory( &g_aDevices[ g_dwDeviceCount ].guid, sizeof(GUID) ); } // Copy the description of the driver into the structure lstrcpyn( g_aDevices[ g_dwDeviceCount ].strDescription, strDriverDescription, 256 ); lstrcpyn( g_aDevices[ g_dwDeviceCount ].strDriverName, strDriverName, 64 ); // Retrive the modes this device can support g_aDevices[ g_dwDeviceCount ].dwModeCount = 0; hr = pDD->EnumDisplayModes( 0, NULL, NULL, EnumModesCallback ); // Increase the counter for the number of devices found g_dwDeviceCount++; // Release this device SAFE_RELEASE( pDD ); } // Continue looking for more devices return TRUE; } //----------------------------------------------------------------------------- // Name: EnumModesCallback() // Desc: Enumerates the available modes for the device from which // EnumDisplayModes() was called. It records the unique mode sizes in // the g_aDevices[g_dwDeviceCount].aModeSize array //----------------------------------------------------------------------------- HRESULT WINAPI EnumModesCallback( LPDDSURFACEDESC pddsd, LPVOID pContext ) { DWORD i; DWORD dwModeSizeX; DWORD dwModeSizeY; DWORD dwModeCount; // For each mode, look through all previously found modes // to see if this mode has already been added to the list dwModeCount = g_aDevices[ g_dwDeviceCount ].dwModeCount; for( i = 0; i < dwModeCount; i ++ ) { dwModeSizeX = g_aDevices[ g_dwDeviceCount ].aModeSize[i].cx; dwModeSizeY = g_aDevices[ g_dwDeviceCount ].aModeSize[i].cy; if ( ( dwModeSizeX == pddsd->dwWidth ) && ( dwModeSizeY == pddsd->dwHeight ) ) { // If this mode has been added, then stop looking break; } } // If this mode was not in g_aDevices[g_dwDeviceCount].aModeSize[] // then added it. if( i == g_aDevices[ g_dwDeviceCount ].dwModeCount ) { g_aDevices[ g_dwDeviceCount ].aModeSize[i].cx = pddsd->dwWidth; g_aDevices[ g_dwDeviceCount ].aModeSize[i].cy = pddsd->dwHeight; // Increase the number of modes found for this device g_aDevices[ g_dwDeviceCount ].dwModeCount++; } return TRUE; } //----------------------------------------------------------------------------- // Name: OnInitDialog() // Desc: Initializes the dialogs (sets up UI controls, etc.) //----------------------------------------------------------------------------- VOID OnInitDialog() { // Load the icon #ifdef _WIN64 HINSTANCE hInst = (HINSTANCE) GetWindowLongPtr( g_hDlg, GWLP_HINSTANCE ); #else HINSTANCE hInst = (HINSTANCE) GetWindowLong( g_hDlg, GWL_HINSTANCE ); #endif HICON hIcon = LoadIcon( hInst, MAKEINTRESOURCE( IDI_ICON1 ) ); // Set the icon for this dialog. PostMessage( g_hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon ); // Set big icon PostMessage( g_hDlg, WM_SETICON, ICON_SMALL, (LPARAM) hIcon ); // Set small icon // Show all available DirectDraw devices in the listbox HWND hWndDeviceList = GetDlgItem( g_hDlg, IDC_DDRAW_DEVICE_LIST ); for( UINT i = 0; i < g_dwDeviceCount; i++ ) { SendMessage( hWndDeviceList, LB_ADDSTRING, 0, (LPARAM) g_aDevices[i].strDescription ); SendMessage( hWndDeviceList, LB_SETITEMDATA, i, (LPARAM) i); } // Select the first device by default DWORD dwCurrentSelect = 0; SendMessage( hWndDeviceList, LB_SETCURSEL, dwCurrentSelect, 0); if( FAILED( UpdateModesListBox( dwCurrentSelect ) ) ) { MessageBox( g_hDlg, "Error enumerating DirectDraw modes." "Sample will now exit.", "DirectDraw Sample", MB_OK | MB_ICONERROR ); EndDialog( g_hDlg, IDABORT ); } SetFocus( hWndDeviceList ); } //----------------------------------------------------------------------------- // Name: ResetDeviceModes() // Desc: If the user makes a mistake, and accidently says YES when a mode // cannot be seen, or NO when a mode really can be seen (thus ending up // with a lower refresh rate than possible) this allows the user to reset // the test results and try again. //----------------------------------------------------------------------------- HRESULT ResetDeviceModes( DWORD dwDeviceIndex ) { LPDIRECTDRAW7 pDD = NULL; HRESULT hr; // Create a DirectDraw device based using the selected device guid hr = DirectDrawCreateEx( &g_aDevices[dwDeviceIndex].guid, (VOID**) &pDD, IID_IDirectDraw7, NULL); if( SUCCEEDED(hr) ) { // Set the cooperative level to normal if( SUCCEEDED( hr = pDD->SetCooperativeLevel( g_hDlg, DDSCL_NORMAL ) ) ) { // Clear the previous mode tests // // We ignore the return code, since we would do nothing different on error returns. // Note that most applications would never need to call StartRefreshRate this way. // The reset functionality is intended to be used when a user accidentally accepted // a mode that didn't actually display correctly. pDD->StartModeTest( NULL, 0, 0 ); } // Release this device SAFE_RELEASE( pDD ); } hr = UpdateModesListBox( dwDeviceIndex ); return hr; } //----------------------------------------------------------------------------- // Name: UpdateModesListBox() // Desc: Updates the "modes to test" and "all modes" list boxes //----------------------------------------------------------------------------- HRESULT UpdateModesListBox( DWORD dwDeviceIndex ) { LPDIRECTDRAW7 pDD = NULL; HRESULT hr; HWND hWndModesToTest = GetDlgItem( g_hDlg, IDC_TEST_MODES_LIST ); SendMessage( hWndModesToTest, LB_RESETCONTENT, 0, 0 ); // Update the "modes to test" list box based on the display device selected for( DWORD i = 0; i < g_aDevices[dwDeviceIndex].dwModeCount; i++ ) { CHAR strMode[64]; // Make a string based on the this mode's size wsprintf( strMode, TEXT("%u x %u"), g_aDevices[dwDeviceIndex].aModeSize[i].cx, g_aDevices[dwDeviceIndex].aModeSize[i].cy ); // Add it to the list box SendMessage( hWndModesToTest, LB_ADDSTRING, 0, (LPARAM) strMode ); SendMessage( hWndModesToTest, LB_SETITEMDATA, i, (LPARAM) i ); } // Create a DirectDraw device based using the selected device guid if( SUCCEEDED( hr = DirectDrawCreateEx( &g_aDevices[dwDeviceIndex].guid, (VOID**) &pDD, IID_IDirectDraw7, NULL) ) ) { HWND hWndAllModes = GetDlgItem( g_hDlg, IDC_ALL_MODES_LIST ); SendMessage( hWndAllModes, LB_RESETCONTENT, 0, 0 ); // Enumerate and display all supported modes along // with supported bit depth, and refresh rates // in the "All Modes" listbox hr = pDD->EnumDisplayModes( DDEDM_REFRESHRATES, NULL, (VOID*) hWndAllModes, EnumAllModesCallback ); // Release this device SAFE_RELEASE( pDD ); } return hr; } //----------------------------------------------------------------------------- // Name: EnumAllModesCallback() // Desc: For each mode enumerated, it adds it to the "All Modes" listbox. //----------------------------------------------------------------------------- HRESULT WINAPI EnumAllModesCallback( LPDDSURFACEDESC2 pddsd, LPVOID pContext ) { CHAR strMode[64]; HWND hWnd = (HWND) pContext; wsprintf( strMode, TEXT("%ux%ux%u - %u Hz"), pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount, pddsd->dwRefreshRate ); SendMessage( hWnd, LB_ADDSTRING, 0, (LPARAM) strMode ); return TRUE; } //----------------------------------------------------------------------------- // Name: OnRefreshRate() // Desc: User hit the "Test" button //----------------------------------------------------------------------------- HRESULT OnRefreshRate() { HWND hWndModesToTest = GetDlgItem( g_hDlg, IDC_TEST_MODES_LIST ); DWORD dwSelectCount = (DWORD)SendMessage( hWndModesToTest, LB_GETSELCOUNT, 0, 0 ); if( dwSelectCount > 0 ) { LPDIRECTDRAW7 pDD = NULL; HRESULT hr; HWND hWndDeviceList; DWORD dwDeviceIndex; // Get the currently selected DirectDraw device hWndDeviceList = GetDlgItem( g_hDlg, IDC_DDRAW_DEVICE_LIST ); dwDeviceIndex = (DWORD)SendMessage( hWndDeviceList, LB_GETCURSEL, 0, 0 ); // Create a DirectDraw device based using the selected device guid if( FAILED( hr = DirectDrawCreateEx( &g_aDevices[dwDeviceIndex].guid, (VOID**) &pDD, IID_IDirectDraw7, NULL) ) ) return hr; // This is a good usage of DDSCL_CREATEDEVICEWINDOW: DirectDraw will create a window that covers // the monitor, and won't mess around with our dialog box. Any mouse clicks on the cover window // will therefore not be received and misinterpreted by the dialog box, since such clicks will // be sent to DirectDraw's internal message procedure and therein ignored. if( FAILED( hr = pDD->SetCooperativeLevel( g_hDlg, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_CREATEDEVICEWINDOW | DDSCL_SETFOCUSWINDOW ) ) ) { SAFE_RELEASE( pDD ); return hr; } SIZE aTestModes[256]; DWORD dwTestModes = 0; // Find out which modes are selected, then just test those for( DWORD i = 0; i < g_aDevices[dwDeviceIndex].dwModeCount; i++ ) { if( SendMessage( hWndModesToTest, LB_GETSEL, i, 0 ) ) { // Record the selected modes in aTestModes[] aTestModes[dwTestModes] = g_aDevices[dwDeviceIndex].aModeSize[i]; dwTestModes++; } } // Perform test on each of the selected modes on the selected device hr = PerformDirectDrawRefreshRate( pDD, aTestModes, dwTestModes ); // Release this device SAFE_RELEASE( pDD ); switch (hr) { case DDERR_NOMONITORINFORMATION: // No EDID data is present for the current monitor. MessageBox(g_hDlg, "The current monitor cannot be identified electronically.\n" "High refresh rates are not allowed on such monitors, so the test will not be performed.", "Testing Will Not Proceed", MB_OK | MB_ICONINFORMATION); break; case DDERR_NODRIVERSUPPORT: // The driver cannot support refresh rate testing. MessageBox(g_hDlg, "This driver does not support the refresh rate feature of DirectDraw. Test cannot be performed.", "Testing Cannot Proceed", MB_OK | MB_ICONINFORMATION); break; default: if( SUCCEEDED(hr) ) { MessageBox( g_hDlg, TEXT("Mode test completed"), TEXT("Result"), MB_OK ); break; } else { // A StartRefreshRate error occurred. MessageBox(g_hDlg, "StartRefreshRate returned an unexpected value when called with the DDSMT_ISTESTREQUIRED flag.", "StartRefreshRate Error", MB_OK | MB_ICONEXCLAMATION); return hr; } } // Update the mode list boxes based on the device selected if( FAILED( hr = UpdateModesListBox( dwDeviceIndex ) ) ) return hr; } else { // There weren't any modes selected to test MessageBox( g_hDlg, TEXT("Select one or more modes to test from the list box"), TEXT("No modes selected"), MB_OK ); } return S_OK; } //----------------------------------------------------------------------------- // Name: SetupPrimarySurface() // Desc: Setups a primary DirectDraw surface //----------------------------------------------------------------------------- HRESULT SetupPrimarySurface( LPDIRECTDRAW7 pDD, LPDIRECTDRAWSURFACE7* ppDDS ) { DDSURFACEDESC2 ddsd; ZeroMemory( &ddsd, sizeof(ddsd) ); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; return pDD->CreateSurface(&ddsd, ppDDS, NULL); } //----------------------------------------------------------------------------- // Name: UpdatePrimarySurface() // Desc: Fills the primary surface with white, and diplays the timeout value // on screen //----------------------------------------------------------------------------- HRESULT UpdatePrimarySurface( LPDIRECTDRAWSURFACE7 pDDS, DWORD dwTimeout ) { DDBLTFX ddbltfx; HDC hDC; char strTimeout[128]; RECT rect; HRESULT hr; // Clear the screen: ZeroMemory( &ddbltfx, sizeof(ddbltfx) ); ddbltfx.dwSize = sizeof(ddbltfx); ddbltfx.dwFillColor = 0xFFFFFFFF; hr = pDDS->Blt( NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx ); if( FAILED( hr ) ) return hr; // Display the timeout value if( FAILED( hr = pDDS->GetDC( &hDC ) ) ) return hr; GetWindowRect( g_hDlg, &rect ); wsprintf( strTimeout, TEXT("Press space to accept or escape to reject. ") TEXT("%2d seconds until timeout"), dwTimeout ); DrawText( hDC, strTimeout, strlen(strTimeout), &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); // Cleanup pDDS->ReleaseDC( hDC ); return S_OK; } //----------------------------------------------------------------------------- // Name: PerformDirectDrawRefreshRate() // Desc: Perform the IDirectDraw7::StartRefreshRate and IDirectDraw7::EvaluateMode // tests // Returns: S_OK if no modes needed testing, or all modes tested successfully, // informative error code otherwise. //----------------------------------------------------------------------------- HRESULT PerformDirectDrawRefreshRate( LPDIRECTDRAW7 pDD, SIZE* aTestModes, DWORD dwTestModes ) { LPDIRECTDRAWSURFACE7 pDDSPrimary = NULL; HRESULT hr; MSG msg; DWORD dwFlags = 0; DWORD dwTimeout; BOOL bMsgReady; // First call StartRefreshRate with the DDSMT_ISTESTREQUIRED flag to determine // whether the tests can be performed and need to be performed. hr = pDD->StartModeTest( aTestModes, dwTestModes, DDSMT_ISTESTREQUIRED); switch (hr) { case DDERR_NEWMODE: // DDERR_NEWMODE means that there are modes that need testing. break; case DDERR_TESTFINISHED: // DDERR_TESTFINISHED means that all the modes that we wish to test have already been tested correctly return S_OK; default: //Possible return codes here include DDERR_NOMONITORINFORMATION or DDERR_NODRIVERSUPPORT or //other fatal error codes (DDERR_INVALIDPARAMS, DDERR_NOEXCLUSIVEMODE, etc.) return hr; } hr = pDD->StartModeTest( aTestModes, dwTestModes, 0 ); if( hr == DDERR_TESTFINISHED ) { // The tests completed early, so return return S_OK; } // Create the primary DirectDraw surface if( FAILED( SetupPrimarySurface( pDD, &pDDSPrimary ) ) ) return hr; // Loop until the mode tests are complete while( TRUE ) { bMsgReady = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); if( bMsgReady ) { if (msg.message == WM_KEYDOWN) { switch (msg.wParam) { case VK_SPACE: dwFlags = DDEM_MODEPASSED; break; case VK_ESCAPE: dwFlags = DDEM_MODEFAILED; break; } } else { TranslateMessage( &msg ); DispatchMessage( &msg ); } } else { // This method will only succeed with monitors that contain EDID data. // If the monitor is not EDID compliant, then the method will return // DDERR_TESTFINISHED without testing any modes. If the EDID table does not // contain values higher than 60hz, no modes will tested. Refresh rates // higher than 100 hz will only be tested if the EDID table contains values // higher than 85hz. hr = pDD->EvaluateMode(dwFlags, &dwTimeout); if( hr == DD_OK ) { if( pDDSPrimary ) { // Clear the screen, and display the timeout value UpdatePrimarySurface( pDDSPrimary, dwTimeout ); } } else if( hr == DDERR_NEWMODE ) { // Cleanup the last DirectDraw surface, and create // a new one for the new mode SAFE_RELEASE( pDDSPrimary ); if( FAILED( SetupPrimarySurface( pDD, &pDDSPrimary ) ) ) return hr; dwFlags = 0; } else if( hr == DDERR_TESTFINISHED ) { // Test complete, so stop looping break; } Sleep( 100 ); } } // Cleanup SAFE_RELEASE( pDDSPrimary ); return S_OK; }