Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rcore_desktop] Window sizes and position with multiple monitor configurations #3693

Closed
4 tasks done
SelfReflection31 opened this issue Dec 31, 2023 · 15 comments · Fixed by #3950
Closed
4 tasks done

Comments

@SelfReflection31
Copy link

Please, before submitting a new issue verify and check:

  • I tested it on latest raylib version from master branch
  • I checked there is no similar issue already reported
  • I checked the documentation on the wiki
  • My code has no errors or misuse of raylib

using current raylib v5.0 release

Issue description

I'm having this odd issue where if the game is in a release configuration aka Windows (/SUBSYSTEM:WINDOWS), /ENTRY: mainCRTStartup If I start the game on my primary monitor it behaves as expected but if I try to open the game while on a different monitor, the game is running but there is no window... yet if I drag the raylib.dll over the exe to open it will trigger the window.
doing it with the console works.

Environment

Windows 11, OpenGL 3.3.0 , RTX 3080

Issue Screenshot

example.mp4

Code Example

Just a really simple example.

#include <raylib.h>

int main(void) {
    const int screenWidth = 800;
    const int screenHeight = 450;
    InitWindow(screenWidth, screenHeight, "Simple Game");
    SetTargetFPS(60);

    while (!WindowShouldClose()) {

        BeginDrawing();
        ClearBackground(BLUE);
        DrawText("Hello World!", 32,32,23,WHITE);
        EndDrawing();
    }

    CloseWindow();

    return 0;
}
@dertseha
Copy link
Contributor

dertseha commented Jan 1, 2024

Hi there, @SelfReflection31 - While I can not yet fully explain what is happening (nor properly reproduce your issue), while trying to reproduce your issue, I found some odd behaviour on my machine. I am adding my findings to help further pin down the issue.

My setup: MS Windows 11, One UHD monitor (3840x2160) as the main display; To the right (top) of that, an FHD monitor (1920x1080) - see layout here:
desktop-layout
(The UHD is nr3, FHD is nr2; nr1 is disabled - more on that later)
For all my tests, I was using the built-in Intel graphics card, so this may also affect things.

I used the example binary created by .../raylib/projects/scripts/build-windows.bat, which also sets the flags to skip a console window.

When I start the created binary from an explorer window on the UHD monitor, the created window is centred on this monitor:
window-centred
(screenshot of desktop, two monitors)

However, when I start the binary with file browser on the FHD monitor, the created window still appears on the UHD monitor, the window is shifted, has a different size and even different centre alignment for its content:
window-offset
(screenshot of desktop, two monitors)

The position of the FHD monitor relative to the UHD monitor does not change this (top-aligned, bottom-aligned, center-aligned; left-of, right-of).

If I enable the third monitor (built-in one of laptop, resolution 2880x1920) and run the application from this one, the resulting window has yet another location (still on UHD) and size.
window-three-monitors
(screenshot of desktop, three monitors)

Hence, I suspect different resolutions of the monitors seem to have a role in this. However, it may also be relevant what functions GetCurrentMonitor(), GetMonitorWidth(), and GetMonitorHeight() return in all your cases. My hunch for all of this goes to this code in InitPlatform(), which attempts to centre the window after creating it. I suspect the window created in your second case is outside visible boundaries.

@SelfReflection31 , may I ask for your desktop layout and involved monitor resolutions? And if you have extra time, perhaps let your application write a text file with the results of the mentioned Get* functions in your cases.

@dertseha
Copy link
Contributor

dertseha commented Jan 2, 2024

Addendum: After changing the linked line in rcore_desktop.c to also include the position of the monitor for the new window location, it works:

      Vector2 pos = GetMonitorPosition(GetCurrentMonitor());
      SetWindowPosition(pos.x + GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, pos.y + GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2);

This places the window at the centre of the monitor where file explorer was running.
What I haven't figured out is how the resolution played into the size of the window - but that's academic at this point.
This change also somewhat fixes it for "borderless fullscreen" windows (call InitWindow() with zero for sizes) - but places the window on my rightmost monitor with wrong size if I start it on the second. Will investigate further when I have time.

