Lol, IDirectDrawSurface7::Blt changes Aero scheme to Windows 7 Basic

Started by
5 comments, last by Dim_Yimma_H 7 years, 11 months ago
Because, I wrote Win32 code that uses DirectDraw 7.0 to display the game through Blt on a resizable window.

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?
Advertisement
DirectDraw 7.0

...you know this has been deprecated for years, right? So the fact that it misbehaves on modern systems should not be that surprising.

Yes that's why I laugh, but 'm still looking for a workaround.

Not sure if it can be fixed.. OpenGL apps does that sometimes too. I think it's fixed in OpenGL by selecting a pixel-format with the PFD_SUPPORT_COMPOSITION flag. Maybe you can set the pixel-format on the window before creating the DD surface or somehow specify that flag in your caps when you create the primary surface..

Apparently this is a common problem with old games using DirectDraw, including Age of Empires and Age of Empires 2, which have sentimental value to me.

Perhaps use GDI instead? It should be fast enough.

This is likely because some part of the mode configured by the driver is incompatible with DWM. Whenever you see Windows switch from Aero to Classic, that's just about always because DWM has been disabled, and hence the desktop can no longer do window compositing.

If not the mode, it may be because DDraw8 or the cooperative level you've selected requires direct video buffer access or the like, which is likewise incompatible with compositing. Or it could be your video driver not supporting the appropriate back-compat hooks to allow DWM to coexist with the old mode.

Sean Middleditch – Game Systems Engineer – Join my team!

OpenGL apps does that sometimes too. I think it's fixed in OpenGL by selecting a pixel-format with the PFD_SUPPORT_COMPOSITION flag.

That's interesting to know, i'll think of it.

Apparently this is a common problem with old games using DirectDraw, including Age of Empires and Age of Empires 2, which have sentimental value to me.

Oh that's news to me, good to know those solid games also suffer from the problem, which means I'm not alone with the problem.

Perhaps use GDI instead? It should be fast enough.

You know what; i actually plan to do that if making another iteration of the engine. Haha, I'll be a blast from the past.

If not the mode, it may be because DDraw7 or the cooperative level you've selected requires direct video buffer access or the like, which is likewise incompatible with compositing. Or it could be your video driver not supporting the appropriate back-compat hooks to allow DWM to coexist with the old mode.

Oh DWM, seems to mean that each window is drawing graphics in a hardware accelerated buffer of the DWM, I guess there becomes a conflict when IDirectDrawSurface7::Blt applies hardware acceleration to stretch into the same buffer.

Reading on about DWM now,
https://en.wikipedia.org/wiki/Desktop_Window_Manager

This topic is closed to new replies.

Advertisement