• Advertisement
Sign in to follow this  

Playing nice with other applications

This topic is 4743 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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)...

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
Quote:
Original post by RenderTarget
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.
I'm handling all three of those, but, for one thing, the window remains as topmost when I alt+tab to another window. But only if I run the (debug) binary directly. It behaves correctly if I run it from within MSVC in debug mode.

It's official. I need a multimon system.

Meh, I'll put this aside until I hit beta.

Share this post


Link to post
Share on other sites
Quote:
Original post by Oluseyi
It's official. I need a multimon system.

Yes, yes you do [smile]



Please excuse the messy desk hehe [oh]

Share this post


Link to post
Share on other sites
I would officially hate you if your workspace wasn't such an aesthetic disaster. [smile] As is, I can remain secure in the knowledge that my work area is structurally and aesthetically superior to yours (he says, as he leans back in his leather manager's chair). Plus, I'll be out of school in less than three months, which means money to invest in a custom multimon setup that will make you cry!

Ha!

Share this post


Link to post
Share on other sites
Quote:
Original post by circlesoft
Yes, yes you do [smile]

... Image Removed ...

Please excuse the messy desk hehe [oh]


Is that a large speaker on top of your computer?! [lol] BTW I am jealous [smile] - laptop and multimon

Share this post


Link to post
Share on other sites
Since you guys obviously think my sacred workplace is "an aesthetic disaster" [headshake], I've taken the liberty to show everyone that my room is in PERFECT working order.



I'm sure Coder will agree with me on this one.

Quote:

Plus, I'll be out of school in less than three months, which means money to invest in a custom multimon setup that will make you cry!


Oh yea!? Can you compete with this baby?



I'll be coding multi-headed worms and dual-phaser hedras and all kinds of McGyver stuff like in "Swordfish" in no time at all.

You may be able to top it with this [lol], although you're gonna need 24/7 air conditioning and you'll have a $2000 electric bill hehe

Share this post


Link to post
Share on other sites
Quote:
Original post by Oluseyi
Dude, all I have to do is improve on that dreadful wallpaper! [smile]


Sorry for not staying on topic, but I would have to agree [smile]. I saw that wall paper and I'm like the heck?! Anyways, Oluseyi, might I ask what you are doing to consider how your app interacts with multimon systems? I mean, I would have never thought to test anything I work on for this little condition of multimons - even though I'm not a DX person yet, so let's say 'generall speaking'. Or, was this more of a 'make sure my app doesn't screw with windows' kind of thing?

- Drew

Share this post


Link to post
Share on other sites
The DirectX8 SDK sample framework would release and reset the device on WM_MOVE (which is quite slow frankly, when you automatically reload the textures on every move).
There are some workarounds for this (using a swapchain AFAIK).

The Alt-Enter doesn't have a special notification, it should have IMHO. Just use WM_SYSKEYDOWN and check for VK_RETURN.

Share this post


Link to post
Share on other sites
I'll test it on a multihead card if you want. I'll also test it in widescreen too. With these.

-Mezz

Share this post


Link to post
Share on other sites
Hi Oluseyi,

You only need to handle resize and activation events. Those are the ones that break devices and the associated resources. Moving works fine because the size of the context does not change. Anytime where the device is resized or the visibility is toggled will reset a direct3d device.

If you need any further help just let me know!

~Graham

Share this post


Link to post
Share on other sites
Quote:
Original post by Drew_Benton
Anyways, Oluseyi, might I ask what you are doing to consider how your app interacts with multimon systems? I mean, I would have never thought to test anything I work on for this little condition of multimons - even though I'm not a DX person yet, so let's say 'generall speaking'.
The first consideration is to make sure that you're selecting the appropriate adapter. Most people always use D3DADAPTER_DEFAULT, which will frustrate multimon users who might want to, for instance, move the application from monitor 1 to monitor 2.

Here's the relevant code:

int Renderer::AdapterFromWindow()
{
// determine monitor from window. find corresponding adapter.
//
HMONITOR monitor = MonitorFromWindow(hwnd, 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;
}



MonitorFromWindow is a Win32 API. As you can see, I specified MONITOR_DEFAULTTONEAREST as the decision predicate, so a window that straddles two monitors will have its Direct3D device created on whichever monitor contains more of the window, if I'm interpreting the documentation correctly. In such a situation, as well as when you drag the application from monitor 1 to monitor 2 in windowed mode, Direct3D will copy the bits over behind the scenes. When you switch tasks or enter fullscreen mode, however, the adapter will be checked and the device recreated if necessary.


bool Renderer::ResetDevice()
{
int adapter = AdapterFromWindow();

// check if we can simply perform a D3D device reset on an existing device
//
if(device)
{
D3DDEVICE_CREATION_PARAMETERS creationParams;
device->GetCreationParameters(&creationParams);

if(creationParams.AdapterOrdinal == adapter)
{
D3DPRESENT_PARAMETERS presentParams;
IDirect3DSwapChain9 * swapChain;
swapChain->GetPresentParameters(&presentParams);
device->GetSwapChain(0, &swapChain);

// TODO: release all D3DPOOL_DEFAULT resources

device->Reset(&presentParams);

// TODO: recreate all D3DPOOL_DEFAULT resources

return true;
}
}

return false;
}



Of course, AdapterFromWindow is called for the original device initiation, so starting the app on monitor 2 works seamlessly as well - in theory. This is where I'll need testers when I get to beta, which is by Friday (because I have a project checkpoint then).

Quote:
Original post by gwihlidal
You only need to handle resize and activation events. Those are the ones that break devices and the associated resources. Moving works fine because the size of the context does not change. Anytime where the device is resized or the visibility is toggled will reset a direct3d device.
Thanks Graham! I took out all the window state messages except activation; I'll handle resizing, too, and add a bit of code so that maximize goes to fullscreen.

@Mezz:
I'll try to update this thread on either Friday or Saturday - definitely before I leave for GDC.

Share this post


Link to post
Share on other sites
Quote:
Original post by Endurion
The DirectX8 SDK sample framework would release and reset the device on WM_MOVE (which is quite slow frankly, when you automatically reload the textures on every move).
There are some workarounds for this (using a swapchain AFAIK).

The Alt-Enter doesn't have a special notification, it should have IMHO. Just use WM_SYSKEYDOWN and check for VK_RETURN.
Thanks for the tips. I'll look into it.

Actually...
Quote:
From WM_SYSKEYDOWN Notification
The DefWindowProc function examines the specified key and generates a WM_SYSCOMMAND message if the key is either TAB or ENTER.
I wasn't entirely off, but I'll still trap it your way.

Share this post


Link to post
Share on other sites
Quote:
Original post by Mezz
I'll test it on a multihead card if you want.

I'm game for this, too. I have an ATI 9600xt at the moment.

Quote:

Dude, all I have to do is improve on that dreadful wallpaper!

Actually, that's not his wallpaper. It's real-time flight simulation software. I guess he got like a complete panoramic view or something. But who's that into flight sims...I don't know.

Quote:

before I leave for GDC

You lucky dog [cool]...

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement