How to properly switch to fullscreen with DXGI?

Started by
19 comments, last by DwarvesH 9 years, 2 months ago

One strange and probably harmless behavior I have encountered with you code is that FRAPS framerate counter is rendered differently:

[attachment=25563:fraps.png]

The one on the left is how it is rendered in you application. The one on the right is how it is rendered in other applications. I'm investigating this a bit...

Advertisement

FRAPS basically hacks into the app as far as I can tell and intercepts D3D the graphics.. not sure that's really something to take into account when designing an app.

About the "unknown" format, using DXGI_FORMAT_UNKNOWN for ResizeBuffers retains the old format. In the example only modes of that particular format are enumerated anyway so it won't matter, but if you enumerate modes of both R8G8B8A8 and R10G10B10A2 for example, switching from one to the other needs to reset the back-buffer format in ResizeBuffers.

You could do that for example like this:


UINT numModes = 1024;
DXGI_MODE_DESC modes[1024];
pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &numModes, modes);
UINT numModes2 = 1024 - numModes;
pOutput->GetDisplayModeList(DXGI_FORMAT_R10G10B10A2_UNORM, 0, &numModes2, modes + numModes);
numModes += numModes2;

I did some further testing and the actual mode change seems to be very stable. I tested also on our dedicated computer with a broken GPU that kinda sorta sometimes maybe works well but generally there are tons of problems and the code showed standard behavior for that system.

Then I spent over 2 hours making the border style of the window changeable. The actual change was easy, but Windows API seems to be a bit buggy here. After you change the border to none and back, things like GetWindowRect do not return the appropriate value. I fixed it eventually.

The problems didn't end here. Windows API again insists to clip window sizes to desktop if they are bigger and they have a frame, so changing to certain larger modes would cause the window to become smaller than the mode. This wasn't a problem since ResizeTarget/ResizeBuffers used this smaller size, but I found no way to make it behave well.

So I did a hackish fix, where if the window becomes arbitrarily large compared to the desktop, I remove the window border and if needed set the left or top to 0 so I can maximize the visible area. I also override the user setting to "no frame" in order for it to seem less weird why the user asked one thing and got something else.

I couldn't find a better solution and I won't bother with it any more.

Finally, I will need to add support for life MSAA mode change and I think I'm done with window management. I'll try to figure that out tomorrow...

If you want DXGI to handle your fullscreen switches for you, don't touch the window. If you want to change window parameters, disable DXGI handling using MakeWindowAssociation and handle it yourself. When DXGI monitors your window it will automatically handle changes to the window as well as change the window when needed. Two separate programs (yours and DXGI) trying to simultaneously handle the same window is asking for trouble and sync issues.

Not sure what you mean by switching to larger modes, if you let DXGI handle your window and change to an actual enumerated mode of an output it should automatically handle the window so it covers that output. If you make the window cover more area than the monitor and want the offscreen parts drawn to (or areas on another monitor), then you don't want fullscreen mode (and can't really have it, as it is defined as exactly the area of one monitor).

As far as I can tell the window itself doesn't really matter in true fullscreen.. and you can get pretty interesting issues by resizing the window to a smaller size than the screen while still drawing in fullscreen. What happens is the screen will still be filled without clipping to the window, and any background windows will look funny at best afterwards.

If you want DXGI to handle your fullscreen switches for you, don't touch the window. If you want to change window parameters, disable DXGI handling using MakeWindowAssociation and handle it yourself. When DXGI monitors your window it will automatically handle changes to the window as well as change the window when needed.

Important to add to this because it might not be immediately obvious. There is no middle ground here. Either DXGI does everything and you do nothing, or else you have to do everything yourself (i.e just like back in the old D3D9 days).

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

If you want DXGI to handle your fullscreen switches for you, don't touch the window. If you want to change window parameters, disable DXGI handling using MakeWindowAssociation and handle it yourself. When DXGI monitors your window it will automatically handle changes to the window as well as change the window when needed. Two separate programs (yours and DXGI) trying to simultaneously handle the same window is asking for trouble and sync issues.

If you want DXGI to handle your fullscreen switches for you, don't touch the window. If you want to change window parameters, disable DXGI handling using MakeWindowAssociation and handle it yourself. When DXGI monitors your window it will automatically handle changes to the window as well as change the window when needed.

Important to add to this because it might not be immediately obvious. There is no middle ground here. Either DXGI does everything and you do nothing, or else you have to do everything yourself (i.e just like back in the old D3D9 days).

Well, DXGI does virtually nothing except for switching to fullscreen. Thanks to Erik I figured out that it is not even capable of going fullscreen on its own because without a manual call to ResizeBuffers performance is really bad.

Besides going to fullscreen there is a ton of window management that I think is needed for a well behaved rendering window. DXGI attempts none of it.

I am trying a middle-ground. It seems to work almost perfectly. What is needed is pending 3 days on about 50 lines of Win API/DXGI babysitting code and fine tune it until it works almost the way it is ideal. Ideal of course being subjective.

I am almost sure that things are good and I did test on 5 computers, but testing would be a lot easier if I had a text output on my window showing me the exact sizes.

But I can't have text output because DirectX 11. So I need to write my own class for text output. Which would be a lot easier if I had sprites. But I can't have sprites because of DirectX 11. So I am writing my own SpriteBatch class. Which is easy, except for orthographic cameras. I have never ever before used an orthographic camera. For the past two hours I've been trying to render a single simple quad with an orthographic camera and no luck yet. with projection it works flawlessly, but if i use my orthographic matrix nothing renders. I need to figure out that orthographic camera business and the figure out how to draw pixel perfect sprites in a MSAA render target. After that my SpriteBatch class will work but will barely have any features.

