pygame-ce icon indicating copy to clipboard operation
pygame-ce copied to clipboard

Switching to fullscreen in the startup resolution fails if user has switched desktop resolution mid-game (2427)

Open GalacticEmperor1 opened this issue 2 years ago • 1 comments
trafficstars

Issue №2427 opened by onpon4 at 2020-12-28 23:44:45

Tested with Pygame 2.0.1, built from source, on Debian 10 "Buster" with this example:

test.zip

Steps to reproduce:

  1. Run test.py. (It should display a nice painting of a velociraptor; press F11 to toggle fullscreen, Escape to quit. You can verify that it works as intended.)
  2. While test.py is running, go to your system settings and change your desktop resolution to something else. Preferably choose something noticeably different.
  3. Toggle fullscreen by pressing F11.

What ends up happening is that it tries to go into fullscreen at the resolution that the desktop was in at the start (obtained from pygame.display.Info()). The code simply expects the display surface to be the requested size; this is in fact what it is documented to do. However, when requesting that resolution, it instead returns a display at the current resolution.

For example, if my display was at 1920x1080 when starting the program, but I then switch my desktop resolution to 1280x720, then I still end up requesting 1920x1080. But instead of 1920x1080, I actually get a 1280x720 surface. This 1280x720 surface works perfectly, but:

  1. It means I now can't pick 1920x1080 as a resolution and actually get it; and
  2. It causes me to get a different resolution than I requested, leading to errors as you can see with this example (and the docs promise that you will get a surface in the requested size).

What it should actually do in this circumstance is switch to 1080p in "true fullscreen" mode (or if that's impossible, an error should occur or something). Alternatively, if it's the case now that you can get a differently sized surface than requested, then this needs to be documented.

It's worth noting that this same code will fail in Pygame 1 under the same conditions, but rather than giving a surface that's the wrong size, it crashes, at least under X11.

🕵️


Comments

# # robertpfeiffer commented at 2021-01-11 16:28:14

In the long run, pygame.display.Info() will probably be superseded by pygame.display.get_desktop_sizes() for your use case, as the former makes assumptions about a single desktop and fullscreen mode that are no longer true now that borderless window fullscreen is preferred.

(I have noticed that you have encountered many problems I have already worked on, I really should document my work better.)


# # onpon4 commented at 2021-01-11 16:56:44

pygame.display.Info() has limitations, but I believe the primary bug here is in pygame.display.set_mode(), where switching to the resolution that was the current resolution at startup leads to the new current resolution being picked. I believe the same error would be reached if I hardcoded it to use 1920x1080, started the game while at 1920x1080, and then switched to 1280x720 while the game is running before switching the game to fullscreen. Basically, assuming the game started with x*y set as the system display resolution it seems to treat pygame.display.set_mode((x, y)) to the same as pygame.display.set_mode((0, 0)).

If 1080p somehow isn't supported if current resolution is 720p, then this isn't reported by pygame.display.get_desktop_sizes(). On my display, in the circumstance where requesting 1080p will actually give me 720p, it still reports 1080p as an available resolution.

Since I don't believe SDL2 has such a limitation, though, I believe this is a bug in Pygame's implementation which is assuming that the resolution at startup is always the current resolution when deciding whether to use "windowed fullscreen" or "true fullscreen". I haven't looked at the Pygame code but there must be something which is seeing that 1920x1080 matches some value stored for "current resolution", and fails to account for the possibility that the OS display resolution as it was at startup may not be the current display resolution at this very moment.

For the record, what I've done in my project is simply to stop relying on the promise of the documentation which states, "The returned surface will still always match the requested size." This means that I check the size of the display surface immediately after setting it, even if I requested an exact size. In any case, that it isn't possible to use a supported resolution correctly and requesting it returns the current resolution (even if it is bigger or smaller than the requested resolution) is a problem.

🕵️


# # robertpfeiffer commented at 2021-01-12 15:06:03

Are you quite sure this example actually proves what you claim? The display info is only queried once, and the fullscreen resolution is explicitly passed in (based on the same never-updated info data structure).


# # onpon4 commented at 2021-01-12 19:42:52

Yes, I'm quite sure, and it's exactly because the display info is only queried once. If the display info updated somehow then this example would have no such bug because it would say 1280x720 and therefore there would be no mismatch. In fact I double-checked this.

On my end, I could get the exact same effect by hardcoding in 1920x1080 as the resolution. My monitor is capable of this, but if the game started while I was in 1920x1080 resolution, and I then switched it to 720p, it would not give me a 1920x1080 size, it would give me a size equal to my current resolution... because Pygame still thinks that 1080p is my current resolution for this purpose, even though it isn't.

I don't understand why you're bringing up how pygame.display.Info works here, as it's irrelevant. This has to do entirely with pygame.display.set_mode(). The reason I used pygame.display.Info in the example is simply because not everyone has a 1920x1080 display, so using pygame.display.Info makes the result easier to reproduce.

Just to prove that this isn't because Pygame can't use 1080p when I'm at 720p resolution, if I choose 4k as the fullscreen resolution, it simply gives me a window rather than the requested fullscreen. The behavior of giving me a 720p fullscreen display when I requested 1080p is only when my current resolution is 720p, but the game started up with my resolution set to 1080p. Please verify this for yourself if you don't believe me. Additionally, as you can also verify for yourself, my monitor's support for 1080p resolution is still reported by pygame.display.list_modes() even if my current resolution is 720p. (You mentioned a pygame.display.get_desktop_sizes() function but the Pygame documentation doesn't mention such a function so far as I can tell, so I don't know about that one.)

So in all, I think it's fair to classify this as an error not in documentation, but in the behavior of pygame.display.set_mode() assuming that the desktop resolution will never change during a game's runtime, which just isn't necessarily true. Ideally Pygame should be checking the current desktop resolution each time set_mode() is called to determine whether or not "windowed fullscreen" will result in the correct size. If not, then some workaround should be put in place to ensure that the display size choice is respected, ideally in "true fullscreen" mode in a case like this, but failing that, it should at least show a window which is the correct size, as it does for me when I request 4k.

EDIT: What I would personally suggest is, if current desktop resolution cannot be checked:

  1. Assume, as it does now, that startup desktop resolution is the same as now, and use "windowed fullscreen" mode on this basis.
  2. If the display surface is something other than what was requested, check to see if the size fits in a supported resolution. If so, use "true fullscreen" at that resolution. If not, use windowed mode at the requested size.

🕵️


# # robertpfeiffer commented at 2021-01-13 11:14:53

~~Hmm. First things first: You're right, there is a bug there and it is a regression. I spent a lot of time getting toggle_fullscreen to work, but I never checked back on edge cases with set_mode in the 2.0 release. This is difficult to test in an automated fashion.~~

~~That said, I had trouble finding what your test case was supposed to prove until I edited it to use set_mode((0,0), FULLSCREEN). I still have trouble parsing what exactly your problem is. As far as I can tell, there is a problem when switching to a different resolution from a fullscreen window in pygame 2.0.1, but that is not the bug you are describing.~~

On my end, I could get the exact same effect by hardcoding in 1920x1080 as the resolution. My monitor is capable of this, but if the game started while I was in 1920x1080 resolution, and I then switched it to 720p, it would not give me a 1920x1080 size, it would give me a size equal to my current resolution... because Pygame still thinks that 1080p is my current resolution for this purpose, even though it isn't.

So:

  1. You make a 640x480 window on a 1920x1080 desktop
  2. You set the X11 resolution to 1280x720
  3. You tell PyGame to go to fullscreen at 1920x1080
  4. At this point you expect the monitor to physically switch resolution back to 1920x1080
  5. What actually happens: It switches to a 1280x720 window.

~~If this is what is happening, it is indeed a bug, but not one I can reproduce on my machine. I double-checked in the current display.c: If the requested resolution is (0,0) or equal to the current desktop size (queried at the time you call set_mode), we use borderless fullscreen window, otherwise we change the physical resolution. I can only assume that SDL2 is doing something weird on your machine.~~

Finally, a plea to everybody else reading this (I know this is me being opinionated, and this bug/these bugs will have to be fixed):

  • Please do not write games that change the physical resolution of the screen!
  • Please do not use list_modes or Info to set the size of a window. Window sizes can be anything as long as it's smaller than the current size of the desktop, but list_modes can report something that is supported by the hardware, but bigger than the desktop.
  • Please use pygame.display.set_mode((0,0), pygame.FULLSCREEN). It should do the right thing, by which I mean not change the current resolution of the screen.
  • Please consider using SCALED mode or OPENGL for anything bigger than 720p. PyGame rendering was not made for high resolutions anyway.
  • Try using pygame.display.toggle_fullscreen() if you don't need to change the window flags or resolution.

# # robertpfeiffer commented at 2021-01-13 11:24:46

I am very sorry. I compiled PyGame from source again and could reproduce the bug immediately


# # robertpfeiffer commented at 2021-01-13 14:05:33

This testing program shines more light on the problem:

import pygame

def main():
    pygame.init()
    disp_info = pygame.display.Info()
    screen_w = 640
    screen_h = 480
    screen_w_start = disp_info.current_w
    screen_h_start = disp_info.current_h
    fullscreen = False
    screen = pygame.display.set_mode((screen_w, screen_h))
    clock = pygame.time.Clock()
    image = pygame.image.load("Velociraptor_Mongoliensis_Painting.jpg")

    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_F11:
                    if fullscreen:
                        print("Before (fullscreen):")
                    else:
                        print("Before (windowed):")
                    print("window size", screen.get_size())
                    print("desktop:", pygame.display.get_desktop_sizes())
                    fullscreen = not fullscreen
                    if fullscreen:
                        screen_w = disp_info.current_w
                        screen_h = disp_info.current_h
                        screen = pygame.display.set_mode((screen_w_start,screen_h_start),
                                                         pygame.FULLSCREEN)
                        print("After (fullscreen):")
                        screen_w , screen_h  = screen.get_size()
                    else:
                        screen_w = 640
                        screen_h = 480
                        screen = pygame.display.set_mode((screen_w, screen_h))
                        print("After (windowed):")
                    
                    print("window size", screen.get_size())
                    print("desktop:", pygame.display.get_desktop_sizes())
                    print()
                elif event.key == pygame.K_ESCAPE:
                    return
            elif event.type == pygame.QUIT:
                return

        scaled_surf = pygame.transform.scale(image, (screen_w, screen_h))
        screen.blit(scaled_surf, (0, 0))

        clock.tick()
        pygame.display.flip()

    pygame.quit()

main()

Looks like the size of the display surface is wrong, too!

GalacticEmperor1 avatar Feb 12 '23 14:02 GalacticEmperor1