raysan5 / raylib

A simple and easy-to-use library to enjoy videogames programming
http://www.raylib.com
zlib License
20.65k stars 2.12k forks source link

[rcore] Window Mode Overhaul #4147

Open SoloByte opened 2 weeks ago

SoloByte commented 2 weeks ago

Issue Description

  1. I think we should think about a new clean window mode setup for raylib building on top of whats already there. 
  2. For instance both fullscreen & borderless fullscreen set/clear certain config flags to work properly but they do not restore them to the values set by the user when exiting (borderless fullscreen).  
  3. The user can set this certain flags without checking of current window mode allows this. 
  4. Does losing & regaining focus work in all window modes?
  5. Do minimize & maximize restore previous window state. Like going minimize from fullscreen should restore fullscreen when clearing minimize ? And then exiting fullscreen should restore window before fullscreen ? Should they do that in all cases?
  6. How to handle dpi scale ?
  7. What does Screen Size & Render Size represent exactly.

If any statements or assumptions on my side are wrong please correct them. Any ideas are welcome to make to window handling in raylib simple, easy, and consistent.

Related Issues

SoloByte commented 2 weeks ago

@raysan5 @SuperUserNameMan @JeffM2501 Please add any ideas, suggestions, etc ;)

SuperUserNameMan commented 2 weeks ago

Ok, I'm a little confused because i don't know in which issue/thread/conversation to begin, so i'm randomly picking this one.

I think i need to clarify some logic first. (at least to myself), and see if we all agree.


1) if the application requires HDPI support, then, it means the application is meant to work in windowed mode so the user can keep using other desktop applications at the same time. HDPI mode should be compatible with this methods :

On the other hand, making FLAG_WINDOW_HIGHDPI compatible with TogglFullscreen() is irrelevant, since there is no reason for a desktop application to switch to an exclusive fullscreen hardware display mode.


2) if the application requires real exclusive hardware fullscreen mode using ToggleFullscreen(), it means it is meant to be a fullscreen game with an optional windowed mode. In consequence :

Problem : if the resolution provided in InitWindow( w , h , "game" ) is not supported by the hardware, glfw will try to find another one that will best fit the content. We have no control over this arbitrary choice, so we might end up with a cropped content. However, once we ToggledFullscreen(), it is up to our game code to check the new screen/render resolution, and to adapt the rendering to it.


To recapitulate : (i hope no reader is colorblind)

purpose of our program ==> make a desktop app make a video-game
enabling FLAG_WINDOW_HIGHDPI :green_circle: relevant :red_circle: irrelevant
enabling FLAG_WINDOW_RESIZABLE :green_circle: relevant :orange_circle: relevant only in windowed mode ?
using ToggleFullscreen() :red_circle: irrelevant :green_circle: relevant
using ToggleBorderlessWindowed() :green_circle: relevant :green_circle: relevant
using MaximizeWindow(), etc :green_circle: relevant :orange_circle: relevant only in windowed mode ?
InitWindow( w , h , "random resolution" ) :green_circle: relevant :red_circle: irrelevant
InitWindow( w , h , "standard resolution" ) :red_circle: irrelevant :green_circle: relevant

edit : note that desktops games like mine sweeping, solitary and whatever similar games, enters in the same category as "desktop app" in the above array.


So, my point is that if we take relevant and irrelevant use-case scenario into account, we could simplify our expectation from current implementation, and identify what are real issues that need to be fixed, and what are wrong issues that need the user to clarify the purpose of their project and their expectation.

What do you think ?

paulmelis commented 2 weeks ago

Why would ToggleFullscreen() not be relevant for a desktop app?

SuperUserNameMan commented 2 weeks ago

Why would ToggleFullscreen() not be relevant for a desktop app?

From a logical perspective, it's because a desktop app is meant to work in the desktop, with other desktop apps.

When a desktop app needs to use the maximum available screen space, it is either maximized, or set to a maximized borderless window (like a "no distraction mode").

On the other hand, ToggleFullscreen() is meant to have an exclusive access to the display, and might change the display resolution mode to an hardware resolution supported by both the graphics card and the display.

If you write a 3D editor, or a "3D model poser", or a "chiptune editor", or a desktop game, or whatever desktop app, you don't need an exclusive access to the display. Miximized window or "no distraction mode" (ToggleBorderlessWindowed()) are more relevant and should suffice.

edit: an exception would be an app that is a "game editor" that might allow the user to "run" the edited game into exclusive fullscreen mode ... but as mentioned below, the DPI rescaling of text and UI could be done by code using GetWindowScaleDPI()

Also, from a technical point of view, if your desktop is in HDPI mode, you'd probably want the UI and the text of your app to be rescaled accordingly to the DPI. To do so :

