Switch to new IOS windowing system.

This replaces the SDL-based system and should allow Metal to work on iOS.
OpenGL and raster will render but there's no touch input yet.

Bug: skia:8737
Change-Id: I863accc47f0e1781192d567dbe54d1e321c3cd2e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/231561
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 61baf01..cae7672 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2375,11 +2375,12 @@
       ]
     } else if (is_ios) {
       sources += [
-        "tools/sk_app/ios/GLWindowContext_ios.cpp",
-        "tools/sk_app/ios/RasterWindowContext_ios.cpp",
-        "tools/sk_app/ios/Window_ios.cpp",
-        "tools/sk_app/ios/main_ios.cpp",
+        "tools/sk_app/ios/GLWindowContext_ios.mm",
+        "tools/sk_app/ios/RasterWindowContext_ios.mm",
+        "tools/sk_app/ios/Window_ios.mm",
+        "tools/sk_app/ios/main_ios.mm",
       ]
+      libs += [ "QuartzCore.framework" ]
     }
 
     if (skia_use_vulkan) {
@@ -2401,8 +2402,9 @@
       sources += [ "tools/sk_app/MetalWindowContext.mm" ]
       if (is_mac) {
         sources += [ "tools/sk_app/mac/MetalWindowContext_mac.mm" ]
+      } else if (is_ios) {
+        #        sources += [ "tools/sk_app/mac/MetalWindowContext_ios.mm" ]
       }
-      libs += [ "MetalKit.framework" ]
     }
 
     deps = [
@@ -2410,8 +2412,6 @@
     ]
     if (is_android) {
       deps += [ "//third_party/native_app_glue" ]
-    } else if (is_ios) {
-      deps += [ "//third_party/libsdl" ]
     }
     if (skia_use_angle) {
       deps += [ "//third_party/angle2" ]
diff --git a/gn/gen_plist_ios.py b/gn/gen_plist_ios.py
index e4041be..f3c24d5 100644
--- a/gn/gen_plist_ios.py
+++ b/gn/gen_plist_ios.py
@@ -24,6 +24,11 @@
     <key>CFBundleExecutable</key> <string>{app}</string>
     <key>CFBundleIdentifier</key> <string>com.google.{app}</string>
     <key>CFBundlePackageType</key> <string>APPL</string>
+    <key>LSRequiresIPhoneOS</key> <true/>
+    <key>UIDeviceFamily</key> <array>
+      <integer>1</integer>
+      <integer>2</integer>
+    </array>
   </dict>
 </plist>
 '''.format(app=app))
diff --git a/tools/sk_app/ios/GLWindowContext_ios.cpp b/tools/sk_app/ios/GLWindowContext_ios.cpp
deleted file mode 100644
index c442f6d..0000000
--- a/tools/sk_app/ios/GLWindowContext_ios.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include <OpenGLES/ES2/gl.h>
-#include "tools/sk_app/GLWindowContext.h"
-#include "SDL.h"
-#include "include/gpu/gl/GrGLInterface.h"
-#include "tools/sk_app/ios/WindowContextFactory_ios.h"
-
-using sk_app::DisplayParams;
-using sk_app::window_context_factory::IOSWindowInfo;
-using sk_app::GLWindowContext;
-
-namespace {
-
-class GLWindowContext_ios : public GLWindowContext {
-public:
-    GLWindowContext_ios(const IOSWindowInfo&, const DisplayParams&);
-
-    ~GLWindowContext_ios() override;
-
-    void onSwapBuffers() override;
-
-    sk_sp<const GrGLInterface> onInitializeContext() override;
-    void onDestroyContext() override {}
-
-private:
-    SDL_Window*   fWindow;
-    SDL_GLContext fGLContext;
-
-    typedef GLWindowContext INHERITED;
-};
-
-GLWindowContext_ios::GLWindowContext_ios(const IOSWindowInfo& info, const DisplayParams& params)
-    : INHERITED(params)
-    , fWindow(info.fWindow)
-    , fGLContext(info.fGLContext) {
-
-    // any config code here (particularly for msaa)?
-
-    this->initializeContext();
-}
-
-GLWindowContext_ios::~GLWindowContext_ios() {
-    this->destroyContext();
-}
-
-sk_sp<const GrGLInterface> GLWindowContext_ios::onInitializeContext() {
-    SkASSERT(fWindow);
-    SkASSERT(fGLContext);
-
-    if (0 == SDL_GL_MakeCurrent(fWindow, fGLContext)) {
-        glClearStencil(0);
-        glClearColor(0, 0, 0, 0);
-        glStencilMask(0xffffffff);
-        glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
-
-        SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &fStencilBits);
-        SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fSampleCount);
-        fSampleCount = SkTMax(fSampleCount, 1);
-
-        SDL_GL_GetDrawableSize(fWindow, &fWidth, &fHeight);
-        glViewport(0, 0, fWidth, fHeight);
-    } else {
-        SkDebugf("MakeCurrent failed: %s\n", SDL_GetError());
-    }
-    return GrGLMakeNativeInterface();
-}
-
-void GLWindowContext_ios::onSwapBuffers() {
-    if (fWindow && fGLContext) {
-        SDL_GL_SwapWindow(fWindow);
-    }
-}
-
-}  // anonymous namespace
-
-namespace sk_app {
-namespace window_context_factory {
-
-std::unique_ptr<WindowContext> MakeGLForIOS(const IOSWindowInfo& info,
-                                            const DisplayParams& params) {
-    std::unique_ptr<WindowContext> ctx(new GLWindowContext_ios(info, params));
-    if (!ctx->isValid()) {
-        return nullptr;
-    }
-    return ctx;
-}
-
-}  // namespace window_context_factory
-}  // namespace sk_app
diff --git a/tools/sk_app/ios/GLWindowContext_ios.mm b/tools/sk_app/ios/GLWindowContext_ios.mm
new file mode 100644
index 0000000..9e52cd8
--- /dev/null
+++ b/tools/sk_app/ios/GLWindowContext_ios.mm
@@ -0,0 +1,169 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/gpu/gl/GrGLInterface.h"
+#include "tools/sk_app/GLWindowContext.h"
+#include "tools/sk_app/ios/WindowContextFactory_ios.h"
+
+#include <OpenGLES/ES3/gl.h>
+#include <UIKit/UIKit.h>
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::IOSWindowInfo;
+using sk_app::GLWindowContext;
+
+@interface RasterView : MainView
+@end
+
+@implementation RasterView
++ (Class) layerClass {
+    return [CAEAGLLayer class];
+}
+@end
+
+namespace {
+
+class GLWindowContext_ios : public GLWindowContext {
+public:
+    GLWindowContext_ios(const IOSWindowInfo&, const DisplayParams&);
+
+    ~GLWindowContext_ios() override;
+
+    void onSwapBuffers() override;
+
+    sk_sp<const GrGLInterface> onInitializeContext() override;
+    void onDestroyContext() override;
+
+    void resize(int w, int h) override;
+
+private:
+    sk_app::Window_ios*  fWindow;
+    UIViewController*    fViewController;
+    RasterView*          fRasterView;
+    EAGLContext*         fGLContext;
+    GLuint               fFramebuffer;
+    GLuint               fRenderbuffer;
+
+    typedef GLWindowContext INHERITED;
+};
+
+GLWindowContext_ios::GLWindowContext_ios(const IOSWindowInfo& info, const DisplayParams& params)
+    : INHERITED(params)
+    , fWindow(info.fWindow)
+    , fViewController(info.fViewController)
+    , fGLContext(nil) {
+
+    // any config code here (particularly for msaa)?
+
+    this->initializeContext();
+}
+
+GLWindowContext_ios::~GLWindowContext_ios() {
+    this->onDestroyContext();
+    [fRasterView removeFromSuperview];
+    [fRasterView release];
+}
+
+sk_sp<const GrGLInterface> GLWindowContext_ios::onInitializeContext() {
+    SkASSERT(nil != fViewController);
+    SkASSERT(!fGLContext);
+
+    CGRect frameRect = [fViewController.view frame];
+    fRasterView = [[[RasterView alloc] initWithFrame:frameRect] initWithWindow:fWindow];
+    [fViewController.view addSubview:fRasterView];
+
+    fGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
+
+    if (!fGLContext)
+    {
+        SkDebugf("Could Not Create OpenGL ES Context\n");
+        return nullptr;
+    }
+
+    if (![EAGLContext setCurrentContext:fGLContext]) {
+        SkDebugf("Could Not Set OpenGL ES Context As Current\n");
+        this->onDestroyContext();
+        return nullptr;
+    }
+
+    // Set up EAGLLayer
+    CAEAGLLayer* eaglLayer = (CAEAGLLayer*)fRasterView.layer;
+    eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @NO,
+                                     kEAGLDrawablePropertyColorFormat     : kEAGLColorFormatRGBA8 };
+    eaglLayer.opaque = YES;
+    eaglLayer.frame = frameRect;
+    eaglLayer.contentsGravity = kCAGravityTopLeft;
+
+    // Set up framebuffer
+    glGenFramebuffers(1, &fFramebuffer);
+    glBindFramebuffer(GL_FRAMEBUFFER, fFramebuffer);
+
+    glGenRenderbuffers(1, &fRenderbuffer);
+    glBindRenderbuffer(GL_RENDERBUFFER, fRenderbuffer);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fRenderbuffer);
+
+    [fGLContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer];
+
+    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (status != GL_FRAMEBUFFER_COMPLETE) {
+        SkDebugf("Invalid Framebuffer\n");
+        this->onDestroyContext();
+        return nullptr;
+    }
+
+    glClearStencil(0);
+    glClearColor(0, 0, 0, 255);
+    glStencilMask(0xffffffff);
+    glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+    fStencilBits = 8;
+    fSampleCount = 1; // TODO: handle multisampling
+
+    fWidth = fViewController.view.frame.size.width;
+    fHeight = fViewController.view.frame.size.height;
+
+    glViewport(0, 0, fWidth, fHeight);
+
+    return GrGLMakeNativeInterface();
+}
+
+void GLWindowContext_ios::onDestroyContext() {
+    glDeleteFramebuffers(1, &fFramebuffer);
+    glDeleteRenderbuffers(1, &fRenderbuffer);
+    [EAGLContext setCurrentContext:nil];
+    [fGLContext release];
+    fGLContext = nil;
+}
+
+void GLWindowContext_ios::onSwapBuffers() {
+    glBindRenderbuffer(GL_RENDERBUFFER, fRenderbuffer);
+    [fGLContext presentRenderbuffer:GL_RENDERBUFFER];
+}
+
+void GLWindowContext_ios::resize(int w, int h) {
+    // TODO: handle rotation
+    // [fGLContext update];
+     INHERITED::resize(w, h);
+}
+
+}  // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+std::unique_ptr<WindowContext> MakeGLForIOS(const IOSWindowInfo& info,
+                                            const DisplayParams& params) {
+    std::unique_ptr<WindowContext> ctx(new GLWindowContext_ios(info, params));
+    if (!ctx->isValid()) {
+        return nullptr;
+    }
+    return ctx;
+}
+
+}  // namespace window_context_factory
+}  // namespace sk_app
diff --git a/tools/sk_app/ios/RasterWindowContext_ios.cpp b/tools/sk_app/ios/RasterWindowContext_ios.cpp
deleted file mode 100644
index 61ddbf5..0000000
--- a/tools/sk_app/ios/RasterWindowContext_ios.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/core/SkCanvas.h"
-#include "include/core/SkColorFilter.h"
-#include "include/gpu/gl/GrGLInterface.h"
-#include "tools/ToolUtils.h"
-#include "tools/sk_app/GLWindowContext.h"
-#include "tools/sk_app/ios/WindowContextFactory_ios.h"
-
-#include <OpenGLES/ES2/gl.h>
-
-#include "SDL.h"
-
-using sk_app::DisplayParams;
-using sk_app::window_context_factory::IOSWindowInfo;
-using sk_app::GLWindowContext;
-
-namespace {
-
-// We use SDL to support Mac windowing mainly for convenience's sake. However, it
-// does not allow us to support a purely raster backend because we have no hooks into
-// the NSWindow's drawRect: method. Hence we use GL to handle the update. Should we
-// want to avoid this, we will probably need to write our own windowing backend.
-
-class RasterWindowContext_ios : public GLWindowContext {
-public:
-    RasterWindowContext_ios(const IOSWindowInfo&, const DisplayParams&);
-
-    ~RasterWindowContext_ios() override;
-
-    sk_sp<SkSurface> getBackbufferSurface() override;
-
-    void onSwapBuffers() override;
-
-    sk_sp<const GrGLInterface> onInitializeContext() override;
-    void onDestroyContext() override;
-
-private:
-    SDL_Window*   fWindow;
-    SDL_GLContext fGLContext;
-    sk_sp<SkSurface> fBackbufferSurface;
-
-    typedef GLWindowContext INHERITED;
-};
-
-RasterWindowContext_ios::RasterWindowContext_ios(const IOSWindowInfo& info,
-                                                 const DisplayParams& params)
-    : INHERITED(params)
-    , fWindow(info.fWindow)
-    , fGLContext(nullptr) {
-
-    // any config code here (particularly for msaa)?
-
-    this->initializeContext();
-}
-
-RasterWindowContext_ios::~RasterWindowContext_ios() {
-    this->destroyContext();
-}
-
-sk_sp<const GrGLInterface> RasterWindowContext_ios::onInitializeContext() {
-    SkASSERT(fWindow);
-    SkASSERT(fGLContext);
-
-    if (0 == SDL_GL_MakeCurrent(fWindow, fGLContext)) {
-        glClearStencil(0);
-        glClearColor(0, 0, 0, 0);
-        glStencilMask(0xffffffff);
-        glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
-
-        SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &fStencilBits);
-        SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fSampleCount);
-        fSampleCount = SkTMax(fSampleCount, 1);
-
-        SDL_GL_GetDrawableSize(fWindow, &fWidth, &fHeight);
-        glViewport(0, 0, fWidth, fHeight);
-    } else {
-        SkDebugf("MakeCurrent failed: %s\n", SDL_GetError());
-    }
-
-    // make the offscreen image
-    SkImageInfo info = SkImageInfo::Make(fWidth, fHeight, fDisplayParams.fColorType,
-                                         kPremul_SkAlphaType, fDisplayParams.fColorSpace);
-    fBackbufferSurface = SkSurface::MakeRaster(info);
-    return GrGLMakeNativeInterface();
-}
-
-void RasterWindowContext_ios::onDestroyContext() {
-    fBackbufferSurface.reset(nullptr);
-}
-
-sk_sp<SkSurface> RasterWindowContext_ios::getBackbufferSurface() { return fBackbufferSurface; }
-
-void RasterWindowContext_ios::onSwapBuffers() {
-    if (fWindow && fGLContext) {
-        // We made/have an off-screen surface. Get the contents as an SkImage:
-        sk_sp<SkImage> snapshot = fBackbufferSurface->makeImageSnapshot();
-
-        sk_sp<SkSurface> gpuSurface = INHERITED::getBackbufferSurface();
-        SkCanvas* gpuCanvas = gpuSurface->getCanvas();
-        gpuCanvas->drawImage(snapshot, 0, 0);
-        gpuCanvas->flush();
-
-        SDL_GL_SwapWindow(fWindow);
-    }
-}
-
-}  // anonymous namespace
-
-namespace sk_app {
-namespace window_context_factory {
-
-std::unique_ptr<WindowContext> MakeRasterForIOS(const IOSWindowInfo& info,
-                                                const DisplayParams& params) {
-    std::unique_ptr<WindowContext> ctx(new RasterWindowContext_ios(info, params));
-    if (!ctx->isValid()) {
-        return nullptr;
-    }
-    return ctx;
-}
-
-}  // namespace window_context_factory
-}  // namespace sk_app
diff --git a/tools/sk_app/ios/RasterWindowContext_ios.mm b/tools/sk_app/ios/RasterWindowContext_ios.mm
new file mode 100644
index 0000000..bddcf9d
--- /dev/null
+++ b/tools/sk_app/ios/RasterWindowContext_ios.mm
@@ -0,0 +1,197 @@
+
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColorFilter.h"
+#include "include/gpu/gl/GrGLInterface.h"
+#include "tools/ToolUtils.h"
+#include "tools/sk_app/GLWindowContext.h"
+#include "tools/sk_app/ios/WindowContextFactory_ios.h"
+
+#include <OpenGLES/ES3/gl.h>
+
+#include <UIKit/UIKit.h>
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::IOSWindowInfo;
+using sk_app::GLWindowContext;
+
+@interface GLView : MainView
+@end
+
+@implementation GLView
++ (Class) layerClass {
+    return [CAEAGLLayer class];
+}
+@end
+
+namespace {
+
+// TODO: This still uses GL to handle the update rather than using a purely raster backend,
+// for historical reasons. Writing a pure raster backend would be better in the long run.
+
+class RasterWindowContext_ios : public GLWindowContext {
+public:
+    RasterWindowContext_ios(const IOSWindowInfo&, const DisplayParams&);
+
+    ~RasterWindowContext_ios() override;
+
+    sk_sp<SkSurface> getBackbufferSurface() override;
+
+    void onSwapBuffers() override;
+
+    sk_sp<const GrGLInterface> onInitializeContext() override;
+    void onDestroyContext() override;
+
+    void resize(int w, int h) override;
+
+private:
+    sk_app::Window_ios*  fWindow;
+    UIViewController*    fViewController;
+    GLView*              fGLView;
+    EAGLContext*         fGLContext;
+    GLuint               fFramebuffer;
+    GLuint               fRenderbuffer;
+    sk_sp<SkSurface>     fBackbufferSurface;
+
+    typedef GLWindowContext INHERITED;
+};
+
+RasterWindowContext_ios::RasterWindowContext_ios(const IOSWindowInfo& info,
+                                                 const DisplayParams& params)
+    : INHERITED(params)
+    , fWindow(info.fWindow)
+    , fViewController(info.fViewController)
+    , fGLContext(nil) {
+
+    // any config code here (particularly for msaa)?
+
+    this->initializeContext();
+}
+
+RasterWindowContext_ios::~RasterWindowContext_ios() {
+    this->onDestroyContext();
+    [fGLView removeFromSuperview];
+    [fGLView release];
+}
+
+sk_sp<const GrGLInterface> RasterWindowContext_ios::onInitializeContext() {
+    SkASSERT(nil != fViewController);
+    SkASSERT(!fGLContext);
+
+    CGRect frameRect = [fViewController.view frame];
+    fGLView = [[[GLView alloc] initWithFrame:frameRect] initWithWindow:fWindow];
+    [fViewController.view addSubview:fGLView];
+
+    fGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
+
+    if (!fGLContext)
+    {
+        SkDebugf("Could Not Create OpenGL ES Context\n");
+        return nullptr;
+    }
+
+    if (![EAGLContext setCurrentContext:fGLContext]) {
+        SkDebugf("Could Not Set OpenGL ES Context As Current\n");
+        this->onDestroyContext();
+        return nullptr;
+    }
+
+    // Set up EAGLLayer
+    CAEAGLLayer* eaglLayer = (CAEAGLLayer*)fGLView.layer;
+    eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @NO,
+                                     kEAGLDrawablePropertyColorFormat     : kEAGLColorFormatRGBA8 };
+    eaglLayer.opaque = YES;
+    eaglLayer.frame = frameRect;
+    eaglLayer.contentsGravity = kCAGravityTopLeft;
+
+    // Set up framebuffer
+    glGenFramebuffers(1, &fFramebuffer);
+    glBindFramebuffer(GL_FRAMEBUFFER, fFramebuffer);
+
+    glGenRenderbuffers(1, &fRenderbuffer);
+    glBindRenderbuffer(GL_RENDERBUFFER, fRenderbuffer);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fRenderbuffer);
+
+    [fGLContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer];
+
+    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (status != GL_FRAMEBUFFER_COMPLETE) {
+        SkDebugf("Invalid Framebuffer\n");
+        this->onDestroyContext();
+        return nullptr;
+    }
+
+    glClearStencil(0);
+    glClearColor(0, 0, 0, 255);
+    glStencilMask(0xffffffff);
+    glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+    fStencilBits = 8;
+    fSampleCount = 1; // TODO: handle multisampling
+
+    fWidth = fViewController.view.frame.size.width;
+    fHeight = fViewController.view.frame.size.height;
+
+    glViewport(0, 0, fWidth, fHeight);
+
+    // make the offscreen image
+    SkImageInfo info = SkImageInfo::Make(fWidth, fHeight, fDisplayParams.fColorType,
+                                         kPremul_SkAlphaType, fDisplayParams.fColorSpace);
+    fBackbufferSurface = SkSurface::MakeRaster(info);
+    return GrGLMakeNativeInterface();
+}
+
+void RasterWindowContext_ios::onDestroyContext() {
+    glDeleteFramebuffers(1, &fFramebuffer);
+    glDeleteRenderbuffers(1, &fRenderbuffer);
+    [EAGLContext setCurrentContext:nil];
+    [fGLContext release];
+    fGLContext = nil;
+}
+
+sk_sp<SkSurface> RasterWindowContext_ios::getBackbufferSurface() {
+    return fBackbufferSurface;
+}
+
+void RasterWindowContext_ios::onSwapBuffers() {
+    if (fBackbufferSurface) {
+        // We made/have an off-screen surface. Get the contents as an SkImage:
+        sk_sp<SkImage> snapshot = fBackbufferSurface->makeImageSnapshot();
+
+        sk_sp<SkSurface> gpuSurface = INHERITED::getBackbufferSurface();
+        SkCanvas* gpuCanvas = gpuSurface->getCanvas();
+        gpuCanvas->drawImage(snapshot, 0, 0);
+        gpuCanvas->flush();
+        glBindRenderbuffer(GL_RENDERBUFFER, fRenderbuffer);
+        [fGLContext presentRenderbuffer:GL_RENDERBUFFER];
+    }
+}
+
+void RasterWindowContext_ios::resize(int w, int h) {
+    // TODO: handle rotation
+    // [fGLContext update];
+     INHERITED::resize(w, h);
+}
+
+}  // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+std::unique_ptr<WindowContext> MakeRasterForIOS(const IOSWindowInfo& info,
+                                                const DisplayParams& params) {
+    std::unique_ptr<WindowContext> ctx(new RasterWindowContext_ios(info, params));
+    if (!ctx->isValid()) {
+        return nullptr;
+    }
+    return ctx;
+}
+
+}  // namespace window_context_factory
+}  // namespace sk_app
diff --git a/tools/sk_app/ios/WindowContextFactory_ios.h b/tools/sk_app/ios/WindowContextFactory_ios.h
index 87604b2..64df86f 100644
--- a/tools/sk_app/ios/WindowContextFactory_ios.h
+++ b/tools/sk_app/ios/WindowContextFactory_ios.h
@@ -9,7 +9,9 @@
 #ifndef WindowContextFactory_ios_DEFINED
 #define WindowContextFactory_ios_DEFINED
 
-#include "SDL.h"
+#include "tools/sk_app/ios/Window_ios.h"
+
+#import <UIKit/UIKit.h>
 
 #include "tools/sk_app/WindowContext.h"
 
@@ -22,12 +24,17 @@
 namespace window_context_factory {
 
 struct IOSWindowInfo {
-    SDL_Window*   fWindow;
-    SDL_GLContext fGLContext;
+    sk_app::Window_ios* fWindow;
+    UIViewController*   fViewController;
 };
 
 inline std::unique_ptr<WindowContext> MakeVulkanForIOS(const IOSWindowInfo&, const DisplayParams&) {
-    // No Vulkan support on iOS.
+    // No Vulkan support on iOS yet.
+    return nullptr;
+}
+
+inline std::unique_ptr<WindowContext> MakeMetalForIOS(const IOSWindowInfo&, const DisplayParams&) {
+    // No Metal support on iOS yet.
     return nullptr;
 }
 
diff --git a/tools/sk_app/ios/Window_ios.cpp b/tools/sk_app/ios/Window_ios.cpp
deleted file mode 100644
index d7ef20b..0000000
--- a/tools/sk_app/ios/Window_ios.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
-* Copyright 2017 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
-
-#include "src/core/SkUtils.h"
-#include "tools/sk_app/ios/WindowContextFactory_ios.h"
-#include "tools/sk_app/ios/Window_ios.h"
-#include "tools/skui/ModifierKey.h"
-#include "tools/timer/Timer.h"
-
-namespace sk_app {
-
-SkTDynamicHash<Window_ios, Uint32> Window_ios::gWindowMap;
-
-Window* Window::CreateNativeWindow(void*) {
-    Window_ios* window = new Window_ios();
-    if (!window->initWindow()) {
-        delete window;
-        return nullptr;
-    }
-
-    return window;
-}
-
-bool Window_ios::initWindow() {
-    if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
-        this->closeWindow();
-    }
-    // we already have a window
-    if (fWindow) {
-        return true;
-    }
-
-    constexpr int initialWidth = 1280;
-    constexpr int initialHeight = 960;
-
-    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
-    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
-
-    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
-    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
-    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
-    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
-    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
-    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
-
-    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
-
-    if (fRequestedDisplayParams.fMSAASampleCount > 1) {
-        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
-        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fRequestedDisplayParams.fMSAASampleCount);
-    } else {
-        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
-    }
-    // TODO: handle other display params
-
-    uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_ALLOW_HIGHDPI;
-    fWindow = SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
-                               initialWidth, initialHeight, windowFlags);
-
-    if (!fWindow) {
-        return false;
-    }
-
-    fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
-
-    // add to hashtable of windows
-    fWindowID = SDL_GetWindowID(fWindow);
-    gWindowMap.add(this);
-
-    fGLContext = SDL_GL_CreateContext(fWindow);
-    if (!fGLContext) {
-        SkDebugf("%s\n", SDL_GetError());
-        this->closeWindow();
-        return false;
-    }
-
-    return true;
-}
-
-void Window_ios::closeWindow() {
-    if (fGLContext) {
-        SDL_GL_DeleteContext(fGLContext);
-        fGLContext = nullptr;
-    }
-
-    if (fWindow) {
-        gWindowMap.remove(fWindowID);
-        SDL_DestroyWindow(fWindow);
-        fWindowID = 0;
-        fWindow = nullptr;
-    }
-}
-
-static skui::Key get_key(const SDL_Keysym& keysym) {
-    static const struct {
-        SDL_Keycode fSDLK;
-        skui::Key fKey;
-    } gPair[] = {
-        { SDLK_BACKSPACE, skui::Key::kBack     },
-        { SDLK_CLEAR,     skui::Key::kBack     },
-        { SDLK_RETURN,    skui::Key::kOK       },
-        { SDLK_UP,        skui::Key::kUp       },
-        { SDLK_DOWN,      skui::Key::kDown     },
-        { SDLK_LEFT,      skui::Key::kLeft     },
-        { SDLK_RIGHT,     skui::Key::kRight    },
-        { SDLK_TAB,       skui::Key::kTab      },
-        { SDLK_PAGEUP,    skui::Key::kPageUp   },
-        { SDLK_PAGEDOWN,  skui::Key::kPageDown },
-        { SDLK_HOME,      skui::Key::kHome     },
-        { SDLK_END,       skui::Key::kEnd      },
-        { SDLK_DELETE,    skui::Key::kDelete   },
-        { SDLK_ESCAPE,    skui::Key::kEscape   },
-        { SDLK_LSHIFT,    skui::Key::kShift    },
-        { SDLK_RSHIFT,    skui::Key::kShift    },
-        { SDLK_LCTRL,     skui::Key::kCtrl     },
-        { SDLK_RCTRL,     skui::Key::kCtrl     },
-        { SDLK_LALT,      skui::Key::kOption   },
-        { SDLK_LALT,      skui::Key::kOption   },
-        { 'A',            skui::Key::kA        },
-        { 'C',            skui::Key::kC        },
-        { 'V',            skui::Key::kV        },
-        { 'X',            skui::Key::kX        },
-        { 'Y',            skui::Key::kY        },
-        { 'Z',            skui::Key::kZ        },
-    };
-    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
-        if (gPair[i].fSDLK == keysym.sym) {
-            return gPair[i].fKey;
-        }
-    }
-    return skui::Key::kNONE;
-}
-
-static skui::ModifierKey get_modifiers(const SDL_Event& event) {
-    static const struct {
-        unsigned    fSDLMask;
-        skui::ModifierKey fSkMask;
-    } gModifiers[] = {
-        { KMOD_SHIFT, skui::ModifierKey::kShift },
-        { KMOD_CTRL,  skui::ModifierKey::kControl },
-        { KMOD_ALT,   skui::ModifierKey::kOption },
-    };
-
-    skui::ModifierKey modifiers = skui::ModifierKey::kNone;
-
-    switch (event.type) {
-        case SDL_KEYDOWN:
-            // fall through
-        case SDL_KEYUP: {
-            for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
-                if (event.key.keysym.mod & gModifiers[i].fSDLMask) {
-                    modifiers |= gModifiers[i].fSkMask;
-                }
-            }
-            if (0 == event.key.repeat) {
-                modifiers |= skui::ModifierKey::kFirstPress;
-            }
-            break;
-        }
-
-        default: {
-            SDL_Keymod mod = SDL_GetModState();
-            for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
-                if (mod & gModifiers[i].fSDLMask) {
-                    modifiers |= gModifiers[i].fSkMask;
-                }
-            }
-            break;
-        }
-    }
-    return modifiers;
-}
-
-bool Window_ios::HandleWindowEvent(const SDL_Event& event) {
-    Window_ios* win = gWindowMap.find(event.window.windowID);
-    if (win && win->handleEvent(event)) {
-        return true;
-    }
-
-    return false;
-}
-
-bool Window_ios::handleEvent(const SDL_Event& event) {
-    switch (event.type) {
-        case SDL_WINDOWEVENT:
-            if (SDL_WINDOWEVENT_EXPOSED == event.window.event) {
-                this->onPaint();
-            } else if (SDL_WINDOWEVENT_RESIZED == event.window.event) {
-                this->onResize(event.window.data1, event.window.data2);
-            }
-            break;
-
-        case SDL_FINGERDOWN:
-            this->onTouch(event.tfinger.fingerId, skui::InputState::kDown,
-                          (int)(this->width()*event.tfinger.x),
-                          (int)(this->height()*event.tfinger.y));
-            break;
-
-        case SDL_FINGERUP:
-            this->onTouch(event.tfinger.fingerId, skui::InputState::kUp,
-                          (int)(this->width()*event.tfinger.x),
-                          (int)(this->height()*event.tfinger.y));
-            break;
-
-        case SDL_FINGERMOTION:
-            this->onTouch(event.tfinger.fingerId, skui::InputState::kMove,
-                          (int)(this->width()*event.tfinger.x),
-                          (int)(this->height()*event.tfinger.y));
-            break;
-
-        case SDL_KEYDOWN: {
-            skui::Key key = get_key(event.key.keysym);
-            if (key != skui::Key::kNONE) {
-                if (!this->onKey(key, skui::InputState::kDown, get_modifiers(event))) {
-                    if (event.key.keysym.sym == SDLK_ESCAPE) {
-                        return true;
-                    }
-                }
-            }
-        } break;
-
-        case SDL_KEYUP: {
-            skui::Key key = get_key(event.key.keysym);
-            if (key != skui::Key::kNONE) {
-                (void) this->onKey(key, skui::InputState::kUp,
-                                   get_modifiers(event));
-            }
-        } break;
-
-        case SDL_TEXTINPUT: {
-            const char* textIter = &event.text.text[0];
-            while (SkUnichar c = SkUTF8_NextUnichar(&textIter)) {
-                (void) this->onChar(c, get_modifiers(event));
-            }
-        } break;
-
-        default:
-            break;
-    }
-
-    return false;
-}
-
-void Window_ios::setTitle(const char* title) {
-    SDL_SetWindowTitle(fWindow, title);
-}
-
-void Window_ios::show() {
-    SDL_ShowWindow(fWindow);
-}
-
-bool Window_ios::attach(BackendType attachType) {
-    this->initWindow();
-
-    window_context_factory::IOSWindowInfo info;
-    info.fWindow = fWindow;
-    info.fGLContext = fGLContext;
-    switch (attachType) {
-        case kRaster_BackendType:
-            fWindowContext = MakeRasterForIOS(info, fRequestedDisplayParams);
-            break;
-
-        case kNativeGL_BackendType:
-        default:
-            fWindowContext = MakeGLForIOS(info, fRequestedDisplayParams);
-            break;
-    }
-    this->onBackendCreated();
-
-    return (SkToBool(fWindowContext));
-}
-
-void Window_ios::onInval() {
-    SDL_Event sdlevent;
-    sdlevent.type = SDL_WINDOWEVENT;
-    sdlevent.window.windowID = fWindowID;
-    sdlevent.window.event = SDL_WINDOWEVENT_EXPOSED;
-    SDL_PushEvent(&sdlevent);
-}
-
-}   // namespace sk_app
diff --git a/tools/sk_app/ios/Window_ios.h b/tools/sk_app/ios/Window_ios.h
index a548253..037f992 100644
--- a/tools/sk_app/ios/Window_ios.h
+++ b/tools/sk_app/ios/Window_ios.h
@@ -12,7 +12,7 @@
 #include "src/core/SkTDynamicHash.h"
 #include "tools/sk_app/Window.h"
 
-#include "SDL.h"
+#import <UIKit/UIKit.h>
 
 namespace sk_app {
 
@@ -20,47 +20,42 @@
 public:
     Window_ios()
             : INHERITED()
-            , fWindow(nullptr)
-            , fWindowID(0)
-            , fGLContext(nullptr)
-            , fMSAASampleCount(1) {}
+            , fWindow(nil) {}
     ~Window_ios() override { this->closeWindow(); }
 
     bool initWindow();
 
-    void setTitle(const char*) override;
-    void show() override;
+    void setTitle(const char*) override {}
+    void show() override {}
 
     bool attach(BackendType) override;
 
     void onInval() override;
 
-    static bool HandleWindowEvent(const SDL_Event& event);
+    static void PaintWindow();
 
-    static const Uint32& GetKey(const Window_ios& w) {
-        return w.fWindowID;
-    }
+    UIWindow* uiWindow() { return fWindow; }
 
-    static uint32_t Hash(const Uint32& winID) {
-        return winID;
-    }
+    static Window_ios* MainWindow() { return gWindow; }
 
 private:
-    bool handleEvent(const SDL_Event& event);
-
     void closeWindow();
 
-    static SkTDynamicHash<Window_ios, Uint32> gWindowMap;
+    UIWindow*    fWindow;
 
-    SDL_Window*   fWindow;
-    Uint32        fWindowID;
-    SDL_GLContext fGLContext;
-
-    int          fMSAASampleCount;
+    static Window_ios* gWindow; // there should be only one
 
     typedef Window INHERITED;
 };
 
 }   // namespace sk_app
 
+//////////////////////////////////////////////////////////////////////////
+
+@interface MainView : UIView
+
+- (MainView*)initWithWindow:(sk_app::Window_ios*)initWindow;
+
+@end
+
 #endif
diff --git a/tools/sk_app/ios/Window_ios.mm b/tools/sk_app/ios/Window_ios.mm
new file mode 100644
index 0000000..e70d15f
--- /dev/null
+++ b/tools/sk_app/ios/Window_ios.mm
@@ -0,0 +1,148 @@
+/*
+* Copyright 2017 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "tools/sk_app/ios/WindowContextFactory_ios.h"
+#include "tools/sk_app/ios/Window_ios.h"
+
+@interface WindowViewController : UIViewController
+
+- (WindowViewController*)initWithWindow:(sk_app::Window_ios*)initWindow;
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////
+
+using sk_app::Window;
+
+namespace sk_app {
+
+Window_ios* Window_ios::gWindow = nullptr;
+
+Window* Window::CreateNativeWindow(void*) {
+    // already have a window
+    if (Window_ios::MainWindow()) {
+        return nullptr;
+    }
+
+    Window_ios* window = new Window_ios();
+    if (!window->initWindow()) {
+        delete window;
+        return nullptr;
+    }
+
+    return window;
+}
+
+bool Window_ios::initWindow() {
+    // we already have a window
+    if (fWindow) {
+        return true;
+    }
+
+    // Create a delegate to track certain events
+    WindowViewController* viewController = [[WindowViewController alloc] initWithWindow:this];
+    if (nil == viewController) {
+        return false;
+    }
+
+    fWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+    if (nil == fWindow) {
+        [viewController release];
+        return false;
+    }
+    fWindow.backgroundColor = [UIColor whiteColor];
+
+    viewController.view = nil;
+    [fWindow setRootViewController:viewController];
+    [fWindow makeKeyAndVisible];
+
+    gWindow = this;
+
+    return true;
+}
+
+void Window_ios::closeWindow() {
+    if (nil != fWindow) {
+        gWindow = nullptr;
+        [fWindow release];
+        fWindow = nil;
+    }
+}
+
+bool Window_ios::attach(BackendType attachType) {
+    this->initWindow();
+
+    window_context_factory::IOSWindowInfo info;
+    info.fWindow = this;
+    info.fViewController = fWindow.rootViewController;
+    switch (attachType) {
+        case kRaster_BackendType:
+            fWindowContext = MakeRasterForIOS(info, fRequestedDisplayParams);
+            break;
+//#ifdef SK_METAL
+//        case kMetal_BackendType:
+//            fWindowContext = MakeMetalForMac(info, fRequestedDisplayParams);
+//            break;
+//#endif
+        case kNativeGL_BackendType:
+        default:
+            fWindowContext = MakeGLForIOS(info, fRequestedDisplayParams);
+            break;
+    }
+    this->onBackendCreated();
+
+    return (SkToBool(fWindowContext));
+}
+
+void Window_ios::PaintWindow() {
+    gWindow->onPaint();
+}
+
+void Window_ios::onInval() {
+    // TODO: send expose event
+}
+
+}   // namespace sk_app
+
+///////////////////////////////////////////////////////////////////////////////
+
+@implementation WindowViewController {
+    sk_app::Window_ios* fWindow;
+}
+
+- (WindowViewController*)initWithWindow:(sk_app::Window_ios *)initWindow {
+    fWindow = initWindow;
+
+    return self;
+}
+
+- (void)viewDidLoad {
+    // nothing yet
+}
+
+- (void)didReceiveMemoryWarning {
+    // nothing yet
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////
+
+@implementation MainView {
+    sk_app::Window_ios* fWindow;
+}
+
+- (MainView*)initWithWindow:(sk_app::Window_ios *)initWindow {
+    self = [super init];
+
+    fWindow = initWindow;
+
+    return self;
+}
+
+@end
+
diff --git a/tools/sk_app/ios/main_ios.cpp b/tools/sk_app/ios/main_ios.cpp
deleted file mode 100644
index 6982a5a..0000000
--- a/tools/sk_app/ios/main_ios.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-* Copyright 2017 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
-
-#include "include/core/SkTypes.h"
-#include "include/private/SkTHash.h"
-#include "tools/sk_app/Application.h"
-#include "tools/sk_app/ios/Window_ios.h"
-#include "tools/timer/Timer.h"
-
-#include "SDL.h"
-
-using sk_app::Application;
-
-int main(int argc, char* argv[]) {
-    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
-        SkDebugf("Could not initialize SDL!\n");
-        return 1;
-    }
-
-    Application* app = Application::Create(argc, argv, nullptr);
-
-    SDL_Event event;
-    bool done = false;
-    while (!done) {
-        while (SDL_PollEvent(&event)) {
-            switch (event.type) {
-                // events handled by the windows
-                case SDL_WINDOWEVENT:
-                case SDL_FINGERDOWN:
-                case SDL_FINGERMOTION:
-                case SDL_FINGERUP:
-                case SDL_KEYDOWN:
-                case SDL_KEYUP:
-                case SDL_TEXTINPUT:
-                    done = sk_app::Window_ios::HandleWindowEvent(event);
-                    break;
-
-                case SDL_QUIT:
-                    done = true;
-                    break;
-
-                default:
-                    break;
-            }
-        }
-
-        app->onIdle();
-    }
-    delete app;
-
-    SDL_Quit();
-
-    return 0;
-}
diff --git a/tools/sk_app/ios/main_ios.mm b/tools/sk_app/ios/main_ios.mm
new file mode 100644
index 0000000..3bcc236
--- /dev/null
+++ b/tools/sk_app/ios/main_ios.mm
@@ -0,0 +1,106 @@
+/*
+* Copyright 2017 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkTHash.h"
+#include "tools/sk_app/Application.h"
+#include "tools/sk_app/ios/Window_ios.h"
+#include "tools/timer/Timer.h"
+
+#import <UIKit/UIKit.h>
+
+using sk_app::Application;
+
+////////////////////////////////////////////////////////////////////
+
+@interface AppDelegate : UIResponder<UIApplicationDelegate>
+
+@property (nonatomic, assign) BOOL done;
+@property (strong, nonatomic) UIWindow *window;
+
+@end
+
+@implementation AppDelegate
+
+@synthesize done = _done;
+@synthesize window = _window;
+
+- (void)applicationWillTerminate:(UIApplication *)sender {
+    _done = TRUE;
+}
+
+- (void)launchApp {
+    // Extract argc and argv from NSProcessInfo
+    NSArray *arguments = [[NSProcessInfo processInfo] arguments];
+    int argc = arguments.count;
+    char** argv = (char **)malloc((argc+1) * sizeof(char *));
+    int i = 0;
+    for (NSString* string in arguments) {
+        size_t bufferSize = (string.length+1) * sizeof(char);
+        argv[i] = (char*)malloc(bufferSize);
+        [string getCString:argv[i]
+                 maxLength:bufferSize
+                  encoding:NSUTF8StringEncoding];
+        ++i;
+    }
+    argv[i] = NULL;
+
+    Application* app = Application::Create(argc, argv, nullptr);
+
+    // Free the memory we used for argc and argv
+    for (i = 0; i < argc; i++) {
+        free(argv[i]);
+    }
+    free(argv);
+
+    sk_app::Window_ios* mainWindow = sk_app::Window_ios::MainWindow();
+    if (!mainWindow) {
+        return;
+    }
+    self.window = mainWindow->uiWindow();
+
+    // take over the main event loop
+    bool done = false;
+    while (!done) {
+        // TODO: consider using a dispatch queue or CADisplayLink instead of this
+        const CFTimeInterval kSeconds = 0.000002;
+        CFRunLoopRunResult result;
+        do {
+            result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kSeconds, TRUE);
+        } while (result == kCFRunLoopRunHandledSource);
+
+        // TODO: is this the right approach for iOS?
+        // Rather than depending on an iOS event to drive this, we treat our window
+        // invalidation flag as a separate event stream. Window::onPaint() will clear
+        // the invalidation flag, effectively removing it from the stream.
+        sk_app::Window_ios::PaintWindow();
+
+        app->onIdle();
+    }
+    delete app;
+}
+
+- (BOOL)application:(UIApplication *)application
+        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+    // let the system event loop run once, then launch into our main loop
+    [self performSelector:@selector(launchApp) withObject:nil afterDelay:0.0];
+
+    return YES;
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////
+
+int main(int argc, char **argv) {
+    /* Give over control to run loop, AppDelegate will handle most things from here */
+    @autoreleasepool {
+        UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+    }
+
+    return EXIT_SUCCESS;
+}