Vulkan: Port renderer to Fuchsia

Add DisplayVk and WindowSurfaceVk subclasses for Fuchsia to the vulkan
renderer, as well as an implementation of OSWindow that renders
fullscreen for the test suite.

Disallow use of the vulkan loader from third_party as Fuchsia uses a fork
of the loader and has not sent those changes upstream yet.

Add a small wayland-inspired library libfuchsia-egl to provide a type
"struct fuchsia_egl_window" to use as EGLNativeWindowType. This type
combines a zx_handle_t to an image pipe channel and a surface size.

Image pipes can only be used once to create a VkSurfaceKHR. This means we
have to recreate the pipe in tests that call eglCreateWindowSurface more
than once with a single OSWindow, or the second call will fail. Add a
resetNativeWindow() method to accomplish this.

BUG=angleproject:2475
TEST=angle_end2end_tests on Fuchsia

Change-Id: I71a613a362dd1c8aada49a3c02ae461e064457bf
Reviewed-on: https://chromium-review.googlesource.com/c/1446496
Commit-Queue: Michael Spang <spang@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/.gitignore b/.gitignore
index b1f9c12..d82234a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,18 +21,22 @@
 *.vsp
 *~
 .*.sw*
+.cipd
 .gclient
 .gclient_entries
 /src/tests/third_party/gles_conformance_tests
 /testing
 /third_party/cherry
 /third_party/deqp/src
+/third_party/fuchsia-sdk
 /third_party/gles1_conform
 /third_party/glslang/src
 /third_party/googletest/src
 /third_party/jsoncpp
 /third_party/libpng/src
 /third_party/llvm-build
+/third_party/qemu-linux-x64
+/third_party/qemu-mac-x64
 /third_party/spirv-headers/src
 /third_party/spirv-tools/src
 /third_party/vulkan-headers/src
diff --git a/BUILD.gn b/BUILD.gn
index d40da71..1fbd198 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -494,6 +494,9 @@
       ]
       libs = [ "vulkan" ]
     }
+    if (is_fuchsia) {
+      defines = [ "VK_USE_PLATFORM_FUCHSIA" ]
+    }
   }
 
   # Use this target to include everything ANGLE needs for Vulkan.
@@ -503,7 +506,7 @@
     ]
     public_configs = [ ":vulkan_config" ]
     data_deps = []
-    if (!is_android) {
+    if (!is_android && !is_fuchsia) {
       deps = [
         "$angle_root/third_party/vulkan-loader:libvulkan",
       ]
@@ -511,11 +514,24 @@
       public_configs +=
           [ "$angle_root/third_party/vulkan-loader:vulkan_loader_config" ]
     }
+    if (is_fuchsia) {
+      public_deps += [
+        "$angle_root/src/common/fuchsia_egl",
+        "//third_party/fuchsia-sdk:vulkan_base",
+        "//third_party/fuchsia-sdk/sdk:vulkan",
+      ]
+    }
 
     if (angle_enable_vulkan_validation_layers) {
-      data_deps += [ "$angle_root/third_party/vulkan-validation-layers:vulkan_validation_layers" ]
-      if (!is_android) {
-        data_deps += [ "$angle_root/third_party/vulkan-validation-layers:vulkan_gen_json_files" ]
+      if (is_fuchsia) {
+        deps = [
+          "//third_party/fuchsia-sdk:vulkan_validation",
+        ]
+      } else {
+        data_deps += [ "$angle_root/third_party/vulkan-validation-layers:vulkan_validation_layers" ]
+        if (!is_android) {
+          data_deps += [ "$angle_root/third_party/vulkan-validation-layers:vulkan_gen_json_files" ]
+        }
       }
     }
   }
@@ -618,6 +634,13 @@
     if (is_linux) {
       sources += libangle_vulkan_xcb_sources
     }
+    if (is_fuchsia) {
+      sources += libangle_vulkan_fuchsia_sources
+      deps += [
+        "$angle_root/src/common/fuchsia_egl",
+        "$angle_root/src/common/fuchsia_egl:backend",
+      ]
+    }
     if (is_android) {
       sources += libangle_vulkan_android_sources
       libs += [ "vulkan" ]
@@ -924,10 +947,6 @@
       ]
     }
 
-    if (use_ozone) {
-      sources += util_ozone_sources
-    }
-
     configs += [ ":debug_annotations_config" ]
 
     public_configs += [ ":angle_util_config" ]
@@ -937,6 +956,24 @@
       ":angle_util_loader_headers",
     ]
 
+    public_deps = []
+    if (is_fuchsia) {
+      sources += util_fuchsia_sources
+      public_deps += [
+        "$angle_root/src/common/fuchsia_egl",
+        "//third_party/fuchsia-sdk/sdk:async_loop_cpp",
+        "//third_party/fuchsia-sdk/sdk:fdio",
+        "//third_party/fuchsia-sdk/sdk:images",
+        "//third_party/fuchsia-sdk/sdk:scenic_cpp",
+        "//third_party/fuchsia-sdk/sdk:ui_gfx",
+        "//third_party/fuchsia-sdk/sdk:ui_policy",
+        "//third_party/fuchsia-sdk/sdk:ui_scenic",
+        "//third_party/fuchsia-sdk/sdk:ui_viewsv1",
+      ]
+    } else if (use_ozone) {
+      sources += util_ozone_sources
+    }
+
     if (is_shared_library) {
       defines = [ "LIBANGLE_UTIL_IMPLEMENTATION" ]
 
@@ -944,9 +981,7 @@
         sources += util_win_shared_sources
       }
 
-      public_deps = [
-        ":angle_util_loader",
-      ]
+      public_deps += [ ":angle_util_loader" ]
 
       if (is_mac && !is_component_build) {
         ldflags = [
diff --git a/DEPS b/DEPS
index e038f99..eb8ac5f 100644
--- a/DEPS
+++ b/DEPS
@@ -36,12 +36,12 @@
 deps = {
 
   '{angle_root}/build': {
-    'url': '{chromium_git}/chromium/src/build.git@9dbdd5c2ae8c298bef55ca7c42754079aabe60c7',
+    'url': '{chromium_git}/chromium/src/build.git@9a53be87ebb636c35f2ed9772e5deaeb350d790b',
     'condition': 'not build_with_chromium',
   },
 
   '{angle_root}/buildtools': {
-    'url': '{chromium_git}/chromium/buildtools.git@24ebce4578745db15274e180da1938ebc1358243',
+    'url': '{chromium_git}/chromium/buildtools.git@6fbda1b24c1893a893b17aa219b765b9e7c801d8',
     'condition': 'not build_with_chromium',
   },
 
@@ -85,6 +85,28 @@
     'condition': 'not build_with_chromium',
    },
 
+  '{angle_root}/third_party/qemu-linux-x64': {
+      'packages': [
+          {
+              'package': 'fuchsia/qemu/linux-amd64',
+              'version': '9cc486c5b18a0be515c39a280ca9a309c54cf994'
+          },
+      ],
+      'condition': 'not build_with_chromium and (host_os == "linux" and checkout_fuchsia)',
+      'dep_type': 'cipd',
+  },
+
+  '{angle_root}/third_party/qemu-mac-x64': {
+      'packages': [
+          {
+              'package': 'fuchsia/qemu/mac-amd64',
+              'version': '2d3358ae9a569b2d4a474f498b32b202a152134f'
+          },
+      ],
+      'condition': 'not build_with_chromium and (host_os == "mac" and checkout_fuchsia)',
+      'dep_type': 'cipd',
+  },
+
   '{angle_root}/third_party/spirv-headers/src': {
     'url': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@{spirv_headers_revision}',
     'condition': 'not build_with_chromium',
@@ -120,6 +142,11 @@
     'url': '{chromium_git}/chromium/src/tools/clang.git@3114fbc11f9644c54dd0a4cdbfa867bac50ff983',
     'condition': 'not build_with_chromium',
   },
+
+  '{angle_root}/third_party/fuchsia-sdk': {
+    'url': '{chromium_git}/chromium/src/third_party/fuchsia-sdk.git@8e8db13b538ecb251e5ce9d5c781fc142f9752fd',
+    'condition': 'checkout_fuchsia and not build_with_chromium',
+  },
 }
 
 hooks = [
@@ -248,6 +275,16 @@
                 '-s', '{angle_root}/build/toolchain/win/rc/win/rc.exe.sha1',
     ],
   },
+
+  {
+    'name': 'fuchsia_sdk',
+    'pattern': '.',
+    'condition': 'checkout_fuchsia and not build_with_chromium',
+    'action': [
+      'python',
+      '{angle_root}/build/fuchsia/update_sdk.py',
+    ],
+  },
 ]
 
 recursedeps = [
diff --git a/gni/angle.gni b/gni/angle.gni
index dc16be4..5f16b63 100644
--- a/gni/angle.gni
+++ b/gni/angle.gni
@@ -63,7 +63,7 @@
                      (use_x11 && !is_chromeos)) && !is_fuchsia
 
   angle_enable_vulkan = is_win || (is_linux && use_x11 && !is_chromeos) ||
-                        (is_android && ndk_supports_vulkan)
+                        (is_android && ndk_supports_vulkan) || is_fuchsia
   angle_enable_null = true
   angle_enable_essl = true
   angle_enable_glsl = true
diff --git a/src/common/fuchsia_egl/BUILD.gn b/src/common/fuchsia_egl/BUILD.gn
new file mode 100644
index 0000000..84a1231
--- /dev/null
+++ b/src/common/fuchsia_egl/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("../../../gni/angle.gni")
+
+assert(is_fuchsia)
+
+config("config") {
+  include_dirs = [ "." ]
+}
+
+angle_shared_library("fuchsia_egl") {
+  sources = [
+    "fuchsia_egl.c",
+  ]
+  public = [
+    "fuchsia_egl.h",
+  ]
+  public_configs = [":config"]
+  deps = [
+    ":backend",
+  ]
+}
+
+angle_source_set("backend") {
+  public = [
+    "fuchsia_egl_backend.h",
+  ]
+}
diff --git a/src/common/fuchsia_egl/fuchsia_egl.c b/src/common/fuchsia_egl/fuchsia_egl.c
new file mode 100644
index 0000000..d22f1b5
--- /dev/null
+++ b/src/common/fuchsia_egl/fuchsia_egl.c
@@ -0,0 +1,75 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia_egl.h"
+#include "fuchsia_egl_backend.h"
+
+#include <zircon/assert.h>
+#include <zircon/syscalls.h>
+
+#define FUCHSIA_EGL_WINDOW_MAGIC 0x80738870  // "FXIP"
+
+struct fuchsia_egl_window
+{
+    uint32_t magic;
+    zx_handle_t image_pipe_handle;
+    int32_t width;
+    int32_t height;
+};
+
+fuchsia_egl_window *fuchsia_egl_window_create(zx_handle_t image_pipe_handle,
+                                              int32_t width,
+                                              int32_t height)
+{
+    if (width <= 0 || height <= 0)
+        return NULL;
+
+    fuchsia_egl_window *egl_window = malloc(sizeof(*egl_window));
+    egl_window->magic              = FUCHSIA_EGL_WINDOW_MAGIC;
+    egl_window->image_pipe_handle  = image_pipe_handle;
+    egl_window->width              = width;
+    egl_window->height             = height;
+    return egl_window;
+}
+
+void fuchsia_egl_window_destroy(fuchsia_egl_window *egl_window)
+{
+    ZX_ASSERT(egl_window->magic == FUCHSIA_EGL_WINDOW_MAGIC);
+    if (egl_window->image_pipe_handle != ZX_HANDLE_INVALID)
+    {
+        zx_handle_close(egl_window->image_pipe_handle);
+        egl_window->image_pipe_handle = ZX_HANDLE_INVALID;
+    }
+    egl_window->magic = -1U;
+    free(egl_window);
+}
+
+void fuchsia_egl_window_resize(fuchsia_egl_window *egl_window, int32_t width, int32_t height)
+{
+    ZX_ASSERT(egl_window->magic == FUCHSIA_EGL_WINDOW_MAGIC);
+    if (width <= 0 || height <= 0)
+        return;
+    egl_window->width  = width;
+    egl_window->height = height;
+}
+
+int32_t fuchsia_egl_window_get_width(fuchsia_egl_window *egl_window)
+{
+    ZX_ASSERT(egl_window->magic == FUCHSIA_EGL_WINDOW_MAGIC);
+    return egl_window->width;
+}
+
+int32_t fuchsia_egl_window_get_height(fuchsia_egl_window *egl_window)
+{
+    ZX_ASSERT(egl_window->magic == FUCHSIA_EGL_WINDOW_MAGIC);
+    return egl_window->height;
+}
+
+zx_handle_t fuchsia_egl_window_release_image_pipe(fuchsia_egl_window *egl_window)
+{
+    ZX_ASSERT(egl_window->magic == FUCHSIA_EGL_WINDOW_MAGIC);
+    zx_handle_t image_pipe_handle = egl_window->image_pipe_handle;
+    egl_window->image_pipe_handle = ZX_HANDLE_INVALID;
+    return image_pipe_handle;
+}
diff --git a/src/common/fuchsia_egl/fuchsia_egl.h b/src/common/fuchsia_egl/fuchsia_egl.h
new file mode 100644
index 0000000..068b87d
--- /dev/null
+++ b/src/common/fuchsia_egl/fuchsia_egl.h
@@ -0,0 +1,42 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_EGL_H_
+#define FUCHSIA_EGL_H_
+
+#include <inttypes.h>
+#include <zircon/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !defined(FUCHSIA_EGL_EXPORT)
+#    define FUCHSIA_EGL_EXPORT __attribute__((__visibility__("default")))
+#endif
+
+typedef struct fuchsia_egl_window fuchsia_egl_window;
+
+FUCHSIA_EGL_EXPORT
+fuchsia_egl_window *fuchsia_egl_window_create(zx_handle_t image_pipe_handle,
+                                              int32_t width,
+                                              int32_t height);
+
+FUCHSIA_EGL_EXPORT
+void fuchsia_egl_window_destroy(fuchsia_egl_window *egl_window);
+
+FUCHSIA_EGL_EXPORT
+void fuchsia_egl_window_resize(fuchsia_egl_window *egl_window, int32_t width, int32_t height);
+
+FUCHSIA_EGL_EXPORT
+int32_t fuchsia_egl_window_get_width(fuchsia_egl_window *egl_window);
+
+FUCHSIA_EGL_EXPORT
+int32_t fuchsia_egl_window_get_height(fuchsia_egl_window *egl_window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // FUCHSIA_EGL_H_
diff --git a/src/common/fuchsia_egl/fuchsia_egl_backend.h b/src/common/fuchsia_egl/fuchsia_egl_backend.h
new file mode 100644
index 0000000..dcbc3f7
--- /dev/null
+++ b/src/common/fuchsia_egl/fuchsia_egl_backend.h
@@ -0,0 +1,25 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_EGL_BACKEND_H_
+#define FUCHSIA_EGL_BACKEND_H_
+
+#include <zircon/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !defined(FUCHSIA_EGL_EXPORT)
+#    define FUCHSIA_EGL_EXPORT __attribute__((__visibility__("default")))
+#endif
+
+FUCHSIA_EGL_EXPORT
+zx_handle_t fuchsia_egl_window_release_image_pipe(fuchsia_egl_window *egl_window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // FUCHSIA_EGL_BACKEND_H_
diff --git a/src/common/platform.h b/src/common/platform.h
index 144b5b0..acb2157 100644
--- a/src/common/platform.h
+++ b/src/common/platform.h
@@ -11,6 +11,9 @@
 
 #if defined(_WIN32)
 #    define ANGLE_PLATFORM_WINDOWS 1
+#elif defined(__Fuchsia__)
+#    define ANGLE_PLATFORM_FUCHSIA 1
+#    define ANGLE_PLATFORM_POSIX 1
 #elif defined(__APPLE__)
 #    define ANGLE_PLATFORM_APPLE 1
 #    define ANGLE_PLATFORM_POSIX 1
diff --git a/src/libANGLE/Display.cpp b/src/libANGLE/Display.cpp
index f830c88..7cc97a7 100644
--- a/src/libANGLE/Display.cpp
+++ b/src/libANGLE/Display.cpp
@@ -70,6 +70,8 @@
 #        include "libANGLE/renderer/vulkan/xcb/DisplayVkXcb.h"
 #    elif defined(ANGLE_PLATFORM_ANDROID)
 #        include "libANGLE/renderer/vulkan/android/DisplayVkAndroid.h"
+#    elif defined(ANGLE_PLATFORM_FUCHSIA)
+#        include "libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h"
 #    else
 #        error Unsupported Vulkan platform.
 #    endif
@@ -189,6 +191,8 @@
             impl = new rx::DisplayGLX(state);
 #elif defined(ANGLE_PLATFORM_APPLE)
             impl = new rx::DisplayCGL(state);
+#elif defined(ANGLE_PLATFORM_FUCHSIA)
+            impl = new rx::DisplayVkFuchsia(state);
 #elif defined(ANGLE_USE_OZONE)
             impl = new rx::DisplayOzone(state);
 #elif defined(ANGLE_PLATFORM_ANDROID)
@@ -261,6 +265,8 @@
             impl = new rx::DisplayVkXcb(state);
 #    elif defined(ANGLE_PLATFORM_ANDROID)
             impl = new rx::DisplayVkAndroid(state);
+#    elif defined(ANGLE_PLATFORM_FUCHSIA)
+            impl = new rx::DisplayVkFuchsia(state);
 #    else
 #        error Unsupported Vulkan platform.
 #    endif
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index ec830a7..3aca00e 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -355,7 +355,7 @@
 // Changing CWD and setting environment variables makes no sense on Android,
 // since this code is a part of Java application there.
 // Android Vulkan loader doesn't need this either.
-#if !defined(ANGLE_PLATFORM_ANDROID)
+#if !defined(ANGLE_PLATFORM_ANDROID) && !defined(ANGLE_PLATFORM_FUCHSIA)
         if (enableMockICD)
         {
             // Override environment variable to use built Mock ICD
diff --git a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
index 792d480..7921e75 100644
--- a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
@@ -363,7 +363,8 @@
 
     // TODO(jmadill): Support devices which don't support copy. We use this for ReadPixels.
     ANGLE_VK_CHECK(displayVk,
-                   (mSurfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0,
+                   (mSurfaceCaps.supportedUsageFlags & kSurfaceVKColorImageUsageFlags) ==
+                       kSurfaceVKColorImageUsageFlags,
                    VK_ERROR_INITIALIZATION_FAILED);
 
     EGLAttrib attribWidth  = mState.attributes.get(EGL_WIDTH, 0);
diff --git a/src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.cpp b/src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.cpp
new file mode 100644
index 0000000..19d08e8
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.cpp
@@ -0,0 +1,59 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// DisplayVkFuchsia.h:
+//    Implements methods from DisplayVkFuchsia
+//
+
+#include "libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h"
+
+#include "libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.h"
+#include "libANGLE/renderer/vulkan/vk_caps_utils.h"
+
+namespace rx
+{
+
+DisplayVkFuchsia::DisplayVkFuchsia(const egl::DisplayState &state) : DisplayVk(state) {}
+
+bool DisplayVkFuchsia::isValidNativeWindow(EGLNativeWindowType window) const
+{
+    return WindowSurfaceVkFuchsia::isValidNativeWindow(window);
+}
+
+SurfaceImpl *DisplayVkFuchsia::createWindowSurfaceVk(const egl::SurfaceState &state,
+                                                     EGLNativeWindowType window,
+                                                     EGLint width,
+                                                     EGLint height)
+{
+    ASSERT(isValidNativeWindow(window));
+    return new WindowSurfaceVkFuchsia(state, window, width, height);
+}
+
+egl::ConfigSet DisplayVkFuchsia::generateConfigs()
+{
+    constexpr GLenum kColorFormats[] = {GL_BGRA8_EXT, GL_BGRX8_ANGLEX};
+    constexpr EGLint kSampleCounts[] = {0};
+    return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, kSampleCounts,
+                                   this);
+}
+
+bool DisplayVkFuchsia::checkConfigSupport(egl::Config *config)
+{
+    // TODO(geofflang): Test for native support and modify the config accordingly.
+    // anglebug.com/2692
+    return true;
+}
+
+const char *DisplayVkFuchsia::getWSIExtension() const
+{
+    return VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME;
+}
+
+const char *DisplayVkFuchsia::getWSILayer() const
+{
+    return "VK_LAYER_FUCHSIA_imagepipe_swapchain";
+}
+
+}  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h b/src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h
new file mode 100644
index 0000000..c6f3c6c
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h
@@ -0,0 +1,39 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// DisplayVkFuchsia.h:
+//    Subclasses DisplayVk for the Fuchsia platform.
+//
+
+#ifndef LIBANGLE_RENDERER_VULKAN_FUCHSIA_DISPLAYVKFUCHSIA_H_
+#define LIBANGLE_RENDERER_VULKAN_FUCHSIA_DISPLAYVKFUCHSIA_H_
+
+#include "libANGLE/renderer/vulkan/DisplayVk.h"
+
+namespace rx
+{
+
+class DisplayVkFuchsia : public DisplayVk
+{
+  public:
+    DisplayVkFuchsia(const egl::DisplayState &state);
+
+    bool isValidNativeWindow(EGLNativeWindowType window) const override;
+
+    SurfaceImpl *createWindowSurfaceVk(const egl::SurfaceState &state,
+                                       EGLNativeWindowType window,
+                                       EGLint width,
+                                       EGLint height) override;
+
+    egl::ConfigSet generateConfigs() override;
+    bool checkConfigSupport(egl::Config *config) override;
+
+    const char *getWSIExtension() const override;
+    const char *getWSILayer() const override;
+};
+
+}  // namespace rx
+
+#endif  // LIBANGLE_RENDERER_VULKAN_FUCHSIA_DISPLAYVKFUCHSIA_H_
diff --git a/src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.cpp b/src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.cpp
new file mode 100644
index 0000000..fa8f4e2
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.cpp
@@ -0,0 +1,66 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// WindowSurfaceVkFuchsia.cpp:
+//    Implements methods from WindowSurfaceVkFuchsia.
+//
+
+#include "libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.h"
+
+#include <fuchsia_egl.h>
+#include <fuchsia_egl_backend.h>
+#include <zircon/syscalls.h>
+#include <zircon/syscalls/object.h>
+
+#include "libANGLE/renderer/vulkan/RendererVk.h"
+#include "libANGLE/renderer/vulkan/vk_utils.h"
+
+namespace rx
+{
+
+WindowSurfaceVkFuchsia::WindowSurfaceVkFuchsia(const egl::SurfaceState &surfaceState,
+                                               EGLNativeWindowType window,
+                                               EGLint width,
+                                               EGLint height)
+    : WindowSurfaceVk(surfaceState, window, width, height)
+{}
+
+WindowSurfaceVkFuchsia::~WindowSurfaceVkFuchsia() {}
+
+// static
+bool WindowSurfaceVkFuchsia::isValidNativeWindow(EGLNativeWindowType window)
+{
+    fuchsia_egl_window *egl_window = reinterpret_cast<fuchsia_egl_window *>(window);
+    return fuchsia_egl_window_get_width(egl_window) >= 0;
+}
+
+angle::Result WindowSurfaceVkFuchsia::createSurfaceVk(vk::Context *context, gl::Extents *extentsOut)
+{
+    InitImagePipeSurfaceFUCHSIAFunctions(context->getRenderer()->getInstance());
+    fuchsia_egl_window *egl_window = reinterpret_cast<fuchsia_egl_window *>(mNativeWindowType);
+
+    VkImagePipeSurfaceCreateInfoFUCHSIA createInfo = {};
+    createInfo.sType           = VK_STRUCTURE_TYPE_IMAGEPIPE_SURFACE_CREATE_INFO_FUCHSIA;
+    createInfo.imagePipeHandle = fuchsia_egl_window_release_image_pipe(egl_window);
+    ANGLE_VK_TRY(context, vkCreateImagePipeSurfaceFUCHSIA(context->getRenderer()->getInstance(),
+                                                          &createInfo, nullptr, &mSurface));
+
+    return getCurrentWindowSize(context, extentsOut);
+}
+
+angle::Result WindowSurfaceVkFuchsia::getCurrentWindowSize(vk::Context *context,
+                                                           gl::Extents *extentsOut)
+{
+    fuchsia_egl_window *egl_window = reinterpret_cast<fuchsia_egl_window *>(mNativeWindowType);
+
+    int32_t width  = fuchsia_egl_window_get_width(egl_window);
+    int32_t height = fuchsia_egl_window_get_height(egl_window);
+
+    *extentsOut = gl::Extents(width, height, 0);
+
+    return angle::Result::Continue;
+}
+
+}  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.h b/src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.h
new file mode 100644
index 0000000..e9ce818
--- /dev/null
+++ b/src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.h
@@ -0,0 +1,36 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// WindowSurfaceVkFuchsia.h:
+//    Subclasses WindowSurfaceVk for the Fuchsia platform.
+//
+
+#ifndef LIBANGLE_RENDERER_VULKAN_FUCHSIA_WINDOWSURFACEVKFUCHSIA_H_
+#define LIBANGLE_RENDERER_VULKAN_FUCHSIA_WINDOWSURFACEVKFUCHSIA_H_
+
+#include "libANGLE/renderer/vulkan/SurfaceVk.h"
+
+namespace rx
+{
+
+class WindowSurfaceVkFuchsia : public WindowSurfaceVk
+{
+  public:
+    WindowSurfaceVkFuchsia(const egl::SurfaceState &surfaceState,
+                           EGLNativeWindowType window,
+                           EGLint width,
+                           EGLint height);
+    ~WindowSurfaceVkFuchsia() override;
+
+    static bool isValidNativeWindow(EGLNativeWindowType window);
+
+  private:
+    angle::Result createSurfaceVk(vk::Context *context, gl::Extents *extentsOut) override;
+    angle::Result getCurrentWindowSize(vk::Context *context, gl::Extents *extentsOut) override;
+};
+
+}  // namespace rx
+
+#endif  // LIBANGLE_RENDERER_VULKAN_FUCHSIA_WINDOWSURFACEVKFUCHSIA_H_
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.cpp b/src/libANGLE/renderer/vulkan/vk_utils.cpp
index b4f4f69..37d0c7c 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_utils.cpp
@@ -480,6 +480,11 @@
 PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT   = nullptr;
 PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT = nullptr;
 
+#if defined(ANGLE_PLATFORM_FUCHSIA)
+// VK_FUCHSIA_imagepipe_surface
+PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA = nullptr;
+#endif
+
 #define GET_FUNC(vkName)                                                                   \
     do                                                                                     \
     {                                                                                      \
@@ -499,6 +504,13 @@
     GET_FUNC(vkDestroyDebugReportCallbackEXT);
 }
 
+#if defined(ANGLE_PLATFORM_FUCHSIA)
+void InitImagePipeSurfaceFUCHSIAFunctions(VkInstance instance)
+{
+    GET_FUNC(vkCreateImagePipeSurfaceFUCHSIA);
+}
+#endif
+
 #undef GET_FUNC
 
 namespace gl_vk
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.h b/src/libANGLE/renderer/vulkan/vk_utils.h
index 70c12b7..3416750 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_utils.h
@@ -391,6 +391,12 @@
 void InitDebugUtilsEXTFunctions(VkInstance instance);
 void InitDebugReportEXTFunctions(VkInstance instance);
 
+#if defined(ANGLE_PLATFORM_FUCHSIA)
+// VK_FUCHSIA_imagepipe_surface
+extern PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA;
+void InitImagePipeSurfaceFUCHSIAFunctions(VkInstance instance);
+#endif
+
 namespace gl_vk
 {
 VkRect2D GetRect(const gl::Rectangle &source);
diff --git a/src/libGLESv2.gni b/src/libGLESv2.gni
index d667596..9e813a7 100644
--- a/src/libGLESv2.gni
+++ b/src/libGLESv2.gni
@@ -835,6 +835,13 @@
   "src/libANGLE/renderer/vulkan/xcb/WindowSurfaceVkXcb.h",
 ]
 
+libangle_vulkan_fuchsia_sources = [
+  "src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.cpp",
+  "src/libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h",
+  "src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.cpp",
+  "src/libANGLE/renderer/vulkan/fuchsia/WindowSurfaceVkFuchsia.h",
+]
+
 libangle_null_sources = [
   "src/libANGLE/renderer/null/BufferNULL.cpp",
   "src/libANGLE/renderer/null/BufferNULL.h",
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 274f859..89ed860 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -39,6 +39,12 @@
     ]
     public_configs += [ ":angle_internal_gtest_config" ]
     configs -= [ "${angle_root}:extra_warnings" ]
+    if (is_fuchsia) {
+      deps = [
+        "//third_party/fuchsia-sdk/sdk:fdio",
+        "//third_party/fuchsia-sdk/sdk:zx",
+      ]
+    }
   }
 
   config("angle_internal_gmock_config") {
@@ -67,10 +73,14 @@
       "//src/tests:angle_end2end_tests",
       "//src/tests:angle_perftests",
       "//src/tests:angle_unittests",
-      "//src/tests:angle_white_box_perftests",
-      "//src/tests:angle_white_box_tests",
     ]
-    if (build_angle_deqp_tests) {
+    if (!is_fuchsia) {
+      deps += [
+        "//src/tests:angle_white_box_perftests",
+        "//src/tests:angle_white_box_tests",
+      ]
+    }
+    if (build_angle_deqp_tests && !is_fuchsia) {
       deps += [
         "//src/tests:angle_deqp_egl_no_gtest",
         "//src/tests:angle_deqp_egl_tests",
@@ -141,7 +151,7 @@
          ]
 }
 
-if (is_win || is_linux || is_mac || is_android) {
+if (is_win || is_linux || is_mac || is_android || is_fuchsia) {
   import("angle_end2end_tests.gni")
 
   angle_test("angle_end2end_tests") {
@@ -187,7 +197,9 @@
       use_native_activity = true
     }
   }
+}
 
+if (is_win || is_linux || is_mac || is_android) {
   import("angle_white_box_tests.gni")
 
   angle_test("angle_white_box_tests") {
@@ -259,6 +271,10 @@
       use_native_activity = true
     }
   }
+}
+
+if (is_win || is_linux || is_android || is_mac || is_fuchsia) {
+  import("angle_perftests.gni")
 
   # This test suite is designed to run against a generic GL implementation.
   angle_test("angle_perftests") {
@@ -517,7 +533,7 @@
 ### dEQP tests
 ###-----------------------------------------------------
 
-if (build_angle_deqp_tests) {
+if (build_angle_deqp_tests && !is_fuchsia) {
   import("deqp.gni")
 
   angle_tests_main("angle_deqp_tests_main") {
diff --git a/src/tests/test_utils/angle_test_instantiate.cpp b/src/tests/test_utils/angle_test_instantiate.cpp
index 1f0bde5..9c32ad1 100644
--- a/src/tests/test_utils/angle_test_instantiate.cpp
+++ b/src/tests/test_utils/angle_test_instantiate.cpp
@@ -122,6 +122,15 @@
 #endif
 }
 
+bool IsFuchsia()
+{
+#if defined(ANGLE_PLATFORM_FUCHSIA)
+    return true;
+#else
+    return false;
+#endif
+}
+
 bool IsConfigWhitelisted(const SystemInfo &systemInfo, const PlatformParameters &param)
 {
     VendorID vendorID = systemInfo.gpus[systemInfo.primaryGPUIndex].vendorId;
@@ -191,6 +200,17 @@
         return (param.getRenderer() == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE);
     }
 
+    if (IsFuchsia())
+    {
+        // Currently we only support the Vulkan back-end on Fuchsia.
+        if (param.driver != GLESDriverType::AngleEGL)
+        {
+            return false;
+        }
+
+        return (param.getRenderer() == EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE);
+    }
+
     if (IsOzone())
     {
         // Currently we only support the GLES back-end on Ozone.
diff --git a/src/tests/test_utils/angle_test_instantiate.h b/src/tests/test_utils/angle_test_instantiate.h
index 86618ab..b73bd85 100644
--- a/src/tests/test_utils/angle_test_instantiate.h
+++ b/src/tests/test_utils/angle_test_instantiate.h
@@ -23,6 +23,7 @@
 bool IsOSX();
 bool IsOzone();
 bool IsWindows();
+bool IsFuchsia();
 
 bool IsPlatformAvailable(const PlatformParameters &param);
 
diff --git a/third_party/vulkan-loader/BUILD.gn b/third_party/vulkan-loader/BUILD.gn
index dd83ffb..966b000 100644
--- a/third_party/vulkan-loader/BUILD.gn
+++ b/third_party/vulkan-loader/BUILD.gn
@@ -9,6 +9,10 @@
 import("../../gni/angle.gni")
 import("$angle_root/third_party/vulkan-headers/vulkan_headers_script_deps.gni")
 
+# Fuchsia has non-upstream changes to the vulkan loader, so we don't want
+# to build it from upstream sources.
+assert(!is_fuchsia)
+
 if (!is_android) {
   vulkan_undefine_configs = []
 }
diff --git a/third_party/vulkan-tools/BUILD.gn b/third_party/vulkan-tools/BUILD.gn
index 5299278..8fe74ff 100644
--- a/third_party/vulkan-tools/BUILD.gn
+++ b/third_party/vulkan-tools/BUILD.gn
@@ -10,6 +10,10 @@
 
 import("$angle_root/third_party/vulkan-headers/vulkan_headers_script_deps.gni")
 
+# Vulkan-tools isn't ported to Fuchsia yet.
+# TODO(spang): Port mock ICD to Fuchsia.
+assert(!is_fuchsia)
+
 vulkan_undefine_configs = []
 if (is_win) {
   vulkan_undefine_configs += [
diff --git a/third_party/vulkan-validation-layers/BUILD.gn b/third_party/vulkan-validation-layers/BUILD.gn
index c4d22ba..e77a9fa 100644
--- a/third_party/vulkan-validation-layers/BUILD.gn
+++ b/third_party/vulkan-validation-layers/BUILD.gn
@@ -8,6 +8,10 @@
 
 import("../../gni/angle.gni")
 
+# Fuchsia has non-upstream changes to the vulkan layers, so we don't want
+# to build it from upstream sources.
+assert(!is_fuchsia)
+
 import("$angle_root/third_party/vulkan-headers/vulkan_headers_script_deps.gni")
 
 vulkan_undefine_configs = []
diff --git a/util/EGLWindow.cpp b/util/EGLWindow.cpp
index b2432b27..0c2ae48 100644
--- a/util/EGLWindow.cpp
+++ b/util/EGLWindow.cpp
@@ -305,6 +305,8 @@
 
     surfaceAttributes.push_back(EGL_NONE);
 
+    osWindow->resetNativeWindow();
+
     mSurface = eglCreateWindowSurface(mDisplay, mConfig, osWindow->getNativeWindow(),
                                       &surfaceAttributes[0]);
     if (eglGetError() != EGL_SUCCESS || (mSurface == EGL_NO_SURFACE))
diff --git a/util/OSWindow.h b/util/OSWindow.h
index cc45b82..3cdb458 100644
--- a/util/OSWindow.h
+++ b/util/OSWindow.h
@@ -39,6 +39,12 @@
     // just grab the pixels of the window. Returns if it was successful.
     virtual bool takeScreenshot(uint8_t *pixelData);
 
+    // Re-initializes the native window. This is used on platforms which do not
+    // have a reusable EGLNativeWindowType in order to recreate it, and is
+    // needed by the test suite because it re-uses the same OSWindow for
+    // multiple EGLSurfaces.
+    virtual void resetNativeWindow() = 0;
+
     virtual EGLNativeWindowType getNativeWindow() const   = 0;
     virtual EGLNativeDisplayType getNativeDisplay() const = 0;
 
diff --git a/util/android/AndroidWindow.cpp b/util/android/AndroidWindow.cpp
index 930d9ec..ec6df6b 100644
--- a/util/android/AndroidWindow.cpp
+++ b/util/android/AndroidWindow.cpp
@@ -31,6 +31,8 @@
 }
 void AndroidWindow::destroy() {}
 
+void AndroidWindow::resetNativeWindow() {}
+
 EGLNativeWindowType AndroidWindow::getNativeWindow() const
 {
     // Return the entire Activity Surface for now
diff --git a/util/android/AndroidWindow.h b/util/android/AndroidWindow.h
index a0aca3d..fc6592d 100644
--- a/util/android/AndroidWindow.h
+++ b/util/android/AndroidWindow.h
@@ -20,6 +20,7 @@
     bool initialize(const std::string &name, size_t width, size_t height) override;
     void destroy() override;
 
+    void resetNativeWindow() override;
     EGLNativeWindowType getNativeWindow() const override;
     EGLNativeDisplayType getNativeDisplay() const override;
 
diff --git a/util/fuchsia/ScenicWindow.cpp b/util/fuchsia/ScenicWindow.cpp
new file mode 100644
index 0000000..4a8a2d1
--- /dev/null
+++ b/util/fuchsia/ScenicWindow.cpp
@@ -0,0 +1,184 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ScenicWindow.cpp:
+//    Implements methods from ScenicWindow
+//
+
+#include "util/fuchsia/ScenicWindow.h"
+
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/util.h>
+#include <lib/fidl/cpp/interface_ptr.h>
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/zx/channel.h>
+#include <zircon/status.h>
+
+#include "common/debug.h"
+
+namespace
+{
+
+async::Loop *GetDefaultLoop()
+{
+    static async::Loop *defaultLoop = new async::Loop(&kAsyncLoopConfigAttachToThread);
+    return defaultLoop;
+}
+
+zx::channel ConnectToServiceRoot()
+{
+    zx::channel clientChannel;
+    zx::channel serverChannel;
+    zx_status_t result = zx::channel::create(0, &clientChannel, &serverChannel);
+    ASSERT(result == ZX_OK);
+    result = fdio_service_connect("/svc/.", serverChannel.release());
+    ASSERT(result == ZX_OK);
+    return clientChannel;
+}
+
+template <typename Interface>
+zx_status_t ConnectToService(zx_handle_t serviceRoot, fidl::InterfaceRequest<Interface> request)
+{
+    ASSERT(request.is_valid());
+    return fdio_service_connect_at(serviceRoot, Interface::Name_, request.TakeChannel().release());
+}
+
+template <typename Interface>
+fidl::InterfacePtr<Interface> ConnectToService(zx_handle_t serviceRoot)
+{
+    fidl::InterfacePtr<Interface> result;
+    ConnectToService(serviceRoot, result.NewRequest());
+    return result;
+}
+
+}  // namespace
+
+ScenicWindow::ScenicWindow()
+    : mLoop(GetDefaultLoop()),
+      mServiceRoot(ConnectToServiceRoot()),
+      mScenic(ConnectToService<fuchsia::ui::scenic::Scenic>(mServiceRoot.get())),
+      mViewManager(ConnectToService<fuchsia::ui::viewsv1::ViewManager>(mServiceRoot.get())),
+      mPresenter(ConnectToService<fuchsia::ui::policy::Presenter>(mServiceRoot.get())),
+      mScenicSession(mScenic.get()),
+      mParent(&mScenicSession),
+      mShape(&mScenicSession),
+      mMaterial(&mScenicSession),
+      mViewListenerBinding(this)
+{}
+
+ScenicWindow::~ScenicWindow()
+{
+    destroy();
+}
+
+bool ScenicWindow::initialize(const std::string &name, size_t width, size_t height)
+{
+    // Set up scenic resources.
+    zx::eventpair parentExportToken;
+    mParent.BindAsRequest(&parentExportToken);
+    mParent.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask);
+    mParent.AddChild(mShape);
+    mShape.SetShape(scenic::Rectangle(&mScenicSession, width, height));
+    mShape.SetMaterial(mMaterial);
+
+    // Create view and present it.
+    zx::eventpair viewHolderToken;
+    zx::eventpair viewToken;
+    zx_status_t status = zx::eventpair::create(0 /* options */, &viewToken, &viewHolderToken);
+    ASSERT(status == ZX_OK);
+    mPresenter->Present2(std::move(viewHolderToken), nullptr);
+    mViewManager->CreateView2(mView.NewRequest(), std::move(viewToken),
+                              mViewListenerBinding.NewBinding(), std::move(parentExportToken),
+                              name);
+    mView.set_error_handler(fit::bind_member(this, &ScenicWindow::OnScenicError));
+    mViewListenerBinding.set_error_handler(fit::bind_member(this, &ScenicWindow::OnScenicError));
+
+    mWidth  = width;
+    mHeight = height;
+
+    resetNativeWindow();
+
+    return true;
+}
+
+void ScenicWindow::destroy()
+{
+    mFuchsiaEGLWindow.reset();
+}
+
+void ScenicWindow::resetNativeWindow()
+{
+    fuchsia::images::ImagePipePtr imagePipe;
+    uint32_t imagePipeId = mScenicSession.AllocResourceId();
+    mScenicSession.Enqueue(scenic::NewCreateImagePipeCmd(imagePipeId, imagePipe.NewRequest()));
+    mMaterial.SetTexture(imagePipeId);
+    mScenicSession.ReleaseResource(imagePipeId);
+    mScenicSession.Present(0, [](fuchsia::images::PresentationInfo info) {});
+
+    mFuchsiaEGLWindow.reset(
+        fuchsia_egl_window_create(imagePipe.Unbind().TakeChannel().release(), mWidth, mHeight));
+}
+
+EGLNativeWindowType ScenicWindow::getNativeWindow() const
+{
+    return reinterpret_cast<EGLNativeWindowType>(mFuchsiaEGLWindow.get());
+}
+
+EGLNativeDisplayType ScenicWindow::getNativeDisplay() const
+{
+    return EGL_DEFAULT_DISPLAY;
+}
+
+void ScenicWindow::messageLoop()
+{
+    mLoop->Run(zx::deadline_after({}), true /* once */);
+}
+
+void ScenicWindow::setMousePosition(int x, int y)
+{
+    UNIMPLEMENTED();
+}
+
+bool ScenicWindow::setPosition(int x, int y)
+{
+    UNIMPLEMENTED();
+    return false;
+}
+
+bool ScenicWindow::resize(int width, int height)
+{
+    mWidth  = width;
+    mHeight = height;
+
+    fuchsia_egl_window_resize(mFuchsiaEGLWindow.get(), width, height);
+
+    return true;
+}
+
+void ScenicWindow::setVisible(bool isVisible) {}
+
+void ScenicWindow::signalTestEvent() {}
+
+void ScenicWindow::OnPropertiesChanged(fuchsia::ui::viewsv1::ViewProperties properties,
+                                       OnPropertiesChangedCallback callback)
+{
+    UNIMPLEMENTED();
+}
+
+void ScenicWindow::OnScenicEvents(std::vector<fuchsia::ui::scenic::Event> events)
+{
+    UNIMPLEMENTED();
+}
+
+void ScenicWindow::OnScenicError(zx_status_t status)
+{
+    WARN() << "OnScenicError: " << zx_status_get_string(status);
+}
+
+// static
+OSWindow *OSWindow::New()
+{
+    return new ScenicWindow;
+}
diff --git a/util/fuchsia/ScenicWindow.h b/util/fuchsia/ScenicWindow.h
new file mode 100644
index 0000000..02348d2
--- /dev/null
+++ b/util/fuchsia/ScenicWindow.h
@@ -0,0 +1,83 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ScenicWindow.h:
+//    Subclasses OSWindow for Fuchsia's Scenic compositor.
+//
+
+#ifndef UTIL_FUCHSIA_SCENIC_WINDOW_H
+#define UTIL_FUCHSIA_SCENIC_WINDOW_H
+
+#include <fuchsia/ui/policy/cpp/fidl.h>
+#include <fuchsia/ui/scenic/cpp/fidl.h>
+#include <fuchsia/ui/viewsv1/cpp/fidl.h>
+#include <fuchsia_egl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/ui/scenic/cpp/commands.h>
+#include <lib/ui/scenic/cpp/resources.h>
+#include <lib/ui/scenic/cpp/session.h>
+#include <zircon/types.h>
+#include <string>
+
+#include "util/OSWindow.h"
+#include "util/util_export.h"
+
+struct FuchsiaEGLWindowDeleter
+{
+    void operator()(fuchsia_egl_window *eglWindow) { fuchsia_egl_window_destroy(eglWindow); }
+};
+
+class ANGLE_UTIL_EXPORT ScenicWindow : public OSWindow, public fuchsia::ui::viewsv1::ViewListener
+{
+  public:
+    ScenicWindow();
+    ~ScenicWindow();
+
+    // OSWindow:
+    bool initialize(const std::string &name, size_t width, size_t height) override;
+    void destroy() override;
+    void resetNativeWindow() override;
+    EGLNativeWindowType getNativeWindow() const override;
+    EGLNativeDisplayType getNativeDisplay() const override;
+    void messageLoop() override;
+    void setMousePosition(int x, int y) override;
+    bool setPosition(int x, int y) override;
+    bool resize(int width, int height) override;
+    void setVisible(bool isVisible) override;
+    void signalTestEvent() override;
+
+    // views::ViewListener:
+    void OnPropertiesChanged(fuchsia::ui::viewsv1::ViewProperties properties,
+                             OnPropertiesChangedCallback callback) override;
+
+    // FIDL callbacks:
+    void OnScenicEvents(std::vector<fuchsia::ui::scenic::Event> events);
+    void OnScenicError(zx_status_t status);
+
+  private:
+    // Default message loop.
+    async::Loop *mLoop;
+
+    // System services.
+    zx::channel mServiceRoot;
+    fuchsia::ui::scenic::ScenicPtr mScenic;
+    fuchsia::ui::viewsv1::ViewManagerPtr mViewManager;
+    fuchsia::ui::policy::PresenterPtr mPresenter;
+
+    // Scenic session & resources.
+    scenic::Session mScenicSession;
+    scenic::ImportNode mParent;
+    scenic::ShapeNode mShape;
+    scenic::Material mMaterial;
+
+    // Scenic view & listener.
+    fuchsia::ui::viewsv1::ViewPtr mView;
+    fidl::Binding<fuchsia::ui::viewsv1::ViewListener> mViewListenerBinding;
+
+    // EGL native window.
+    std::unique_ptr<fuchsia_egl_window, FuchsiaEGLWindowDeleter> mFuchsiaEGLWindow;
+};
+
+#endif  // UTIL_FUCHSIA_SCENIC_WINDOW_H
diff --git a/util/osx/OSXWindow.h b/util/osx/OSXWindow.h
index 26fe65d..4f7a691 100644
--- a/util/osx/OSXWindow.h
+++ b/util/osx/OSXWindow.h
@@ -38,6 +38,7 @@
     bool initialize(const std::string &name, size_t width, size_t height) override;
     void destroy() override;
 
+    void resetNativeWindow() override;
     EGLNativeWindowType getNativeWindow() const override;
     EGLNativeDisplayType getNativeDisplay() const override;
 
diff --git a/util/osx/OSXWindow.mm b/util/osx/OSXWindow.mm
index 7293de4..665698f 100644
--- a/util/osx/OSXWindow.mm
+++ b/util/osx/OSXWindow.mm
@@ -679,6 +679,8 @@
     mWindow = nil;
 }
 
+void OSXWindow::resetNativeWindow() {}
+
 EGLNativeWindowType OSXWindow::getNativeWindow() const
 {
     return [mView layer];
diff --git a/util/ozone/OzoneWindow.cpp b/util/ozone/OzoneWindow.cpp
index 384dbe2..3f9bac4 100644
--- a/util/ozone/OzoneWindow.cpp
+++ b/util/ozone/OzoneWindow.cpp
@@ -29,6 +29,8 @@
 
 void OzoneWindow::destroy() {}
 
+void OzoneWindow::resetNativeWindow() {}
+
 EGLNativeWindowType OzoneWindow::getNativeWindow() const
 {
     return reinterpret_cast<EGLNativeWindowType>(&mNative);
diff --git a/util/ozone/OzoneWindow.h b/util/ozone/OzoneWindow.h
index 661e459..d5bda02 100644
--- a/util/ozone/OzoneWindow.h
+++ b/util/ozone/OzoneWindow.h
@@ -22,6 +22,7 @@
     bool initialize(const std::string &name, size_t width, size_t height) override;
     void destroy() override;
 
+    void resetNativeWindow() override;
     EGLNativeWindowType getNativeWindow() const override;
     EGLNativeDisplayType getNativeDisplay() const override;
 
diff --git a/util/linux/LinuxTimer.cpp b/util/posix/PosixTimer.cpp
similarity index 72%
rename from util/linux/LinuxTimer.cpp
rename to util/posix/PosixTimer.cpp
index e74b399..85b91ce 100644
--- a/util/linux/LinuxTimer.cpp
+++ b/util/posix/PosixTimer.cpp
@@ -4,12 +4,12 @@
 // found in the LICENSE file.
 //
 
-// LinuxTimer.cpp: Implementation of a high precision timer class on Linux
+// PosixTimer.cpp: Implementation of a high precision timer class on POSIX
 
-#include "util/linux/LinuxTimer.h"
+#include "util/posix/PosixTimer.h"
 #include <iostream>
 
-LinuxTimer::LinuxTimer() : mRunning(false) {}
+PosixTimer::PosixTimer() : mRunning(false) {}
 
 namespace
 {
@@ -21,19 +21,19 @@
 }
 }  // anonymous namespace
 
-void LinuxTimer::start()
+void PosixTimer::start()
 {
     mStartTimeNs = getCurrentTimeNs();
     mRunning     = true;
 }
 
-void LinuxTimer::stop()
+void PosixTimer::stop()
 {
     mStopTimeNs = getCurrentTimeNs();
     mRunning    = false;
 }
 
-double LinuxTimer::getElapsedTime() const
+double PosixTimer::getElapsedTime() const
 {
     uint64_t endTimeNs;
     if (mRunning)
@@ -48,12 +48,12 @@
     return (endTimeNs - mStartTimeNs) * 1e-9;
 }
 
-double LinuxTimer::getAbsoluteTime()
+double PosixTimer::getAbsoluteTime()
 {
     return getCurrentTimeNs() * 1e-9;
 }
 
 Timer *CreateTimer()
 {
-    return new LinuxTimer();
+    return new PosixTimer();
 }
diff --git a/util/linux/LinuxTimer.h b/util/posix/PosixTimer.h
similarity index 69%
rename from util/linux/LinuxTimer.h
rename to util/posix/PosixTimer.h
index 566e664..5ffd83c 100644
--- a/util/linux/LinuxTimer.h
+++ b/util/posix/PosixTimer.h
@@ -4,20 +4,20 @@
 // found in the LICENSE file.
 //
 
-// LinuxTimer.h: Definition of a high precision timer class on Linux
+// PosixTimer.h: Definition of a high precision timer class on Linux
 
-#ifndef UTIL_LINUX_TIMER_H
-#define UTIL_LINUX_TIMER_H
+#ifndef UTIL_POSIX_TIMER_H
+#define UTIL_POSIX_TIMER_H
 
 #include <stdint.h>
 #include <time.h>
 
 #include "util/Timer.h"
 
-class ANGLE_UTIL_EXPORT LinuxTimer : public Timer
+class ANGLE_UTIL_EXPORT PosixTimer : public Timer
 {
   public:
-    LinuxTimer();
+    PosixTimer();
 
     void start() override;
     void stop() override;
@@ -31,4 +31,4 @@
     uint64_t mStopTimeNs;
 };
 
-#endif  // UTIL_LINUX_TIMER_H
+#endif  // UTIL_POSIX_TIMER_H
diff --git a/util/posix/Posix_system_utils.cpp b/util/posix/Posix_system_utils.cpp
index e3e740f..9210088 100644
--- a/util/posix/Posix_system_utils.cpp
+++ b/util/posix/Posix_system_utils.cpp
@@ -8,12 +8,15 @@
 
 #include "util/system_utils.h"
 
-#include <dlfcn.h>
 #include <sched.h>
-#include <sys/resource.h>
 #include <time.h>
 #include <unistd.h>
 
+#if !defined(ANGLE_PLATFORM_FUCHSIA)
+#    include <dlfcn.h>
+#    include <sys/resource.h>
+#endif
+
 #include "common/platform.h"
 
 namespace angle
@@ -40,7 +43,9 @@
 
 void SetLowPriorityProcess()
 {
+#if !defined(ANGLE_PLATFORM_FUCHSIA)
     setpriority(PRIO_PROCESS, getpid(), 10);
+#endif
 }
 
 void WriteDebugMessage(const char *format, ...)
@@ -53,6 +58,7 @@
 
 bool StabilizeCPUForBenchmarking()
 {
+#if !defined(ANGLE_PLATFORM_FUCHSIA)
     bool success = true;
     errno        = 0;
     setpriority(PRIO_PROCESS, getpid(), -20);
@@ -80,5 +86,8 @@
 #endif
 
     return success;
+#else  // defined(ANGLE_PLATFORM_FUCHSIA)
+    return false;
+#endif
 }
 }  // namespace angle
diff --git a/util/util.gni b/util/util.gni
index 6fa3caa..ad42975 100644
--- a/util/util.gni
+++ b/util/util.gni
@@ -53,8 +53,8 @@
 ]
 
 util_linux_sources = [
-  "util/linux/LinuxTimer.cpp",
-  "util/linux/LinuxTimer.h",
+  "util/posix/PosixTimer.cpp",
+  "util/posix/PosixTimer.h",
   "util/posix/Posix_system_utils.cpp",
 ]
 
@@ -65,6 +65,14 @@
   "util/x11/X11Window.h",
 ]
 
+util_fuchsia_sources = [
+  "util/posix/PosixTimer.cpp",
+  "util/posix/PosixTimer.h",
+  "util/posix/Posix_system_utils.cpp",
+  "util/fuchsia/ScenicWindow.cpp",
+  "util/fuchsia/ScenicWindow.h",
+]
+
 util_ozone_sources = [
   "util/ozone/OzonePixmap.cpp",
   "util/ozone/OzoneWindow.cpp",
diff --git a/util/windows/win32/Win32Window.cpp b/util/windows/win32/Win32Window.cpp
index 3303c89..7d51457 100644
--- a/util/windows/win32/Win32Window.cpp
+++ b/util/windows/win32/Win32Window.cpp
@@ -703,6 +703,8 @@
     return !error;
 }
 
+void Win32Window::resetNativeWindow() {}
+
 EGLNativeWindowType Win32Window::getNativeWindow() const
 {
     return mNativeWindow;
diff --git a/util/windows/win32/Win32Window.h b/util/windows/win32/Win32Window.h
index d78a26e..1846a1f 100644
--- a/util/windows/win32/Win32Window.h
+++ b/util/windows/win32/Win32Window.h
@@ -26,6 +26,7 @@
 
     bool takeScreenshot(uint8_t *pixelData) override;
 
+    void resetNativeWindow() override;
     EGLNativeWindowType getNativeWindow() const override;
     EGLNativeDisplayType getNativeDisplay() const override;
 
diff --git a/util/windows/winrt/WinRTWindow.cpp b/util/windows/winrt/WinRTWindow.cpp
index 261373f..11f0551 100644
--- a/util/windows/winrt/WinRTWindow.cpp
+++ b/util/windows/winrt/WinRTWindow.cpp
@@ -217,6 +217,8 @@
     mCoreDispatcher.Reset();
 }
 
+void WinRTWindow::resetNativeWindow() {}
+
 EGLNativeWindowType WinRTWindow::getNativeWindow() const
 {
     return mNativeWindow;
diff --git a/util/windows/winrt/WinRTWindow.h b/util/windows/winrt/WinRTWindow.h
index 9eabc04..1a78c6a 100644
--- a/util/windows/winrt/WinRTWindow.h
+++ b/util/windows/winrt/WinRTWindow.h
@@ -25,6 +25,7 @@
     bool initialize(const std::string &name, size_t width, size_t height) override;
     void destroy() override;
 
+    void resetNativeWindow() override;
     EGLNativeWindowType getNativeWindow() const override;
     EGLNativeDisplayType getNativeDisplay() const override;
 
diff --git a/util/x11/X11Window.cpp b/util/x11/X11Window.cpp
index 25c0574..d50067e 100644
--- a/util/x11/X11Window.cpp
+++ b/util/x11/X11Window.cpp
@@ -387,6 +387,8 @@
     WM_PROTOCOLS     = None;
 }
 
+void X11Window::resetNativeWindow() {}
+
 EGLNativeWindowType X11Window::getNativeWindow() const
 {
     return mWindow;
diff --git a/util/x11/X11Window.h b/util/x11/X11Window.h
index 851f946..74e5d0c 100644
--- a/util/x11/X11Window.h
+++ b/util/x11/X11Window.h
@@ -27,6 +27,7 @@
     bool initialize(const std::string &name, size_t width, size_t height) override;
     void destroy() override;
 
+    void resetNativeWindow() override;
     EGLNativeWindowType getNativeWindow() const override;
     EGLNativeDisplayType getNativeDisplay() const override;