Immediately when IDirectDrawSurface7::Blt applies stretching, the whole desktop changes Aero scheme to Windows 7 Basic color.
- Another side effect of the Windows 7 Basic scheme is that memory slowly leaks from 25 MB to over 35 MB
- After closing the game, Windows changes back to the Aero scheme.
Cases:
¤ Confirmed bug on Windows 8 too.
¤ Happens regardless of applying Clipper
¤ Happens if calling Blt on DDSCAPS_PRIMARYSURFACE with DDSCAPS_OFFSCREENPLAIN + DDSCAPS_SYSTEMMEMORY
¤ Avoided if calling Blt on DDSCAPS_PRIMARYSURFACE with DDBLT_COLORFILL (cool)
¤ Happens both with link time code generation (ddraw.lib) or DLL
- No programmatical error is returned
Why do you think the Windows 7 Basic scheme happens?
Here's my shortest code to reproduce the bug:
//Character Set = Use Multi-Byte Character Set
//link input: ddraw.lib dxguid.lib
#define DIRECTDRAW_VERSION 0x0700 //compile for DirectDraw 7
#include <ddraw.h>
#include <windows.h>
#define BLT_BUG //define to enable Windows 7 Basic color theme bug (or Windows 8)
LRESULT CALLBACK MyWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
//DirectDraw variables
LPDIRECTDRAW7 pDDraw = NULL; //long pointer to DirectDraw object
LPDIRECTDRAWSURFACE7 pDDSurf = NULL; //pointer to live screen surface
LPDIRECTDRAWSURFACE7 pDDBuffer = NULL; //pointer to buffering surface
LPDIRECTDRAWCLIPPER pDDClip = NULL;
void MyReleaseDDraw();
bool MyCreateDDraw(HWND hWnd);
void MyDraw(HWND hWnd);
//enter here from Windows
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
//class for my window
WNDCLASS wc;
wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MyWinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL; //let game erase its background alone
wc.lpszMenuName = NULL;
wc.lpszClassName = "MyWindClass";
if (RegisterClass(&wc) == 0)
return 0;
HWND myWind = CreateWindow("MyWindClass", "Caption",
WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, 0, 0, hInstance, NULL);
//window message loop
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (true)
{
if (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg); //calls MyWinProc
}
else
break;
}
return (int) msg.wParam;
}
//window process/event
LRESULT CALLBACK MyWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
MyCreateDDraw(hWnd);
break;
case WM_CLOSE:
MyReleaseDDraw();
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_ERASEBKGND:
//lie that the background was erased,
//so Win32 doesn't erase it in addition to MyDraw
return 1;
case WM_PAINT:
//note: assert can cause recursive WM_PAINT messages hang
//i don't use RedrawWindow, so GetUpdateRect can be skipped
MyDraw(hWnd);
ValidateRect(hWnd, NULL); //to stop getting WM_PAINT
return 0;
}
//let Win32 handle the message
return DefWindowProc(hWnd, msg, wParam, lParam);
}
void MyReleaseDDraw()
{
if (pDDBuffer)
{
pDDBuffer->Release();
pDDBuffer = NULL;
}
if (pDDClip)
{
pDDClip->Release();
pDDClip = NULL;
}
if (pDDSurf)
{
pDDSurf->Release();
pDDSurf = NULL;
}
if (pDDraw)
{
pDDraw->Release();
pDDraw = NULL;
}
}
bool MyCreateDDraw(HWND hWnd)
{
//create DirectDraw interface
if (DD_OK != DirectDrawCreateEx(NULL, (void**) &pDDraw, IID_IDirectDraw7, NULL))
return false;
if (DD_OK != pDDraw->SetCooperativeLevel(hWnd, DDSCL_NORMAL))
return false;
//create primary surface (on screen)
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd); //CreateSurface checks size before reading the data
ddsd.dwFlags = DDSD_CAPS; //allow ddsCaps
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
if (DD_OK != pDDraw->CreateSurface(&ddsd, &pDDSurf, NULL))
return false;
//attach clipping object to the primary surface,
//to clip it under other windows or mouse cursor
if (DD_OK != pDDraw->CreateClipper(0, &pDDClip, NULL))
false;
if (DD_OK != pDDSurf->SetClipper(pDDClip))
false;
if (DD_OK != pDDClip->SetHWnd(0, hWnd))
false;
//create offscreeen surface (buffer)
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
ddsd.dwWidth = 320;
ddsd.dwHeight = 240;
if (DD_OK != pDDraw->CreateSurface(&ddsd, &pDDBuffer, NULL))
return false;
return true;
}
void MyDraw(HWND hWnd)
{
//get size inside window
RECT rWin;
GetClientRect(hWnd, &rWin);
//get coordinates inside window, convert to screen
POINT point;
point.x = rWin.left;
point.y = rWin.top;
ClientToScreen(hWnd, &point);
rWin.left = point.x;
rWin.top = point.y;
rWin.bottom += point.y;
rWin.right += point.x;
#ifdef BLT_BUG
//blt buffer to window, stretching causes Windows 7 Basic color theme bug
RECT rBuff;
rBuff.top = 0;
rBuff.bottom = 240;
rBuff.right = 320;
rBuff.left = 0;
pDDSurf->Blt(&rWin, pDDBuffer, &rBuff, DDBLT_WAIT, NULL);
#else
//blt color inside window, no Windows 7 Basic color theme bug
DDBLTFX fx;
ZeroMemory(&fx, sizeof(fx));
fx.dwSize = sizeof(fx);
fx.dwFillColor = 0x00FF7700; //orange if ARGB
pDDSurf->Blt(&rWin, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
#endif
}
The Windows error dialog says "The color scheme has been changed""The following program has performed an action that requires Windows to temporarily change color scheme to Windows 7 Basic."
In Windows Help and support I can run the troubleshooter for Aero to activate the window manager for the desktop, which temporarily restarts Aero but the bug returns when restarting the code.
How do you think I can stretch my game to the window and avoid the Windows 7 Basic scheme problem?