Introduce wgpu::Surface and implement it for HWND, X11 and Metal

This is another step to implement webgpu.h swapchains, Surface is
essentially a union type of all the types of windows that can be used to
create swapchains.

Changes to allow implementing wgpu::Surface and test its creation are:

 - Add GLFWUtils.cpp/.h/_metal.mm  that contains helpers used to use
WebGPU with GLFW. This deprecates BackendBinding.h that will be removed
when the NXT swapchain is removed.
 - Add a `dawn_use_x11` GN variable to factor all the places in BUILD.gn
where we checked whether we should use X11.
 - Add a `supports_glfw_for_windowing` GN variable in the main BUILD.gn
file to control which configuration tests and samples using GLFW can be
built.
 - Add a ObjCUtils.h to contain some ObjC functionality that we'd need
in files that otherwise would be C++ (so that they can be compiled on
all platforms).

Bug: dawn:269

Change-Id: I25548142a1d1d1f05b0f4d71aa3bdc4698d19622
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15081
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index c013899..866b2d4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -263,6 +263,8 @@
     "src/dawn_native/ShaderModule.h",
     "src/dawn_native/StagingBuffer.cpp",
     "src/dawn_native/StagingBuffer.h",
+    "src/dawn_native/Surface.cpp",
+    "src/dawn_native/Surface.h",
     "src/dawn_native/SwapChain.cpp",
     "src/dawn_native/SwapChain.h",
     "src/dawn_native/Texture.cpp",
@@ -346,8 +348,10 @@
       "Cocoa.framework",
       "IOKit.framework",
       "IOSurface.framework",
+      "QuartzCore.framework",
     ]
     sources += [
+      "src/dawn_native/Surface_metal.mm",
       "src/dawn_native/metal/BackendMTL.h",
       "src/dawn_native/metal/BackendMTL.mm",
       "src/dawn_native/metal/BufferMTL.h",
@@ -541,6 +545,10 @@
           [ "DAWN_SWIFTSHADER_VK_ICD_JSON=\"${swiftshader_icd_file_name}\"" ]
     }
   }
+
+  if (dawn_use_x11) {
+    libs += [ "X11" ]
+  }
 }
 
 # The static and shared libraries for libdawn_native. Most of the files are
@@ -662,10 +670,12 @@
 # GLFW wrapping target
 ###############################################################################
 
+supports_glfw_for_windowing = is_win || (is_linux && !is_chromeos) || is_mac
+
 # GLFW does not support ChromeOS, Android or Fuchsia, so provide a small mock
 # library that can be linked into the Dawn tests on these platforms. Otherwise,
 # use the real library from third_party/.
-if (is_win || (is_linux && !is_chromeos) || is_mac) {
+if (supports_glfw_for_windowing) {
   group("dawn_glfw") {
     public_deps = [
       "third_party:glfw",
@@ -724,25 +734,43 @@
     "src/utils/WGPUHelpers.cpp",
     "src/utils/WGPUHelpers.h",
   ]
-
-  if (is_win) {
-    sources += [ "src/utils/WindowsTimer.cpp" ]
-  } else if (is_mac) {
-    sources += [ "src/utils/OSXTimer.cpp" ]
-  } else {
-    sources += [ "src/utils/PosixTimer.cpp" ]
-  }
-
-  public_deps = [
-    "${dawn_root}/src/dawn:dawncpp_headers",
-  ]
-
   deps = [
     ":libdawn_native",
     ":libdawn_wire",
     "${dawn_root}/src/common",
     "${dawn_shaderc_dir}:libshaderc",
   ]
+  libs = []
+
+  if (is_win) {
+    sources += [ "src/utils/WindowsTimer.cpp" ]
+  } else if (is_mac) {
+    sources += [
+      "src/utils/OSXTimer.cpp",
+      "src/utils/ObjCUtils.h",
+      "src/utils/ObjCUtils.mm",
+    ]
+    libs += [ "QuartzCore.framework" ]
+  } else {
+    sources += [ "src/utils/PosixTimer.cpp" ]
+  }
+
+  if (supports_glfw_for_windowing) {
+    sources += [
+      "src/utils/GLFWUtils.cpp",
+      "src/utils/GLFWUtils.h",
+    ]
+    deps += [ ":dawn_glfw" ]
+
+    if (dawn_enable_metal) {
+      sources += [ "src/utils/GLFWUtils_metal.mm" ]
+      libs += [ "Metal.framework" ]
+    }
+  }
+
+  public_deps = [
+    "${dawn_root}/src/dawn:dawncpp_headers",
+  ]
 }
 
 ###############################################################################
@@ -915,12 +943,6 @@
 
   libs = []
 
-  if (dawn_enable_metal) {
-    sources += [ "src/tests/end2end/IOSurfaceWrappingTests.cpp" ]
-
-    libs += [ "IOSurface.framework" ]
-  }
-
   if (dawn_enable_d3d12) {
     sources += [ "src/tests/end2end/D3D12ResourceWrappingTests.cpp" ]
     libs += [
@@ -929,7 +951,17 @@
     ]
   }
 
+  if (dawn_enable_metal) {
+    sources += [ "src/tests/end2end/IOSurfaceWrappingTests.cpp" ]
+    libs += [ "IOSurface.framework" ]
+  }
+
   if (dawn_enable_opengl) {
+    assert(supports_glfw_for_windowing)
+  }
+
+  if (supports_glfw_for_windowing) {
+    sources += [ "src/tests/end2end/WindowSurfaceTests.cpp" ]
     deps += [ ":dawn_glfw" ]
   }
 }
diff --git a/dawn.json b/dawn.json
index 8f1b0b7..c8a318ae 100644
--- a/dawn.json
+++ b/dawn.json
@@ -771,7 +771,16 @@
         ]
     },
     "instance": {
-        "category": "object"
+        "category": "object",
+        "methods": [
+            {
+                "name": "create surface",
+                "returns": "surface",
+                "args": [
+                    {"name": "descriptor", "type": "surface descriptor", "annotation": "const*"}
+                ]
+            }
+        ]
     },
     "instance descriptor": {
         "category": "structure",
@@ -1274,6 +1283,39 @@
             {"name": "pass op", "type": "stencil operation", "default": "keep"}
         ]
     },
+    "surface": {
+        "category": "object"
+    },
+    "surface descriptor": {
+        "category": "structure",
+        "extensible": true,
+        "members": [
+            {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}
+        ]
+    },
+    "surface descriptor from metal layer": {
+        "category": "structure",
+        "chained": true,
+        "members": [
+            {"name": "layer", "type": "void", "annotation": "*"}
+        ]
+    },
+    "surface descriptor from windows HWND": {
+        "category": "structure",
+        "chained": true,
+        "members": [
+            {"name": "hinstance", "type": "void", "annotation": "*"},
+            {"name": "hwnd", "type": "void", "annotation": "*"}
+        ]
+    },
+    "surface descriptor from xlib": {
+        "category": "structure",
+        "chained": true,
+        "members": [
+            {"name": "display", "type": "void", "annotation": "*"},
+            {"name": "window", "type": "uint32_t"}
+        ]
+    },
     "swap chain": {
         "category": "object",
         "methods": [
@@ -1301,7 +1343,10 @@
     "s type": {
         "category": "enum",
         "values": [
-            {"value": 0, "name": "invalid"}
+            {"value": 0, "name": "invalid"},
+            {"value": 1, "name": "surface descriptor from metal layer"},
+            {"value": 2, "name": "surface descriptor from windows HWND"},
+            {"value": 3, "name": "surface descriptor from xlib"}
         ]
     },
     "texture": {
diff --git a/dawn_wire.json b/dawn_wire.json
index a8cffcb..6493349 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -89,7 +89,10 @@
     },
     "special items": {
         "client_side_structures": [
-            "CreateBufferMappedResult"
+            "CreateBufferMappedResult",
+            "SurfaceDescriptorFromMetalLayer",
+            "SurfaceDescriptorFromWindowsHWND",
+            "SurfaceDescriptorFromXlib"
         ],
         "client_side_commands": [
             "BufferMapReadAsync",
diff --git a/examples/SampleUtils.cpp b/examples/SampleUtils.cpp
index b3d87a1..031f5d5 100644
--- a/examples/SampleUtils.cpp
+++ b/examples/SampleUtils.cpp
@@ -18,6 +18,7 @@
 #include "common/Log.h"
 #include "common/Platform.h"
 #include "utils/BackendBinding.h"
+#include "utils/GLFWUtils.h"
 #include "utils/TerribleCommandBuffer.h"
 
 #include <dawn/dawn_proc.h>
diff --git a/generator/templates/dawn_native/ProcTable.cpp b/generator/templates/dawn_native/ProcTable.cpp
index 1713ac2..88b780c 100644
--- a/generator/templates/dawn_native/ProcTable.cpp
+++ b/generator/templates/dawn_native/ProcTable.cpp
@@ -33,6 +33,7 @@
     using FenceBase = Fence;
     using RenderPassEncoderBase = RenderPassEncoder;
     using RenderBundleEncoderBase = RenderBundleEncoder;
+    using SurfaceBase = Surface;
 
     namespace {
 
diff --git a/scripts/dawn_features.gni b/scripts/dawn_features.gni
index c82d228..2ef8e15 100644
--- a/scripts/dawn_features.gni
+++ b/scripts/dawn_features.gni
@@ -54,6 +54,9 @@
   # Enables error injection for faking failures to native API calls
   dawn_enable_error_injection =
       is_debug || (build_with_chromium && use_fuzzing_engine)
+
+  # Whether Dawn should enable X11 support.
+  dawn_use_x11 = is_linux && !is_chromeos
 }
 
 # GN does not allow reading a variable defined in the same declare_args().
diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn
index 6306ed9..14bcf0a 100644
--- a/src/common/BUILD.gn
+++ b/src/common/BUILD.gn
@@ -75,7 +75,7 @@
     defines += [ "DAWN_ENABLE_BACKEND_VULKAN" ]
   }
 
-  if (is_linux && !is_chromeos) {
+  if (dawn_use_x11) {
     defines += [ "DAWN_USE_X11" ]
   }
 
diff --git a/src/common/windows_with_undefs.h b/src/common/windows_with_undefs.h
index e19552f..381116a 100644
--- a/src/common/windows_with_undefs.h
+++ b/src/common/windows_with_undefs.h
@@ -26,6 +26,7 @@
 #include <windows.h>
 
 // Macros defined for ANSI / Unicode support
+#undef CreateWindow
 #undef GetMessage
 
 // Macros defined to produce compiler intrinsics
diff --git a/src/common/xlib_with_undefs.h b/src/common/xlib_with_undefs.h
index 794ce0f..f82a19a 100644
--- a/src/common/xlib_with_undefs.h
+++ b/src/common/xlib_with_undefs.h
@@ -30,4 +30,6 @@
 #undef None
 #undef Always
 
+using XErrorHandler = int (*)(Display*, XErrorEvent*);
+
 #endif  // COMMON_XLIB_WITH_UNDEFS_H_
diff --git a/src/dawn_native/Instance.cpp b/src/dawn_native/Instance.cpp
index 735c617..982c979 100644
--- a/src/dawn_native/Instance.cpp
+++ b/src/dawn_native/Instance.cpp
@@ -17,6 +17,7 @@
 #include "common/Assert.h"
 #include "common/Log.h"
 #include "dawn_native/ErrorData.h"
+#include "dawn_native/Surface.h"
 
 namespace dawn_native {
 
@@ -211,4 +212,12 @@
         return mPlatform;
     }
 
+    Surface* InstanceBase::CreateSurface(const SurfaceDescriptor* descriptor) {
+        if (ConsumedError(ValidateSurfaceDescriptor(this, descriptor))) {
+            return nullptr;
+        }
+
+        return new Surface(this, descriptor);
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/Instance.h b/src/dawn_native/Instance.h
index 5095664..e07d04b 100644
--- a/src/dawn_native/Instance.h
+++ b/src/dawn_native/Instance.h
@@ -29,6 +29,8 @@
 
 namespace dawn_native {
 
+    class Surface;
+
     // This is called InstanceBase for consistency across the frontend, even if the backends don't
     // specialize this class.
     class InstanceBase final : public RefCounted {
@@ -64,6 +66,9 @@
         void SetPlatform(dawn_platform::Platform* platform);
         dawn_platform::Platform* GetPlatform() const;
 
+        // Dawn API
+        Surface* CreateSurface(const SurfaceDescriptor* descriptor);
+
       private:
         InstanceBase() = default;
         ~InstanceBase() = default;
diff --git a/src/dawn_native/Surface.cpp b/src/dawn_native/Surface.cpp
new file mode 100644
index 0000000..0d02144
--- /dev/null
+++ b/src/dawn_native/Surface.cpp
@@ -0,0 +1,172 @@
+// Copyright 2020 the Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn_native/Surface.h"
+
+#include "common/Platform.h"
+#include "dawn_native/Instance.h"
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+#    include "common/windows_with_undefs.h"
+#endif  // DAWN_PLATFORM_WINDOWS
+
+#if defined(DAWN_USE_X11)
+#    include "common/xlib_with_undefs.h"
+#endif  // defined(DAWN_USE_X11)
+
+namespace dawn_native {
+
+#if defined(DAWN_ENABLE_BACKEND_METAL)
+    bool InheritsFromCAMetalLayer(void* obj);
+#endif  // defined(DAWN_ENABLE_BACKEND_METAL)
+
+    MaybeError ValidateSurfaceDescriptor(const InstanceBase* instance,
+                                         const SurfaceDescriptor* descriptor) {
+        // TODO(cwallez@chromium.org): Have some type of helper to iterate over all the chained
+        // structures.
+        if (descriptor->nextInChain == nullptr) {
+            return DAWN_VALIDATION_ERROR("Surface cannot be created with just the base descriptor");
+        }
+
+        const ChainedStruct* chainedDescriptor = descriptor->nextInChain;
+        if (chainedDescriptor->nextInChain != nullptr) {
+            return DAWN_VALIDATION_ERROR("Cannot specify two windows for a single surface");
+        }
+
+        switch (chainedDescriptor->sType) {
+#if defined(DAWN_ENABLE_BACKEND_METAL)
+            case wgpu::SType::SurfaceDescriptorFromMetalLayer: {
+                const SurfaceDescriptorFromMetalLayer* metalDesc =
+                    static_cast<const SurfaceDescriptorFromMetalLayer*>(chainedDescriptor);
+
+                // Check that the layer is a CAMetalLayer (or a derived class).
+                if (!InheritsFromCAMetalLayer(metalDesc->layer)) {
+                    return DAWN_VALIDATION_ERROR("layer must be a CAMetalLayer");
+                }
+            } break;
+#endif  // defined(DAWN_ENABLE_BACKEND_METAL)
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+            case wgpu::SType::SurfaceDescriptorFromWindowsHWND: {
+                const SurfaceDescriptorFromWindowsHWND* hwndDesc =
+                    static_cast<const SurfaceDescriptorFromWindowsHWND*>(chainedDescriptor);
+
+                // It is not possible to validate an HINSTANCE.
+
+                // Validate the hwnd using the windows.h IsWindow function.
+                if (IsWindow(static_cast<HWND>(hwndDesc->hwnd)) == 0) {
+                    return DAWN_VALIDATION_ERROR("Invalid HWND");
+                }
+            } break;
+#endif  // defined(DAWN_PLATFORM_WINDOWS)
+
+#if defined(DAWN_USE_X11)
+            case wgpu::SType::SurfaceDescriptorFromXlib: {
+                const SurfaceDescriptorFromXlib* xDesc =
+                    static_cast<const SurfaceDescriptorFromXlib*>(chainedDescriptor);
+
+                // It is not possible to validate an X Display.
+
+                // Check the validity of the window by calling a getter function on the window that
+                // returns a status code. If the window is bad the call return a status of zero. We
+                // need to set a temporary X11 error handler while doing this because the default
+                // X11 error handler exits the program on any error.
+                XErrorHandler oldErrorHandler =
+                    XSetErrorHandler([](Display*, XErrorEvent*) { return 0; });
+                XWindowAttributes attributes;
+                int status = XGetWindowAttributes(reinterpret_cast<Display*>(xDesc->display),
+                                                  xDesc->window, &attributes);
+                XSetErrorHandler(oldErrorHandler);
+
+                if (status == 0) {
+                    return DAWN_VALIDATION_ERROR("Invalid X Window");
+                }
+            } break;
+#endif  // defined(DAWN_USE_X11)
+
+            default:
+                return DAWN_VALIDATION_ERROR("Unsupported sType");
+        }
+
+        return {};
+    }
+
+    Surface::Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor)
+        : mInstance(instance) {
+        ASSERT(descriptor->nextInChain != nullptr);
+        const ChainedStruct* chainedDescriptor = descriptor->nextInChain;
+
+        switch (chainedDescriptor->sType) {
+            case wgpu::SType::SurfaceDescriptorFromMetalLayer: {
+                const SurfaceDescriptorFromMetalLayer* metalDesc =
+                    static_cast<const SurfaceDescriptorFromMetalLayer*>(chainedDescriptor);
+                mType = Type::MetalLayer;
+                mMetalLayer = metalDesc->layer;
+            } break;
+
+            case wgpu::SType::SurfaceDescriptorFromWindowsHWND: {
+                const SurfaceDescriptorFromWindowsHWND* hwndDesc =
+                    static_cast<const SurfaceDescriptorFromWindowsHWND*>(chainedDescriptor);
+                mType = Type::WindowsHWND;
+                mHInstance = hwndDesc->hinstance;
+                mHWND = hwndDesc->hwnd;
+            } break;
+
+            case wgpu::SType::SurfaceDescriptorFromXlib: {
+                const SurfaceDescriptorFromXlib* xDesc =
+                    static_cast<const SurfaceDescriptorFromXlib*>(chainedDescriptor);
+                mType = Type::Xlib;
+                mXDisplay = xDesc->display;
+                mXWindow = xDesc->window;
+            } break;
+
+            default:
+                UNREACHABLE();
+        }
+    }
+
+    Surface::~Surface() = default;
+
+    InstanceBase* Surface::GetInstance() {
+        return mInstance.Get();
+    }
+
+    Surface::Type Surface::GetType() const {
+        return mType;
+    }
+
+    void* Surface::GetMetalLayer() const {
+        ASSERT(mType == Type::MetalLayer);
+        return mMetalLayer;
+    }
+
+    void* Surface::GetHInstance() const {
+        ASSERT(mType == Type::WindowsHWND);
+        return mHInstance;
+    }
+    void* Surface::GetHWND() const {
+        ASSERT(mType == Type::WindowsHWND);
+        return mHWND;
+    }
+
+    void* Surface::GetXDisplay() const {
+        ASSERT(mType == Type::Xlib);
+        return mXDisplay;
+    }
+    uint32_t Surface::GetXWindow() const {
+        ASSERT(mType == Type::Xlib);
+        return mXWindow;
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/Surface.h b/src/dawn_native/Surface.h
new file mode 100644
index 0000000..8f9ca14
--- /dev/null
+++ b/src/dawn_native/Surface.h
@@ -0,0 +1,71 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_SURFACE_H_
+#define DAWNNATIVE_SURFACE_H_
+
+#include "dawn_native/Error.h"
+#include "dawn_native/Forward.h"
+#include "dawn_native/RefCounted.h"
+
+#include "dawn_native/dawn_platform.h"
+
+namespace dawn_native {
+
+    MaybeError ValidateSurfaceDescriptor(const InstanceBase* instance,
+                                         const SurfaceDescriptor* descriptor);
+
+    // A surface is a sum types of all the kind of windows Dawn supports. The OS-specific types
+    // aren't used because they would cause compilation errors on other OSes (or require
+    // ObjectiveC).
+    class Surface final : public RefCounted {
+      public:
+        Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor);
+        ~Surface();
+
+        // These are valid to call on all Surfaces.
+        enum class Type { MetalLayer, WindowsHWND, Xlib };
+        Type GetType() const;
+        InstanceBase* GetInstance();
+
+        // Valid to call if the type is MetalLayer
+        void* GetMetalLayer() const;
+
+        // Valid to call if the type is WindowsHWND
+        void* GetHInstance() const;
+        void* GetHWND() const;
+
+        // Valid to call if the type is WindowsXlib
+        void* GetXDisplay() const;
+        uint32_t GetXWindow() const;
+
+      private:
+        Ref<InstanceBase> mInstance;
+        Type mType;
+
+        // MetalLayer
+        void* mMetalLayer = nullptr;
+
+        // WindowsHwnd
+        void* mHInstance = nullptr;
+        void* mHWND = nullptr;
+
+        // Xlib
+        void* mXDisplay = nullptr;
+        uint32_t mXWindow = 0;
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_SURFACE_H_
diff --git a/src/dawn_native/Surface_metal.mm b/src/dawn_native/Surface_metal.mm
new file mode 100644
index 0000000..9989674
--- /dev/null
+++ b/src/dawn_native/Surface_metal.mm
@@ -0,0 +1,30 @@
+// Copyright 2020 the Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Contains a helper function for Surface.cpp that needs to be written in ObjectiveC.
+
+#if !defined(DAWN_ENABLE_BACKEND_METAL)
+#    error "Surface_metal.mm requires the Metal backend to be enabled."
+#endif  // !defined(DAWN_ENABLE_BACKEND_METAL)
+
+#import <QuartzCore/CAMetalLayer.h>
+
+namespace dawn_native {
+
+    bool InheritsFromCAMetalLayer(void* obj) {
+        id<NSObject> object = static_cast<id>(obj);
+        return [object isKindOfClass:[CAMetalLayer class]];
+    }
+
+}  // namespace dawn_native
diff --git a/src/tests/end2end/WindowSurfaceTests.cpp b/src/tests/end2end/WindowSurfaceTests.cpp
new file mode 100644
index 0000000..beb301b
--- /dev/null
+++ b/src/tests/end2end/WindowSurfaceTests.cpp
@@ -0,0 +1,236 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/Log.h"
+#include "common/Platform.h"
+#include "dawn/dawn_proc.h"
+#include "dawn_native/DawnNative.h"
+#include "utils/GLFWUtils.h"
+
+#include <gtest/gtest.h>
+#include "GLFW/glfw3.h"
+
+#include <cstdlib>
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+#    include "common/windows_with_undefs.h"
+#endif  // defined(DAWN_PLATFORM_WINDOWS)
+
+#if defined(DAWN_USE_X11)
+#    include "common/xlib_with_undefs.h"
+#endif  // defined(DAWN_USE_X11)
+
+#if defined(DAWN_ENABLE_BACKEND_METAL)
+#    include "utils/ObjCUtils.h"
+#endif  // defined(DAWN_ENABLE_BACKEND_METAL)
+
+#include "GLFW/glfw3native.h"
+
+class WindowSurfaceInstanceTests : public testing::Test {
+  public:
+    void SetUp() override {
+        glfwSetErrorCallback([](int code, const char* message) {
+            dawn::ErrorLog() << "GLFW error " << code << " " << message;
+        });
+        glfwInit();
+
+        DawnProcTable procs = dawn_native::GetProcs();
+        dawnProcSetProcs(&procs);
+
+        mInstance = wgpu::CreateInstance();
+    }
+
+    void TearDown() override {
+        if (mWindow != nullptr) {
+            glfwDestroyWindow(mWindow);
+            mWindow = nullptr;
+        }
+    }
+
+    void AssertSurfaceCreation(const wgpu::SurfaceDescriptor* descriptor, bool succeeds) {
+        ASSERT_EQ(mInstance.CreateSurface(descriptor).Get() != nullptr, succeeds);
+    }
+
+    GLFWwindow* CreateWindow() {
+        // The WindowSurfaceInstance tests don't create devices so we don't need to call
+        // SetupGLFWWindowHintsForBackend. Set GLFW_NO_API anyway to avoid GLFW bringing up a GL
+        // context that we won't use.
+        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+        mWindow = glfwCreateWindow(400, 400, "WindowSurfaceInstanceTests window", nullptr, nullptr);
+        return mWindow;
+    }
+
+  private:
+    wgpu::Instance mInstance;
+    GLFWwindow* mWindow = nullptr;
+};
+
+// Test that a valid chained descriptor works (and that GLFWUtils creates a valid chained
+// descriptor).
+TEST_F(WindowSurfaceInstanceTests, ControlCase) {
+    GLFWwindow* window = CreateWindow();
+    std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor =
+        utils::SetupWindowAndGetSurfaceDescriptorForTesting(window);
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = chainedDescriptor.get();
+
+    AssertSurfaceCreation(&descriptor, true);
+}
+
+// Test that just wgpu::SurfaceDescriptor isn't enough and needs a chained descriptor.
+TEST_F(WindowSurfaceInstanceTests, NoChainedDescriptors) {
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = nullptr;  // That's the default value but we set it for clarity.
+
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+// Test that a chained descriptor with a garbage sType produces an error.
+TEST_F(WindowSurfaceInstanceTests, BadChainedDescriptors) {
+    wgpu::ChainedStruct chainedDescriptor;
+    chainedDescriptor.sType = wgpu::SType::Invalid;  // The default but we set it for clarity.
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+// Test that it is invalid to give two valid chained descriptors
+TEST_F(WindowSurfaceInstanceTests, TwoChainedDescriptors) {
+    GLFWwindow* window = CreateWindow();
+    std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor1 =
+        utils::SetupWindowAndGetSurfaceDescriptorForTesting(window);
+    std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor2 =
+        utils::SetupWindowAndGetSurfaceDescriptorForTesting(window);
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = chainedDescriptor1.get();
+    chainedDescriptor1->nextInChain = chainedDescriptor2.get();
+
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+
+// Tests that GLFWUtils returns a descriptor of HWND type
+TEST_F(WindowSurfaceInstanceTests, CorrectSTypeHWND) {
+    GLFWwindow* window = CreateWindow();
+    std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor =
+        utils::SetupWindowAndGetSurfaceDescriptorForTesting(window);
+    ASSERT_EQ(chainedDescriptor->sType, wgpu::SType::SurfaceDescriptorFromWindowsHWND);
+}
+
+// Test with setting an invalid hwnd
+TEST_F(WindowSurfaceInstanceTests, InvalidHWND) {
+    wgpu::SurfaceDescriptorFromWindowsHWND chainedDescriptor;
+    chainedDescriptor.hinstance = GetModuleHandle(nullptr);
+    chainedDescriptor.hwnd = 0;  // This always is an invalid HWND value.
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#else  // defined(DAWN_PLATFORM_WINDOWS)
+
+// Test using HWND when it is not supported
+TEST_F(WindowSurfaceInstanceTests, HWNDSurfacesAreInvalid) {
+    wgpu::SurfaceDescriptorFromWindowsHWND chainedDescriptor;
+    chainedDescriptor.hinstance = nullptr;
+    chainedDescriptor.hwnd = 0;
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#endif  // defined(DAWN_PLATFORM_WINDOWS)
+
+#if defined(DAWN_USE_X11)
+
+// Tests that GLFWUtils returns a descriptor of Xlib type
+TEST_F(WindowSurfaceInstanceTests, CorrectSTypeXlib) {
+    GLFWwindow* window = CreateWindow();
+    std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor =
+        utils::SetupWindowAndGetSurfaceDescriptorForTesting(window);
+    ASSERT_EQ(chainedDescriptor->sType, wgpu::SType::SurfaceDescriptorFromXlib);
+}
+
+// Test with setting an invalid window
+TEST_F(WindowSurfaceInstanceTests, InvalidXWindow) {
+    wgpu::SurfaceDescriptorFromXlib chainedDescriptor;
+    chainedDescriptor.display = XOpenDisplay(nullptr);
+    // From the "X Window System Protocol" "X Version 11, Release 6.8" page 2 at
+    // https://www.x.org/releases/X11R7.5/doc/x11proto/proto.pdf
+    //    WINDOW 32-bit value (top three bits guaranteed to be zero.
+    // So UINT32_MAX should be an invalid window.
+    chainedDescriptor.window = 0xFFFFFFFF;
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#else  // defined(DAWN_USE_X11)
+
+// Test using Xlib when it is not supported
+TEST_F(WindowSurfaceInstanceTests, XlibSurfacesAreInvalid) {
+    wgpu::SurfaceDescriptorFromXlib chainedDescriptor;
+    chainedDescriptor.display = nullptr;
+    chainedDescriptor.window = 0;
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#endif  // defined(DAWN_USE_X11)
+
+#if defined(DAWN_ENABLE_BACKEND_METAL)
+
+// Tests that GLFWUtils returns a descriptor of Metal type
+TEST_F(WindowSurfaceInstanceTests, CorrectSTypeMetal) {
+    GLFWwindow* window = CreateWindow();
+    std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor =
+        utils::SetupWindowAndGetSurfaceDescriptorForTesting(window);
+    ASSERT_EQ(chainedDescriptor->sType, wgpu::SType::SurfaceDescriptorFromMetalLayer);
+}
+
+// Test with setting an invalid layer
+TEST_F(WindowSurfaceInstanceTests, InvalidMetalLayer) {
+    wgpu::SurfaceDescriptorFromMetalLayer chainedDescriptor;
+    // The CALayer is autoreleased. Releasing it causes a test failure when the Chromium GTest
+    // autoreleasepool is emptied.
+    chainedDescriptor.layer = utils::CreateDummyCALayer();
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#else  // defined(DAWN_ENABLE_BACKEND_METAL)
+
+// Test using Metal when it is not supported
+TEST_F(WindowSurfaceInstanceTests, MetalSurfacesAreInvalid) {
+    wgpu::SurfaceDescriptorFromMetalLayer chainedDescriptor;
+    chainedDescriptor.layer = nullptr;
+
+    wgpu::SurfaceDescriptor descriptor;
+    descriptor.nextInChain = &chainedDescriptor;
+    AssertSurfaceCreation(&descriptor, false);
+}
+
+#endif  // defined(DAWN_ENABLE_BACKEND_METAL)
diff --git a/src/utils/BackendBinding.cpp b/src/utils/BackendBinding.cpp
index 0e4fff3..4fe17b0 100644
--- a/src/utils/BackendBinding.cpp
+++ b/src/utils/BackendBinding.cpp
@@ -44,17 +44,6 @@
         : mWindow(window), mDevice(device) {
     }
 
-    void SetupGLFWWindowHintsForBackend(wgpu::BackendType type) {
-        if (type == wgpu::BackendType::OpenGL) {
-            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
-            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
-            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
-            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
-        } else {
-            glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
-        }
-    }
-
     void DiscoverAdapter(dawn_native::Instance* instance,
                          GLFWwindow* window,
                          wgpu::BackendType type) {
diff --git a/src/utils/BackendBinding.h b/src/utils/BackendBinding.h
index 26d749a..ca1c91f 100644
--- a/src/utils/BackendBinding.h
+++ b/src/utils/BackendBinding.h
@@ -36,7 +36,6 @@
         WGPUDevice mDevice = nullptr;
     };
 
-    void SetupGLFWWindowHintsForBackend(wgpu::BackendType type);
     void DiscoverAdapter(dawn_native::Instance* instance,
                          GLFWwindow* window,
                          wgpu::BackendType type);
diff --git a/src/utils/GLFWUtils.cpp b/src/utils/GLFWUtils.cpp
new file mode 100644
index 0000000..fe9195e
--- /dev/null
+++ b/src/utils/GLFWUtils.cpp
@@ -0,0 +1,83 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "utils/GLFWUtils.h"
+
+#include "GLFW/glfw3.h"
+#include "common/Platform.h"
+
+#include <cstdlib>
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+#    define GLFW_EXPOSE_NATIVE_WIN32
+#elif defined(DAWN_USE_X11)
+#    define GLFW_EXPOSE_NATIVE_X11
+#endif
+#include "GLFW/glfw3native.h"
+
+namespace utils {
+
+    void SetupGLFWWindowHintsForBackend(wgpu::BackendType type) {
+        if (type == wgpu::BackendType::OpenGL) {
+            // Ask for OpenGL 4.4 which is what the GL backend requires for compute shaders and
+            // texture views.
+            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
+            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
+            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+        } else {
+            // Without this GLFW will initialize a GL context on the window, which prevents using
+            // the window with other APIs (by crashing in weird ways).
+            glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+        }
+    }
+
+    wgpu::Surface CreateSurfaceForWindow(wgpu::Instance instance, GLFWwindow* window) {
+        std::unique_ptr<wgpu::ChainedStruct> chainedDescriptor =
+            SetupWindowAndGetSurfaceDescriptorForTesting(window);
+
+        wgpu::SurfaceDescriptor descriptor;
+        descriptor.nextInChain = chainedDescriptor.get();
+        wgpu::Surface surface = instance.CreateSurface(&descriptor);
+
+        return surface;
+    }
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+    std::unique_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorForTesting(
+        GLFWwindow* window) {
+        std::unique_ptr<wgpu::SurfaceDescriptorFromWindowsHWND> desc =
+            std::make_unique<wgpu::SurfaceDescriptorFromWindowsHWND>();
+        desc->hwnd = glfwGetWin32Window(window);
+        desc->hinstance = GetModuleHandle(nullptr);
+        return desc;
+    }
+#elif defined(DAWN_USE_X11)
+    std::unique_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorForTesting(
+        GLFWwindow* window) {
+        std::unique_ptr<wgpu::SurfaceDescriptorFromXlib> desc =
+            std::make_unique<wgpu::SurfaceDescriptorFromXlib>();
+        desc->display = glfwGetX11Display();
+        desc->window = glfwGetX11Window(window);
+        return desc;
+    }
+#elif defined(DAWN_ENABLE_BACKEND_METAL)
+    // SetupWindowAndGetSurfaceDescriptorForTesting defined in GLFWUtils_metal.mm
+#else
+    std::unique_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorForTesting(GLFWwindow*) {
+        return nullptr;
+    }
+#endif
+
+}  // namespace utils
diff --git a/src/utils/GLFWUtils.h b/src/utils/GLFWUtils.h
new file mode 100644
index 0000000..f2299cb
--- /dev/null
+++ b/src/utils/GLFWUtils.h
@@ -0,0 +1,42 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef UTILS_GLFWUTILS_H_
+#define UTILS_GLFWUTILS_H_
+
+#include "dawn/webgpu_cpp.h"
+
+#include <memory>
+
+struct GLFWwindow;
+
+namespace utils {
+
+    // Adds all the necessary glfwWindowHint calls for the next GLFWwindow created to be used with
+    // the specified backend.
+    void SetupGLFWWindowHintsForBackend(wgpu::BackendType type);
+
+    // Does the necessary setup on the GLFWwindow to allow creating a wgpu::Surface with it and
+    // calls `instance.CreateSurface` with the correct descriptor for this window.
+    // Returns a null wgpu::Surface on failure.
+    wgpu::Surface CreateSurfaceForWindow(wgpu::Instance instance, GLFWwindow* window);
+
+    // Use for testing only. Does everything that CreateSurfaceForWindow does except the call to
+    // CreateSurface so the descriptor can be modified for testing.
+    std::unique_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorForTesting(
+        GLFWwindow* window);
+
+}  // namespace utils
+
+#endif  // UTILS_GLFWUTILS_H_
diff --git a/src/utils/GLFWUtils_metal.mm b/src/utils/GLFWUtils_metal.mm
new file mode 100644
index 0000000..ff09428
--- /dev/null
+++ b/src/utils/GLFWUtils_metal.mm
@@ -0,0 +1,54 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if !defined(DAWN_ENABLE_BACKEND_METAL)
+#    error "GLFWUtils_metal.mm requires the Metal backend to be enabled."
+#endif  // !defined(DAWN_ENABLE_BACKEND_METAL)
+
+#include "utils/GLFWUtils.h"
+
+#import <QuartzCore/CAMetalLayer.h>
+#include "GLFW/glfw3.h"
+
+#include <cstdlib>
+
+#define GLFW_EXPOSE_NATIVE_COCOA
+#include "GLFW/glfw3native.h"
+
+namespace utils {
+
+    std::unique_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorForTesting(
+        GLFWwindow* window) {
+        if (@available(macOS 10.11, *)) {
+            NSWindow* nsWindow = glfwGetCocoaWindow(window);
+            NSView* view = [nsWindow contentView];
+
+            // Create a CAMetalLayer that covers the whole window that will be passed to
+            // CreateSurface.
+            [view setWantsLayer:YES];
+            [view setLayer:[CAMetalLayer layer]];
+
+            // Use retina if the window was created with retina support.
+            [[view layer] setContentsScale:[nsWindow backingScaleFactor]];
+
+            std::unique_ptr<wgpu::SurfaceDescriptorFromMetalLayer> desc =
+                std::make_unique<wgpu::SurfaceDescriptorFromMetalLayer>();
+            desc->layer = [view layer];
+            return desc;
+        }
+
+        return nullptr;
+    }
+
+}  // namespace utils
diff --git a/src/utils/ObjCUtils.h b/src/utils/ObjCUtils.h
new file mode 100644
index 0000000..17b3956
--- /dev/null
+++ b/src/utils/ObjCUtils.h
@@ -0,0 +1,29 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef UTILS_OBJCUTILS_H_
+#define UTILS_OBJCUTILS_H_
+
+// Contains helper function to manipulate ObjC objects. This helps having C++ files do a little bit
+// of ObjectiveC calls, when they cannot be converted to ObjectiveC++ because they are used on
+// multiple platforms.
+
+namespace utils {
+
+    // The returned CALayer is autoreleased.
+    void* CreateDummyCALayer();
+
+}  // namespace utils
+
+#endif  // UTILS_OBJCUTILS_H_
diff --git a/src/utils/ObjCUtils.mm b/src/utils/ObjCUtils.mm
new file mode 100644
index 0000000..5eba147
--- /dev/null
+++ b/src/utils/ObjCUtils.mm
@@ -0,0 +1,25 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "utils/ObjCUtils.h"
+
+#include <QuartzCore/CALayer.h>
+
+namespace utils {
+
+    void* CreateDummyCALayer() {
+        return [CALayer layer];
+    }
+
+}  // namespace utils