Commit 69958441be6c2bc482778fbfdef24f172973a4fe

Mark Callow 2018-02-21T09:58:21

Fix high-dpi support on macOS and simplify it and iOS variant. The detault drawableSize for a CAMetalLayer is its bounds x its scale. So it is sufficient to set the *layer's* scale to the desired value.

diff --git a/include/SDL_video.h b/include/SDL_video.h
index 28fc4c3..83f49fa 100644
--- a/include/SDL_video.h
+++ b/include/SDL_video.h
@@ -110,7 +110,9 @@ typedef enum
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
     SDL_WINDOW_FOREIGN = 0x00000800,            /**< window not created by SDL */
-    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported.
+                                                     On macOS NSHighResolutionCapable must be set true in the
+                                                     application's Info.plist for this to have any effect. */
     SDL_WINDOW_MOUSE_CAPTURE = 0x00004000,      /**< window has mouse captured (unrelated to INPUT_GRABBED) */
     SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000,      /**< window should always be above others */
     SDL_WINDOW_SKIP_TASKBAR  = 0x00010000,      /**< window should not be added to the taskbar */
diff --git a/include/SDL_vulkan.h b/include/SDL_vulkan.h
index 803b5fe..d238e22 100644
--- a/include/SDL_vulkan.h
+++ b/include/SDL_vulkan.h
@@ -240,6 +240,9 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(
  * platform with high-DPI support (Apple calls this "Retina"), and not disabled
  * by the \c SDL_HINT_VIDEO_HIGHDPI_DISABLED hint.
  *
+ *  \note On macOS high-DPI support must be enabled for an application by
+ *        setting NSHighResolutionCapable to true in its Info.plist.
+ *
  *  \sa SDL_GetWindowSize()
  *  \sa SDL_CreateWindow()
  */
diff --git a/src/video/cocoa/SDL_cocoametalview.h b/src/video/cocoa/SDL_cocoametalview.h
index 131dad5..c0a582f 100644
--- a/src/video/cocoa/SDL_cocoametalview.h
+++ b/src/video/cocoa/SDL_cocoametalview.h
@@ -41,11 +41,10 @@
 
 @interface SDL_cocoametalview : NSView {
     NSInteger _tag;
-    bool _useHighDPI;
 }
 
 - (instancetype)initWithFrame:(NSRect)frame
-                   useHighDPI:(bool)useHighDPI;
+                        scale:(CGFloat)scale;
 
 /* Override superclass tag so this class can set it. */
 @property (assign, readonly) NSInteger tag;
diff --git a/src/video/cocoa/SDL_cocoametalview.m b/src/video/cocoa/SDL_cocoametalview.m
index 43e2636..e9c08a0 100644
--- a/src/video/cocoa/SDL_cocoametalview.m
+++ b/src/video/cocoa/SDL_cocoametalview.m
@@ -57,17 +57,19 @@
 }
 
 - (instancetype)initWithFrame:(NSRect)frame
-                   useHighDPI:(bool)useHighDPI
+                        scale:(CGFloat)scale
 {
 	if ((self = [super initWithFrame:frame])) {
+        _tag = METALVIEW_TAG;
         self.wantsLayer = YES;
 
         /* Allow resize. */
         self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
-        _tag = METALVIEW_TAG;
 
-        _useHighDPI = useHighDPI;
-        [self updateDrawableSize];
+        /* Set the desired scale. The default drawableSize of a CAMetalLayer
+         * is its bounds x its scale so nothing further needs to be done.
+         */
+        self.layer.contentsScale = scale;
 	}
   
 	return self;
@@ -77,16 +79,6 @@
 - (void)resizeWithOldSuperviewSize:(NSSize)oldSize
 {
     [super resizeWithOldSuperviewSize:oldSize];
-    [self updateDrawableSize];
-}
-
-- (void)updateDrawableSize
-{
-    NSRect bounds = [self bounds];
-    if (_useHighDPI) {
-        bounds = [self convertRectToBacking:bounds];
-    }
-    ((CAMetalLayer *) self.layer).drawableSize = NSSizeToCGSize(bounds.size);
 }
 
 @end
@@ -94,12 +86,26 @@
 SDL_cocoametalview*
 Cocoa_Mtl_AddMetalView(SDL_Window* window)
 {
-    SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
+    SDL_WindowData* data = (__bridge SDL_WindowData *)window->driverdata;
     NSView *view = data->nswindow.contentView;
-
+    CGFloat scale = 1.0;
+
+    if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
+        /* Set the scale to the natural scale factor of the screen - then
+         * the backing dimensions of the Metal view will match the pixel
+         * dimensions of the screen rather than the dimensions in points
+         * yielding high resolution on retine displays.
+         *
+         * N.B. In order for backingScaleFactor to be > 1,
+         * NSHighResolutionCapable must be set to true in the app's Info.plist.
+         */
+        NSWindow* nswindow = data->nswindow;
+        if ([nswindow.screen respondsToSelector:@selector(backingScaleFactor)])
+            scale = data->nswindow.screen.backingScaleFactor;
+    }
+        
     SDL_cocoametalview *metalview
-        = [[SDL_cocoametalview alloc] initWithFrame:view.frame
-                       useHighDPI:(window->flags & SDL_WINDOW_ALLOW_HIGHDPI)];
+        = [[SDL_cocoametalview alloc] initWithFrame:view.frame scale:scale];
     [view addSubview:metalview];
     return metalview;
 }
@@ -119,6 +125,8 @@ Cocoa_Mtl_GetDrawableSize(SDL_Window * window, int * w, int * h)
         if (h) {
             *h = layer.drawableSize.height;
         }
+    } else {
+        SDL_GetWindowSize(window, w, h);
     }
 }
 
diff --git a/src/video/uikit/SDL_uikitmetalview.h b/src/video/uikit/SDL_uikitmetalview.h
index 31fefd4..bc97778 100644
--- a/src/video/uikit/SDL_uikitmetalview.h
+++ b/src/video/uikit/SDL_uikitmetalview.h
@@ -43,8 +43,7 @@
 @interface SDL_uikitmetalview : SDL_uikitview
 
 - (instancetype)initWithFrame:(CGRect)frame
-                        scale:(CGFloat)scale
-                        tag:(int)tag;
+                        scale:(CGFloat)scale;
 
 @end
 
diff --git a/src/video/uikit/SDL_uikitmetalview.m b/src/video/uikit/SDL_uikitmetalview.m
index 4ee8b94..104189d 100644
--- a/src/video/uikit/SDL_uikitmetalview.m
+++ b/src/video/uikit/SDL_uikitmetalview.m
@@ -46,14 +46,12 @@
 
 - (instancetype)initWithFrame:(CGRect)frame
                         scale:(CGFloat)scale
-                          tag:(int)tag
 {
     if ((self = [super initWithFrame:frame])) {
-        /* Set the appropriate scale (for retina display support) */
-        self.contentScaleFactor = scale;
-        self.tag = tag;
-
-        [self updateDrawableSize];
+        self.tag = METALVIEW_TAG;
+        /* Set the desired scale. The default drawableSize of a CAMetalLayer
+         * is its bounds x its scale so nothing further needs to be done. */
+        self.layer.contentsScale = scale;
     }
 
     return self;
@@ -63,16 +61,6 @@
 - (void)layoutSubviews
 {
     [super layoutSubviews];
-    [self updateDrawableSize];
-}
-
-- (void)updateDrawableSize
-{
-    CGSize size  = self.bounds.size;
-    size.width  *= self.contentScaleFactor;
-    size.height *= self.contentScaleFactor;
-
-    ((CAMetalLayer *) self.layer).drawableSize = size;
 }
 
 @end
@@ -89,9 +77,10 @@ UIKit_Mtl_AddMetalView(SDL_Window* window)
 	}
 
     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
-        /* Set the scale to the natural scale factor of the screen - the
-         * backing dimensions of the Metal view will match the pixel
-         * dimensions of the screen rather than the dimensions in points.
+        /* Set the scale to the natural scale factor of the screen - then
+         * the backing dimensions of the Metal view will match the pixel
+         * dimensions of the screen rather than the dimensions in points
+         * yielding high resolution on retine displays.
          */
 #ifdef __IPHONE_8_0
         if ([data.uiwindow.screen respondsToSelector:@selector(nativeScale)]) {
@@ -104,8 +93,7 @@ UIKit_Mtl_AddMetalView(SDL_Window* window)
     }
     SDL_uikitmetalview *metalview
          = [[SDL_uikitmetalview alloc] initWithFrame:view.frame
-                                          scale:scale
-                                            tag:METALVIEW_TAG];
+                                               scale:scale];
     [metalview setSDLWindow:window];
 
     return metalview;