Fullscreen mode is unaffected by this change, and always shows up the window on the primary monitor, regardless of this change, and regardless from where the app is started.

However, I don't know the intent of the library. With fullscreen apps always starting on primary monitor, should this be the case also for windowed apps? If so, then the size/position calculation is wrong. If not, then monitor position needs to be taken into account.

@SelfReflection31
Copy link
Author

SelfReflection31 commented Jan 3, 2024

my monitor layout
1 is UHD (2560x1440)
2 is (1920X1080)
3 is (1920x1080)
in the video left was 2, right was 1. trying it on the 3rd one also did the same thing with 2.
image

I should've also mentioned that the version of raylib I'm using was downloaded by vcpkg. (latest 5.0)

@SelfReflection31
Copy link
Author

Apparently, this is a deeper issue.. 👁️ not even raylib but with msi/glfw. I found a fix so far aka shutting down this "Nahimic" service.
glfw/glfw#782

It was blocking it. looking it up, it seems this has been an ongoing issue with glfw windows or more.

with your problem, you already found the fix I thinks :P

@dertseha
Copy link
Contributor

dertseha commented Jan 3, 2024

Cool - so with that service stopped, the window opens centred in the respective monitor you started the application on?
Because if so, then there's yet another difference between our systems, where my "fix" only fixes it for my machine - and potentially break it on yours :/

@SelfReflection31
Copy link
Author

oh my apologies, starting the application, the window opens only on the primary monitor. It even with the offset like you showed...so yes.

@dertseha
Copy link
Contributor

dertseha commented Jan 3, 2024

OK, thank you for clarifying - so this issue still remains for all.

Meanwhile I could also figure out why (with the position fix) a "borderless fullscreen" window would open up with a wrong size on my third monitor even if started on the second.

Short:
The window initialization for borderless fullscreen windows assumes the window size to be that of the resolution of the primary monitor - not of the resolution of the monitor where the window is meant to be, which may be different.

Long:

  1. For a borderless fullscreen window, InitWindow() is called with zeroes for size. This means that the CORE.Window.screen.width/.height are set to zero as well (code here).
  2. InitPlatform() makes sure CORE.Window.screen.width/.height are set to something else than zero and uses the resolution of the primary monitor (!) (code here) - in my case, this is 3840x2160.
  3. InitPlatform() then tries to centre the window (code as found before). For this is uses GetCurrentMonitor()
  4. GetCurrentMonitor() then has some detailed code to match the monitor closest to the centre of the window. (starting here)
  5. The "centre of the window" is calculated by the x/y of the window, plus half of the current size (code here) -- in my example, x/y are 3853x58 (The extra 13x58 I guess because of window edge rounding and stuff), plus half of 3840x2160, result in 5773x1138.
  6. Because X=5773 is more than the right corner of my second monitor, the "current" monitor is considered to be the third one - and the placement assumes the third monitor instead of second.

What to change?
I guess this is a rather tricky thing to get right. And I guess it's not going to be any easier when considering Linux (X11) or how monitors are handled on Mac...

Core question remains: With fullscreen windows opened on the primary monitor exclusively, which monitor should be taken for regular windows, and which one for borderless fullscreen windows?

Regular windows: As mentioned before, they either need the positioning fix for the right monitor, or a sizing fix for the primary monitor.

About borderless fullscreen ones I am not sure:
a) are they similar to regular fullscreen?
b) they should open on the monitor they are started on - then the code needs changing how the to-be-used monitor is resolved, and which which resolution to use. (I have ideas here - but that's far down the decision tree)

In summary: This positioning stuff is confusing from users perspective, yet affects "only" those with multiple monitors which furthermore have different resolutions - AND the app needs to be started from explorer on a different monitor... . How to proceed?

@raysan5
Copy link
Owner

raysan5 commented Jan 3, 2024

@dertseha @SelfReflection31 Thank you very much for investigating this issue and the detailed report!

GetCurrentMonitor() then has some detailed code to match the monitor closest to the centre of the window.

That code was added lately by a contributor and personally I'm not sure if that's the best approach... Would it help removing it and avoid that extra step?

I guess this is a rather tricky thing to get right. And I guess it's not going to be any easier when considering Linux (X11) or how monitors are handled on Mac...

Yes, I'm afraid it is, there are too many possible combinations and dealing with HighDPI and non-HighDPI and multiple resolutions on multiple monitors could be quite tricky. Still maybe the issue could be minimized?

About the questions:

a) are they similar to regular fullscreen?

