raysan5 / raylib

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

[rcore] `ToggleFullscreen()` not working as expected with HighDPI mode enabled #3972

Open fishbowlforever opened 4 months ago

fishbowlforever commented 4 months ago

when enabling the highDPI flag in raylib (SetConfigFlags(FLAG_WINDOW_HIGHDPI);) and using ToggleFullscreen(); the fullscreen Window is twice the size it should be and you only see the top left quarter of your software. Toggling again will half the window size each time you toggle fullscreen until the window is gone.

image

steps to repdroduce: SetConfigFlags(FLAG_WINDOW_HIGHDPI); on a high dpi monitor call ToggleFullscreen();

I am using Windows 11

raysan5 commented 3 months ago

@fishbowlforever Thanks for reporting, could you reproduce this issue on Windows 10? It sounds like platform dependant...

In any case, I'm afraid I don't have a HighDPI monitor at the moment for testing on my side...

M1NGS commented 3 months ago

@fishbowlforever Thanks for reporting, could you reproduce this issue on Windows 10? It sounds like platform dependant...

In any case, I'm afraid I don't have a HighDPI monitor at the moment for testing on my side...

set display scaling to 125% enough

SoloByte commented 2 months ago

MacOS

Edit: I think it might be better to put it in a separate issue? It is kind of related but on macOS. (see 3rd image)

I am experiencing similar issues on macOS as well. I made a simple raylib cs project to test all of the following things.

When in windowed mode the render size is 2x the screen size, which is correct for retina/high dpi screens. (dpi value shows up as 2, 2 as well)

Setting the high dpi config flag has no effect on my system. I have tested it without setting it and with setting it before & after InitWindow.

There are 3 types of fullscreen mode:

MacOS Fullscreen works almost correctly. 

  1. DPI value is 1, 1 instead of 2, 2 but everything looks correctly. I don’t know if this is correct behavior because the macOS scaling factor for retina/ high dpi is always 2!
  2. Screen size and render size are the same (dpi factor of 1, 1) but it renders over the entire screen

    macOS-fullscreen-mode

Borderless fullscreen behaves almost correctly as well.

  1. Render Size is 2x of the screen size and values are correct for the monitor I am using (dpi factor is 2, 2)
  2. Window is cut off at the top even though numbers are correct (looks more like a maximized window) borderless-fullscreen-mode

Fullscreen is were things get complicated. 

  1. To have fullscreen mode work at all the window size must be set to the current monitor size before enabling fullscreen. 
  2. DPI value is suddenly 1, 1 instead of 2, 2 like with the macOS fullscreen mode but it looks wrong too.
  3. It seems to only render a quarter of the screen
  4. The screen size value is off, it should be the monitor size
  5. Screen size and render size should be the same with a dpi factor of 1, 1 but screen size value is off (point 4) fullscreen-mode

Windowed Mode for comparison

windowed-mode

The good news is that all the fullscreen modes work & behave the same no matter what resolution I set in the macOS display settings.

I think that the dpi value is the problem. If I have a 4k monitor and display settings resolution set to something other than 4k macOS will always use a dpi value of 2. In windows the scale value can be set directly but in macOS target resolution is set. For example:

So for macOS there are only 2 possbile values (as far as I know) for dpi factor and that is 1 or 2.

Similar issues with fullscreen:

SoloByte commented 2 months ago

If I multiply the monitor size by 2 before entering fullscreen another weird thing happens...

var monitor = GetCurrentMonitor();
SetWindowSize(GetMonitorWidth(monitor) * 2, GetMonitorHeight(monitor) * 2);
ToggleFullscreen();

The monitor size should be 1920x1080 before entering fullscreen (my target resolution on a 4k monitor). If I multiply this by 2 I should get the 4k resolution and if I then set the window size to the 4k resolution and enter fullscreen (were the dpi value is suddenly 1) I expect those value:

Why is only width affect and height suddenly not? :)

scaled-fullscreen

SoloByte commented 2 months ago

Windows

I have tested fullscreen & high dpi functionality on windows 11 with a 4k monitor as well. I have set the scaling to 200% in settings. I have also tested everything with the scaling set to 100%. If something is not clear or I did something wrong or missed a step, please feel free to correct me. 

Windowed

Maximized

Borderless Fullscreen

Fullscreen

I have tested everything with a 100% scaling on a 4k monitor as well. Everything works but it reported the exact same values as with a scaling of 200%.

My findings:

Screen Size, Render Size, Monitor Size

The following things are what I understand about screen size, monitor size, and render size. I do not know if I am correct at all. My main goal here is to figure out together what each of this numbers should represent independent of the the operating system.

  1. Monitor Size should always report the native resolution of the monitor unaffected by any scaling ?
  2. The dpi value should always report the dpi scaling of the system irregardless of the window mode. (windowed & fullscreen should report the same dpi value)?
  3. The screen size should be the actual size of the window ? Does this mean it should be in native resolution or in scaled resolution? Should a window covering 1/4 of a 4k monitor with a dpi scale of 2 have a Screen Size of 1920x1080 or 960x540?
  4. Should the render size always be the screen size x dpi factor? Or should they be the same? (which kinda makes no sense)

To me personally only Example A makes sense. (on all operating systems. I do not know how scaling works on windows but on macOS it can only be 1, 1 or 2, 2. There are other combinations possible as well not just the ones from example A & B but I didn’t want to write them all down ;)

Example A

Example B

SoloByte commented 2 months ago

@raysan5 If you need anything else just tell me. As mentioned above I can also put all my comments in a seperate issue. If needed I can also put a zip file here with the minimal project I used to test this (all raylib cs btw).

SuperUserNameMan commented 2 months ago

@SoloByte : could you share a code example ?

I can also put a zip file here with the minimal project I used to test this

Yes please, i'd like to test it too.

SoloByte commented 2 months ago

@SuperUserNameMan Here you go :) I am very interested in your results :D

Minimal Reproduction Example

minimal-reproduction-raylib-fullscreen.txt

using System.Numerics;
using Raylib_cs;
using static Raylib_cs.Raylib;
using Color = Raylib_cs.Color;

namespace FullscreenTest;

public static class Program
{
    public static void Main(string[] args)
    {
        var bgColor = Raylib.ColorBrightness(Raylib_cs.Color.DarkBlue, -0.5f);
        var test = new Test(800, 800, "Test", bgColor, 60);
        test.Run();

    }
}

public class FullscreenTest
{
    private Font fontDefault;
    public Color BackgroundColor;
    public float DeltaTime { get; private set; } = 0f;
    public Vector2 GetScreenSize() => new Vector2(GetScreenWidth(), GetScreenHeight());
    public Vector2 GetRenderSize() => new Vector2(GetRenderWidth(), GetRenderHeight());
    public Vector2 GetDPI() => GetWindowScaleDPI();
    public Vector2 GetMonitorSize()
    {
        var monitor = GetCurrentMonitor();
        return new Vector2(GetMonitorWidth(monitor), GetMonitorHeight(monitor));
    }

    private int prevFullscreenWidth = 0;
    private int prevFullscreenHeight = 0;
    private int prevFullscreenX = 0;
    private int prevFullscreenY = 0;

    public Test(int width, int height, string title, Color backgroundColor, int targetFramerate = 60)
    {
        Raylib.SetWindowState(ConfigFlags.Msaa4xHint);
        Raylib.InitWindow(width, height, title);
        Raylib.InitAudioDevice();
        Raylib.ClearWindowState(ConfigFlags.VSyncHint);
        Raylib.SetTargetFPS(targetFramerate);
        Raylib.SetWindowState(ConfigFlags.ResizableWindow);

        fontDefault = Raylib.GetFontDefault();
        BackgroundColor = backgroundColor;
    }
    public void Run()
    {
        StartApplication();

        //Main loop
        while (!Raylib.WindowShouldClose())
        {
            //Update
            var dt = Raylib.GetFrameTime();
            DeltaTime = dt;
            Update(dt);

            //Drawing
            Raylib.BeginDrawing();
            Raylib.ClearBackground(BackgroundColor);
            Draw();
            Raylib.EndDrawing();
        }

        //End
        CloseApplication();
        Raylib.CloseAudioDevice();
        Raylib.CloseWindow();
    }

    protected virtual void Update(float dt)
    {
        //I am not checking if fullscreen is already active when pressing borderless fullscreen and vice versa.

        //Fullscreen
        if (IsKeyPressed(KeyboardKey.F))
        {
            if (IsWindowFullscreen())
            {
                ToggleFullscreen();
                SetWindowSize(prevFullscreenWidth, prevFullscreenHeight);
                SetWindowPosition(prevFullscreenX, prevFullscreenY);
            }
            else
            {
                prevFullscreenWidth = GetScreenWidth();
                prevFullscreenHeight = GetScreenHeight();
                prevFullscreenX = (int)GetWindowPosition().X;
                prevFullscreenY = (int)GetWindowPosition().Y;
                var monitor = GetCurrentMonitor();
                SetWindowSize(GetMonitorWidth(monitor), GetMonitorHeight(monitor));
                ToggleFullscreen();
            }

        }

        //Borderless Fullscreen
        else if (IsKeyPressed(KeyboardKey.B))
        {
            ToggleBorderlessWindowed();
        }
    }

    protected virtual void Draw()
    {
        var size = GetScreenSize();
        var infoRect = new Rectangle(new(), size);

        //draw the border of the screen
        DrawRectangleLinesEx(infoRect, 4f, Color.Gray);

        //draw the text in the center
        DrawScreenInformation(infoRect, 50, Color.Lime);
    }

    protected virtual void StartApplication() { }
    protected virtual void CloseApplication() { }

    private void DrawScreenInformation(Rectangle rect, int fontSize, Color color)
    {
        var sw = GetScreenWidth();
        var sh = GetScreenHeight();

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

        var dpi = GetWindowScaleDPI();

        var monitor = GetCurrentMonitor();
        var mw = GetMonitorWidth(monitor);
        var mh = GetMonitorHeight(monitor);

        var text1 = $"Screen {sw}x{sh} | Render {rw}x{rh}";
        var text2 = $"Monitor {mw}x{mh} | DPI {dpi}";

        var textSize1 = MeasureTextEx(fontDefault, text1, fontSize, 1f);
        var textSize2 = MeasureTextEx(fontDefault, text2, fontSize, 1f);

        var width = rect.Size.X * 0.75f;
        if (textSize1.X > textSize2.X)
        {
            if (textSize1.X > width)
            {
                var f = width / textSize1.X;
                fontSize = (int)(fontSize * f);
                textSize1 = MeasureTextEx(fontDefault, text1, fontSize, 1f);
                textSize2 = MeasureTextEx(fontDefault, text2, fontSize, 1f);
            }
        }
        else
        {
            if (textSize2.X > width)
            {
                var f = width / textSize2.X;
                fontSize = (int)(fontSize * f);
                textSize1 = MeasureTextEx(fontDefault, text1, fontSize, 1f);
                textSize2 = MeasureTextEx(fontDefault, text2, fontSize, 1f);
            }
        }

        var rectCenter = rect.Position + (rect.Size / 2);
        var textPos1 = rectCenter - (textSize1 / 2);
        var textPos2 = rectCenter - (textSize2 / 2);
        DrawText(text1, (int)textPos1.X, (int)textPos1.Y, fontSize, color);
        DrawText(text2, (int)textPos2.X, (int)(textPos2.Y + textSize1.Y), fontSize, color);
    }
}
SuperUserNameMan commented 2 months ago

OMG ! Yuck ! C# ! I feel like someone barfed into my eyes X-(

I hope this is what you meant :

#include "raylib.h"

void update();
void draw();

int main( int argc , char **argv )
{
    SetWindowState( FLAG_MSAA_4X_HINT );

    SetConfigFlags( FLAG_WINDOW_HIGHDPI ); // <===== ???

    InitWindow( 800 , 800 , "Test" );

    ClearWindowState( FLAG_VSYNC_HINT );

    SetTargetFPS( 60 );

    SetWindowState( FLAG_WINDOW_RESIZABLE );

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

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

    CloseWindow();
}

void update()
{
    // He said he does not check if fullscreen is already active 
    // when pressing borderless fullscreen and vice versa.

    static int prevFullscreenWidth = 0;
    static int prevFullscreenHeight = 0;
    static int prevFullscreenX = 0;
    static int prevFullscreenY = 0;

    // Fullscreen :
    if ( IsKeyPressed( KEY_F ) )
    {
        if ( IsWindowFullscreen() )
        {
            ToggleFullscreen();
            SetWindowSize( prevFullscreenWidth , prevFullscreenHeight );
            SetWindowPosition( prevFullscreenX , prevFullscreenY );
        }
        else
        {
            prevFullscreenWidth  = GetScreenWidth();
            prevFullscreenHeight = GetScreenHeight();

            prevFullscreenX = (int)GetWindowPosition().x;
            prevFullscreenY = (int)GetWindowPosition().y;

            int monitor = GetCurrentMonitor();

            SetWindowSize( GetMonitorWidth(monitor) , GetMonitorHeight(monitor) );

            ToggleFullscreen();
        }
    }
    else // Borderless Fullscreen :
    if ( IsKeyPressed( KEY_B ) )
    {
        ToggleBorderlessWindowed();
    }
}

void draw()
{
    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 infoRect = { 0.0 , 0.0 , sw , sh };

    // Draw the border of the screen :
    DrawRectangleLinesEx( infoRect , 4.0f , RED );

    // Draw the text NOT in the center :

    DrawText( TextFormat( "Screen : %d x %d" , sw , sh ) , 10 , 10 , 30 , BROWN );
    DrawText( TextFormat( "Render : %d x %d" , rw , rh ) , 10 , 40 , 30 , DARKGREEN );
    DrawText( TextFormat( "Monitor : %d x %d" , mw , mh ) , 10 , 70 , 30 , DARKBLUE );
    DrawText( TextFormat( "DPI : %f x %f" , dpi.x , dpi.y ) , 10 , 100 , 30 , BLACK );
}
SuperUserNameMan commented 2 months ago

Edit : ok, i'm currently proceeeding to various test on a more simplified example, because there seems to be many other issues related to both ToggleFullscreen() and FLAG_WINDOW_HIGHDPI also on Linux. (using GLFW backend).

Maybe an separate issue should be opened for each different backends and OS ...

On my Linux desktop, here are my observations :

#include "raylib.h"

void update();
void draw();

int main( int argc , char **argv )
{
    //SetWindowState( FLAG_MSAA_4X_HINT );

    SetConfigFlags(FLAG_WINDOW_HIGHDPI); // <======= ???

    InitWindow( 1280 , 720 , "Test" );

    //ClearWindowState( FLAG_VSYNC_HINT );

    SetTargetFPS( 60 );

    //SetWindowState( FLAG_WINDOW_RESIZABLE );

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

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

    CloseWindow();
}

void update()
{
    if ( IsKeyPressed( KEY_F ) )
    {
//      SetWindowSize( 1280 , 720 );
        ClearWindowState( FLAG_WINDOW_RESIZABLE );
        ToggleFullscreen();
//      SetWindowState( FLAG_WINDOW_RESIZABLE );
//      SetWindowSize( 1280 , 720 );
    }
    else 
    if ( ! IsWindowFullscreen() && IsKeyPressed( KEY_B ) )
    {
        ToggleBorderlessWindowed();
    }
}

void draw()
{
    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 infoRect = { 0.0 , 0.0 , sw , sh };

    // Draw the border of the screen :
    DrawRectangleLinesEx( infoRect , 300.0f , RED );

    // Draw the text NOT in the center :

    DrawText( TextFormat( "Screen : %d x %d" , sw , sh ) , 10 , 10 , 30 , BROWN );
    DrawText( TextFormat( "Render : %d x %d" , rw , rh ) , 10 , 40 , 30 , DARKGREEN );
    DrawText( TextFormat( "Monitor : %d x %d" , mw , mh ) , 10 , 70 , 30 , DARKBLUE );
    DrawText( TextFormat( "DPI : %f x %f" , dpi.x , dpi.y ) , 10 , 100 , 30 , BLACK );

    DrawText( TextFormat( "infoRect : %f x %f" , infoRect.width , infoRect.height ) , 10 , 140 , 30 , RED ); // <===
}
SuperUserNameMan commented 2 months ago

Ok so if I replicated @fishbowlforever issue correctly, and if @fishbowlforever uses the GLFW backend, i think the culprit is here :

https://github.com/raysan5/raylib/blob/9764fef26260e6fcf671ddffb230360cc1efa1f8/src/platforms/rcore_desktop_glfw.c#L1670-L1682

I think that it is not required to divide by windowScaleDPI because GLFW already handles that with glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); set here :

https://github.com/raysan5/raylib/blob/9764fef26260e6fcf671ddffb230360cc1efa1f8/src/platforms/rcore_desktop_glfw.c#L1300-L1310

@fishbowlforever , can you recompile Raylib to test this fix on your Windows OS ? @SoloByte can you try too and tell me if it improve anything ? It should have no impact on Macos

In file raylib/src/platforms/rcore_desktop_glfw.c, you just have to replace line 1671 with a if (0) to disable the suspect branch.

    // if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
    if (0)
    {
        Vector2 windowScaleDPI = GetWindowScaleDPI();

        CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x);
        CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y);
    }
    else
    {
        CORE.Window.screen.width = width;
        CORE.Window.screen.height = height;
    }

Or apply this patch : https://github.com/raysan5/raylib/pull/4143

fishbowlforever commented 2 months ago

@SuperUserNameMan thanks a lot for your suggestion and research I compiled RL with your suggestion like this: image

Fullscreen works like a charm now!

The only issue left is that, when disabling Fullscreen, the window only displays the top left quarter of the screen. This does not increment to 4x, 8x, 16x, etc. like it previously did though. image

SuperUserNameMan commented 2 months ago

@fishbowlforever : Did you use the code example i provided ? If not, could you please share a minimal code example of your own so i can see if there is something special that triggers this behavior ?

SoloByte commented 2 months ago

@SuperUserNameMan can confirm that disabling resizable flag before entering fullscreen helps & can´t test it on macOS because I am not set up for raylib development.

@raysan5 should I open new issues for the other problems I have found mention above?

SuperUserNameMan commented 2 months ago

@SoloByte : It change nothing for Macos. There should be no diff.

@SuperUserNameMan can confirm that disabling resizable flag before entering fullscreen helps & can´t test it on macOS because I am not set up for raylib development.

Edit oh i've just noticed i misinterpreted your answer. Yes, disabling resizable flag before toggling to fullscreen should help, and might have an effect on Macos too.

raysan5 commented 2 months ago

@SoloByte @SuperUserNameMan Thank you very much for the extensive review of this issue, full-screen and high-dpi are big concerns on raylib that despite being reviewed many many times, still have issues, mostly platform and OS dependant...

@raysan5 should I open new issues for the other problems I have found mention above?

@SoloByte Yes, please, I think it would be easier to track the issues separately (despite being highly dependant). If you want I can also reopen this issue for reference.

SoloByte commented 2 months ago

@SuperUserNameMan @fishbowlforever I am currently investigating the fullscreen & borderless fullscreen issues.

What I have found is that fullscreen mode sets the window size to the current screen size before entering fullscreen instead of setting it to the monitor size. (Borderless fullscreen sets the window size to the monitor size)

Fullscreen

Fullscreen

Borderless Fullscreen

Setting the window size calls the window resized callback and before fix #4143 it would then scale the current window size further down each time fullscreen is exited.

I think this loop may also causes the issue that is still there with the 1/4 of the screen in the topleft.

@raysan5 I will open a new issue for fullscreen / borderless fullscreen mode & 1 issue for the screen/render/monitor size confusion with the dpi scale ?

raysan5 commented 2 months ago

Adding @JeffM2501 to the loop, I remember he had some ideas about ToggleFullscreen()/ToggleFullscreenBorderless().

SoloByte commented 2 months ago

@SuperUserNameMan I have already tested it and it improves things. The screen size values are correct and no longer missing a few pixels.

raysan5 commented 2 months ago

@SoloByte I'm reopening to continue the discussion but feel free to open separate issues for related problems.

SoloByte commented 2 months ago

@SuperUserNameMan I have not tested it with the new patch

SuperUserNameMan commented 2 months ago

@SuperUserNameMan I have not tested it with the new patch

ok then, let's start again from a fresh code base then.

SoloByte commented 2 months ago

@SuperUserNameMan @fishbowlforever

The window resize callback also calls SetupViewport which introdues dpi scale again. I don‘t know if that causes any problems though.

What I have found is that fullscreen mode sets the window size to the current screen size before entering fullscreen instead of setting it to the monitor size. (Borderless fullscreen sets the window size to the monitor size) Setting the window size calls the window resized callback and before fix #4143 it would then scale the current window size further down each time fullscreen is exited.

SoloByte commented 2 months ago

@SuperUserNameMan I have not tested it with the new patch

ok then, let's start again from a fresh code base then.

I agree but just for clarification, I have tested it on macOS with the same project as above. The patch you did should not have affected macOS anyway, correct?

SuperUserNameMan commented 2 months ago

@SoloByte

@SuperUserNameMan I have not tested it with the new patch

ok then, let's start again from a fresh code base then.

I agree but just for clarification, I have tested it on macOS with the same project as above. The patch you did should not have affected macOS anyway, correct?

correct. because with the patch, all desktop platforms now use the same __APPLE__ code branch in GetWindowScaleDPI().

Now, i'm going to read the new issues and conversation you opened.

fishbowlforever commented 2 months ago

i don't know how far you are with everything and so on but i tested your provided code on win11 to the same results in case thats still useful:

image

Fullscreen (F) works fantastically, but when disabling fullscreen for the first time, same issue happens as with my code.

SuperUserNameMan commented 2 months ago

@fishbowlforever

If you use FLAG_WINDOW_HIGHDPI at the same time as ToggleFullscreen(), they are currently incompatible.

You have to choose one or another ~or replace ToggleFullscreen() with ToggleBorderlessWindowed().~

edit if you absolutely need to rescale your content accordingly to DPI, you better use the values of GetWindowScaleDPI() to rescale your content manually by code.

SuperUserNameMan commented 2 months ago

Could you please test this PR #4151 ?