Playing nice with other applications

Started by
22 comments, last by circlesoft 19 years, 1 month ago
I'm refactoring my renderer and windowing subsystems to get along nicely with other applications - gracefully handle maximizing, minimizing, restoring, focus changes, etc. Everything seems to be going smoothly according to my tests (still need to check alt+tab), but I do have one problem. When the app minimizes from windowed mode (it gracefully handles fullscreen to windowed mode transitions, and it should work just fine on multimon systems, though not multihead; any volunteers to test?), it leaves a region exactly the size of its window that is not properly updated by other apps. Context menus write to it, but that data remains there after the context menu is closed. Other apps can't write to it, so there's a floating rectangle of comparative gibberish - whatever was underneath the window when it was maximized, usually Visual Studio in my case - that remains over all other windows until I terminate my app. Actually, I cleared all breakpoints to observe behavior just now to ensure that I provide you with accurate data. It doesn't minimize, but it maximizes just fine. Alt+tabbing works fine, and it responds to Close and Restore from the task bar button context menu. Here's the relevant sources (forgive the formatting; I haven't bothered to set VC++ to use spaces rather than tabs). My message handler (r is a Renderer * and a member of Handler):

struct Handler : MessageHandler
{
	// other methods
	//

	LRESULT operator () (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
	{
		switch(msg)
		{
		// window state management messages
		//
		case WM_ACTIVATE:
			switch(LOWORD(wparam))
			{
			case WA_ACTIVE:
			case WA_CLICKACTIVE:
				active = true;
				r->Reset(hwnd);
				break;

			case WA_INACTIVE:
				r->Release();
				active = false;
				break;
			}
			return 0;

		case WM_MOUSEACTIVATE:
			r->SetWindowed(true);
			r->Reset(hwnd);
			return MA_NOACTIVATE;

		case WM_SIZE:
			switch(wparam)
			{
			case SIZE_MAXHIDE:
			case SIZE_MAXSHOW:
			case SIZE_MINIMIZED:
				break;

			case SIZE_MAXIMIZED:
				r->SetWindowed(false);
			case SIZE_RESTORED:
				r->Reset(hwnd);
				active = true;
				break;
			}
			}
			return 0;

		case WM_SYSCOMMAND:
			switch(wparam)
			{
			case SC_MAXIMIZE:
				r->SetWindowed(false);
				r->Reset(hwnd);
				active = true;
				break;

			case SC_RESTORE:
				r->SetWindowed(true);
				r->Reset(hwnd);
				active = true;
				break;

			case SC_NEXTWINDOW:
			case SC_PREVWINDOW:
				r->SetWindowed(true);
			case SC_MINIMIZE:
				active = false;
				r->Release();
				break;

			case SC_MONITORPOWER:
				if(2 == lparam)
				{
					active = false;
					r->Release();
				}
				break;

			case SC_CLOSE:
				PostMessage(hwnd, WM_CLOSE, 0, 0);
				break;
			}
			return 0;

		// other messages
		//

		}

		return DefWindowProc(hwnd, msg, wparam, lparam);
	}

	// other methods
	//
};


Relevant methods from my renderer's implementation:

void Renderer::Release()
{
	if(indices)
	{
		indices->Release();
		indices = 0;
	}
	if(vertices)
	{
		vertices->Release();
		vertices = 0;
	}
	if(device)
	{
		device->Release();
		device = 0;
	}
}

void Renderer::Reset(HWND hwnd)
{
	Release();
	InitDevice(hwnd);
	InitBuffers();
}

void Renderer::InitDevice(HWND wnd)
{
	int adapter = AdapterFromWindow(wnd);

	D3DDISPLAYMODE mode;
	GetDisplayMode(adapter, mode);

	D3DPRESENT_PARAMETERS presentParams;
	ZeroMemory(&presentParams, sizeof(D3DPRESENT_PARAMETERS));


	// initialize device. the device may be reinitialized after a context 
	// change or fullscreen-windowed mode switch or a resolution change from 
	// the options menu.
	//
	presentParams.BackBufferCount = 1;  // double buffering
	presentParams.BackBufferFormat = D3DFMT_A8R8G8B8;
	presentParams.hDeviceWindow = wnd;
	presentParams.MultiSampleType = D3DMULTISAMPLE_NONE;
	presentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
	presentParams.Windowed = windowed;

	if(!windowed)
	{
		// fullscreen-specific initialization.
		//
		presentParams.BackBufferHeight = mode.Height;
		presentParams.BackBufferWidth = mode.Width;
		presentParams.FullScreen_RefreshRateInHz = mode.RefreshRate;
	}

	// TODO: error handling
	//
	d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, wnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentParams, &device);

}

void Renderer::InitBuffers()
{
	// no buffer initialization as yet
	//
}

int Renderer::AdapterFromWindow(HWND wnd)
{
	// determine monitor from window. find corresponding adapter.
	//
	HMONITOR monitor = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
	for(int adapter = 0; adapter < d3d->GetAdapterCount(); ++adapter)
	{
		if(d3d->GetAdapterMonitor(adapter) == monitor)
			return adapter;
	}

	// something went wrong, return default.
	// TODO: throw exception
	//
	return D3DADAPTER_DEFAULT;
}

void Renderer::GetDisplayMode(int adapter, D3DDISPLAYMODE & mode)
{
	if(windowed)
	{
		// retrieve current display mode for windowed operation.
		//
		d3d->GetAdapterDisplayMode(adapter, &mode);
	}
	else
	{
		// enumerate available modes for fullscreen. select first valid.
		// TODO: robust mode selection
		//
		D3DFORMAT formats[] = { D3DFMT_A1R5G5B5, D3DFMT_A8R8G8B8, 
			D3DFMT_R5G6B5, D3DFMT_X1R5G5B5, D3DFMT_X8R8G8B8 };
		int formatCount = sizeof(formats);
		for(int fmt = 0; fmt < formatCount; ++fmt)
		{
			int modeCount = d3d->GetAdapterModeCount(adapter, formats[fmt]);
			for(int index = 0; index < modeCount; ++index)
			{
				d3d->EnumAdapterModes(adapter, formats[fmt], index, &mode);
				if(SUCCEEDED(d3d->CheckDeviceType(adapter, D3DDEVTYPE_HAL, 
					mode.Format, D3DFMT_A8R8G8B8, windowed)))
				{
					// TODO: verify support for desired level of functionality
					//
					fmt = formatCount;
					break;
				}
			}
		}
	}
}

void Renderer::SetWindowed(bool state)
{
	windowed = state;
}


Sorry about the amount of code. All insights welcome, particularly those that pontificate and ramble about good design and best practices, methodology and so forth. [smile]
Advertisement
(reaching for straws)

The only suspicous thing I find is the fact that you release the renderer when minimizing... which I really don't se the need for (unless I'm wrong)... that shouldn't cause any problem... but my applications minimize just fine without releasing.

Another thing I found was the fact that you set a backbuffer format even for windowed mode... which generally shouldn't be done in my understanding unless you specify the same as the current one (which will give the same result as not doing it)...


Quote:Original post by Syranide
The only suspicous thing I find is the fact that you release the renderer when minimizing... which I really don't se the need for (unless I'm wrong)... that shouldn't cause any problem... but my applications minimize just fine without releasing.
It shouldn't hurt, should it? What messages do you handle, and what do you do in response to minimization?

Quote:Another thing I found was the fact that you set a backbuffer format even for windowed mode... which generally shouldn't be done in my understanding unless you specify the same as the current one (which will give the same result as not doing it)...
My interpretation was that a linear color space conversion would be performed, that the only restriction is on the display format, which can not differ from the current display mode's format. I'll give that a shot, though.
It would appear that the problem has something to do with running in debug mode from within Visual Studio, because running the debug binary directly works perfectly. (Well, not perfectly; restoring by clicking on the taskbar button is a little buggy.)

Thanks for all the help.

A related question, though: what notification is received in response to Alt+Enter?
I further narrowed down my restore problems to having to do with moving. I don't handle WM_MOVE, and minimizing and restoring after moving create visual discrepancies. What's the traditional response to moving?

I'll take a look in the Forum FAQ, which, by the way, is overwhelming loaded with good stuff! Nice work, Coder.
Ok, so sorry about the backbufferformat, did some reading again, and now it says "the back buffer format no longer needs to match the display-mode format because color conversion...", so you are right.

Regarding minimizing I do nothing except putting it in my own suspended-mode with no updates/presents.

But as regarding your problem, if you haven't understood it yourself (which I guess you have), when you minimize, all applications behind redraw... for some reason yours don't... which likely is because they are not recieving a redraw-notification, meaning that whatever shit windows can find is shown there. (as D3D doesn't provide a buffer for windows to read from)

This should be related some how to the fact that you release the renderer when minimizing, if that is the case, you should move the release-code to after/before the window is minimized (not when you have selected it in the menu). That is my guess...

(Have you tried any other windowed D3D application so it isn't anything driver-related in your case?)

EDIT: Good good, you solved it :)

EDIT: Regarding Alt-Enter... if you mean using WM_CHAR I guess it is VK_ENTER (or what it is called) plus a modifier for ALT... if that's what you meant? Using DInput should be as easy. (probably not what you meant?)

EDIT (once again): As for moving, I (me me me) don't handle that at all, my guess is that your problem is something with MSVC and nothing related to reality, I've had similar things with MSVC and certain other experiments where running through has created really weird errors.


I really should have read the Forum FAQ first, as it contained the answers to most of my questions already.

What I should have done is TestCooperativeLevel in my Renderer::Render method, and called IDirect3DDevice9::Reset if it returned D3DERR_DEVICENOTRESET, which I have since done. That allowed me to remove virtually all of my message handling presented here, which was nice.

The only problem I still have is restoring from minimizing after moving. Should I handle WM_MOVE? What are common strategies in this regard?
Windows gives WM_ACTIVATEAPP when you alt+tab to or away from your app. There might be other messages like WM_ACTIVATE or WM_MOUSEACTIVATE, but WM_ACTIVATEAPP is the key one I belive.
[sub]My spoon is too big.[/sub]
Quote:Original post by Syranide
Regarding minimizing I do nothing except putting it in my own suspended-mode with no updates/presents.
Yeah, that's what I just implemented.

Quote:EDIT: Regarding Alt-Enter... if you mean using WM_CHAR I guess it is VK_ENTER (or what it is called) plus a modifier for ALT... if that's what you meant? Using DInput should be as easy. (probably not what you meant?)
No. Alt+Enter is typically used to promote applications from fullscreen to windowed and vice versa. Try it with a console mode application sometime.

Quote:EDIT (once again): As for moving, I (me me me) don't handle that at all, my guess is that your problem is something with MSVC and nothing related to reality, I've had similar things with MSVC and certain other experiments where running through has created really weird errors.
No, I still get the moving bugs running the debug binary directly from the output folder.
My approach to minimizing/restoring (aka activate) is only suspending the updates, a boolean or such... don't know what the "optimal" approach is though. But releasing the device doesn't seem useful as D3D already has control over the window and knows when it has been minimized.

Perhaps someone else has any other approach?


This topic is closed to new replies.

Advertisement