SDL: Leave the Metal view active on the window when recreating the Metal renderer

From 4b38d4c96b03b6e1e845ee2a0338d78aca652f5a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 7 Jan 2022 12:37:28 -0800
Subject: [PATCH] Leave the Metal view active on the window when recreating the
 Metal renderer

Fixes https://github.com/libsdl-org/SDL/issues/5140

Also move the metal tag definition to SDL_syswm.h so it can be used by applications
---
 include/SDL_syswm.h                  |  4 +++
 src/render/metal/SDL_render_metal.m  | 45 ++++++++++++++++++++++++++--
 src/video/cocoa/SDL_cocoametalview.h |  1 -
 src/video/cocoa/SDL_cocoametalview.m |  6 ++--
 src/video/uikit/SDL_uikitmetalview.h |  1 -
 src/video/uikit/SDL_uikitmetalview.m |  8 +++--
 6 files changed, 55 insertions(+), 10 deletions(-)

diff --git a/include/SDL_syswm.h b/include/SDL_syswm.h
index 363400d96bb..f7cd670cdef 100644
--- a/include/SDL_syswm.h
+++ b/include/SDL_syswm.h
@@ -98,6 +98,10 @@ typedef struct _UIViewController UIViewController;
 typedef Uint32 GLuint;
 #endif
 
+#if defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL)
+#define SDL_METALVIEW_TAG 255
+#endif
+
 #if defined(SDL_VIDEO_DRIVER_ANDROID)
 typedef struct ANativeWindow ANativeWindow;
 typedef void *EGLSurface;
diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m
index dc9e41ec9fc..e093fd4d8bf 100644
--- a/src/render/metal/SDL_render_metal.m
+++ b/src/render/metal/SDL_render_metal.m
@@ -32,6 +32,7 @@
 #import <QuartzCore/CAMetalLayer.h>
 
 #ifdef __MACOSX__
+#import <AppKit/NSWindow.h>
 #import <AppKit/NSView.h>
 #endif
 
@@ -1565,7 +1566,11 @@ - (void)dealloc
 
         DestroyAllPipelines(data.allpipelines, data.pipelinescount);
 
-        SDL_Metal_DestroyView(data.mtlview);
+        /* Release the metal view instead of destroying it,
+           in case we want to use it later (recreating the renderer)
+         */
+        /* SDL_Metal_DestroyView(data.mtlview); */
+        CFBridgingRelease(data.mtlview);
     }
 
     SDL_free(renderer);
@@ -1608,6 +1613,33 @@ - (void)dealloc
     return SDL_SetError("This Apple OS does not support displaySyncEnabled!");
 }
 
+static SDL_MetalView GetWindowView(SDL_Window *window)
+{
+    SDL_SysWMinfo info;
+
+    SDL_VERSION(&info.version);
+    if (SDL_GetWindowWMInfo(window, &info)) {
+#ifdef __MACOSX__
+        if (info.subsystem == SDL_SYSWM_COCOA) {
+            NSView *view = info.info.cocoa.window.contentView;
+            if (view.subviews.count > 0) {
+                view = view.subviews[0];
+                if (view.tag == SDL_METALVIEW_TAG) {
+                    return (SDL_MetalView)CFBridgingRetain(view);
+                }
+            }
+        }
+#else
+        if (info.subsystem == SDL_SYSWM_UIKIT) {
+            UIView *view = info.info.uikit.window.rootViewController.view;
+            if (view.tag == SDL_METALVIEW_TAG) {
+                return (SDL_MetalView)CFBridgingRetain(view);
+            }
+        }
+#endif
+    }
+    return nil;
+}
 
 static SDL_Renderer *
 METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
@@ -1659,7 +1691,10 @@ - (void)dealloc
         return NULL;
     }
 
-    view = SDL_Metal_CreateView(window);
+    view = GetWindowView(window);
+    if (view == nil) {
+        view = SDL_Metal_CreateView(window);
+    }
 
     if (view == NULL) {
 #if !__has_feature(objc_arc)
@@ -1679,7 +1714,11 @@ - (void)dealloc
 #if !__has_feature(objc_arc)
         [mtldevice release];
 #endif
-        SDL_Metal_DestroyView(view);
+        /* Release the metal view instead of destroying it,
+           in case we want to use it later (recreating the renderer)
+         */
+        /* SDL_Metal_DestroyView(view); */
+        CFBridgingRelease(view);
         SDL_free(renderer);
         if (changed_window) {
             SDL_RecreateWindow(window, window_flags);
diff --git a/src/video/cocoa/SDL_cocoametalview.h b/src/video/cocoa/SDL_cocoametalview.h
index 87f1d35551c..d8948b3d6a0 100644
--- a/src/video/cocoa/SDL_cocoametalview.h
+++ b/src/video/cocoa/SDL_cocoametalview.h
@@ -39,7 +39,6 @@
 #import <Metal/Metal.h>
 #import <QuartzCore/CAMetalLayer.h>
 
-#define METALVIEW_TAG 255
 
 @interface SDL_cocoametalview : NSView
 
diff --git a/src/video/cocoa/SDL_cocoametalview.m b/src/video/cocoa/SDL_cocoametalview.m
index d73e2b37722..6e33c4899ae 100644
--- a/src/video/cocoa/SDL_cocoametalview.m
+++ b/src/video/cocoa/SDL_cocoametalview.m
@@ -31,6 +31,8 @@
 #if SDL_VIDEO_DRIVER_COCOA && (SDL_VIDEO_VULKAN || SDL_VIDEO_METAL)
 
 #include "SDL_events.h"
+#include "SDL_syswm.h"
+
 
 static int SDLCALL
 SDL_MetalViewEventWatch(void *userdata, SDL_Event *event)
@@ -103,7 +105,7 @@ - (void)dealloc
 
 - (NSInteger)tag
 {
-    return METALVIEW_TAG;
+    return SDL_METALVIEW_TAG;
 }
 
 - (void)updateDrawableSize
@@ -173,7 +175,7 @@ - (NSView *)hitTest:(NSPoint)point {
 { @autoreleasepool {
     SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
     NSView *contentView = data->sdlContentView;
-    SDL_cocoametalview* metalview = [contentView viewWithTag:METALVIEW_TAG];
+    SDL_cocoametalview* metalview = [contentView viewWithTag:SDL_METALVIEW_TAG];
     if (metalview) {
         CAMetalLayer *layer = (CAMetalLayer*)metalview.layer;
         SDL_assert(layer != NULL);
diff --git a/src/video/uikit/SDL_uikitmetalview.h b/src/video/uikit/SDL_uikitmetalview.h
index 1972a4a5749..91cd0b0a292 100644
--- a/src/video/uikit/SDL_uikitmetalview.h
+++ b/src/video/uikit/SDL_uikitmetalview.h
@@ -38,7 +38,6 @@
 #import <Metal/Metal.h>
 #import <QuartzCore/CAMetalLayer.h>
 
-#define METALVIEW_TAG 255
 
 @interface SDL_uikitmetalview : SDL_uikitview
 
diff --git a/src/video/uikit/SDL_uikitmetalview.m b/src/video/uikit/SDL_uikitmetalview.m
index 97bbe37b614..8274eaff9fb 100644
--- a/src/video/uikit/SDL_uikitmetalview.m
+++ b/src/video/uikit/SDL_uikitmetalview.m
@@ -30,7 +30,9 @@
 
 #if SDL_VIDEO_DRIVER_UIKIT && (SDL_VIDEO_VULKAN || SDL_VIDEO_METAL)
 
-#import "../SDL_sysvideo.h"
+#include "SDL_syswm.h"
+#include "../SDL_sysvideo.h"
+
 #import "SDL_uikitwindow.h"
 #import "SDL_uikitmetalview.h"
 
@@ -47,7 +49,7 @@ - (instancetype)initWithFrame:(CGRect)frame
                         scale:(CGFloat)scale
 {
     if ((self = [super initWithFrame:frame])) {
-        self.tag = METALVIEW_TAG;
+        self.tag = SDL_METALVIEW_TAG;
         self.layer.contentsScale = scale;
         [self updateDrawableSize];
     }
@@ -122,7 +124,7 @@ - (void)updateDrawableSize
     @autoreleasepool {
         SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata;
         SDL_uikitview *view = (SDL_uikitview*)data.uiwindow.rootViewController.view;
-        SDL_uikitmetalview* metalview = [view viewWithTag:METALVIEW_TAG];
+        SDL_uikitmetalview* metalview = [view viewWithTag:SDL_METALVIEW_TAG];
         if (metalview) {
             CAMetalLayer *layer = (CAMetalLayer*)metalview.layer;
             assert(layer != NULL);