Porting from DX9 is not easy... sad.png

And yes, I know about DXTK or how it is called, but that has an unpleasant Windows 8 smell around it and it also locks you into DX11 I think. I found some other font engines but they are pretty big for my needs and I doubt will satisfy me because I'm a huge Unicode nazi.

Not sure what you mean by switching to larger modes, if you let DXGI handle your window and change to an actual enumerated mode of an output it should automatically handle the window so it covers that output. If you make the window cover more area than the monitor and want the offscreen parts drawn to (or areas on another monitor), then you don't want fullscreen mode (and can't really have it, as it is defined as exactly the area of one monitor).

As far as I can tell the window itself doesn't really matter in true fullscreen.. and you can get pretty interesting issues by resizing the window to a smaller size than the screen while still drawing in fullscreen. What happens is the screen will still be filled without clipping to the window, and any background windows will look funny at best afterwards.

I am talking about windowed mode switches. Let's say you want 720p and have a 768p monitor. No problem. But now if you switch to 768p, because of you window border and probably non-zero window location, the bottom right corner of the window is off-screen. This wouldn't be a problem except for Windows API: once the sizable window goes out of bounds, Win API will start to clip it. Asking for a 900p window for example will clip it randomly to a value that is neither 768p nor 900p. Even asking for 768p will clip it to a bit bellow.This only happens if the border is sizable and I found no way around it. For frame-less windows it behaves as expected.


I am talking about windowed mode switches. Let's say you want 720p and have a 768p monitor. No problem. But now if you switch to 768p, because of you window border and probably non-zero window location, the bottom right corner of the window is off-screen. This wouldn't be a problem except for Windows API: once the sizable window goes out of bounds, Win API will start to clip it. Asking for a 900p window for example will clip it randomly to a value that is neither 768p nor 900p. Even asking for 768p will clip it to a bit bellow.This only happens if the border is sizable and I found no way around it. For frame-less windows it behaves as expected.

You are really doing things you shouldn't be doing.

If you want a 720p front buffer on a 768p monitor, then you're not in fullscreen in the first place. In that case yes, cover the area you want with a window is a reasonable option, but then most certainly disable DXGI fullscreen. And again, the swap-chain is not and should not be in fullscreen then.

One simple option in that case is to simply set the view-port to a 720p area on a normal 768p fullscreen back-buffer, to avoid the problem.

You seem to be doing some pretty weird things that makes matters much more complicated than they need to be.

You are really doing things you shouldn't be doing.

If you want a 720p front buffer on a 768p monitor, then you're not in fullscreen in the first place. In that case yes, cover the area you want with a window is a reasonable option, but then most certainly disable DXGI fullscreen. And again, the swap-chain is not and should not be in fullscreen then.

One simple option in that case is to simply set the view-port to a 720p area on a normal 768p fullscreen back-buffer, to avoid the problem.

You seem to be doing some pretty weird things that makes matters much more complicated than they need to be.

I'm not doing anything super complicated, just what I consider the bare minimum rules for a well behaved application:

1. It should handle windowed applications with a sizable border, a non- sizable border or no border while respecting windows conventions (non-sizable border should not have maximize box, etc.). In windowed mode any buffer size should be supported.

2. It should handle fullscreen mode. In fullscreen mode only one of the display modes of the adapter can be used.

3. It should handle transition form windowed to fullscreen in a consistent manner, i.e. restoring window border and position. If it can't it will do its best to maintain a stable application but give a beep.

4. All parameters must be changeable at run time as long as they don't contradict other rules.

5. Stretching is never an option. The back-buffer must always have the same size as the front buffer. Centered smaller viewport than the resolution is forbidden.

These are all a bit challenging with DXGI but not impossible. There are just a ton of special cases. Like how the back buffer in windowed mode can't have odd dimensions. Making in even in a simplistic manner is easy. Yet a more complicated method is used because there is no guarantee what theme the user has. He may even have something like Windows blinds installed. I found this to be a good general code to handle odd sizes:


case WM_SIZING: {
			RECT w, c;
			GetWindowRect(hWnd, &w);
			GetClientRect(hWnd, &c);
			RECT* r = (RECT*)lParam;

			int bx = abs((w.right - w.left) - (c.right - c.left));
			int by = abs((w.bottom - w.top) - (c.bottom - c.top));

			if ((r->right - r ->left - bx) % 2)
				 r->right--;
			if ((r->bottom - r->top - by)% 2)
				 r->bottom--;

			return TRUE;
		}

Anyway, this only has to be done once. Unfortunately, the solutions form DirectX 9 do not work with DXGI, so new solutions must be found. I did solve 95% of them. Sometimes when in windowed mode on a random resolution DXGI switches to fullscreen mode that makes no sense. I might fix that too, but it is not a priority task.

I am far more concerned with the rest. I wrote my SpriteBatch class which finally works. Doesn't do that much batching though, but at least I can do 2D stuff like GUI. The good part is that I will port it to DirectX 9 too and no longer use the one that is supplied thus making my GUI code more portable and able to run on all 3 versions.

Now to write the Font whatever class...


Centered smaller viewport than the resolution is forbidden.

This means that any aspect ratio not matching the monitor is always window mode. Perhaps that's obvious and I misunderstood your post that you wanted fullscreen mode for a different aspect ratio.


Like how the back buffer in windowed mode can't have odd dimensions

Do you have any reference for that?

Never had that problem, can use windows with client area of prime x prime without issue...

Do you or do you not use MakeWindowAssociation with DXGI_MWA_NO_WINDOW_CHANGES?

This topic is closed to new replies.

Advertisement