-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
New High DPI Support API is needed #2119
Comments
As far as I can tell, SDL2 on Windows still has no HighDPI support - does anyone (@ericwa or @slouken ?) know what happened to this? Is there any chance projects using SDL will ever be able to get rid of their custom workarounds? |
I'll take a look at dusting off my PR this weekend, bringing it up to date with current sdl2 etc. |
Awesome, thanks a lot! |
@ericwa Have any advancements been made on this front yet? I'm willing to try dusting the patch off too if necessary. |
@PJB3005 Sorry for the delay, here's the current status: My previous work is in a branch called Overall, it's working OK, I think I've ironed out most of the issues. My plan for a while has been to split it into 2 separate patches which I'm hoping to make PR's for soon:
|
The hint allows setting a specific DPI awareness ("unaware", "system", "permonitor", "permonitorv2"). This is the first part of High-DPI support on Windows ( libsdl-org#2119 ). It doesn't implement a virtualized SDL coordinate system, which will be addressed in a later commit. (This hint could be useful for SDL apps that want 1 SDL unit = 1 pixel, though.) Detecting and behaving correctly under per-monitor V2 (calling AdjustWindowRectExForDpi where needed) should fix the following issues: libsdl-org#3286 libsdl-org#4712
The hint allows setting a specific DPI awareness ("unaware", "system", "permonitor", "permonitorv2"). This is the first part of High-DPI support on Windows ( #2119 ). It doesn't implement a virtualized SDL coordinate system, which will be addressed in a later commit. (This hint could be useful for SDL apps that want 1 SDL unit = 1 pixel, though.) Detecting and behaving correctly under per-monitor V2 (calling AdjustWindowRectExForDpi where needed) should fix the following issues: #3286 #4712
I had some thoughts last night about display/window API changes to better support high dpi systems. There are a few other github issues scattered around (#6038, #5290, and mouse integer vs float talk in #2999) so I'm not sure if this is the best place to discuss, but here they are. With this design I'm also assuming that there aren't any systems which have unique DPI scales for horizontal versus vertical resolution. I think that's true but someone correct me if I'm wrong - some stuff here would have to change.
typedef struct
{
Uint32 format; /**< pixel format */
int w; /**< width, in DPI-scaled points */
int h; /**< height, in DPI-scaled points */
float dpi_scale; /**< DPI scale factor used to convert from points to pixels */
int refresh_rate; /**< refresh rate (or zero for unspecified) */
void *driverdata; /**< driver-specific data, initialize to 0 */
} SDL_DisplayMode; Rationale: This allows SDL users to know the DPI-scaled size and the pixel size of a display / display mode. Not all platforms have the concept of per-displaymode DPI scales, but I think it's important to have that info available for those that do. All platforms do have at least per-display DPI scales, so the dpi scale field of a display mode can be filled in with that on platforms where there's no real per-mode scale. I'm not entirely sold on having a dpi scale field here instead of pixel width/height fields (along with the existing dpi-scaled width/height fields). Having extra pixel width/height fields is arguably more immediately useful, but maybe less flexible than the dpi scale field (although there are pitfalls to a dpi scale value, see below). I suppose we could have both even if it's a bit redundant. edit: after re-reading Eric's description of Windows display mode behaviour (#5778 (comment)) I'm now more in favour of having all 3 of dpiscaledsize, pixelsize, and dpiscale in the display mode struct. (If we end up not using DPI scale values at all anywhere else then that one could be removed in display modes too, but otherwise I still feel like it's handy.) Add new functions to convert between pixels and DPI-scaled points using the same rounding method as the active platform backend uses: // These names could use a lot of workshopping, it's just a demo.
// I'm also not sure if we should have both the vector and single component versions or just pick one.
int SDL_GetPointComponentFromPixel(int pixel, float dpiscale);
int SDL_GetPixelComponentFromPoint(int point, float dpiscale);
void SDL_GetPointFromPixel(int pixelx, int pixely, float dpiscale, int * pointx, int * pointy);
void SDL_GetPixelFromPoint(int pointx, int pointy, float dpiscale, int * pixelx, int * pixely); Rationale: each platform has a specific way of rounding when converting between pixels and DPI-scaled units (#5827 (comment)). These functions would call into per-backend code to guarantee that they round in the same manner as the platform would. This makes sure there aren't 1 pixel/point differences between user code which uses this API, versus the actual backbuffer size and other OS-computed sizes. When users just want high precision floating point conversions between DPI-scaled points and pixels, they can do floating point multiplications and divisions with the DPI scale instead of using these functions. Keep void SDL_GetWindowSize(SDL_Window * window, int *w, int *h);
void SDL_GetWindowSizeInPixels(SDL_Window * window, int *w, int *h);
float SDL_GetWindowDPIScale(SDL_Window * window); Rationale: Also, |
Would it be better for these functions to take a window handle instead of the scale float value? These are going to be used to convert coordinates related to the current state of a window, and using the window handle would take the onus of querying the DPI scale off of the user and prevent potential bad behavior like caching the scale value, which could cause issues on mixed DPI setups if an application doesn't update the value in every case where it's required and performs calculations with a stale value. |
Yeah using it with a window definitely seems like the most common use case and it makes sense to design it to avoid mistakes while using the API. I was thinking with the more generic versions they could also be used when a window hasn't been created yet, and for screens or display modes a window isn't currently active in (since the other proposed changes would allow you to query the DPI scale in those situations). But I don't know if that would actually be useful in practice. We could have a |
I would appreciate if there was a mode (maybe set via flag when creating a window) that just lets me use physical pixels for everything, so the backbuffer size matches the real window size in pixels on the screen and the mouse coordinates etc also match. Not sure what |
I think a mode like that would have to be set globally (probably via a hint before video initialization). Various platforms don't natively work like that, so there would have to be a lot of conversion code inside SDL to turn units into pixel coordinates automatically for you. I think it could also lead to a lot of trouble and buggy user code in multi-display setups and such. Personally I'm against the idea, but I won't stop anyone from implementing it as an opt-in feature of course. With SDL2's existing APIs plus my proposals above, you could already use pixel units for everything if you call the appropriate conversion functions. |
Better than having this in user code. Remember, SDL is for games, and while I'm not sure how modern games handle High DPI, a big usecase are sourceports of old games, and those definitely expect all sizes to match. And having such a mode would also help sdl12-compat (and possibly sdl2-compat), because SDL1.2 had no concept of HighDPI at all, and I wouldn't expect all existing SDL2 applications to handle it correctly either (and apart from that, SDL2 doesn't even handle it correctly itself, see https://discourse.libsdl.org/t/how-do-i-bugfix-surface-size-in-windows-high-dpi/41647/18) |
In the contrary: When moving a Window from one screen to another, (in this mode) SDL should keep its physical size, so if the SDL application has the window size cached somewhere or doesn't handle some event to resize the OpenGL framebuffer or whatever (not unreasonable if the application didn't set the window as resizable), it still works |
Again I think there are big problems with user display setups in practice when you try to do this, especially for code that doesn't know about DPI scales at all (which you're suggesting is one of the main reasons for this change). The way legacy apps have been handled across pretty much every operating system in the past when running on a high dpi screen is for the OS itself to pretend to the app that it's running at 1x DPI scale, and then the OS scales up the window's contents automatically to be in DPI-scaled units. This is what happens in SDL already when you don't enable high dpi support programmatically, and it's sort of the inverse of what you suggest. I don't think there are plans to change app-handled DPI scaling from being opt-in right now (but it hasn't been brought up either as far as I know, so maybe people have opinions.) I have some bad memories of certain AAA games just telling the OS they support high DPI, without doing any actual scaling. I could barely read any UI text and every UI element was tiny, definitely not a fun experience.
Keep in mind the physical size to the user is not actually the size in pixels, it's the DPI-scaled size. So just keeping the size in pixels consistent will make the window extremely big or extremely small (from the user's perspective) as soon as it's moved to a different display with a different DPI scale. |
My main experience with this is on Windows, and there that breaks many applications, and even more so games ("why does it render only in one corner of the window?!"). I can imagine that in some games there are problems with UI elements being too small on HighDPI, but I don't think you can rely on some kind of scaling magic to work, you'll likely just get different problems instead. If they didn't have HighDPI in mind for scaling the UI, they most likely won't like it when the OpenGL (or DirectX or whatever) framebuffer has a different size than what they think the window has.
If this were true, Yamagi Quake II and dhewm3 wouldn't need this kind of code: https://github.com/dhewm/dhewm3/blob/477252308d1ea8660e2d040902d58a599122047f/neo/sys/win32/win_main.cpp#L891-L939 (Ok, maybe it is true now, but when we added that code it wasn't and I haven't tried if it's still needed in latest SDL2)
That's the users problem then, they can resize it before or afterwards (and apart from that, who moves a game window from one screen to another, esp. when they have vastly different resolutions) |
I agree Windows has had many problems with proper high DPI support. That's why I don't want to replicate its mistakes and I'd rather learn from them instead. The way it dealt with coordinates in its own APIs is a big reason why so many apps on Windows have had poor user experience in high DPI situations. Contrast that with macOS where I don't think I've seen an app that has broken layout or sizing issues as a result of DPI scaling. SDL's current approach, and changes we've discussed in the past to try to make things more consistent and useful, have kept closer to the macOS model (points-first) than the Windows model (pixels-first) and I think it's been the right choice. |
I also had confusing bugreports from Mac users regarding High DPI, but couldn't debug them in detail because I don't have a Mac. But note that I'm not arguing against your approach, I just want an option for it to behave like many games expect, to minimize breakage (and required changes to the games code) |
Yeah, macOS exposes both low-DPI and high-DPI exclusive fullscreen modes which is a little different than most other desktop operating systems I think. The trouble there has been that SDL doesn't provide enough information to tell what's what. #2119 (comment) should help with that at least. |
The issue I can see here is that in a mixed-DPI setup, the actual, final scale value isn't known until the window is shown and in its final position. Even on platforms that let you specify where the created window will be, there's no guarantee that the OS or something else won't override your request and put the window somewhere else.
Not only Mac, but basically every platform besides Windows uses logical points for scaled desktops. If someone really wants to get a window with a physical backbuffer of a certain size, they can use the proposed Having one opinionated way of doing things that gives identical results across platforms ultimately means fewer bugs and badly-behaved applications in the long-term.
Laptop users for one. If it can be done, someone will do it, and it should be handled. |
Maybe I'm misunderstanding, but that sounds like everything other than Windows is always guaranteed to be blurry? You need to know both the desired "scaling" factor and the actual number of pixels or it can never be sharp. |
Logical points is the coordinate system (which can have fractional values), not the resolution of things. Maybe this will help illustrate it: https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html Apple devices in particular are pretty well known at this point for having high pixel density displays with sharp / non-blurry content. :) In SDL2, when you opt into high dpi, you can get the pixel dimensions of a window's backbuffer via SDL_GetWindowSizeInPixels, and the dimensions in points via SDL_GetWindowSize. The ratio of the two is the scaling factor but you don't always need that ratio directly if you have those two other values. |
Reading those docs it sounds like it's still describing blurry.
If you render everything at "2x" (or 3x, etc.) followed by downscaling then you're not only rendering much more than needed but you're making it blurry by downscaling. A better type of blurry is still blurry. But that seems to be a Cocoa-specific implementation oddity, orthogonal to the logical coordinates and real pixels you described, which sound like they should be good. |
Sounds good to me, so something like this? typedef struct
{
Uint32 format; /**< pixel format */
int w_points; /**< width, in DPI-scaled points. */
int h_points; /**< height, in DPI-scaled points */
int w_pixels; /**< width, in pixels */
int h_pixels; /**< height, in pixels */
float dpi_scale; /**< DPI scale factor used to convert from points to pixels */
float refresh_rate; /**< refresh rate (or zero for unspecified) */
void *driverdata; /**< driver-specific data, initialize to 0 */
} SDL_DisplayMode; Observation: I believe we will have the invariant that, once a SDL window successfully enters exclusive fullscreen, the current display mode's
Small comment here - on Windows I would suggest the non-desktop modes to be filled in with a scale factor of 1.0. Reasons:
Also, one Windows-specific question: if the process is DPI aware, but SDL's highdpi mode isn't enabled (
Sounds good, we should also document the official way to convert e.g. mouse coodinates back to pixels is: float x_pixels = event.motion.x * SDL_GetWindowDPIScale(window); Precision question: say the SDL game requests a (103x100) window, and the OS scale factor is 1.5x. This introduces a possible inconsistency of two different interpretations:
I don't know how much this matters.. but doing a
Agreed. To add to the confusion about
For physical DPI, if we want to keep this, I would drop the /* returns an error code */
int SDL_GetDisplayPhysicalDPI(int displayIndex, float *xdpi, float *ydpi); We'll probably also want a way to get the monitor's DPI scale factor - this would replace current uses of float SDL_GetDisplayDPIScale(int displayIndex); Not sure if it needs to return an int error code. One special attribute of this (matching current behaviour of This would give some ability for games to bypass SDL's hihghdpi support but maybe still adjust the initial window size. Other thoughts: Should we unify the ways of activating highdpi mode in SDL3? Currently we have:
We could replace these with a single hint, |
I don't think there are any platforms where it shouldn't be 1.0, as exclusive fullscreen in general implies that the application owns the display (even if every modern desktop just fakes it now, iirc even Windows doesn't actually change modes anymore) and any scaling will be handled further down the chain.
This is a situation where the aforementioned functions such as Something else that might be useful would be a setting to lock window resize increments to whole pixels so that if someone resizes a window by dragging, they won't wind up with a case where the backbuffer size has to be rounded off.
A hint is all-or-nothing though, so you couldn't have mixed high-dpi/non-high-dpi windows in the same application. Might be a problem in SDL2-compat, as SDL2 allows mixing window types. It might be also be worth thinking about having dpi-awareness be opt-out instead of opt-in going forward, as anything new really should be thinking about dpi-awareness from the start and legacy apps can opt out. |
macOS has display modes with different DPI scales (#5290 (comment)). Each display mode is associated with a different intended user experience in that case: 2880x1800@1x is different from 1440x900@2x in terms of expected scaling. e.g. the former mode doesn't allow a fullscreen app to use the operating system's scaling when the app doesn't support its own scaling. |
This bug report was migrated from our old Bugzilla tracker.
These attachments are available in the static archive:
Reported in version: HG 2.1
Reported for operating system, platform: All, All
Comments on the original bug report:
On 2016-03-01 19:08:34 +0000, Yuya Kumagai wrote:
On 2017-08-12 04:34:18 +0000, Sam Lantinga wrote:
On 2017-08-12 04:40:58 +0000, Alex Szpakowski wrote:
On 2017-08-12 20:00:57 +0000, Eric Wasylishen wrote:
On 2017-08-27 23:14:23 +0000, Alex Szpakowski wrote:
On 2017-08-27 23:16:57 +0000, Alex Szpakowski wrote:
On 2017-09-04 07:25:37 +0000, Alex Szpakowski wrote:
On 2017-09-05 07:27:45 +0000, Eric Wasylishen wrote:
On 2017-10-26 23:04:06 +0000, Eric Wasylishen wrote:
On 2018-10-24 11:13:08 +0000, Ellie wrote:
On 2018-10-24 11:16:26 +0000, Ellie wrote:
On 2018-10-24 11:27:43 +0000, Ellie wrote:
On 2019-10-15 04:19:51 +0000, Anthony Pesch wrote:
On 2019-10-15 23:45:20 +0000, Alex Szpakowski wrote:
On 2019-10-15 23:47:34 +0000, Alex Szpakowski wrote:
On 2019-10-19 20:06:40 +0000, Sam Lantinga wrote:
On 2019-10-20 11:47:54 +0000, Ellie wrote:
On 2019-10-20 20:47:06 +0000, Alex Szpakowski wrote:
On 2019-10-21 07:52:30 +0000, Eric Wasylishen wrote:
On 2019-10-21 14:46:46 +0000, Alex Szpakowski wrote:
The text was updated successfully, but these errors were encountered: