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_glfw] Reviewed ToggleFullScreen(), ToggleBorderlessWindowed() and FLAG_WINDOW_HIGHDPI issues #4151

Conversation

SuperUserNameMan
Copy link
Contributor

@SuperUserNameMan SuperUserNameMan commented Jul 10, 2024

dont merge it yet

You can test this PR again, code updated with new pipeline.


  1. should fix most issues related to ToggleFullScreen(), ToggleBorderlessWindowed() and FLAG_WINDOW_HIGHDPI

  2. use a custom implementation of SetupFramebuffer() without affecting other platform backends and without changing rcore.c (edit: well, i had to add some changes to rcore.c but they should not impact other platforms)

  3. adds a new experimental FLAG_RESCALE_CONTENT that keeps the size of what you requested in InitWindow() and rescale and center it with borders. (so if you requested a 500x500 drawing surface, you'll allways have 500x500 drawing surface rescaled with the same aspect ratio that always fits inside the window or fullscreen window). see screen capture here


Need confirmations and testing on :

  • MacOs <-- (i don't have access to this system)
  • Windows + WLS ? (i won't bother install WLS on my Windows machines, so if one of you use that, you'll be welcome to test)
  • GNOME 3
  • Non-ubuntu Linux distro with Wayland based desktop
  • Ubuntu + Wayland <--- functional
  • Ubuntu + Wayland + Xwayland <--- functional, but less reliable than true X11 or full Wayland
  • Linux + X11 (Cinnamon desktop)
  • Linux + X11 (MATE desktop)
  • Windows 11 - using Msys2 + UCRT64
  • Windows 10 - using Msys2 + UCRT64

I posted a minimal test source code in a message below. Press B for borderless fullscreen, and F for hardware fullscreen.

⚠️ If you have VirtualBox running at the same time on your computer, it is recommend to close it when testing. (i found that it could interfere with fullscreen mode on my computer. I don't know what cause that.)


Here is a list of the concerned issues :


thanks to @SoloByte and to @paulmelis for their help and efforts to solve this issue
(but i'd need you to test again the new update of this PR)

should fix issue raysan5#4149

Linux MATE : tested
Macos : tested

Windows : need confirmation

---

thanks to @SoloByte and to @paulmelis for their help and efforts to solve this issue
Work perfect on Linux wirth X11
Needs confirmation on Linux Wayland, MacOS, and Windows.
@SuperUserNameMan SuperUserNameMan changed the title should fix ToggleBorderlessWindowed() on Linux and Macos should fix all ToggleFullScreen(), ToggleBorderlessWindowed() and FLAG_WINDOW_HIGHDPI issues Jul 10, 2024
@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

Here are some notes (and TODO) before I forget :


Ubuntu + Wayland + GLFW_LINUX_ENABLE_X11=FALSE :

  • in Wayland mode, the console is spammed by WARNING : GLFW: Error: 65548 Description: Wayland: The platform does not provide the window position if the application loop make use of a Raylib function that internally calls glfwGetWindowPos(). I don't know if it should be fixed, or left that way so the programmer is really aware that GLFW's Wayland support is incomplete.
  • GetCurrentMonitor() won't work because Wayland does not allow to know the position of a window ...
  • in Wayland mode, every glfwSetWindowPos() and glfwGetWindowPos() generate an error because "The platform does not support setting the window position". This error should be cleared by glGetError(NULL) to avoid future misinterpretation of glGetError() calls.
  • binary compiled with GLFW_LINUX_ENABLE_X11=FALSE fails to init window because or GLFW erros : 65548 (wayland does not provide the window position or does not allow setting the position)
  • test on real computer

Ubuntu + Wayland + GLFW_LINUX_ENABLE_X11=TRUE :

  • despite the effects of ToggleFullscreen() is the same as ToggleBorderlessWindowed(), the GetMonitorWidth/Height() return a 640x480 resolution instead. I don't know if this is because of Ubuntu, because of my old laptop, or because something wrong in the code ... :-/ maybe i should just simplify the problem by calling ToggleBorderlessWindowed() from ToggleFullscreen() on Wayland ?
  • in X11 compilation mode, toggling ToggleBorderlessWindowed() many times fails to restore the position (the restored window move down each time), or shrink it each time if resizeable.
  • check again compiling with GLFW_LINUX_ENABLE_WAYLAND=FALSE and elucidate why it is enabled by default since GLFW's Wayland is still experimental, and since applications compiled for X11 work better on Ubuntu+Wayland than when compiled with wayland support. THat does not make sense.
  • need clarifications regarding X11 / Wayland compatibility
  • try to run a binary compiled on X11 computer for X11, and see if it works on Ubuntu edit: it just work out of the box.
  • test on real computer

MacOS TODO :

  • review changes made by @SoloByte and merge them once Wayland and MS Windows issues are fixed (just in case it change the pipeline)
  • on MacOS, if user requests 1080p on a 4K display, MacOS will upscale the 1080p over the 4K resolution : GetMonitorW/H() will return the 1080p reso, while GetWindowScaleDPI() will return { 2.0 , 2.0 }. From the hardware POV it is true and correct since MacOS upscale the 1080p over 4K with a DPI scale of x2, but from programmer's POV it is misleading because it could be interpreted as DPI scale x2 inside a 1080p resolution.
  • find a version of Xcode that still works under MacOS 10 so i can test it in VirtualBox
  • keep in mind the implementation of SetupViewport() might interfere in __APPLE__ case

LM Cinnamon (X11) TODO :

  • previous display resolution not restored from hardware fullscreeen on my 4K TV (which also has a firmware bug with low resolution) : edit some similar issues mentioned on the web with Unity and Cinnamon. edit2: tried Cinnamon in software rendering mode just to compare, and same problem.
  • WONTFIX : GetWindowScaleDPI() returns { 1.0 , 1.0 } when display is 75% and 100%, and returns { 2.0 , 2.0 } when display is 125%, 150%, 175% and 200%. Must comes from GLFW or Cinnamon.

Windows 11 TODO :


"TODO" list

  • once code stabilized, test if possible to go from strait from a full screen mode to another.
  • check EnableCursor() and DisableCursor() with FLAG_RESCALE_CONTENT
  • check BeginScissorMode()
  • once stabilized, it might be relevant to create an example code to show and explain how and why replicate the effect of GLFW_AUTO_ICONIFY code-side in different setup (single monitor / multi monitor) etc etc
  • update InitWindow() so it checks the value returned by InitPlatform() ==> [rcore] InitWindow() should check InitPlatform() returned value #4164
  • make bi-linear interpolation optional when FLAG_WINDOW_HIGHDPI is on ?
  • check GetMousePosition() and gesture stuff
  • don't forget to allow to toggle to a resolution larger than the desktop size even if the size of the window is limited.
  • check possible interferences with rendering to texture ? (need clarification on this CORE.window.somethingFBO)
  • given that InitWindow(0,0); was interpreted as an implicit FLAG_BORDERLESS_WINDOWED request, we should discuss and formalize the expected behavior of Initwindow( 800,0) and InitWindow(0,800) as it could be interpreted as the desire to force the width or height of the restored window or to set a window with the yet unknown width or height of desktop area ....
  • review CursorEnterCallback() so that it works correctly with FLAG_RESCALE_CONTENT
  • i have no idea if Wayland also require the SetMouseScale() on line 1540.
  • force GetWindowScaleDPI() to return { 1.0 , 1.0 } in hardware fullscreen mode so that it is consistent on all OS / Desktop ? (FLAG_WINDOW_HIGHDPI has no effect in hardware fullscreen mode anyway)
  • depending on the OS, in HDPI mode, the value of CORE.Input.Mouse.scale and CORE.Input.Mouse.offset might be preset by the platform. The consequence is that the behavior of SetMouseScale() and SetMouseOffset() won't be consistent. Maybe we should add CORE.Input.Mouse.userScale and CORE.Input.Mouse.userOffset into the pipeline.
  • check possible interferences with stereo-view / VR rendering example ?
  • SetWindowMonitor() does not work in windowed fullscreen mode ?
  • better monitor resolution choice into IniWindow() and ToggleFullscreen()
  • desktop orignal display resolution not restored when toggling back from lower fullscreen resolution on Cinamon desktop (Because i've done something stupid that i'm going to fix now I was wrongfully accused by myself, in fact, i was innocent)
  • review the issue between FLAG_FULLSCREEN_MODE and FLAG_WINDOW_HIGHDPI
  • review the "windowed fullscreen mode" triggered by InitWindow(0,0)
  • study the role of CORE.Window.renderOffset
  • study the role of SetupFramebuffer() --> WTH is wrong with that func ? it does not use its own args !
  • SoloByte said : We should also test what happens when focus is lost while being in fullscreen/borderless fullscreen mode in general (like tabbing out to another window) and what happens when focus is gained again.
  • understand the purpose of if (requestWindowedFullscreen) glfwWindowHint(GLFW_AUTO_ICONIFY, 0); in InitPlatform()
  • raylib source code mention render content with black borders (need study)
  • mouse is not rescaled on Windows 10 hardware fullscreen mode (single monitor) and DPI 1.25 ? ==> So, in Windows, we have to unscale the mouse when toggling to hardware fullscreen mode, and scale the mouse when in windowed mode. On X11, the default is OK.
  • SoloByte said : Another problem here might be if high dpi flag is disabled (...) If GLFW_SCALE_FRAMEBUFFER defaults to false this should not be a problem but if it defaults to true it is a problem for MacOS and Wayland...
  • test when SetWindowState(FLAG_FULLSCREEN_MODE) calls ToggleFullscreenMode()
  • test when SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE) calls ToggleBorderlessWindowed()
  • add a focus on hardware fullscreen window ?
  • see if the call to WindowSizeCallback() inside ToggleFullscreen() and ToggleBorderlessWindow() is related to the glitch observed on the sluggish N4000 laptop when toggling exaggeratedly fast between hardware-fullscreen and windowed mode when DPI scale is set to 125% on the small 1024x768 display (might be a too long delay between the refresh and reorganization of the Window's taskbar and the updates of the GLFW+Raylib window) seems to have fixed the glitch
  • verify if there is a warning or error message when testing on the N4000 laptop under Linux with DPI scale set to 200% and FLAG_WHATEVER_HDPI is enabled (might be related to mesa and weak intel igp ?)
  • if FLAG_WINDOW_HIGHDPI is enabled, according to GLFW documentation, Wayland needs glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); which is an alias of GLFW_COCOA_RETINA_FRAMEBUFFER
  • Test and fix MaximizeWindow() and MinimizeWindow()
  • SoloByte said : This means that CORE.Window.screen.width & CORE.Window.screen.height will not be correct until the window resize callback will be called.
  • might want to update update the values of CORE.window.screen.width/height directly into ToggleFullscreen() like i did in ToggleBorderlessWindowed() to solve [rcore_desktop] Upon entering fullscreen, uses original window size #3929

"Memo" list

  • in multi-monitor setups, if each display has a different DPI scaling, Windows 11 will resize any window (including Calc.exe) that is moved from a monitor to another despite FLAG_WINDOW_HIGHDPI is off, and despite GLFW is instructed not to apply this bahavior.
  • wherever there is a __APPLE__ special case, it might also apply to Wayland using a if ( glfwGetPlatform() == GLFW_PLATFORM_WAYLAND), so while debugging Wayland support, it is relevant to look for __APPLE__ too just in case.
  • ⚠️ : Wayland special cases should be handled at runtime instead of through C preprocessor, because the binary executable is compiled for Linux, not for Wayland or X11 : if ( glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) instead of #ifdef ⚠️ maybe i was wrong, since the wiki that i did not fully read mention a compile time option required for Wayland
  • better activates at least a tiny DPI scale in the desktop env when developing/testing, so it is possible to actually see the difference when FLAG_WINDOW_HIGHDPI is ON/OFF and not get confused

image

  • full screen mode and windowed full screen mode can be toggled multiple manners :
hardware fullscreen mode windowed fullscreen mode
ToggleFullscreen() ToggleBorderlessWindowed()
SetWindowState(FLAG_FULLSCREEN_MODE) SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE)
ClearWindowState(FLAG_FULLSCREEN_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE)
InitWindow( 0 , 0 )
SetConfigFlags(FLAG_FULLSCREEN_MODE); InitWindow( w ,h ); SetConfigFlags(FLAG_BORDERLESS_WINDOWED_MODE); InitWindow( w ,h );

"Can't reproduce. Fixed ?" list

  • Paulmelis said : When the FLAG_WINDOW_HIGHDPI flag is not set the MATE menu and window bar at top and bottom respectively stay visible sometimes. And in other cases the raylib window fails to gain focus after switching to fullscreen with F.

"Can't fix ? / Known issues" list

  • on Wayland : GLFW 3.4 does not yet support setting the position of the mouse cursor, so SetMousePosition() won't work as expected.
  • on Ubuntu + Wayland : as it not possible to know the position of a window, GetCurrentMonitor() will always return the last monitor that was associated to the window using SetWindowMonitor(). If the user moves the window manually from one monitor to an other, this value won't be updated automatically.
  • on Ubuntu : there seems to be no real "hardware fullscreen mode". ToggleFullscreen() seems to have the exact same effect than ToggleBorderlessWindowed(). Duno if it specific to my graphics drivers, or if it this way for all computers running Ubuntu, or if it is related to the fact that the frame rate is left to GLFW_DONT_CARE.
  • on Linux Mint + Cinnamon (X11) : GetWindowScaleDPI() returns { 1.0 , 1.0 } when display is 75% and 100%, and returns { 2.0 , 2.0 } when display is 125%, 150%, 175% and 200%. Problem must comes from GLFW or Cinnamon.
  • on Linux Mint + Cinnamon (X11) : the display resolution is not restored from hardware fullscreen mode.
  • each version of MS Windows might have a very slightly inconsistent behavior regarding FLAG_WINDOW_HIGHDPI and FLAG_WINDOW_RESIZEABLE. This is because GLFW does not init the window with the same algorithm of DPI awareness : https://github.com/raysan5/raylib/blob/996f50393ec51fa33b361546986afc9762849a76/src/external/glfw/src/win32_init.c#L692C1-L697C30
  • on MS Windows 11 : on multi monitor setup, if each monitor has a different DPI scale, and if the Raylib window is resizable, moving the window from one monitor to an other multiple times might shrink it a little. Same might happen if toggling from fullscreen mode many times.
  • ToggleFullscreen() : on sluggish computers, when toggling from a low resolution fullscreen mode (like 640x480) back to windowed mode, the previous position of the window might not be restored correctly because GLFW might try to restore the window size and position before the OS had time to fully restore the previous monitor resolution and the desktop UI (menu-bar). The consequence is that the size of the desktop might appear too small to GLFW to restore the window.
  • ToggleFullscreen() : on my Linux X11, if VirtualBox is running, the Raylib's fullscreen window fails to keep the focus and is minimized.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

Test source code :

#include <stdio.h>
#include "raylib.h"
#include "raymath.h"

void update();
void draw();


int main( int argc , char **argv )
{
//	SetWindowState( FLAG_MSAA_4X_HINT );
	
	SetConfigFlags(FLAG_WINDOW_HIGHDPI); // <======= ???

//	SetConfigFlags(FLAG_FULLSCREEN_MODE);
//	SetConfigFlags(FLAG_BORDERLESS_WINDOWED_MODE);

	
	SetConfigFlags(FLAG_RESCALE_CONTENT); //!\ HIGHLIY EXPERIMENTAL FLAG disable it for backward compatible mode
//	SetConfigFlags(FLAG_RESCALE_CONTENT_LINEAR); //!\ HIGHLIY EXPERIMENTAL FLAG disable it for backward compatible mode
//	SetConfigFlags(FLAG_TEXT_LINEAR_FILTER);

	SetConfigFlags( FLAG_WINDOW_RESIZABLE );

	InitWindow( 700 , 500 , "Test" );
//	InitWindow( 500 , 0 , "Test" ); // <== full screen windowed mode
//	InitWindow( 0 , 500 , "Test" ); // <== full screen windowed mode
//	InitWindow( 0 , 0 , "Test" ); // <== full screen windowed mode

	if ( ! IsWindowReady() ) 
	{
		// Platform initialization probably failed
		return -1;
	}

//	ClearWindowState( FLAG_VSYNC_HINT );

//	SetMouseOffset( 10, 10);
//	SetMouseScale( 0.5 , 0.5 );

	SetTargetFPS( 60 );

	while( ! WindowShouldClose() )
	{
		update();

		BeginDrawing();
		{
			ClearBackground( GRAY );
			draw();
		}
		EndDrawing();
	}

	CloseWindow();
}

void update()
{
	if ( IsKeyPressed( KEY_ZERO ) )
	{
		SetWindowMonitor(0);
	}
	else
	if ( IsKeyPressed( KEY_ONE ) )
	{
		SetWindowMonitor(1);
	}
	else
	if ( IsKeyPressed( KEY_F ) )
	{
//		SetWindowSize( 1280 , 720 );
		ToggleFullscreen();
		printf("Toggled, screen now %d x %d\n", GetScreenWidth(), GetScreenHeight());
		printf("Toggled, render now %d x %d\n", GetRenderWidth(), GetRenderHeight());
	}
	else
	if ( IsKeyPressed( KEY_G ) )
	{
		SetWindowState(FLAG_FULLSCREEN_MODE);
	}
	else 
	if ( IsKeyPressed( KEY_B ) )
	{
		ToggleBorderlessWindowed();
		printf("Toggled, screen now %d x %d\n", GetScreenWidth(), GetScreenHeight());
		printf("Toggled, render now %d x %d\n", GetRenderWidth(), GetRenderHeight());
	}
	else
	if ( IsKeyPressed( KEY_N ) )
	{
		SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
	}
	else
	if ( IsKeyPressed( KEY_C ) )
	{
		ClearWindowState(FLAG_FULLSCREEN_MODE);
		ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
	}
	else
	if ( IsKeyPressed( KEY_X ) )
	{
		MaximizeWindow();
	}
	else
	if ( IsKeyPressed( KEY_V ) )//|| !IsWindowFocused() )
	{
		MinimizeWindow();
	}
	else
	if ( IsKeyPressed( KEY_R ) )
	{
		RestoreWindow();
	}
	else
	if ( IsKeyPressed( KEY_S ) )
	{
		if ( IsWindowState(FLAG_RESCALE_CONTENT) )
		{
			ClearWindowState(FLAG_RESCALE_CONTENT);
		}
		else
		{
			SetWindowState(FLAG_RESCALE_CONTENT);
		}
	}
	else
	if ( IsKeyPressed( KEY_T ) )
	{
		if ( IsWindowState(FLAG_TEXT_LINEAR_FILTER) )
		{
			ClearWindowState(FLAG_TEXT_LINEAR_FILTER);
		}
		else
		{
			SetWindowState(FLAG_TEXT_LINEAR_FILTER);
		}
	}
	else
	if ( IsKeyPressed( KEY_KP_5 ) ) { SetMousePosition( GetScreenWidth()/2 , GetScreenHeight()/2 ); }
	else
	if ( IsKeyPressed( KEY_KP_7 ) ) { SetMousePosition( 0 , 0 ); }
	else
	if ( IsKeyPressed( KEY_KP_9 ) ) { SetMousePosition( GetScreenWidth() , 0 ); }
	else
	if ( IsKeyPressed( KEY_KP_3 ) ) { SetMousePosition( GetScreenWidth() , GetScreenHeight() ); }
	else
	if ( IsKeyPressed( KEY_KP_1 ) ) { SetMousePosition( 0 , GetScreenHeight() ); }
}

void draw()
{
	Vector2 mouse = GetMousePosition();

	int sw = GetScreenWidth();
	int sh = GetScreenHeight();

	int rw = GetRenderWidth();
	int rh = GetRenderHeight();

	Vector2 dpi = GetWindowScaleDPI();

	int monitor = GetCurrentMonitor();

	int mw = GetMonitorWidth( monitor );
	int mh = GetMonitorHeight( monitor );

	Rectangle screenRect = { 0.0 , 0.0 , sw , sh };
	Rectangle renderRect = { 0.0 , 0.0 , rw , rh };

	// Draw the border of the screen :
	DrawRectangleRec( screenRect , RAYWHITE );
	DrawRectangleLinesEx( screenRect , 4.0f , RED );
	DrawRectangleLinesEx( renderRect , 4.0f , GREEN );

	// Draw the text NOT in the center :

	int font_size = 20 ;
	int y = 0 ;

	DrawText( TextFormat( "Mouse  : %.2f x %.2f / %d x %d" , mouse.x , mouse.y , GetMouseX(), GetMouseY()  ) , 10 , y+=font_size , font_size , BLACK );
	DrawText( TextFormat( "Screen : %d x %d" , sw , sh ) , 10 , y+=font_size , font_size , BROWN );
	DrawText( TextFormat( "Render : %d x %d pixels" , rw , rh ) , 10 , y+=font_size , font_size , DARKGREEN );
	DrawText( TextFormat( "Monitor[%d] : %d x %d pixels" , monitor , mw , mh ) , 10 , y+=font_size , font_size , DARKBLUE );
	DrawText( TextFormat( "DPI : %f x %f" , dpi.x , dpi.y ) , 10 , y+=font_size , font_size , BLACK );
	y+=font_size;
	DrawText( TextFormat( "screenRect : %f x %f" , screenRect.width , screenRect.height ) , 10 , y+=font_size , font_size , RED );
	DrawText( TextFormat( "renderRect : %f x %f" , renderRect.width , renderRect.height ) , 10 , y+=font_size , font_size , GREEN );
	y+=font_size;
	DrawText( IsWindowFullscreen() ? "FULLSCREEN MODE" : "" , 10 , y , font_size , BLUE );
	DrawText( IsWindowState( FLAG_BORDERLESS_WINDOWED_MODE ) ? "BORDERLESS WINDOWED MODE" : "" , 10 , y , font_size , PINK );
	DrawText( !IsWindowFullscreen() && !IsWindowState( FLAG_BORDERLESS_WINDOWED_MODE ) ? "Windowed mode" : "" , 10 , y , font_size , YELLOW );
	y+=font_size;
	DrawText( IsWindowFocused() ? "HAS FOCUS" : "does not have focus" , 10 , y+=font_size , font_size , ORANGE );
	DrawText( IsWindowState(FLAG_RESCALE_CONTENT) ? "RESCALE CONTENT" : "does not rescale content" , 10 , y+=font_size , font_size , DARKPURPLE );
	DrawText( IsCursorOnScreen() ? "CURSOR on SCREEN" : "cursor NOT on screen" , 10 , y+=font_size , font_size , DARKGREEN );
	y+=font_size;
	DrawText( "[F] ToggleFullscreen()" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[G] SetWindowState(FLAG_FULLSCREEN_MODE);" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[B] ToggleBorderlessWindowed()" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[N] SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[C] ClearWindowState(FLAG_xxx_MODE);" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[X] MaximizeWindow();" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[V] MinimizeWindow();" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[R] RestoreWindow();" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[T] toggle `FLAG_TEXT_LINEAR_FILTER`" , 10 , y+=font_size , font_size , GRAY );

	// Draw a software mouse cursor for debug purpose :

	DrawCircleSector( mouse , 30, 45, 90, 1, WHITE);
	DrawCircleSectorLines( mouse , 30, 45, 90, 1, BLACK);

	// Draw a target :

	DrawLine( GetMouseX() , 0 , GetMouseX() , sh , BLUE );
	DrawLine( 0 , GetMouseY() , sw , GetMouseY() , BLUE );

	// Center of screen :

	DrawLine( sw/2-10 , sh/2, sw/2+10, sh/2, RED );
	DrawLine( sw/2 , sh/2-10, sw/2, sh/2+10, RED );
}

@SoloByte
Copy link
Contributor

@SuperUserNameMan thanks for all the work to fix all of this :)

When exiting fullscreen with ToggleFullscreen() function the position of the window is not set to previos position (like in borderless fullscreen) but instead it is set to CORE.Window.position.x, CORE.Window.position.y.
I don´t know if this matters.

@SoloByte
Copy link
Contributor

#3929 maybe related as well?

@paulmelis
Copy link
Contributor

Just tested your testing code above. The ToggleBorderlessWindowed() works fine, but the ToggleFullscreen() gives me a 640x480 (or sometimes 800x600) render, is that to be expected? My display is 1920x1080 (so also no high DPI), even though I tested with the flag set.

When the FLAG_WINDOW_HIGHDPI flag is not set the MATE menu and window bar at top and bottom respectively stay visible sometimes. And in other cases the raylib window fails to gain focus after switching to fullscreen with F.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 11, 2024

Just tested your testing code above. The ToggleBorderlessWindowed() works fine, but the ToggleFullscreen() gives me a 640x480 (or sometimes 800x600) render, is that to be expected? My display is 1920x1080 (so also no high DPI), even though I tested with the flag set.

Yes, because ToggleFullscreen() asks an "hardware fullscreen".

The size of the render is set to the hardware resolution that is arbitrarily picked by GLFW to fit your current window's content dimensions.
GLFW just tries to choose the "closest" hardware resolution supported by the monitor and the GPU.
On your system, you get 640x480 or 800x600, but on some other computers, you might get something totally different.

For now, the only way to somehow influence the choice made by GLFW, is to resize your window to an hardware monitor resolution of your choice before calling ToggleFulscreen()

		SetWindowSize( 1280 , 720 );
		ToggleFullscreen();

1280x720 is an hardware resolution that has great chances to be supported on most monitor and graphics cards, so you'd have great chances that GLFW gives you this fullscreen resolution.

When the FLAG_WINDOW_HIGHDPI flag is not set the MATE menu and window bar at top and bottom respectively stay visible sometimes. And in other cases the raylib window fails to gain focus after switching to fullscreen with F.

Currently trying to find a computer with a Wayland desktop.
So I add that to my TODO list with issues mentioned by @SoloByte

@paulmelis
Copy link
Contributor

Currently trying to find a computer with a Wayland desktop.
So I add that to my TODO list with issues mentioned by @SoloByte

Note that I'm not running wayland, but plain X server

@SoloByte
Copy link
Contributor

Yes, because ToggleFullscreen() asks an "hardware fullscreen".

The size of the render is set to the hardware resolution that is arbitrarily picked by GLFW to fit your current window's content dimensions. GLFW just tries to choose the "closest" hardware resolution supported by the monitor and the GPU. On your system, you get 640x480 or 800x600, but on some other computers, you might get something totally different.

This shows another problem I think. Right now (even with your fixes) if you toggle the fullscreen with a screen size that is not supported by the monitor, glfw will pick the closest one and activate the fullscreen with the new size. This means that CORE.Window.screen.width & CORE.Window.screen.height will not be correct until the window resize callback will be called.

One solution to this could be to get all supported resolutions from the monitor and then:

  • Check if current screen size is in this list, if it is we can proceed as normal
  • If it is not find the closest resolution with the same aspect ratio
  • If we find one pick this as new screen size and activate fullscreen
  • if there is no resolution with the same aspect ratio pick the closest one and proceed

There is also a fundamental question about fullscreen mode.
Should the fullscreen mode factor in the system dpi or not?

The only thing I found the high dpi flag does on window initialization is this.

If I can clear the high dpi flag and make fullscreen use the non-scaled resolution then this question is irrelevant.

Explanation

If I make a game and the user selects fullscreen mode with 4k resolution on a 4k monitor than I might want the screen size & render size to be the 4k and not scaled by the dpi. (Because my UI system might take care of scaling already)

@SuperUserNameMan
Copy link
Contributor Author

If I make a game and the user selects fullscreen mode with 4k resolution on a 4k monitor than I might want the screen size & render size to be the 4k and not scaled by the dpi. (Because my UI system might take care of scaling already)

If i understood correctly, and if my previous tests were correct, the problem is that once we set this, we can't disable it unless we destroy and recreate the window (but we might lose the opengl context in the process if i understood correctly). That's why i was strongly claiming that the programmer should choose between FLAG_WHATEVER_HIGHDPI and ToggleFullscreen(), and should not use them together.

This shows another problem I think. Right now (even with your fixes) if you toggle the fullscreen with a screen size that is not supported by the monitor, glfw will pick the closest one and activate the fullscreen with the new size. This means that CORE.Window.screen.width & CORE.Window.screen.height will not be correct until the window resize callback will be called.

Yeah, that seems to cause some unexpected delays and lags with unexpected side-effects that i'm having some difficulties to grasp yet ...

There is also some unexpected behaviors on my LinuxMint + Cinamon desktop + 4K TV that i'm unable to fix or workaround currently ...

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 11, 2024

https://github.com/SuperUserNameMan/raylib/blob/7ddb880f020d31ea1732473a1bb4848a9c0566ee/src/platforms/rcore_desktop_glfw.c#L191

For unknown reasons, this does not restore the previous monitor/desktop resolution on my X11 Cinamon desktop.
The TV remains at the fullscreen resolution it was just before.

edit: works fine with X11 MATE desktop.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 11, 2024

There is also ancient code for "windowed fullscreen" that might require to be removed or fixed:
https://github.com/SuperUserNameMan/raylib/blob/7ddb880f020d31ea1732473a1bb4848a9c0566ee/src/platforms/rcore_desktop_glfw.c#L1455-L1456

edit: actually, that is only when FLAG_WINDOW_HIGHDPI is enabled that it is buggy.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 11, 2024

One solution to this could be to get all supported resolutions from the monitor and then:

InitPlatform() uses that approach for when FLAG_FULLSCREEN_MODE is set before InitWindow() :

https://github.com/SuperUserNameMan/raylib/blob/7ddb880f020d31ea1732473a1bb4848a9c0566ee/src/platforms/rcore_desktop_glfw.c#L1415C1-L1432C10

edit FLAG_FULLSCREEN_MODE needs to be fixed too, as the window is not fullscreenized once the video mode is changed on X11 MATE Linux.

@SuperUserNameMan
Copy link
Contributor Author

The more i'm digging into the code, the more i have the feeling that i'm going to do the overhaul i did not want to do .........

I need to do a map from InitWindow() up to ToggleFullscreen().

Trying to go straight forward from one fullscreen mode to another make things more complicated to control. 
Seems a full loop is required when trying to change fullscreen modes and window size properly for now.
Maybe will restore the ability to jump from one fullscreen mode to another when more cleaning will be done deeper in the rabbit hole.
Call `WindowSizeCallback()` manually so we don't have to wait for after the next `EndDrawing()` to have access to up to date `GetScreenWidth()` etc value.
@SuperUserNameMan
Copy link
Contributor Author

@SoloByte :

If I make a game and the user selects fullscreen mode with 4k resolution on a 4k monitor than I might want the screen size & render size to be the 4k and not scaled by the dpi. (Because my UI system might take care of scaling already)

In that case, if your UI system already take care of DPI scaling (probably using GetWindowScaleDPI(), you don't need to activate FLAG_WINDOW_HIGHDPI. If you can manage DPI rescaling in fullscreen mode, you can manage it in windowed mode too. Same code i think, no ?

@SoloByte
Copy link
Contributor

The more i'm digging into the code, the more i have the feeling that i'm going to do the overhaul i did not want to do

I hoped that your fix solved all the problems but in reality I was pretty sure an overhaul is needed because otherwise those problems would have already been fixed. Right now it just feels like a rabbit hole of endless despair we went down into and the only way to get out is to start from the beginning ;)


I need to do a map from InitWindow() up to ToggleFullscreen().

That is a really good idea! If I find the time I might also do something like this. Maybe for glfw as well how the window system is done in the docs.


Another problem here might be if high dpi flag is disabled:

  • glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); is called to set GLFW_SCALE_TO_MONITOR to false but
  • glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); is never called to do the same for GLFW_SCALE_FRAMEBUFFER

If GLFW_SCALE_FRAMEBUFFER defaults to false this should not be a problem but if it defaults to true it is a problem for MacOS and Wayland...


If you can manage DPI rescaling in fullscreen mode, you can manage it in windowed mode too. Same code i think, no ?

Yes absolutely.
Right now I am just not sure if it is actually possible to disable high dpi or the scaling of the frame buffer when the window is on a high dpi monitor. (at least not possible for macOS & Wayland)

I will have to test it and I will report back on this. (with probably more questions than answers ...)


I don´t have a lot of time over the weekend, so if don´t respond it is because of that 🙃

@SoloByte
Copy link
Contributor

InitPlatform() uses that approach for when FLAG_FULLSCREEN_MODE is set before InitWindow() :

https://github.com/SuperUserNameMan/raylib/blob/7ddb880f020d31ea1732473a1bb4848a9c0566ee/src/platforms/rcore_desktop_glfw.c#L1415C1-L1432C10

Yes I know but it is the simplified version of just finding the closest resolution. (does not take aspect ratio into account) Doing this before setting Fullscreen is probably always a good idea. (in a potentially new system as well as the current system)

… part 1

When in `FLAT_RESCALE_CONTENT` mode, the default "nearest pixel" filter of the texture font is ugly sometimes. So this change adds `FLAG_RESCALE_CONTENT_LINEAR` and `FLAG_TEXT_LINEAR_FILTER` to offer more control. `FLAG_TEXT_LINEAR_FILTER` can also be used to disable the linear filtering of `FLAG_WINDOW_HIGHDPI`.
When in `FLAT_RESCALE_CONTENT` mode, the default "nearest pixel" filter of the texture font is ugly sometimes. So this change adds `FLAG_RESCALE_CONTENT_LINEAR` and `FLAG_TEXT_LINEAR_FILTER` to offer more control. `FLAG_TEXT_LINEAR_FILTER` can also be used to disable the linear filtering of `FLAG_WINDOW_HIGHDPI`.
…ng - part 3

When in `FLAT_RESCALE_CONTENT` mode, the default "nearest pixel" filter of the texture font is ugly sometimes. So this change adds `FLAG_RESCALE_CONTENT_LINEAR` and `FLAG_TEXT_LINEAR_FILTER` to offer more control. `FLAG_TEXT_LINEAR_FILTER` can also be used to disable the linear filtering of `FLAG_WINDOW_HIGHDPI`.
@SuperUserNameMan
Copy link
Contributor Author

new FLAG_CONTENT_RESCALE_LINEAR and FLAG_TEXT_LINEAR_FILTER

When the FLAG_CONTENT_RESCALE mode is downscaled, the default "nearest pixel" filter make the text font ugly to read.
So it is possible to enable an optional "linear filter".

image

image

Note : ClearWindowState(FLAG_TEXT_LINEAR_FILTER) can also be used to disable the linear filter enabled by the FLAG_WINDOW_HIGHDPI mode.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 24, 2024

SetMousePosition(x,y) coords given in CORE.Window.screen coordinates

Now it applies CORE.Input.Mouse.platformOffset and CORE.Input.Mouse.platformScale so it also works correctly with FLAG_WINDOW_HIGHDPI and FLAG_RESCALE_CONTENT.

This means that the coords provided to this function are always in CORE.Window.screen coordinate space.
If you want to center the mouse : SetMousePosition( GetScreenWidth()/2 , GetScreenHeight()/2 );

I think the behavior is not 100% backward compatible because it now takes the scaling involved by FLAG_WINDOW_HIGHDPI into account. Also the cursor is not allowed to be set outside the "screen" surface.

⚠️ : this is the system's cursor that is moved by this function.

So the user's mouse scaling and offset set using SetMouseScale() and SetMouseOffset() are ignored by this function, as they only affect the values returned by GetMouseX/Y() and GetMousePosition().

EDIT : the system's cursor is not actually moved on Wayland, because GLFW 3.4 does not support it.


New test example code bellow : Keypad [5] to center the cursor, and KP [1], [3] ,[7] and [9] move it in a corner.

#include <stdio.h>
#include "raylib.h"
#include "raymath.h"

void update();
void draw();


int main( int argc , char **argv )
{
//	SetWindowState( FLAG_MSAA_4X_HINT );
	
	SetConfigFlags(FLAG_WINDOW_HIGHDPI); // <======= ???

//	SetConfigFlags(FLAG_FULLSCREEN_MODE);
//	SetConfigFlags(FLAG_BORDERLESS_WINDOWED_MODE);

	
	SetConfigFlags(FLAG_RESCALE_CONTENT); //!\ HIGHLIY EXPERIMENTAL FLAG disable it for backward compatible mode
//	SetConfigFlags(FLAG_RESCALE_CONTENT_LINEAR); //!\ HIGHLIY EXPERIMENTAL FLAG disable it for backward compatible mode
//	SetConfigFlags(FLAG_TEXT_LINEAR_FILTER);

	SetConfigFlags( FLAG_WINDOW_RESIZABLE );

	InitWindow( 700 , 500 , "Test" );
//	InitWindow( 500 , 0 , "Test" ); // <== full screen windowed mode
//	InitWindow( 0 , 500 , "Test" ); // <== full screen windowed mode
//	InitWindow( 0 , 0 , "Test" ); // <== full screen windowed mode

	if ( ! IsWindowReady() ) 
	{
		// Platform initialization probably failed
		return -1;
	}

//	ClearWindowState( FLAG_VSYNC_HINT );

//	SetMouseOffset( 10, 10);
//	SetMouseScale( 0.5 , 0.5 );

	SetTargetFPS( 60 );

	while( ! WindowShouldClose() )
	{
		update();

		BeginDrawing();
		{
			ClearBackground( GRAY );
			draw();
		}
		EndDrawing();
	}

	CloseWindow();
}

void update()
{
	if ( IsKeyPressed( KEY_ZERO ) )
	{
		SetWindowMonitor(0);
	}
	else
	if ( IsKeyPressed( KEY_ONE ) )
	{
		SetWindowMonitor(1);
	}
	else
	if ( IsKeyPressed( KEY_F ) )
	{
//		SetWindowSize( 1280 , 720 );
		ToggleFullscreen();
		printf("Toggled, screen now %d x %d\n", GetScreenWidth(), GetScreenHeight());
		printf("Toggled, render now %d x %d\n", GetRenderWidth(), GetRenderHeight());
	}
	else
	if ( IsKeyPressed( KEY_G ) )
	{
		SetWindowState(FLAG_FULLSCREEN_MODE);
	}
	else 
	if ( IsKeyPressed( KEY_B ) )
	{
		ToggleBorderlessWindowed();
		printf("Toggled, screen now %d x %d\n", GetScreenWidth(), GetScreenHeight());
		printf("Toggled, render now %d x %d\n", GetRenderWidth(), GetRenderHeight());
	}
	else
	if ( IsKeyPressed( KEY_N ) )
	{
		SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
	}
	else
	if ( IsKeyPressed( KEY_C ) )
	{
		ClearWindowState(FLAG_FULLSCREEN_MODE);
		ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
	}
	else
	if ( IsKeyPressed( KEY_X ) )
	{
		MaximizeWindow();
	}
	else
	if ( IsKeyPressed( KEY_V ) )//|| !IsWindowFocused() )
	{
		MinimizeWindow();
	}
	else
	if ( IsKeyPressed( KEY_R ) )
	{
		RestoreWindow();
	}
	else
	if ( IsKeyPressed( KEY_S ) )
	{
		if ( IsWindowState(FLAG_RESCALE_CONTENT) )
		{
			ClearWindowState(FLAG_RESCALE_CONTENT);
		}
		else
		{
			SetWindowState(FLAG_RESCALE_CONTENT);
		}
	}
	else
	if ( IsKeyPressed( KEY_T ) )
	{
		if ( IsWindowState(FLAG_TEXT_LINEAR_FILTER) )
		{
			ClearWindowState(FLAG_TEXT_LINEAR_FILTER);
		}
		else
		{
			SetWindowState(FLAG_TEXT_LINEAR_FILTER);
		}
	}
	else
	if ( IsKeyPressed( KEY_KP_5 ) ) { SetMousePosition( GetScreenWidth()/2 , GetScreenHeight()/2 ); }
	else
	if ( IsKeyPressed( KEY_KP_7 ) ) { SetMousePosition( 0 , 0 ); }
	else
	if ( IsKeyPressed( KEY_KP_9 ) ) { SetMousePosition( GetScreenWidth() , 0 ); }
	else
	if ( IsKeyPressed( KEY_KP_3 ) ) { SetMousePosition( GetScreenWidth() , GetScreenHeight() ); }
	else
	if ( IsKeyPressed( KEY_KP_1 ) ) { SetMousePosition( 0 , GetScreenHeight() ); }
}

void draw()
{
	Vector2 mouse = GetMousePosition();

	int sw = GetScreenWidth();
	int sh = GetScreenHeight();

	int rw = GetRenderWidth();
	int rh = GetRenderHeight();

	Vector2 dpi = GetWindowScaleDPI();

	int monitor = GetCurrentMonitor();

	int mw = GetMonitorWidth( monitor );
	int mh = GetMonitorHeight( monitor );

	Rectangle screenRect = { 0.0 , 0.0 , sw , sh };
	Rectangle renderRect = { 0.0 , 0.0 , rw , rh };

	// Draw the border of the screen :
	DrawRectangleRec( screenRect , RAYWHITE );
	DrawRectangleLinesEx( screenRect , 4.0f , RED );
	DrawRectangleLinesEx( renderRect , 4.0f , GREEN );

	// Draw the text NOT in the center :

	int font_size = 20 ;
	int y = 0 ;

	DrawText( TextFormat( "Mouse  : %.2f x %.2f / %d x %d" , mouse.x , mouse.y , GetMouseX(), GetMouseY()  ) , 10 , y+=font_size , font_size , BLACK );
	DrawText( TextFormat( "Screen : %d x %d" , sw , sh ) , 10 , y+=font_size , font_size , BROWN );
	DrawText( TextFormat( "Render : %d x %d pixels" , rw , rh ) , 10 , y+=font_size , font_size , DARKGREEN );
	DrawText( TextFormat( "Monitor[%d] : %d x %d pixels" , monitor , mw , mh ) , 10 , y+=font_size , font_size , DARKBLUE );
	DrawText( TextFormat( "DPI : %f x %f" , dpi.x , dpi.y ) , 10 , y+=font_size , font_size , BLACK );
	y+=font_size;
	DrawText( TextFormat( "screenRect : %f x %f" , screenRect.width , screenRect.height ) , 10 , y+=font_size , font_size , RED );
	DrawText( TextFormat( "renderRect : %f x %f" , renderRect.width , renderRect.height ) , 10 , y+=font_size , font_size , GREEN );
	y+=font_size;
	DrawText( IsWindowFullscreen() ? "FULLSCREEN MODE" : "" , 10 , y , font_size , BLUE );
	DrawText( IsWindowState( FLAG_BORDERLESS_WINDOWED_MODE ) ? "BORDERLESS WINDOWED MODE" : "" , 10 , y , font_size , PINK );
	DrawText( !IsWindowFullscreen() && !IsWindowState( FLAG_BORDERLESS_WINDOWED_MODE ) ? "Windowed mode" : "" , 10 , y , font_size , YELLOW );
	y+=font_size;
	DrawText( IsWindowFocused() ? "HAS FOCUS" : "does not have focus" , 10 , y+=font_size , font_size , ORANGE );
	DrawText( IsWindowState(FLAG_RESCALE_CONTENT) ? "RESCALE CONTENT" : "does not rescale content" , 10 , y+=font_size , font_size , DARKPURPLE );
	DrawText( IsCursorOnScreen() ? "CURSOR on SCREEN" : "cursor NOT on screen" , 10 , y+=font_size , font_size , DARKGREEN );
	y+=font_size;
	DrawText( "[F] ToggleFullscreen()" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[G] SetWindowState(FLAG_FULLSCREEN_MODE);" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[B] ToggleBorderlessWindowed()" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[N] SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[C] ClearWindowState(FLAG_xxx_MODE);" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[X] MaximizeWindow();" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[V] MinimizeWindow();" , 10 , y+=font_size , font_size , GRAY );
	DrawText( "[R] RestoreWindow();" , 10 , y+=font_size , font_size , GRAY );
	y+=font_size;
	DrawText( "[T] toggle `FLAG_TEXT_LINEAR_FILTER`" , 10 , y+=font_size , font_size , GRAY );

	// Draw a software mouse cursor for debug purpose :

	DrawCircleSector( mouse , 30, 45, 90, 1, WHITE);
	DrawCircleSectorLines( mouse , 30, 45, 90, 1, BLACK);

	// Draw a target :

	DrawLine( GetMouseX() , 0 , GetMouseX() , sh , BLUE );
	DrawLine( 0 , GetMouseY() , sw , GetMouseY() , BLUE );

	// Center of screen :

	DrawLine( sw/2-10 , sh/2, sw/2+10, sh/2, RED );
	DrawLine( sw/2 , sh/2-10, sw/2, sh/2+10, RED );
}

`IsCursorOnScreen()` returns true only if the cursor is over the `CORE.Window.screen` surface, so it works correctly when `FLAG_CONTENT_RESCALE` is enabled.
@SuperUserNameMan
Copy link
Contributor Author

IsCursorOnScreen() now consistent with FLAG_RESCALE_CONTENT

It returns true only if the cursor is over the CORE.Window.screen surface.

So, if the cursor is inside the window but still over the "blank borders" of each sides of the centered "screen", it will return false.

…State()`

i was trying to follow the code-style involved in these two function, but it confused me so much that it caused a bug i could not identify. So I just rewrote them using helpers, and the bug disappeared.
@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 25, 2024

HELP REQUIRED : MS Windows 11 + TV as monitor

I'm having an issue between my Windows 11 laptop and my 4K TV, and I would like to know if it is limited to my hardware, or if it is a general issue.

Issue description : on Windows 11, when the TV is set as the unique monitor, when I ToggleFullscreen() in a low resolution (using the test code provided above), GLFW fails to setup the fullscreen mode with an ASSERT monitor != NULL error. I tracked down the bug as deep as I could into GLFW, and it seems to be caused because of a "monitor disconnected" event triggered somewhere and somehow for unknown reasons.

My TV is know to have a firmware bug with low resolution (thank you Philips and TCL cooperation), but the issue i'm describing only happens specifically with Win11 and this TV. Other OS works fine with this TV, and other monitor work fine with this Win11.

Help required :
Could you please test if you have similar issue with your Win11 and your TV ?

Setup your TV as unique monitor in 4K or 1080p, then run the example code above, press [F].

edit note that vanilla version of Raylib also exhibit the same issue.

@raysan5
Copy link
Owner

raysan5 commented Jul 25, 2024

I'm checking this PR and I'd like to note that it is moving in a rabbit hole redesign that I won't be able to maintain... Please note that at this state I'm hardly considering merging it.

@SuperUserNameMan
Copy link
Contributor Author

I'm checking this PR and I'd like to note that it is moving in a rabbit hole redesign that I won't be able to maintain... Please note that at this state I'm hardly considering merging it.

Thanks you for warning me.

Because I think i've already spent way too much time and energy into it anyway, and this is leading me into debugging GLFW (cause of the mentioned issue above), which is way beyond what i wanted to do initially.

In its current state, this PR managed to solve many issues mentioned in the original message, and any extra flag I added to raylib.h can be just ignored without breaking backward compatiblity.

Beside the Win11 issue mentioned just above, and beside the pending MacOS fixes, it works well on every platforms i tested : Win10, Linux + X11, Linux + Wayland.

If you don't merge it, maybe the code could serve as a study for individuals PR to fix issues in vanilla Raylib code in a manner that you'll be able to maintain.

@SoloByte
Copy link
Contributor

SoloByte commented Jul 25, 2024

I'm checking this PR and I'd like to note that it is moving in a rabbit hole redesign that I won't be able to maintain... Please note that at this state I'm hardly considering merging it.

@SuperUserNameMan @raysan5

After a lot of testing I think on macOS there is no proper way to disable high dpi scaling. It is possible to workaround but it is not nice nor easy to maintain.

My proposition would be (to allow this PR to be merged or a similar new one)

  1. Remove the High DPI flag altogether and make raylib take care of dpi scaling as default. This would make raylib consistent across all platforms, it would reduce complexity and any UI that uses custom dpi scaling should also work with high dpi scaling enabled(backward compatible)
  2. Remove the Content Scale Flag and everything it does (but keep it somewhere so people can implement it themselves)
  3. Keep all the fixes to the fullscreen mode & borderless fullscreen mode (that's what it was about originally and keep the proper restoring window size/position functionality)
  4. I don't know about the linear filter flag because I don't know why linear filter is enabled on high dpi mode in the first place?!

I definitive answer about this would be nice because I am not going to try to fix anything if it is not going to be merged 🙃

In my opinion the best way to go about it is to reduce complexity and try to create consistency across platforms. (The points above would do that)


As a side note:
Problems with Fullscreen, Borderless Fullscreen, Window Size/ Position Restoring and dpi inconsitencies all exist right now and make it hard (for instance) to create cross platform games.
So we should find a way to fix those things that would be able to be merged. ( the good thing about this PR is that we know how to fix most of those things right now)

@SuperUserNameMan
Copy link
Contributor Author

Are you all refering to the FLAG_RESCALE_CONTENT when talking about the content scaling thing that should be removed to try to simplify the PR and ease code maintainability ?

If yes :

I don't know if the way i reimplemented the effect enabled by FLAG_CONTENT_RESCALE is what @raysan5 had in mind when the CORE.Windiw.renderScale and CORE.Window.screenOffset were added to the code for the first time (comments mentionned black borders), but according to my tests of several Raylib's examples, when FLAG_RESCALE_CONTENT is associated to FLAG_WINDOW_RESIZE, in most cases, it resized the 2D UI without having to recompute any UI position and size by code.

With these two flags enabled, even the 3D VR stereo example that i was the most afraid of worked out of the box, so i was very optimitic for the rest of the compatibility.

Personally, i find this FLAG_CONTENT_RESCALE is a very begginer and prototyper friendly feature : Write the UI for the fixed screen dimension requested in InitWindow(), and this UI will be rescaled automatically when you resize or fullscreen the window.

It does not even change the default behavior of Raylib if you don't enable it.

The code changes involved are fully backward compatible and are, most of the time, required by the Windows's and X11's implementation of FLAG_WINDOW_HIGHDPI feature anyway.

Also, this new FLAG_CONTENT_RESCALE mode does not complexify the code maintainability that much, since the computations made into _SetupFramebuffer() are separated from each others and are already functional on all platform I tested.

So, if you really want to simplify this PR for the sake of code maintainability by removing FLAG_CONTENT_RESCALE, i'm afraid this FLAG_CONTENT_RESCALE is just the tree hiding the forest, because i did not went down the rabbit hole just for the fun of it.

If I kept trying to fix each issues independently without digging deeper into the code, I doubt i'd have managed to solve them all at once on all platforms i had access to.

If you want to go tiny PR after tiny PR, it might work, but i think you'd unavoidably have to dig your hole from the inside up to the surface.

But maybe i'm being too pessimistic.


After a lot of testing I think on macOS there is no proper way to disable high dpi scaling. It is possible to workaround but it is not nice nor easy to maintain.

Which one are you trying to disable ? OS's or Raylib's ?

If you're talking about the effects enabled by FLAG_WINDOW_HIGHDPI, have you tried doing it like i've done for Wayland ? => just disable the flag by force into SetConfigThing(), and its effets will be disabled in the rest of the pipeline. If i'm not over-optimistic, you'd only have to double check the mouse bahvior, and search for the rest of the __APPLE__ disseminated into the rest of Raylib's code.

@SoloByte
Copy link
Contributor

SoloByte commented Jul 25, 2024

Although I don't use this part of raylib (rcore), my suggestion would be leaving the scaling to the user side. You'd have to turn raylib into an "engine" to fully manage this.

I don't exactly know what you are refering to ? I was talking about dpi scaling and raylib already does this automatically if you enable the high dpi flag.


Which one are you trying to disable ? OS's or Raylib's ?

If you're talking about the effects enabled by FLAG_WINDOW_HIGHDPI, have you tried doing it like i've done for Wayland ? => just disable the flag by force into SetConfigThing(), and its effets will be disabled in the rest of the pipeline. If i'm not over-optimistic, you'd only have to double check the mouse bahvior, and search for the rest of the APPLE disseminated into the rest of Raylib's code.

I can not implement how raylib behaves when the high dpi flag is disabled. So like Wayland basically and everything works pretty much fine when I use the non high dpi branch of your PR but it does not make it consistent with the rest...

Example:
On a 4k monitor with the system resolution set to 1920x1080 if you request a window with the size of 960x540 then this window will always cover 1/4 of the screen independent of the high dpi flag. On windows it would cover 1/4 of the screen when the high dpi flag is enabled and 1/16 of the screen when the high dpi flag is disabled. (You could fake this on macOS but I don't like this really much)


Right now I am trying to figure out what would be merged by @raysan5. Because I don't want that our work was pretty much for nothing but I also understand that it is a lot of changes and that we have to find some common ground here. @SuperUserNameMan I also understand that you didn't do it for the fun of it and it was probably the easiest way of getting everything to work.

So basically we should decide what raylib should take care of and what it should not.
I personally do not need the behaviour of FLAG_RESCALE_CONTENT and I do not need the behaviour when the HIGH_DPI flag is disabled. I don't know why anyone would need the behaviour when HIGH_DPI is disabled because any UI that automatically scales to different DPI values can therefore also scale to a size the comes from automatic DPI scaling from raylib.

That the flag RESCALE_CONTENT is easier for beginners when they write a hardcoded UI I can understand but on the other hand without the flag RESCALE_CONTENT it is easier to understand why a hardcoded UI can create a lot of problems.

For me personally a functioning cross-platform window system is important. (Fullscreen, Borderless Fullscreen, Maximize, Minimize, Focusing Windows, Restoring Windows, Moving Windows to different Monitors)

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 25, 2024

I can not implement how raylib behaves when the high dpi flag is disabled because on macOS the automatic scaling (that raylib takes care of when high dpi flag is enabled) to different dpi scales is taken care of by the OS.

Look, if the OS enforces DPI scaling, there is nothing you can do.
Wayland does that too.
We're not going to emulate an "unscaled" rendering when FLAG_WINDOW_HIGHDPI is disabled on MacOS and Wayland.
If the OS enforce DPI scaling all the time, then let it be.

The only thing we can do is NOT TO DO IT TWICE (once by the OS's, and once by Raylib) .

So, in the SetConfigThing() code, jsut add a __APPLE__ to disable the FLAG_WINDOW_HIGHDPI flag (like i did for wayland).

You'll still have you window DPI rescaled by the MacOS, but that's the way it is.
When you'll export your game to MS Windows, just add this SetConfigThing(FLAG_WINDOW_HIGHDPI) in your code, and you'll have similar results on both OS. That's it.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 25, 2024

For the rest of your comment, regarding the future of this PR, I don't mind if @raysan5 prefers to restart the overhaul from scratch to ease code maintainability according to Raylib's standards and code-conventions.

It's better to have a code that you can maintain and understand because you wrote it, than a code wrote by someone else that you can't maintain because you don't understand why the hell this and that was added or removed or changed.

As far as i'm concerned, I'll still be able to use this code for the project i was working on just before. And when Raylib will be fixed, maybe i'll switch back to master.

edit : oh ! and there is this ugly GLFW bug that i narrowed down this morning that might need to be fixed if some users complains about Raylib not going fullscreen and terminating when their Windows 11 laptop is connected to some external displays set as single main monitor. It was related to the _glfwPollmonitorsWin32() (write that from memory) (vanilla Raylib also affected, not just this PR)

@SoloByte
Copy link
Contributor

I can not implement how raylib behaves when the high dpi flag is disabled because on macOS the automatic scaling (that raylib takes care of when high dpi flag is enabled) to different dpi scales is taken care of by the OS.

Look, if the OS enforces DPI scaling, there is nothing you can do. Wayland does that too. We're not going to emulate an "unscaled" rendering when FLAG_WINDOW_HIGHDPI is disabled on MacOS and Wayland. If the OS enforce DPI scaling all the time, then let it be.

The only thing we can do is NOT TO DO IT TWICE (once by the OS's, and once by Raylib) .

So, in the SetConfigThing() code, jsut add a __APPLE__ to disable the FLAG_WINDOW_HIGHDPI flag (like i did for wayland).

You'll still have you window DPI rescaled by the MacOS, but that's the way it is. When you'll export your game to MS Windows, just add this SetConfigThing(FLAG_WINDOW_HIGHDPI) in your code, and you'll have similar results on both OS. That's it.

I wasn't going to do that anyway ;) It is just something users of raylib need to be aware of. And the main problem right now is that if you enable the high dpi flag then the DPI scaling is consistent and works but the the image is still blurry because of the linear filter (that in the current master you can not disable or get rid of).

The only thing we can do is NOT TO DO IT TWICE (once by the OS's, and once by Raylib) .

Yes this is the best way to do it.


For the rest of your comment, regarding the future of this PR, I don't mind if @raysan5 prefers to restart the overhaul from scratch to ease code maintainability according to Raylib's standards and code-conventions.

It's better to have a code that you can maintain and understand because you wrote it, than a code wrote by someone else that you can't maintain because you don't understand why the hell this and that was added or removed or changed.

I absolutely understand that. My problem is that this whole thing started because the fullscreen and borderless fullscreen are not working correctly in some way on most platforms.
I still want the borderless fullscreen, fullscreen, and window restoring to be fixed ^^

@raysan5 raysan5 changed the title should fix all ToggleFullScreen(), ToggleBorderlessWindowed() and FLAG_WINDOW_HIGHDPI issues [rcore][desktop_glfw] Reviewed ToggleFullScreen(), ToggleBorderlessWindowed() and FLAG_WINDOW_HIGHDPI issues Jul 27, 2024
@paulmelis
Copy link
Contributor

paulmelis commented Sep 15, 2024

What's the state of this pull request? I was happy to see issues relating to fullscreen handling getting fixed here (e.g. ToggleFullscreen() to get out of fullscreen resulting in a black screen for me), would be a shame if nothing improves after all.

@SoloByte
Copy link
Contributor

What's the state of this pull request? I was happy to see issues relating to fullscreen handling getting fixed here (e.g. ToggleFullscreen() to get out of fullscreen resulting in a black screen for me), would be a shame if nothing improves after all.

I think the high number of small to big problems in the windowing system are too much right now. There are so many different issues all over the place that it is hard to keep track of all of them and maintaing the overview of how to fix all of it.

I made #4215 for discussing some of the smaller stuff.

I think we have to wait until we can come up with a consensus of what raylib should handle and what not and how it should be implemented to fix most (if not all the issues).

@raysan5
Copy link
Owner

raysan5 commented Sep 17, 2024

@paulmelis @SoloByte That's it. There are so many casuistics that even small changes that could fix some specific use-case can indeed break other use-cases. Definitely not an easy problem to solve. It requires lot of time and multiple environments and platforms to tests... and unfortunately at the moment I don't have any of them.

Current raylib windowing/screen system works for most of the common use-case situations in ALL platforms. I'm reticent of merging big changes on the system because, as already happened in the past, it could break some specific use-case.

A redesign/review of this system requires resources that unfortunately I don't have at the moment.

@SuperUserNameMan
Copy link
Contributor Author

Even with a free access to time, ernergy and multiple platforms, IMHO, the main issues are :

  • there is no reference implementation, neither as core platform, neither as OS in the desktop core platform ;

  • all desktop/OS are mixed together into a unique core platform, and the merging of a single tiny PR that pretends to fix something on one OS might destabilize all the others desktop OS without noticing -- a solution, to simplify code maintainability and stability, could be to split each desktop/OS into it's own code platform, and make one of them a reference for all the others (This could be Win11) -- this would allow @raysan5 to maintain the main platform/OS he has access to, and let trusted contributors maintain the others without affecting the mains one(s) (exactly like RLGW is not maintained by @raysan5) ;

  • the current rcore and desktop platform source code contain dead-code, zombie-features and outdated comments that creates heavy code-debts and misleading code interpertation : you basically can't trust what you understand at some place without reviewing all the rest of the code to be sure -- that's why some structural funcs needed to be rewritten, and the whole pipeline reviewed from the bottom to the surface like i was doing in this PR ;

  • the desktop platform relies on some important rcore functions that contains bugs or misleading algorithms or that are heavily impacted by the code-debt, which would benefit from being rewritten too -- but rewriting them would impact all other platforms. -- The solution could be to create an alternative and temporary version of these functions like i was doing with _SetupFramebuffer() which was solving all bugs and restoring zombie-features at the same time on desktops, without even breaking backward compatibility.

Also :

  • the behavior of GLFW is not 100% homogeneous on each Windows version regarding DPI scaling and fullscreen toggling (trying to fix one Windows version might destabilize the others) and GLFW is hard to debug and contributes back when you find a bug because there is no comments and some algorithms are extremely complex to decipher without sacrificing neurons in the process. But at the same time, and as far as I know, GLFW is the only framework that support DPI scaling, so extra care should be taken when approving and merging PR that pretends to fix or improve issues related to DPI scaling on Windows.

  • I'm glad this PR is not going to be merged, because the single idea of having @raysan5 reviewing each and single line like a teacher telling a student "Raylib code does not write this that way. please change that", "This line does not follow the code convention please change that", "You did not read the code convention", "thou arst a bad student, thou can't even read the instruction. thou art stupid, merging thee PR is a privilege, not a contribution, and thou art bad, very bad", etc, was already irritating me at power 10.

Other than that, good luck.

@SuperUserNameMan
Copy link
Contributor Author

@raysan5
Copy link
Owner

raysan5 commented Sep 18, 2024

@SuperUserNameMan Thanks for the time invested in improving this system. There was no need at all to be that rude with your last message. Good luck with your future projects. Bye.

Repository owner locked as too heated and limited conversation to collaborators Sep 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants