sokol icon indicating copy to clipboard operation
sokol copied to clipboard

sokol_app: dpi_scale seems to be off on iPhone 13 mini

Open jspohr opened this issue 2 years ago • 4 comments

Hi, this is more of a question than an issue, thanks in advance! I noticed that I was getting a weird horizontal resolution for an iPhone 13 mini from sapp_width() - it returns 2338 while the screen is actually 2340 pixels as far as I know. As a side effect of this, there's an ugly vertical line of noise on the side of the screen because those pixels aren't cleared.

I traced this back to UIScreen.mainScreen.nativeScale returning a float value of 2.88000011. UIScreen.mainScreen.bounds returns 812x375. When I type 812 * 2.88000011 into my trusty calculator, the result is 2338.56008932. So I thought, maybe I have a configuration issue in Xcode somewhere as usual, maybe the Storyboard? That would mean this is out of sokol_app's control and I have to fix my project. However, UIScreen.mainScreen.nativeBounds returns a CGRect with the correct resolution: 2340x1080. Aren't those values supposed to be linked / derived from each other? Or is there maybe a rounding error involved, because the correct scaling value of 2.881773399014778 is unusually ~~baloney~~ odd?

At least, if I hardcode the framebuffer resolution to 2340x1080 then the noisy line disappears. But I would be really interested in the root cause of this. Thanks again!

jspohr avatar Aug 14 '22 15:08 jspohr

I just noticed this commit, in which rounding was added to the computation of the framebuffer size. This results in a framebuffer resolution of 2339x1080 for the iPhone 13 mini in landscape mode, a 50% reduction in terms of relative error :) However, it's still off by one pixel...

To fix my use case, I now changed _sapp_ios_update_dimensions to calculate framebuffer size like this:

_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) {
    CGRect screen_rect = UIScreen.mainScreen.bounds;
    // mainScreen.bounds respects device orientation, while nativeBounds is always portrait
    const bool is_portrait = screen_rect.size.width < screen_rect.size.height;
    CGRect native_bounds = UIScreen.mainScreen.nativeBounds;
    _sapp.framebuffer_width = is_portrait ? (int)native_bounds.size.width : (int)native_bounds.size.height;
    _sapp.framebuffer_height = is_portrait ? (int)native_bounds.size.height : (int)native_bounds.size.width;
    _sapp.window_width = (int)roundf(screen_rect.size.width);
    _sapp.window_height = (int)roundf(screen_rect.size.height);
    ...

This gives the correct resolution without any rounding necessary. I left the roundf() for window_width/height, although I'm not sure if it's needed. However, there's a caveat, this branch will no longer be entered: https://github.com/floooh/sokol/blob/efc3a2b3d89d1d6371e130dd2d1046c976a4a9cc/sokol_app.h#L4128-L4141

The dimensions are now always exactly the drawable size. I'm not sure if this has side effects in case the orientation changes, since my app is landscape only.

jspohr avatar Aug 14 '22 21:08 jspohr

Thanks for the investigation! I wasn't aware that there are iOS devices with 'odd' scaling factors like that.

I need to investigate why there's this precision problem when multiplying mainScreen.bounds with mainScreen.nativeScale, because IIRC I've been through quite a few iterations in that area (for instance computing dpi_scale from .bounds and .nativeBounds), and all those iterations had one problem or another, so I'd prefer keeping the current code but fixing the precision problem.

floooh avatar Aug 16 '22 09:08 floooh

It's definitely weird that the nativeScale isn't exact, but apparently rounded down to two decimal places. There could be a reason they're doing that, but I couldn't find anything useful via google. And I agree, especially since the dpi_scale is used for converting input positions to pixel positions, it should definitely be based on what iOS uses as the internal scaling value.

jspohr avatar Aug 16 '22 09:08 jspohr

Can be reproduced in the Simulator:

  1. sokol-samples % ./fips set config sapp-metal-ios-xcode-debug; ./fips gen
  2. Open sapp-metal-ios-xcode-debug/sokol-samples.xcodeproj
  3. Right-click sokol-samples project -> New File... -> iOS -> Launch Screen -> Next -> Select target triangle-sapp -> Create
  4. Left-click sokol-samples project, left-click target triangle-sapp -> General -> Launch Screen File -> enter "Launch Screen"
  5. Open triangle-sapp.c, add .high_dpi = true, in sokol_main
  6. Debug triangle-sapp on iOS Simulator iPhone 13 mini
  7. Set a breakpoint in sg_begin_default_pass: width is 2339, but should be 2340

You need to create the launch screen storyboard to get actual fullscreen on iOS 15 (?). At least that's the only way I know that works.

jspohr avatar Aug 16 '22 10:08 jspohr