No, fullscreen borderless scales the application resolution to the monitor resolution and hides window borders (actually it's a windowed mode). Fullscreen tries to scale the application to the the closest fullscreen-resolution available on the monitor and changes monitor resolution, it's like an exclusive mode.

b) they should open on the monitor they are started on

Yes, that's the most logical behaviour.

This positioning stuff is confusing from users perspective, yet affects "only" those with multiple monitors which furthermore have different resolutions

It can be removed.

AND the app needs to be started from explorer on a different monitor... . How to proceed?

No, it shouldn't start on a different monitor.

@dertseha
Copy link
Contributor

dertseha commented Jan 3, 2024

Thank you for your reply.

This then means,

  1. the position needs correction using the monitor's position and
  2. determining the initial monitor and the initial window size needs a different approach.

My proposal for 2 is an extension to the existing GetCurrentMonitor() function. The algorithm itself is fine in my view - for the user, who has a final window.
For the initialisation routine however, I'd only use the X/Y position coordinate as the reference of the created window, not its centre, as the window size is not yet determined, only its position by the OS.

Meaning, GetCurrentMonitor() would call an internal function GetMonitorAt(windowCenterPos); This GetMonitorAt() has the current algorithm using the provided location. And InitPlatform(), instead of defaulting to the primary monitor, would use that of GetMonitorAt(windowPos) and then use that one's resolution as the size to initialize with, in case the window size is zero.

I'll try this out in my fork and report back.


A side note regarding comparing "borderless fullscreen" and "regular fullscreen" - I wasn't clear what I meant with "similar". I understand the difference in how the draw surface is created. What I meant with "similar" was: If I want a "borderless fullscreen" window, would I want it to behave like a regular "fullscreen" app and always be on the primary monitor?

However, as I understand you now: both windows and "borderless fullscreen" windows should open up on the monitor where they were started. Should this also count for "fullscreen" apps, or should they always go on the primary monitor?

@dertseha
Copy link
Contributor

dertseha commented Jan 3, 2024

Addendum - please clarify, because I realize I might misinterpret your statement:

No, it shouldn't start on a different monitor.

Do you mean all cases should start on the primary monitor, or did you mean "if I start the app on monitor 2, it shouldn't show on monitor 3, and instead should show on monitor 2" ?
So far I understood the latter - window should be seen on the monitor the OS proposed.

@raysan5
Copy link
Owner

raysan5 commented Jan 20, 2024

Do you mean all cases should start on the primary monitor, or did you mean "if I start the app on monitor 2, it shouldn't show on monitor 3, and instead should show on monitor 2" ?

@dertseha Per mi understanding, the app should start in the monitor you start it. If you are on monitor 2 and you start an app there, it should be displayed in monitor 2.

@dertseha
Copy link
Contributor

Thank you for the clarification. Incidentally, I finally found time today to tackle this.

I have found a way to implement all (most?) requirements, yet with some open questions.

Branch here: https://github.com/dertseha/raylib/tree/proposal-3693-initial-window-geometry
Diff here: https://github.com/raysan5/raylib/compare/master...dertseha:raylib:proposal-3693-initial-window-geometry?expand=1

Tested under MS Win11, with core_basic_window.c sample under raylib\projects\scripts\.

What this branch does:

  • Fullscreen applications use the primary monitor, exclusively. This is as per recommendation of comment from glfwCreateWindow(), which states "Unless you have a way for the user to choose a specific monitor, it is recommended that you pick the primary monitor."
  • Non-fullscreen applications will have their windows created in two flavours:
    • a) User specified an explicit dimension
    • b) User requested implicit monitor size by specifying width=0, height=0 (which mimics the behaviour of fullscreen apps)

Either way, non-fullscreen applications will be centred on the monitor where they are created.

They way this solution does this is by querying the monitor after creating the window, and then derive necessary sizes. This also needed a special case of implicit-monitor-sized windows to be created with an initial size of 1x1 pixels, as GLFW prohibits using 0x0.

However, this procedure has two open points:
1) compatibility
Previously, the intent for a non-fullscreen implicit-monitor-sized window was pre-arranged. The CORE.Window.screen dimensions were pre-initialized to that of the primary monitor (where some of the encountered issues started with). This allowed for a different check whether auto-iconify hint was to be disabled.
Old code (v5) here, new code (my proposal) here.

The change now has the effect that, should the user have had specified the explicit monitor dimensions to achieve a non-fullscreen explicit-monitor-sized window, then this solution now no longer removes that hint. This solution does not know the monitor before creating the window, so it can not know its resolution to set the hint.

2) Error cases
If, for whatever reason, resolving the monitor fails after the fact, then at least the CORE.Window.display dimensions are not initialized. Even the CORE.Window.screen dimensions are best-guessed using glfwGetWindowSize(). This might have further issues according to documentation of glfwCreateWindow(), as per comment for X11: "Due to the asynchronous nature of X11, it may take a moment for a window to reach its requested state. This means you may not be able to query the final size, position or other attributes directly after window creation."

(However, the previous code was also wrong to assume primary monitor, so this could be used as the fallback?)

Code in question (my proposal) here.

Furthermore, I tested this only on MS Win11 (three monitors of different resolutions), and only with GLFW. SDL might have different behaviour.


The issues regarding querying states, as well as some difficulties with some behaviour, let me second guess the original (unwritten & assumed) requirements:

  • Is it necessary to support a non-fullscreen window creation of 0x0 to imply the used montor sizes?
  • Must a non-fullscreen window be centred on the screen, instead of placed where the window manager placed it?

If these two requirements were dropped, then the code might get simpler for the library:

  • Require positive values for dimensions of non-fullscreen windows
  • Create the window with these sizes, let it be placed wherever window manager does

Yet, I'm not sure if truly much code could be dropped - it still needs CORE.Window.display dimensions to be set, which need to be resolved from the current monitor...

OR, and this could be a completely different approach, all paths use the primary monitor, exclusively. If there is the demand for a different monitor, then the library needs a new function to pre-set the target monitor before InitWindow() is called.

@raysan5
Copy link
Owner

raysan5 commented Apr 21, 2024

@dertseha Excuse my late response. Thank you very much for the detailed report and the proposed solutions, they look great to me and if they work in a configuration like yours it's enough testing for me, I don't have that kind of configuration and I think is really support many use cases. Feel free to send a PR with the proposed changes.

Note that window centering code was updated recently.

About your questions:

Is it necessary to support a non-fullscreen window creation of 0x0 to imply the used monitor sizes?

It would be nice to support it, flag FLAG_BORDERLESS_WINDOWED_MODE was created for that specific pourpose.

Must a non-fullscreen window be centred on the screen, instead of placed where the window manager placed it?

raylib adopted this convention since its first version and I personally like it. Recently it was reviewed to avoid top-left window corner out of monitor in cases the window resolution is bigger than monitor resolution.

Please, let me know if you review this issue or send a PR, I'll be quicker to review it and asnwer, I'd like to close this issue.

NOTE: I didn't mention it but monitor High-DPI could also influence the window/framebuffer size (as seen in one of the shared screenshots) and it's also very dependant on OS and GPU/drivers. It could be tricky.

@raysan5 raysan5 changed the title [rcore] nonprimary monitor oddities without console [rcore_desktop] Window sizes and position with multiple monitor configurations Apr 21, 2024
@dertseha
Copy link
Contributor

@raysan5 Thank you for your response.
Should you want to have this done quickly, then someone else needs to take this over. The earliest I might be able to work on this is next week, on 1st of May.

Thank you also for answering questions about requirements. For the change, however, there are two open points to clarify, in section "However, this procedure has two open points" of my previous comment.
For the error case (second point) I'd simply abort and fail to initialize. For the first: That one will be a slight incompatibility to previous behavior regarding auto-iconify-hint.

@dertseha
Copy link
Contributor

dertseha commented May 1, 2024

@raysan5 pull request created: #3950 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants