2

I'm trying to get the display scaling setting for the current screen so that I can correctly scale inside my OpenGL and/or Vulkan window to match the setting that the OS has. The documentation says to use

float dpi = [window backingScaleFactor];

However this will return 1.0f for no scaling at all, and 2.0f for some scaling.

float dpi = [[window screen] backingScaleFactor];

does the same.

NSRect resolution = [[window screen] frame];

will give you the virtual resolution of the current screen. In System Preferences -> Display -> Scaled, this is the "Looks like" value. For a 3840x2160 screen I have, the possible values are 1920x1080, 2560x1440, 3008x1692, 3360x1890, 3830x2160, depending on the Scaled setting you have chosen. On my MBP's built in screen, which has a native resolution of 2880x1440, the "Looks Like" values can be 1024x640, 1280x800, 1440x900, 1680x1050, 1920x1200. the docs say to use

NSRect test = {0, 0, 1000, 1000};
NSRect dpi = [NSView convertRectToBacking:test];

However this just multiplies the supplied NSRect by the backingScaleFactor.

This is someone trying to get the real resolution of the screen.

So, I want either the real backingScaleFactor, or the native screen resolution for a given NSScreen. Any ideas?

burito
  • 813
  • 1
  • 7
  • 22
  • 1
    What are you actually trying to determine? The number of physical pixels the device has? The `-backingScaleFactor` is giving you the real backing scale factor; you probably just misunderstand what that is. The backing store is **not** the same size as the physical pixel dimensions. Your app renders to the backing store and that is displayed to screen, but that often involves another scaling operation. The right thing for an OpenGL or Vulkan app to do is to render to the backing store resolution, not the physical pixel resolution. Also, see [here](https://stackoverflow.com/a/53596608/1312143). – Ken Thomases Jan 29 '19 at 21:55
  • I'm trying to determine how big to draw user interface controls, so that they match the size of UI controls in the OS. Although, after writing a test to verify your post it would appear you are correct. In the modes where the scaling factor would be greater than 2.0, for example the "Larger Text" mode, which *should* have a scaling factor of 2.8125 on my device, it appears on close inspection that it is in fact just rendered at 2.0 and scaled up, with the associated visual artefacts. This makes me sad, as I'm not just chasing these pixels for visual acuity, but also performance. – burito Jan 31 '19 at 02:12
  • 1
    Is your OpenGL/Vulkan view full-screen? If so, it *might* work to set the drawable size to be the physical pixel dimensions, to override the backing scale factor. I've made this suggestion [elsewhere](https://stackoverflow.com/a/53563363/1312143) and the user reported it didn't work, but I couldn't tell if their drawable was full-screen. If you try, and if the technique for finding the physical pixel count gives a different size for `kDisplayModeNativeFlag` vs. the largest 1x mode, try both. – Ken Thomases Jan 31 '19 at 02:39
  • It can be freely toggled between fullscreen and windowed. However that actually doesn't change anything. As you stated initially, backingScaleFactor is the real scaling factor, and it will allocate a buffer at the given size and scale it to fit the screen, either using too much memory and producing scale down artefacts, or not using enough memory and producing scaling up artefacts. Only in perfect 1x or 2x modes will the scaling not produce unsightly scaling artefacts. If your initial comment was an answer, I'd mark it as accepted. Infact... please repost it as such so I can :-) – burito Feb 01 '19 at 01:05
  • Specifically, the test I wrote is at https://github.com/burito/dpb - check that out, and ```make gfx_gl``` will produce an app bundle that will render a long thin blue bar that is 25pixels * sys_dpi (on macos sys_dpi = backingStoreFactor). 25 pixels is about the size of the title bar of a window. Try changing the System Preferences -> Display -> Scaling option and compare that blue rectangle to the title bar of an application, it holds the same proportions in all modes I've tested. It also does the same on Windows (which will give factors from 1.0 through to 3.5). – burito Feb 01 '19 at 01:45
  • I'm not in a position to test at the moment, but I don't think I explained my hope clearly. I propose: figure out phys. pixels, calc your "real" scale factor (e.g. 2.8125), size GL drawable to the phys. pixels irrespective of view or backing scale (use `CGLSetParameter(..., kCGLCPSurfaceBackingSize, ...)`), render according to your real scale factor. For windowed, that probably won't help. My **hope** is that for full-screen, the system renders directly to the real device framebuffer so things will have the correct size *and* full fidelity (no scaling). – Ken Thomases Feb 01 '19 at 02:13
  • Using ```CGLSetParameter(..., kCGLCPSurfaceBackingSize, ...)``` and ```CGLEnable(..., kCGLCESurfaceBackingSize);``` allows me to change the SurfaceBackingSize, however ```surfaceBackingFactor``` remains at either 1.0 or 2.0, resulting in the same scaling artefacts when the real value is not at either of these two values. Perhaps that's not the correct wording, as ```surfaceBackingFactor``` certainly is the correct value, but I'm after a ```surfaceRenderFactor```, and a method to assign its value to ```surfaceBackingFactor```. I'm thinking if there's a solution, it's an ```NSWindow``` method. – burito Feb 01 '19 at 09:50
  • I tried implementing ```-(CGFloat)backingScaleFactor;``` on an NSWindow, and while it allows me to adjust things to get the correct scaling to give the illusion of 1:1 pixels, it still copied the buffer to the screen using the normal values of ```backingScaleFactor```. In other words, ```backingScaleFactor``` is used to scale the image to window space, but there is another scaling step that occurs somewhere else, and that is what does the final scale to 2.815. So it appears that if someone is not using a 1x or 2x mode, there will be artefacts, and there's nothing that can be done about it. – burito Feb 01 '19 at 10:13
  • I'm confused. `surfaceBackingFactor`? In any case, you shouldn't need to either modify nor pay attention to `backingScaleFactor`. Now, what I was hoping for may not in fact happen, but it shouldn't have required any of that. The idea is that, whatever the size of the view in points or backing-store pixels, you set the drawable and viewport to the size in device pixels. You also compute your own scaling factor to determine how big to draw things in device pixels to achieve the desired visual size. I can't tell if that's quite what you tried. – Ken Thomases Feb 01 '19 at 20:24
  • I tried that, but the device pixels get scaled by ```backingScaleFactor``` to the window, and then the window gets scaled by the real factor when it is displayed on screen. Overriding ```backingScaleFactor``` confirms that there are two distinct scaling steps at play. – burito Feb 01 '19 at 20:59
  • 1
    Even when full-screen? Bummer. Oh, well. – Ken Thomases Feb 01 '19 at 21:09

1 Answers1

3

The -backingScaleFactor is giving you the real backing scale factor. The backing store is not the same size as the physical pixel dimensions. Your app renders to the backing store and that is displayed to screen, but that often involves another scaling operation. The right thing for an OpenGL or Vulkan app to do is to render to the backing store resolution, not the physical pixel resolution.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154