The problem is that using FLAG_WINDOW_HIGHDPI with ToggleFullscreen() leads to issues and over-complications.

SuperUserNameMan commented 2 weeks ago

Note that if FLAG_WINDOW_HIGHDPI is enabled, we could make ToggleFullscreen() detect it and fallback to ToggleBorderlessWindowed()

SoloByte commented 2 weeks ago

Ok, I'm a little confused because i don't know in which issue/thread/conversation to begin, so i'm randomly picking this one.

I think i need to clarify some logic first. (at least to myself), and see if we all agree.

I also didn't know where to begin, so I just put it all out there :)

I think the first thing we need to do (what you already started) is to get on the same page and agree on what is actually needed. So starting with this issue is probably best.

I agree with your assumptions I just need some clarification on the InitWindow() part.

Problem : if the resolution provided in InitWindow( w , h , "game" ) is not supported by the hardware, glfw will try to find another one that will best fit the content. We have no control over this arbitrary choice, so we might end up with a cropped content.

Here the closest resolution is picked on initialization from the supported resolutions from GLFW. We could first check if there are supported resolutions with the same aspect ratio and find the closest resolution with matching aspect ratio. Only if there are no supported resolutions with the same aspect ratio we fall back to just finding the closest resolution.

In any case does this circumvent the problem with not knowing what GLFW would pick for a resolution?

Note that if FLAG_WINDOW_HIGHDPI is enabled, we could make ToggleFullscreen() detect it and fallback to ToggleBorderlessWindowed()

I don´t think this is a good idea? Again raylib would do something under the hood that is hard to understand.

Should ToggleFullscreen() give the most freedom and let the user handle everything or should it also scale somehow with dpi? Because either the window size is set to the monitor size before ToggleFullscreen or ToggleFullscreen(int width, int height)needs 2 parameters for the resolution the user wants for the fullscreen?

There is also the solution to this entire problem of opening the system up and let the user implement the window handling for themselves? At least as an option? Because this might also be a case where it is impossible to meet all the needs of everyone using raylib.

SoloByte commented 2 weeks ago
  • For instance both fullscreen & borderless fullscreen set/clear certain config flags to work properly but they do not restore them to the values set by the user when exiting (borderless fullscreen).
  • The user can set this certain flags without checking of current window mode allows this.

There is still this issue for ToggleBorderlessFullscreen(). Borderless fullscreen sets the topmost flag and clears the decorated flag. What should happen when the user sets any of those 2 flags while in borderless fullscreen.

Also should the resizable flag be cleared automatically when ToggleFullscreen() is called and then restored to previous value when fullscreen is exited?

As for now all these flags are linked in some way regarding window handling:

We could seperate those flags into window modes and supported flags.

Window Mode Supported Window Flags
Windowed Resizable, Decorated, Topmost?, Focused?
Maximized None
Minimized None
Hidden None
BorderlessFullscreen None
Fullscreen None

Random Idea?

Raylib could put its own system on top hiding the current way of having config flags and window modes mixed?

Example:

I have no idea if that makes sense or is usable 😅

SuperUserNameMan commented 2 weeks ago

Why is it important to init the window with a standard resolution instead of some arbitrary resolution when making a game ?

IMO, with current implementation, it is better, because you increase your chances to actually get a standard full screen resolution when toggling to exclusive full screen mode.

If i ask 800x800, i get 800x600 SVGA. if i ask 512x512, i get 640x480 VGA.

If i ask 640x480 or 800x600 or 1024x768 or 1280x720, i get what i want because they are standard hardware reso that are commonly supported by most hardware.


Why does the above not matter for an application that does not use ToggleFullscreen()?

Because the display hardware resolution will remains unchanged. It will remains the desktop resolution. You only want your InitWindow() default size to fit into current desktop resolution.


How can I init the window properly without knowing the monitor the game launches on yet ? Is the list of connected monitors and their information available before calling InitWindow()?

I haven't found any function that allows this in current Raylib API. https://www.raylib.com/cheatsheet/cheatsheet.html


Here the closest resolution is picked on initialization from the supported resolutions from GLFW. We could first check if there are supported resolutions with the same aspect ratio and find the closest resolution with matching aspect ratio. Only if there are no supported resolutions with the same aspect ratio we fall back to just finding the closest resolution.

If we request a hardware resolution that has a width or height greater than our content resolution, we would have to rescale the content to fit without changing its aspect ratio, to center it, and to add black rectangles on the empty sides.

It might also mean that Raylib would have to render to a texture instead of rendering directly to the window's surface, unless there are some glfw function that allows rescaling the content surface ....

Alternatively, all of this could be done "manually" by the programmer in the application code side by rendering to a texture.


Should ToggleFullscreen() give the most freedom and let the user handle everything or should it also scale somehow with dpi? Because either the window size is set to the monitor size before ToggleFullscreen or ToggleFullscreen(int width, int height) needs 2 parameters for the resolution the user wants for the fullscreen?

3 parameters : w , h and refreshRate

Like you mentioned somewhere before, we'd need a function that allow enumerating the list of supported hardware resolution and refresh rates so we could let the end-user choose in which hardware resolution they want to run the program.


Note that if FLAG_WINDOW_HIGHDPI is enabled, we could make ToggleFullscreen() detect it and fallback to ToggleBorderlessWindowed() I don´t think this is a good idea? Again raylib would do something under the hood that is hard to understand.

Anyway there is no point in activating HIGHDPI if the app is intended to run in exclusive fulllscreen mode. If we want HDPI, we're making a desktop application, and we don't need an exclusive fullscreen mode.

So, i think that if the programmer want both HDPI and fullscreen, they actually need maximized borderless window.


There is also the solution to this entire problem of opening the system up and let the user implement the window handling for themselves? At least as an option? Because this might also be a case where it is impossible to meet all the needs of everyone using raylib.

With a function that would allow to enumerate supported hardware resolution, this could be a greater step forward already.

Also, I think ToggleBorderlessWindowed() definitively needs to be fixed, because it does not restore the borders of the window on my Linux ... :-/

orcmid commented 1 week ago

@SuperUserNameMan

From a logical perspective, it's because a desktop app is meant to work in the desktop, with other desktop apps.

I think "meant" is too strong here. I play AAA Desktop applications, and some of them take the full screen, others offer different configurations, but usually not windowed in the way you mean it. I have one graphical application that I have to use "reduced" in order to shrink the vertical enough so that the OS pop-up task bar does not interfere with it. Other than that, I fill my screen with it.

@SoloByte I find your approach very welcome. The one thing that bothers me is saying that the InitWindow parameters have anything to do with resolution. I think we get confused with resolution and the term is misused around computer monitors and desktop applications. DPI is more helpful except it still doesn't account for viewing distance and the physical size of the monitor surface. so much. I think the InitWindow request parameters are in pixels and it is probably the case that the numbers chosen there (and in positioning text and specifying text height) assume what platforms have as default DPI unless the application some means to know otherwise and adjust parameters accordingly.

PS: Some of the effects being noticed have to do with monitors having built-in scaling provisions for particular dimensions (in pixels). Graphical drivers may rely on that. You can usually see what's available in various situations where the different frame sizes (in pixels) are shown as options. The best cinematic-quality game software tends to exploit that. There may also be accessibility considerations here, but I have no idea how that is handled in quality software.

orcmid commented 1 week ago

@SuperUserNameMan

From a logical perspective, it's because a desktop app is meant to work in the desktop, with other desktop apps.

I think "meant" is too strong here. I play AAA Desktop applications, and some of them take the full screen, others offer different configurations, but usually not windowed in the way you mean it. I have one graphical application that I have to use "reduced" in order to shrink the vertical enough so that the OS pop-up task bar does not interfere with it. Other than that, I fill my screen with it.

orcmid commented 1 week ago

@SoloByte I find your approach very welcome. The one thing that bothers me is saying that the InitWindow parameters have anything to do with resolution. I think we get confused with resolution and the term is misused around computer monitors and desktop applications. DPI is more helpful except it still doesn't account for viewing distance and the physical size of the monitor surface. so much. I think the InitWindow request parameters are in pixels and it is probably the case that the numbers chosen there (and in positioning text and specifying text height) assume what platforms have as default DPI unless the application has some means to determine otherwise and adjust parameters accordingly.

PS: Some of the effects being noticed have to do with monitors having built-in scaling provisions for particular dimensions (in pixels). Graphical drivers may rely on that. You can usually see what's available in various situations where the different frame sizes (in pixels) are shown as options. The best cinematic-quality game software tends to exploit that. There may also be accessibility considerations here, but I have no idea how that is handled in quality software.

SoloByte commented 1 week ago

Also, I think ToggleBorderlessWindowed() definitively needs to be fixed, because it does not restore the borders of the window on my Linux ... :-/

ToggleBorderlessFullscreen & Toggle Fullscreen have to be definitely fixed. Another issue with borderless fullscreen is on macOS it keeps the top task bar visible, meaning right now the only difference between borderless fullscreen and a maximized window on macOS is that borderless fullscreen uses the undecorated flag.


I find your approach very welcome. The one thing that bothers me is saying that the InitWindow parameters have anything to do with resolution. I think we get confused with resolution and the term is misused around computer monitors and desktop applications.

Thank you :) I will keep that in mind 👍

SoloByte commented 1 week ago

A little side note InitWindow() could have parameters for each config flag that can only be set before InitWindow() is called.

Currently those 4 flags have to be set before InitWindow():

If they can only be set with the InitWindow() function and no longer with setting config flags or Set/ClearWindowState it would be much less confusing.

I can open a seperate issue for this as well if it is considered a good idea ;)

ColleagueRiley commented 1 week ago

@SoloByte I don't believe it's possible to set MSAA 4x after the window has been created.

SoloByte commented 1 week ago

I don't believe it's possible to set MSAA 4x after the window has been created.

Yes it has to be set before InitWindow like I said above ;) My idea is to have init window set them internally before initializing the window.

orcmid commented 1 week ago

I think you'd better call that InitWIndowX().

Then there's a matter of explaining this to users of the raylib API. That is, how do they determine how to set it.

In particular, how does one create a distributable desktop program that determine the proper situation on a given computer at run-time and also when monitors are adjusted or switched?

I think some clear use cases and what to do with them need to be considered.

SoloByte commented 1 week ago

I think you'd better call that InitWIndowX().

Then there's a matter of explaining this to users of the raylib API. That is, how do they determine how to set it.

I think some clear use cases and what to do with them need to be considered.

Absolutely. It could also be done differently. For me personally it was always confusing which flags actually have to be set before init window. Once I knew it I would forget it again in a couple of months and had to figure it out again ;)

In particular, how does one create a distributable desktop program that determine the proper situation on a given computer at run-time and also when monitors are adjusted or switched?

Those 4 flags I mentioned can´t be changed after InitWindow() was called, right? Can I call CloseWindow() and InitWindow() again whenever I need to change any of those 4 flags? Or do I have to restart the application to change the high dpi flag when a monitor was changed for instance?


My goal would be to clean up the config flags a bit with the window mode overhaul if possible.

For me there a 3 possible ways that I would like regarding window handling in raylib:

  1. Raylib takes care of it and takes the responsibility that it works (means it has to work consistently across all platforms)
  2. The system behind is more opened up to the user and the user is responsible for window handling
  3. Combination of 1 & 2 where built in window handling can be used for prototyping, learning, etc. but it is also possible to do it yourself
paulmelis commented 1 week ago

Why would ToggleFullscreen() not be relevant for a desktop app?

From a logical perspective, it's because a desktop app is meant to work in the desktop, with other desktop apps.

Well, if you define desktop app to be "non-fullscreen" then obviously it doesn't need ToggleFullscreen(). But that feels a bit weird, as the other category you define is "video game". I'd say the content of the what is being rendered with raylib is fairly irrelevant to how the whole window and resolution management works (wether it is a game, something with creative visuals, or a more serious data visualization, it's all just 2D/3D content drawn with the raylib API).

When a desktop app needs to use the maximum available screen space, it is either maximized, or set to a maximized borderless window (like a "no distraction mode").

On the other hand, ToggleFullscreen() is meant to have an exclusive access to the display, and might change the display resolution mode to an hardware resolution supported by both the graphics card and the display.

Yet, such as interpretation of ToggleFullscreen() is currently not clear. The function name really just implies "toggle fullscreen window state" (and thus also leave off window decoration). And the same holds for ToggleBorderlessWindowed(), which only implies showing/hiding window borders, without any change to fullscreen status. So it might also be a case of better defining what each function should do before re-defining (or removing) them.

If you write a 3D editor, or a "3D model poser", or a "chiptune editor", or a desktop game, or whatever desktop app, you don't need an exclusive access to the display. Miximized window or "no distraction mode" (ToggleBorderlessWindowed()) are more relevant and should suffice.

Again, this links to my remarks above on the meaning of the function names above. I would never assume ToggleBorderlessWindowed() would give me a fullscreen window and would expect to have to explicitly set the window resolution to the display resolution after removing borders, while ToggleFullscreen() to me does imply doing exactly those two steps. Whether ToggleFullscreen() allows resolutions different from the native display resolution to me seems orthogonal to toggling the window state (yet you'd want control on allowing/disallowing such display resolution changes, to avoid surprises, and something screwing up your desktop).

SuperUserNameMan commented 1 week ago

I agree that the names of the functions themselves are relatively imprecise. But that's what we have to deal with for the sake of backward compatibility.

And that's also why I interpreted the intention of the functions according to their current source code implementations in the main GLFW platform backend.

ToggleFulllscreen() source code intentionally asks GLFW to change the hardware resolution of the display : it could be considered as an "hardware level fullscreen".

On the other hand, ToggleBorderlessWindowed() source code intentionally asks GLFW to resizes the window without decoration so it covers the whole desktop surface without changing the hardware resolution of the display : it could be considered as a "software level fullscreen".

Beyond that, there a consequences to take into consideration. For example :

So, with current Raylib GLFW backend implementation, i think the programmer needs to be aware that they might not be able to use all features altogether, and that some are better suited for certain type of project, etc.

Of course, it is absolutely possible to rewrite the window management system of Raylib to make an universal solution that would just work out of the box without surprise, and without prior knowledge of what happens under the hood. But that might be a large rework that might lead to backward incompatibilities.

I don't know how long it would take to be ready, but I personally don't want to wait or spend too much energy and time into it. So, in the meantime, i'd personally rather try to fix or warkaround current bugs and issues, and adapt my project to what current Raylib implementation let me do.

orcmid commented 1 week ago

@paulmelis I wonder about the nomenclature also. Earlier today I opened one of the older AAA games I have (Assassins Creed Origins) and checked its options for Window Mode: They are "Full Screen," "Windowed," and "Borderless." There are few variations on this. I have seen "Full Screen (Windowed)" on occasion. None of these are explained. I always just go for Full Screen. I suppose my point is that this is at the end-user level, and I can change those settings even with the game running but paused, although I usually go to such options just after start-up, as when I have installed a new monitor.

SuperUserNameMan commented 1 week ago

If i had to extend current Raylib API without breaking backward compatibility, I'd add these functions that could simplify the life of beginners and prototypers :

// Open a resizable window with a content surface of a fixed dimension 
// that is rescaled and centered with black borders if required
void InitWindowWithFixedContentSize( int windowWidth , int windowHeight, int contentWidth, int contentHeight );

// Change the monitor resolution and display the window's content
// upscaled and centered with black borders if required
bool ActivateFullscreenExclusiveMode( int monitor, int width, int height, int refreshRate ); 

// Like above, except that the resolution of the monitor is kept unchanged
bool ActivateFullscreenDesktopMode( int monitor );

// Restore windowed mode
void RestoreWindowedMode();

// And to work with ToggleFullsceenExclusiveMode() :
int GetMonitorSupportedResolutionsCount( int monitor );
Resolution GetMonitorSupportedResolution( int monitor, int resolutionIndex );
paulmelis commented 1 week ago

On the other hand, ToggleBorderlessWindowed() source code intentionally asks GLFW to resizes the window without decoration so it covers the whole desktop surface without changing the hardware resolution of the display : it could be considered as a "software level fullscreen".

For me this call does something slightly different: the window doesn't end up covering the full desktop, but leaves the MATE menu bar at the top uncovered. I need to set the window position to (0,0) after the call to get full desktop coverage:

if (IsKeyPressed(KEY_F))
{
    ToggleBorderlessWindowed();
    if (IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE))
        SetWindowPosition(0, 0);

    printf("Screen size now %d x %d\n", GetScreenWidth(), GetScreenHeight());
    printf("IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE) %d\n", IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE));
}

However, when ToggleBorderlessWindowed() is then called a second time the window does not get back its decoration. The reported screen size values also appears to be the previous value (i.e. before toggling):

# After toggling borderless fullscreen mode ON (reported screen size is wrong)
Screen size now 1860 x 900
IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE) 1
# After toggling borderless fullscreen mode OFF (reported screen size is wrong)
Screen size now 1920 x 1080
IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE) 0

Edit: accidentally submitted the comment before I was finished Edit 2: some clearifications

SuperUserNameMan commented 1 week ago

@paulmelis : yes, i have similar issue too #4149 on my LinuxMint + MATE, except that the first call covers the whole desktop including the menu bar at the bottom on mine. There is something fishy i could not identify clearly.

paulmelis commented 1 week ago

@paulmelis : yes, i have similar issue too #4149 on my LinuxMint + MATE, except that the first call covers the whole desktop including the menu bar at the bottom on mine. There is something fishy i could not identify clearly.

Ah, good catch, interesting comments on GLFW in that issue

SoloByte commented 1 week ago

For me this call does something slightly different: the window doesn't end up covering the full desktop, but leaves the MATE menu bar at the top uncovered.

It does the same on macOS. It leaves the top bar uncovered.

I need to set the window position to (0,0) after the call to get full desktop coverage

I will test that on macOS and see what it does.

SoloByte commented 1 week ago
// Change the monitor resolution and display the window's content
// upscaled and centered with black borders if required
bool ActivateFullscreenExclusiveMode( int monitor, int width, int height, int refreshRate ); 

It should move the window to the specified monitor if the window is currently on a different monitor. How should the refresh rate be handled regarding vsync? Should/Does RestoreWindowedMode() also restore the refreshRate?


// Like above, except that the resolution of the monitor is kept unchanged
bool ActivateFullscreenDesktopMode( int monitor );

This function therefore sets the window size to the size of the specified monitor? (If a different resolution is required ActivateFullscreenExclusiveMode() can be used.) Currently this does not happen why I always have to set the window size to the monitor size if I want a fullscreen that does not change hardware resolution. The fullscreen also activates much faster this way.

It should also move the window to the specified monitor if the window is currently on a different monitor.


The bool return value tells you if the request was successfull?

SuperUserNameMan commented 1 week ago

It should move the window to the specified monitor if the window is currently on a different monitor. How should the refresh rate be handled regarding vsync? Should/Does RestoreWindowedMode() also restore the refreshRate?

Well, i'm not sure where you see a problem, because in my mind, when FLAG_VSYNC_HINT is enabled, it is up to GLFW and to the GPU to handle that.

From the programmer perspective, only the interaction of vsync with void SetTargetFPS(int fps); should matter.

Regarding RestoreWindowedMode(), i guess ActivateFullscreenExclusiveMode() would have to save current display mode before changing it, and RestoreWindowedMode() could just restore them.


The bool return value tells you if the request was successfull?

Yes, i think it is particularly relevant with ActivateFullscreenExclusiveMode() in case a lazy programmer provided an unsupported resolution.

edit : despite the lazy programmer might not even check the boolean ...

SoloByte commented 1 week ago

For me this call does something slightly different: the window doesn't end up covering the full desktop, but leaves the MATE menu bar at the top uncovered. I need to set the window position to (0,0) after the call to get full desktop coverage:

Does not work on macOS. If I set window position to (0,0) after the call the window moves further up into the corner of the monitor but it is now covered by the top bar.

Screenshot 2024-07-10 at 08 55 50

If I implement my own borderless fullscreen by setting maximize & undecorated flags and then setting the window postion to (0,0) it just moves to window in the corner below the top bar.

Screenshot 2024-07-10 at 08 57 39

It also means that currently you can not implement BorderlessFullscreen on your own and you rely on the ToggleBorderlessFullscreen() function.

SoloByte commented 1 week ago

Regarding RestoreWindowedMode(), i guess ActivateFullscreenExclusiveMode() would have to save current display mode before changing it, and RestoreWindowedMode() could just restore them.

I think this point is very important because currently exiting fullscreen does not properly restore window mode. bool ActivateFullscreenDesktopMode( int monitor ); should also do that.


Well, i'm not sure where you see a problem, because in my mind, when FLAG_VSYNC_HINT is enabled, it is up to GLFW and to the GPU to handle that.

Yes, I see it the same way. I am asking because currently (at least for me) vsync and fullscreen mode do not work well together. (choppy framerate that is lower than it should be)

The refresh rate parameter:

I am just trying to be overly clear so we don´t end up talking about different things were one side asumes something that the other side does not :)


edit : despite the lazy programmer might not even check the boolean ...

But it is still important because the current implementation does not tell you anything 😉

SuperUserNameMan commented 1 week ago

I think this point is very important because currently exiting fullscreen does not properly restore window mode. bool ActivateFullscreenDesktopMode( int monitor ); should also do that.

Maybe that would depend on how this fullscreen desktop mode would be implemented.

Obviously, we could not replicate what is currently done in ToggleBorderlessWidowed() since it leads to many unexpected issues regarding the menu-bar on Linux and Macos.

So, instead of trying to maximize an oversized and undecorated window, we could ask GLFW to do an hardware fullscreen of the same resolution and refresh rate that is currently active on the monitor. This way, there would be no change at the hardware level, and this would be equivalent to what ToggleBorderlessWindowed() tries to do.

I quickly tested that approach yesterday. But I only tested with the main monitor, so i don't know if the desktop remain visible on secondary monitors, or if it is turned off (like back in the old time ways).

Also, the problem with this approach is that it seems to be incompatible with FLAG_WINDOW_HIGHDPI. (i say "seems" because i haven't fully read the GLFW documentation and might have missed details that could solve this issue)


sets the frame rate with void SetTargetFPS(int fps); regardless of the vsync flag within raylib

This one function is to set the frequency of the main loop by software (if i understood correctly).

The programmer might want to set it to very low frequency for various reasons, like debugging purpose, or special effects, or to change the frequency of the program.

if vsync is disable the specified refresh rate becomes the target fps

Not safe, since SetTargetFPS() could be set at arbitrary values as mentioned above.

if vsync is enabled gpu / GLFW handle refresh rate

In that case, the programmer might want to SetTargetFPS( 0 ); so the freq of the main loop does not interfere with the effect of vsync.

SuperUserNameMan commented 1 week ago

Note : there is also this compilation option to take into account : SUPPORT_CUSTOM_FRAME_CONTROL

Note 2 : maybe we should also take other platform backends into consideration regarding their ability to implement what GLFW could.

SoloByte commented 1 week ago

So, instead of trying to maximize an oversized and undecorated window, we could ask GLFW to do an hardware fullscreen of the same resolution and refresh rate that is currently active on the monitor. This way, there would be no change at the hardware level, and this would be equivalent to what ToggleBorderlessWindowed() tries to do.

This is actually exactly what the glfw docs say about how to achieve borderless fullscreen.

Screenshot 2024-07-10 at 09 53 20

So we can simply use this for borderless fullscreen

Screenshot 2024-07-10 at 09 56 23

and the same function where the user can specify position, size, refresh rate for "normal" fullscreen.

SoloByte commented 1 week ago

I have tested this with glfw directly and implemented a borderless fullscreen with glfwSetWindowMonitor and it works perfect. It was just quick & dirty and only 1 monitor and I did not check anything else but the borderless fullscreen window covered the entire screen instantly and it restored the window correctly when exiting.

Beware it is c# again :)

if (currentWindowMode == WindowMode.Windowed)
{

//save current window pos & size
    glfw.GetWindowPos(window, out screenPositioX, out screenPositionY);
    glfw.GetWindowSize(window, out screenWidth, out screenHeight);

//get current monitor resolution
    var monitor = glfw.GetPrimaryMonitor();
    var videoMode = glfw.GetVideoMode(monitor);
    glfw.SetWindowMonitor(window, glfw.GetPrimaryMonitor(), 0, 0, videoMode->Width, videoMode->Height, videoMode->RefreshRate);

    currentWindowMode = WindowMode.BorderlessFullscreen;
}
else if (currentWindowMode == WindowMode.BorderlessFullscreen)
{
    glfw.SetWindowMonitor(window, null, screenPositioX, screenPositionY, screenWidth, screenHeight, 60);
    currentWindowMode = WindowMode.Windowed;
}

Edit

with this change

int mX, mY;
glfw.GetMonitorPos(monitor, out mX, out mY);
var videoMode = GetMonitorResolution(glfw, monitor);
glfw.SetWindowMonitor(window, monitor, mX, mY, videoMode->Width, videoMode->Height, videoMode->RefreshRate);

it also work perfect on multiple monitors with different resolutions. It restores the window to the monitor it was on before fullscreen was entered.

SuperUserNameMan commented 1 week ago

You have no mercy for my delicate eyes ..... EDIT NOPE i retract, it has some unexpect issues.

SuperUserNameMan commented 1 week ago

Ok, back again (false alarm, i pressed the wrong key in my test example)

I confirm it works like a charm on my Linux system with dual screen.

Here is the new candidate :

// Toggle borderless windowed mode
void ToggleBorderlessWindowed(void)
{
    // Leave fullscreen before attempting to set borderless windowed mode and get screen position from it
    bool wasOnFullscreen = false;
    if (CORE.Window.fullscreen)
    {
        CORE.Window.previousPosition = CORE.Window.position;
        ToggleFullscreen();
        wasOnFullscreen = true;
    }

    const int monitor = GetCurrentMonitor();
    int monitorCount;
    GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);

    if ((monitor >= 0) && (monitor < monitorCount))
    {
        const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);

        if (mode)
        {
            if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE))
            {
                // Store screen position and size
                // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here
                if (!wasOnFullscreen) glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
                CORE.Window.previousScreen = CORE.Window.screen;

                // Get monitor position and size

                glfwSetWindowMonitor(platform.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate);

                // Let's not wait for GLFW to call WindowSizeCallback to update these values :
                CORE.Window.screen.width = mode->width ;
                CORE.Window.screen.height = mode->height ;

                // Refocus window
                glfwFocusWindow(platform.handle);

                CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE;
            }
            else
            {
                // Return previous screen size and position
                int prevPosX = CORE.Window.previousPosition.x ;
                int prevPosY = CORE.Window.previousPosition.y ;
                int prevWidth = CORE.Window.previousScreen.width ;
                int prevHeight = CORE.Window.previousScreen.height ;
                glfwSetWindowMonitor(platform.handle, NULL, prevPosX , prevPosY, prevWidth, prevHeight, GLFW_DONT_CARE);

                // Let's not wait for GLFW to call WindowSizeCallback to update these values :
                CORE.Window.screen.width = prevWidth ;
                CORE.Window.screen.height = prevHeight ;

                // Refocus window
                glfwFocusWindow(platform.handle);

                CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE;
            }
        }
        else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
    }
    else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
}

edit: code updated according to the documentation : https://www.glfw.org/docs/latest/group__window.html#ga81c76c418af80a1cce7055bccb0ae0a7

edit2 : update CORE.Window.screen.width/height without further delay so these values are available just after ToggleBorderlessWindowed() call

SuperUserNameMan commented 1 week ago

work like a charm, only if FLAG_WINDOW_HIGHDPI is disabled indeed.

SoloByte commented 1 week ago

@SuperUserNameMan

We could now fix the ToggleFullscreen() function to actually store the prev window position/ size as well and restore the window on exiting fullscreen like you did. ToggleFullscreen could be exact same function as borderless fullscreen but it uses the current screen size instead of videoMode->width/height.

If fullscreen stores prev screen size as well you would could change this here as well.

// Store screen position and size
// NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here
if (!wasOnFullscreen) 
{
glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
CORE.Window.previousScreen = CORE.Window.screen;
}

New functions for proper fullscreen

I just hacked them together real quick ^^

void RestoreWindowWindowed()
{
    if(CORE.Window.fullscreen || CORE.Window.borderlessFullscreen)
    {
        // Return previous screen size and position
        int prevPosX = CORE.Window.previousPosition.x ;
        int prevPosY = CORE.Window.previousPosition.y ;
        int prevWidth = CORE.Window.previousScreen.width ;
        int prevHeight = CORE.Window.previousScreen.height ;
        glfwSetWindowMonitor(platform.handle, NULL, prevPosX , prevPosY, prevWidth, prevHeight, GLFW_DONT_CARE);

        // Let's not wait for GLFW to call WindowSizeCallback to update these values :
        CORE.Window.screen.width = prevWidth ;
        CORE.Window.screen.height = prevHeight ;

        // Refocus window
        glfwFocusWindow(platform.handle);

        CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE;
    }
    else TRACELOG(LOG_WARNING, "GLFW: Fullscreen or Borderless Fullscreen are not active.");

}
void SetWindowFullscreen(int monitor, int x, int y, int w, int h, int refreshRate)
{
    if(CORE.Window.fullscreen) 
    {
        TRACELOG(LOG_WARNING, "GLFW: Fullscreen already enabled");
        return;
    }

    // Leave borderless fullscreen before attempting to set fullscreen mode and get screen position from it
    bool wasOnBorderlessFullscreen = false;
    if (CORE.Window.borderlessFullscreen)
    {
        CORE.Window.previousPosition = CORE.Window.position; // is this necessary? because previous position should be correct from enabled borderless fullscreen
        ToggleBorderlessWindowed(); //Could also use -> RestoreWindowedWindow()
        wasOnBorderlessFullscreen = true;
    }

    int monitorCount;
    GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);

    if ((monitor >= 0) && (monitor < monitorCount))
    {
        // Store screen position and size
        // NOTE: If it was on borderless fullscreen, screen position & size was already stored, so skip setting it here
        if (!wasOnBorderlessFullscreen){
            glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
            CORE.Window.previousScreen = CORE.Window.screen;
        } 

        glfwSetWindowMonitor(platform.handle, monitors[monitor], x, y, w, h, refreshRate);

        // Let's not wait for GLFW to call WindowSizeCallback to update these values :
        CORE.Window.screen.width = w ;
        CORE.Window.screen.height = h ;

        // Refocus window
        glfwFocusWindow(platform.handle);

        CORE.Window.flags |= FLAG_FULLSCREEN_MODE;

    }
    else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
}
SoloByte commented 1 week ago
    bool wasOnFullscreen = false;
    if (CORE.Window.fullscreen)
    {
        CORE.Window.previousPosition = CORE.Window.position;
        ToggleFullscreen();
        wasOnFullscreen = true;
    }
CORE.Window.previousPosition = CORE.Window.position;

This line should also no longer be necessary when toggle fullscreen properly saves previous position/size.


// Store screen position and size
// NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here
if (!wasOnFullscreen) 
{
    glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
    CORE.Window.previousScreen = CORE.Window.screen;
}

This can be changed as well when ToggleFullscreen() properly saves previous position/size

SuperUserNameMan commented 1 week ago

Ok, let's talk about fixing ToggleFullscreen() into your other related issue #4145 so we keep this one about improving Raylib API.

SoloByte commented 1 week ago

I have added this to the related issues.

raysan5 commented 1 week ago

Please, excuse my lack of response on these issues, it's quite overwhelming and I need some time to read through them.

In any case, many thanks to all of you for reviewing windowing system and fullscreen.

SuperUserNameMan commented 1 week ago

@raysan5 : note that some of my statements here might be outdated by the pending and WIP PR #4151