I'm using Monogame to write an XNA game for Windows 8 app store. I'm also using a laptop hooked up to an external monitor. Naturally the resolution on my external monitor is much higher than my laptop's screen. When I drag the app from one screen to another the resolution on the view port changes.
In my constructor I'm using
_graphics = new GraphicsDeviceManager(this);
_graphics.PreferredBackBufferHeight = 768;
_graphics.PreferredBackBufferWidth = 1366;
To set my viewport resolution. This makes the app to work fine when the application runs on either monitors, however dragging the app from one monitor to another changes the resolution on the GraphicsDeviceManager. Is there anyway to prevent this change?
So I figured it out
First I wrote a method that checks to see if the port resolution on the graphic device has changed
private bool hasResolutionChanegd()
{
if ((GraphicsDevice.Viewport.Width != ScreenManager.Instance.ScreenWidth) || (GraphicsDevice.Viewport.Height != ScreenManager.Instance.ScreenHeight))
{
return true;
}
else
{
return false;
}
}
I call this method on every update
if (hasResolutionChanegd())
{
Debug.WriteLine("Resolution Change new width= " + GraphicsDevice.Viewport.Width +" new height="+ GraphicsDevice.Viewport.Height);
_graphics.PreferredBackBufferHeight = 768;
_graphics.PreferredBackBufferWidth = 1366;
_graphics.ApplyChanges();
}
This way every time the resolution changes on the Graphic Device Manager (when the user drags the app from one screen environment to another), the preferred resolution is enforced.
I get the same thing as the original poster, but with XNA, not MonoGame. When I drag the window between the two monitors, the resolution changes, but the ClientSizeChanged event is not (of course) triggered. The suggestion above to use the SizeChanged event is helpful but only for Windows8 the documentation says.
I appear to have fixed it by handling the Window.ScreenDeviceNameChanged event - I hooked it up to the same handler as the ClientSizeChanged.
I would have put this in as a comment to the original post, but I don't have enough "reputation" points for the system to let me.
Related
I am currently converting some of my old games implemented using C# (FW version 4.7.2) WinForms to Direct3D using C++.
Currently, all my real-time graphics games implement the OnPaint override, draw into the Graphics object retrieved from the PaintEventArgs parameter and invalidate right after drawing the current frame. (I don't know if this is not recommended, but it works great). These apps use double buffering of course. This causes 100% utilization of one core, which is OK.
The first obstacle I came accross when porting my games to C++ and Direct3D is the refresh rate of the window even if I had done the same thing by implementing the WndProc() and calling InvalidateRect() after drawing.
I followed this quick start guide: https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart
The problem is,
With my windows forms apps, the refresh rate is around 60 fps drawing over a full-size background image, 300-400 fps when drawing on an empty background. The following example, drawing only a rectangle, produces an amazing 2,500 fps:
public class Surface : Form
{
private int fps = 0;
private int lastFPS = 0;
private double lastSeconds = 0;
private Stopwatch chronometer = Stopwatch.StartNew();
Font font = new Font("Courier New", 10f);
public Surface()
{
BackColor = Color.Black;
DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.White, 100, 100, 100, 100);
e.Graphics.DrawString("FPS: " + lastFPS, font, Brushes.White, 5, 5);
fps++;
if (chronometer.Elapsed.Seconds > lastSeconds)
{
lastSeconds = chronometer.Elapsed.Seconds;
lastFPS = fps;
fps = 0;
}
Invalidate();
}
}
Needless to say, I was expecting a much better frame rate when porting to DirectX, but the problem is that the WM_PAINT event is not fired frequently enough and I am stuck at 100 fps even if I don't draw anything, and cpu utilization is around 10% only.
I only added the following line to the source code of the quick start guide:
case WM_PAINT:
{
pDemoApp->OnRender();
ValidateRect(hwnd, NULL);
InvalidateRect(hwnd, NULL, TRUE); // THIS IS THE LINE I'VE ADDED
}
What am I doing wrong?
I read that the rendering target of Direct 2D is automatically double buffered,
So the question is, why the WM_PAINT event gets fired only 100 times per second?
Note: Checked the windows forms Control.Invalidate() method source, and what it does is to call InvalidateRect (or RedrawWindow() if child controls are to be invalidated as well)
NOTE: Am I using direct 2d drawing incorrectly here? Should I continuously draw on a separate thread and let DirectX refresh the rendering target as it sees fit?
I've found the issue.
The following statement, while creating the render target for Direct2D, uses the default present option D2D1_PRESENT_OPTIONS_NONE, which causes the rendering engine to wait for the vSync, thus the maximum frames per second on my laptop equals 60 Hz (the refresh speed of the laptops monitor)
// Create a Direct2D render target.
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
By changing the present option to immediate, the WM_PAINT message is received right after calling InvalidateRect()
D2D1::HwndRenderTargetProperties(m_hwnd, size, D2D1_PRESENT_OPTIONS_IMMEDIATELY),
So I was too quick to ask this question, because having a refresh rate more than the refresh rate of the monitor doesn't have any effect and what I get from this is that the Direct2D engine is optimizing cpu usage by waiting for the next refresh of the actual screen, which makes good sense.
Trying to draw more than the monitor refresh rate looks like a waste of CPU cycles.
Need to consider implementing a good timing mechanism, because winforms and GDI is most of the time slower than the monitor refresh rate (when you have many sprites and geometries on the screen), and now we are faster than that and should adapt.
InvalidateRect(hwnd,.. itself fires WM_PAINT message on a window with hwnd handle. You've just created a circular reference (race).
I'm creating a Universal (Win 8.1 + Win Phone 8.1) app using C# / Monogame (VS 2013). I'm using MonoGame v3.4.
In my game1.cs initialization code, I set:
TouchPanel.DisplayWidth = _drawState.ScreenBounds.Width;
TouchPanel.DisplayHeight = _drawState.ScreenBounds.Height;
So that touch input is consistent to my game screen size (_drawState.ScreenBounds is a Rectangle containing the dimensions of my virtual screen size).
My game is set to support only portraint orientations: I have picked only the portrait and portrait-flipped orientations in the Package.appxmanifest files for both target (Win and WinPhone), and I have set this in the game1.cs Init method:
graphics.SupportedOrientations = DisplayOrientation.Portrait |
DisplayOrientation.PortraitDown
Testing the game on the Win 8.1 Simulator and on a real Win 8.1 tablet, I found out that after an orientation change (btw, the Simulator rotates and crops the game field, whereas the real device, as expected, keeps the portrait aspect), the TouchPanel has wrong dimensions - it resets to the actual screen dimensions, and so all my touches are off.
This only happens on the Win8.1 target - the Win Phone targets works as intended.
Any ideas?
EDIT:
Since I couldn't find what was going on, I just decided to subscribe to the size changed event and set the TouchPanel.DisplayWidth and TouchPanel.DisplayHeight variables:
this.Window.AllowUserResizing = true;
this.Window.ClientSizeChanged += Window_ClientSizeChanged;
The Window_ClientSizeChanged method is called on the Simulator, but not on the real device.
I tried with:
_window = Windows.UI.Core.CoreWindow.GetForCurrentThread();
_window.SizeChanged += _window_SizeChanged;
Same thing - called on the simulator, not on the device.
Not being able to find a solution to this problem, I just decided to handle the touch input differently; instead of setting the TouchPanel.DisplayWidth & Height, I decided to convert the touch (and mouse) position to the virtual screen coordinates using a simple function:
ScreenYScale = GraphicsDevice.PresentationParameters.BackBufferHeight / 1920.0f;
public Vector2 TranslateCoords(Vector2 coords)
{
return new Vector2(coords.X / ScreenYScale, coords.Y / ScreenYScale);
}
I am seeing an interesting difference between the resolution that is set through Control Panel and the output of Screen.Bounds for my widescreen laptop. The screen is 15.5" and the resolution set through Control Panel is 1920x1080. However when I run some code like this.
Screen[] screens = Screen.AllScreens;
foreach (Screen scr in screens)
{
Console.WriteLine("Width: " + scr.Bounds.Width + ", Height: " + scr.Bounds.Width);
}
The output shows my resolution being 1536x864. I have done some looking around, and I thought it may be related to a DPI issue, and when I look at my display settings, the slider (I am on Windows 8.1) is in the middle, and the checkbox that states "Let me choose one scaling level for all my displays" is unchecked. I ran this little code to get the current DPI setting.
float dpiX, dpiY;
Graphics graphics = new System.Windows.Forms.Form().CreateGraphics();
Console.WriteLine("DPI: " + graphics.DpiX);
The DPI that is returned is 96.0 which by my understanding is the 100% DPI setting (so no enlargement or whatever it is called). What seems odd to me is that the bounds returned by Screen is exactly 80% of my actual resolution, which would make me think my DPI is set to 100 (or 125%) but it is not. I am only having this issue with my laptop screen, as my secondary monitor has bounds that are equal to resolution through Control Panel. Is this due to the fact that my DPI setting is not set to have the displays independent of eachother (that checkbox checked)? For a little bit of background, I am writing a tool that takes the current screens and gets pictures from reddit and fits them to the screens independently of each other, so whatever solution I have, it has to correctly get the resolution of each display.
I had same problem also for screen shot tool.
I found solution and it works for me.
private enum ProcessDPIAwareness
{
ProcessDPIUnaware = 0,
ProcessSystemDPIAware = 1,
ProcessPerMonitorDPIAware = 2
}
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);
private static void SetDpiAwareness()
{
try
{
if (Environment.OSVersion.Version.Major >= 6)
{
SetProcessDpiAwareness(ProcessDPIAwareness.ProcessPerMonitorDPIAware);
}
}
catch (EntryPointNotFoundException)//this exception occures if OS does not implement this API, just ignore it.
{
}
}
You should call SetDpiAwareness() method before call functions to get system resolution, etc.
Also if you have some UI in your application now it is your responsibility to scale your UI in screen with high DPI.
Hope this helps.
I believe you have to notify the operating system that your application is DPI aware. Otherwise the OS pretends that everything is just fine, leading to the behaviour you're observing - the OS handles the resizing.
You can find some information about writing DPI aware applications here - http://msdn.microsoft.com/cs-cz/library/dd464646.aspx Of course, you should make sure your application actually is DPI aware - if not, you better stick with the default. It's not as nice, but at least it will work.
The main difference that Windows 8.1 brought to this is that you can have different DPI on different monitors, and you can query the monitor DPI API. .NET (and especially WPF) by default handles DPI awareness automatically, but only based on system DPI. If your monitors have different DPI settings, it will behave as non-DPI-aware (more precisely, system-DPI-aware, but the end result is your applications graphics are going to be virtualized by Windows). I'd expect that if you disconnect your second display, your application would behave as expected on your sole display (at least after manually setting the DPI, whatever the value).
I am seeing an interesting difference between the resolution that is set through Control Panel and the output of Screen.Bounds for my widescreen laptop. The screen is 15.5" and the resolution set through Control Panel is 1920x1080. However when I run some code like this.
Screen[] screens = Screen.AllScreens;
foreach (Screen scr in screens)
{
Console.WriteLine("Width: " + scr.Bounds.Width + ", Height: " + scr.Bounds.Width);
}
The output shows my resolution being 1536x864. I have done some looking around, and I thought it may be related to a DPI issue, and when I look at my display settings, the slider (I am on Windows 8.1) is in the middle, and the checkbox that states "Let me choose one scaling level for all my displays" is unchecked. I ran this little code to get the current DPI setting.
float dpiX, dpiY;
Graphics graphics = new System.Windows.Forms.Form().CreateGraphics();
Console.WriteLine("DPI: " + graphics.DpiX);
The DPI that is returned is 96.0 which by my understanding is the 100% DPI setting (so no enlargement or whatever it is called). What seems odd to me is that the bounds returned by Screen is exactly 80% of my actual resolution, which would make me think my DPI is set to 100 (or 125%) but it is not. I am only having this issue with my laptop screen, as my secondary monitor has bounds that are equal to resolution through Control Panel. Is this due to the fact that my DPI setting is not set to have the displays independent of eachother (that checkbox checked)? For a little bit of background, I am writing a tool that takes the current screens and gets pictures from reddit and fits them to the screens independently of each other, so whatever solution I have, it has to correctly get the resolution of each display.
I had same problem also for screen shot tool.
I found solution and it works for me.
private enum ProcessDPIAwareness
{
ProcessDPIUnaware = 0,
ProcessSystemDPIAware = 1,
ProcessPerMonitorDPIAware = 2
}
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);
private static void SetDpiAwareness()
{
try
{
if (Environment.OSVersion.Version.Major >= 6)
{
SetProcessDpiAwareness(ProcessDPIAwareness.ProcessPerMonitorDPIAware);
}
}
catch (EntryPointNotFoundException)//this exception occures if OS does not implement this API, just ignore it.
{
}
}
You should call SetDpiAwareness() method before call functions to get system resolution, etc.
Also if you have some UI in your application now it is your responsibility to scale your UI in screen with high DPI.
Hope this helps.
I believe you have to notify the operating system that your application is DPI aware. Otherwise the OS pretends that everything is just fine, leading to the behaviour you're observing - the OS handles the resizing.
You can find some information about writing DPI aware applications here - http://msdn.microsoft.com/cs-cz/library/dd464646.aspx Of course, you should make sure your application actually is DPI aware - if not, you better stick with the default. It's not as nice, but at least it will work.
The main difference that Windows 8.1 brought to this is that you can have different DPI on different monitors, and you can query the monitor DPI API. .NET (and especially WPF) by default handles DPI awareness automatically, but only based on system DPI. If your monitors have different DPI settings, it will behave as non-DPI-aware (more precisely, system-DPI-aware, but the end result is your applications graphics are going to be virtualized by Windows). I'd expect that if you disconnect your second display, your application would behave as expected on your sole display (at least after manually setting the DPI, whatever the value).
Is there a way to make make a windows form application full screen and black out your secondary monitors? So the main display is on your primary display and all your other monitors are just completely black?
You can use the Screen class which give you informations about the current active screens.
// Form myFrm
Rectangle r = new Rectangle();
foreach (Screen s in Screen.AllScreens)
{
if ( s != Screen.CurrentScreen ) // Blackout only the secondary screens
r = Rectangle.Union(r, s.Bounds);
}
myFrm.Top = r.Top;
myFrm.Left = r.Left;
myFrm.Width = r.Width;
myFrm.Height = r.Height;
myFrm.TopMost = true; // This will bring your window in front of all other windows including the taskbar
I can think of one way, and that would be to find out how many monitors there are on the computer, and their layout relative to each other, then create your primary window at 0,0, maximize it and set it to be TopMost, then do the same for the other displays, placing them at the screen locations corresponding to the top left of each monitor of the computer.
The only thing I can think of that would benefit from this in a WinForms environment is an app designed to give a test; the app would cover the entire desktop (except the taskbar; you'd have to disable the Start menu) and pretty much ensure that the user couldn't look at anything except the testing program. It will give you a minimal performance advantage.
Most of the apps that black out all the monitors except the main display are basically using DirectX to control the screen directly (through the lower-level interface to the graphics card). If you're using WinForms to make your program, you're about 50 levels of abstraction above using DirectX.
One way would be to create a second form in your application. One that, when given a set of dimensions, will open without border or menu and with the background set to black.
When your application runs, enumerate your monitors (count them to see how many) and find their dimensions. Your primary one will start at 0,0.
Then spawn a copy of that second form for each of your monitors (except your primary one) and give that form the dimensions of the monitor. They will then turn each screen black.
You will need to remember to keep a handle to each of the forms and terminate them when you terminate your main form.