Implement wrapping IOSurfaces in dawn::Texture.

This allows wrapping IOSurfaces in a dawn::Texture so a Dawn application
can sample from, or render to an IOSurface. It uses Metal's
functionality for wrapping textures in MTLTexture.

Support for single-plane BGRA8, RG8 and R8 IOSurfaces is added as well
as tests for sampling and using BeginRenderPass to clear them.

BUG=dawn:112

Change-Id: I367dbd1a75a0c7b81901fb0aae05f1cd46af3f3a
Reviewed-on: https://dawn-review.googlesource.com/c/5101
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 1bdeb3e..13c130e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -564,6 +564,7 @@
       "Metal.framework",
       "Cocoa.framework",
       "IOKit.framework",
+      "IOSurface.framework",
     ]
     sources += [
       "src/dawn_native/metal/BackendMTL.h",
@@ -1018,6 +1019,14 @@
     "src/tests/end2end/ViewportOrientationTests.cpp",
   ]
 
+  libs = []
+
+  if (dawn_enable_metal) {
+    sources += [ "src/tests/end2end/IOSurfaceWrappingTests.cpp" ]
+
+    libs += [ "IOSurface.framework" ]
+  }
+
   # When building inside Chromium, use their gtest main function because it is
   # needed to run in swarming correctly.
   if (build_with_chromium) {
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index 6969b5d..9bcbf2f 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -51,6 +51,10 @@
 
         MapRequestTracker* GetMapTracker() const;
 
+        TextureBase* CreateTextureWrappingIOSurface(const TextureDescriptor* descriptor,
+                                                    IOSurfaceRef ioSurface,
+                                                    uint32_t plane);
+
         ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
         MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
                                            uint32_t sourceOffset,
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 3037e6f..3a5fa1a 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -204,4 +204,16 @@
         return {};
     }
 
+    TextureBase* Device::CreateTextureWrappingIOSurface(const TextureDescriptor* descriptor,
+                                                        IOSurfaceRef ioSurface,
+                                                        uint32_t plane) {
+        if (ConsumedError(ValidateTextureDescriptor(this, descriptor))) {
+            return nullptr;
+        }
+        if (ConsumedError(ValidateIOSurfaceCanBeWrapped(this, descriptor, ioSurface, plane))) {
+            return nullptr;
+        }
+
+        return new Texture(this, descriptor, ioSurface, plane);
+    }
 }}  // namespace dawn_native::metal
diff --git a/src/dawn_native/metal/MetalBackend.mm b/src/dawn_native/metal/MetalBackend.mm
index d8e3eaa..7f62747 100644
--- a/src/dawn_native/metal/MetalBackend.mm
+++ b/src/dawn_native/metal/MetalBackend.mm
@@ -17,6 +17,7 @@
 
 #include "dawn_native/MetalBackend.h"
 
+#include "dawn_native/Texture.h"
 #include "dawn_native/metal/DeviceMTL.h"
 
 namespace dawn_native { namespace metal {
@@ -26,4 +27,15 @@
         return device->GetMTLDevice();
     }
 
+    dawnTexture WrapIOSurface(dawnDevice cDevice,
+                              const dawnTextureDescriptor* cDescriptor,
+                              IOSurfaceRef ioSurface,
+                              uint32_t plane) {
+        Device* device = reinterpret_cast<Device*>(cDevice);
+        const TextureDescriptor* descriptor =
+            reinterpret_cast<const TextureDescriptor*>(cDescriptor);
+        TextureBase* texture = device->CreateTextureWrappingIOSurface(descriptor, ioSurface, plane);
+        return reinterpret_cast<dawnTexture>(texture);
+    }
+
 }}  // namespace dawn_native::metal
diff --git a/src/dawn_native/metal/TextureMTL.h b/src/dawn_native/metal/TextureMTL.h
index c002f56..51951f6 100644
--- a/src/dawn_native/metal/TextureMTL.h
+++ b/src/dawn_native/metal/TextureMTL.h
@@ -24,11 +24,19 @@
     class Device;
 
     MTLPixelFormat MetalPixelFormat(dawn::TextureFormat format);
+    MaybeError ValidateIOSurfaceCanBeWrapped(const DeviceBase* device,
+                                             const TextureDescriptor* descriptor,
+                                             IOSurfaceRef ioSurface,
+                                             uint32_t plane);
 
     class Texture : public TextureBase {
       public:
         Texture(Device* device, const TextureDescriptor* descriptor);
         Texture(Device* device, const TextureDescriptor* descriptor, id<MTLTexture> mtlTexture);
+        Texture(Device* device,
+                const TextureDescriptor* descriptor,
+                IOSurfaceRef ioSurface,
+                uint32_t plane);
         ~Texture();
 
         id<MTLTexture> GetMTLTexture();
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index c438a40..607b00e 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -16,27 +16,9 @@
 
 #include "dawn_native/metal/DeviceMTL.h"
 
+#include <IOSurface/IOSurface.h>
+
 namespace dawn_native { namespace metal {
-    MTLPixelFormat MetalPixelFormat(dawn::TextureFormat format) {
-        switch (format) {
-            case dawn::TextureFormat::R8G8B8A8Unorm:
-                return MTLPixelFormatRGBA8Unorm;
-            case dawn::TextureFormat::R8G8Unorm:
-                return MTLPixelFormatRG8Unorm;
-            case dawn::TextureFormat::R8Unorm:
-                return MTLPixelFormatR8Unorm;
-            case dawn::TextureFormat::R8G8B8A8Uint:
-                return MTLPixelFormatRGBA8Uint;
-            case dawn::TextureFormat::R8G8Uint:
-                return MTLPixelFormatRG8Uint;
-            case dawn::TextureFormat::R8Uint:
-                return MTLPixelFormatR8Uint;
-            case dawn::TextureFormat::B8G8R8A8Unorm:
-                return MTLPixelFormatBGRA8Unorm;
-            case dawn::TextureFormat::D32FloatS8Uint:
-                return MTLPixelFormatDepth32Float_Stencil8;
-        }
-    }
 
     namespace {
         bool UsageNeedsTextureView(dawn::TextureUsageBit usage) {
@@ -115,27 +97,107 @@
 
             return false;
         }
+
+        ResultOrError<dawn::TextureFormat> GetFormatEquivalentToIOSurfaceFormat(uint32_t format) {
+            switch (format) {
+                case 'BGRA':
+                    return dawn::TextureFormat::B8G8R8A8Unorm;
+                case '2C08':
+                    return dawn::TextureFormat::R8G8Unorm;
+                case 'L008':
+                    return dawn::TextureFormat::R8Unorm;
+                default:
+                    return DAWN_VALIDATION_ERROR("Unsupported IOSurface format");
+            }
+        }
+    }
+
+    MTLPixelFormat MetalPixelFormat(dawn::TextureFormat format) {
+        switch (format) {
+            case dawn::TextureFormat::R8G8B8A8Unorm:
+                return MTLPixelFormatRGBA8Unorm;
+            case dawn::TextureFormat::R8G8Unorm:
+                return MTLPixelFormatRG8Unorm;
+            case dawn::TextureFormat::R8Unorm:
+                return MTLPixelFormatR8Unorm;
+            case dawn::TextureFormat::R8G8B8A8Uint:
+                return MTLPixelFormatRGBA8Uint;
+            case dawn::TextureFormat::R8G8Uint:
+                return MTLPixelFormatRG8Uint;
+            case dawn::TextureFormat::R8Uint:
+                return MTLPixelFormatR8Uint;
+            case dawn::TextureFormat::B8G8R8A8Unorm:
+                return MTLPixelFormatBGRA8Unorm;
+            case dawn::TextureFormat::D32FloatS8Uint:
+                return MTLPixelFormatDepth32Float_Stencil8;
+        }
+    }
+
+    MaybeError ValidateIOSurfaceCanBeWrapped(const DeviceBase*,
+                                             const TextureDescriptor* descriptor,
+                                             IOSurfaceRef ioSurface,
+                                             uint32_t plane) {
+        // IOSurfaceGetPlaneCount can return 0 for non-planar IOSurfaces but we will treat
+        // non-planar like it is a single plane.
+        size_t surfacePlaneCount = std::max(size_t(1), IOSurfaceGetPlaneCount(ioSurface));
+        if (plane >= surfacePlaneCount) {
+            return DAWN_VALIDATION_ERROR("IOSurface plane doesn't exist");
+        }
+
+        if (descriptor->dimension != dawn::TextureDimension::e2D) {
+            return DAWN_VALIDATION_ERROR("IOSurface texture must be 2D");
+        }
+
+        if (descriptor->mipLevelCount != 1) {
+            return DAWN_VALIDATION_ERROR("IOSurface mip level count must be 1");
+        }
+
+        if (descriptor->arrayLayerCount != 1) {
+            return DAWN_VALIDATION_ERROR("IOSurface array layer count must be 1");
+        }
+
+        if (descriptor->sampleCount != 1) {
+            return DAWN_VALIDATION_ERROR("IOSurface sample count must be 1");
+        }
+
+        if (descriptor->size.width != IOSurfaceGetWidthOfPlane(ioSurface, plane) ||
+            descriptor->size.height != IOSurfaceGetHeightOfPlane(ioSurface, plane) ||
+            descriptor->size.depth != 1) {
+            return DAWN_VALIDATION_ERROR("IOSurface size doesn't match descriptor");
+        }
+
+        dawn::TextureFormat ioSurfaceFormat;
+        DAWN_TRY_ASSIGN(ioSurfaceFormat,
+                        GetFormatEquivalentToIOSurfaceFormat(IOSurfaceGetPixelFormat(ioSurface)));
+        if (descriptor->format != ioSurfaceFormat) {
+            return DAWN_VALIDATION_ERROR("IOSurface format doesn't match descriptor");
+        }
+
+        return {};
+    }
+
+    MTLTextureDescriptor* CreateMetalTextureDescriptor(const TextureDescriptor* descriptor) {
+        MTLTextureDescriptor* mtlDesc = [MTLTextureDescriptor new];
+        mtlDesc.textureType = MetalTextureType(descriptor->dimension, descriptor->arrayLayerCount);
+        mtlDesc.usage = MetalTextureUsage(descriptor->usage);
+        mtlDesc.pixelFormat = MetalPixelFormat(descriptor->format);
+
+        mtlDesc.width = descriptor->size.width;
+        mtlDesc.height = descriptor->size.height;
+        mtlDesc.depth = descriptor->size.depth;
+
+        mtlDesc.mipmapLevelCount = descriptor->mipLevelCount;
+        mtlDesc.arrayLength = descriptor->arrayLayerCount;
+        mtlDesc.storageMode = MTLStorageModePrivate;
+
+        return mtlDesc;
     }
 
     Texture::Texture(Device* device, const TextureDescriptor* descriptor)
         : TextureBase(device, descriptor) {
-        auto desc = [MTLTextureDescriptor new];
-        [desc autorelease];
-        desc.textureType = MetalTextureType(GetDimension(), GetArrayLayers());
-        desc.usage = MetalTextureUsage(GetUsage());
-        desc.pixelFormat = MetalPixelFormat(GetFormat());
-
-        const Extent3D& size = GetSize();
-        desc.width = size.width;
-        desc.height = size.height;
-        desc.depth = size.depth;
-
-        desc.mipmapLevelCount = GetNumMipLevels();
-        desc.arrayLength = GetArrayLayers();
-        desc.storageMode = MTLStorageModePrivate;
-
-        auto mtlDevice = device->GetMTLDevice();
-        mMtlTexture = [mtlDevice newTextureWithDescriptor:desc];
+        MTLTextureDescriptor* mtlDesc = CreateMetalTextureDescriptor(descriptor);
+        mMtlTexture = [device->GetMTLDevice() newTextureWithDescriptor:mtlDesc];
+        [mtlDesc release];
     }
 
     Texture::Texture(Device* device, const TextureDescriptor* descriptor, id<MTLTexture> mtlTexture)
@@ -143,6 +205,18 @@
         [mMtlTexture retain];
     }
 
+    Texture::Texture(Device* device,
+                     const TextureDescriptor* descriptor,
+                     IOSurfaceRef ioSurface,
+                     uint32_t plane)
+        : TextureBase(device, descriptor) {
+        MTLTextureDescriptor* mtlDesc = CreateMetalTextureDescriptor(descriptor);
+        mMtlTexture = [device->GetMTLDevice() newTextureWithDescriptor:mtlDesc
+                                                             iosurface:ioSurface
+                                                                 plane:plane];
+        [mtlDesc release];
+    }
+
     Texture::~Texture() {
         [mMtlTexture release];
     }
diff --git a/src/include/dawn_native/MetalBackend.h b/src/include/dawn_native/MetalBackend.h
index fdca226..ae8b58a 100644
--- a/src/include/dawn_native/MetalBackend.h
+++ b/src/include/dawn_native/MetalBackend.h
@@ -18,10 +18,24 @@
 #include <dawn/dawn_wsi.h>
 #include <dawn_native/DawnNative.h>
 
-#import <Metal/Metal.h>
+struct __IOSurface;
+typedef __IOSurface* IOSurfaceRef;
+
+#ifdef __OBJC__
+#    import <Metal/Metal.h>
+#endif  //__OBJC__
 
 namespace dawn_native { namespace metal {
-    DAWN_NATIVE_EXPORT id<MTLDevice> GetMetalDevice(dawnDevice device);
+    DAWN_NATIVE_EXPORT dawnTexture WrapIOSurface(dawnDevice device,
+                                                 const dawnTextureDescriptor* descriptor,
+                                                 IOSurfaceRef ioSurface,
+                                                 uint32_t plane);
 }}  // namespace dawn_native::metal
 
+#ifdef __OBJC__
+namespace dawn_native { namespace metal {
+    DAWN_NATIVE_EXPORT id<MTLDevice> GetMetalDevice(dawnDevice device);
+}}      // namespace dawn_native::metal
+#endif  // __OBJC__
+
 #endif  // DAWNNATIVE_METALBACKEND_H_
diff --git a/src/tests/end2end/IOSurfaceWrappingTests.cpp b/src/tests/end2end/IOSurfaceWrappingTests.cpp
new file mode 100644
index 0000000..8c41fd3
--- /dev/null
+++ b/src/tests/end2end/IOSurfaceWrappingTests.cpp
@@ -0,0 +1,420 @@
+// Copyright 2019 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 "tests/DawnTest.h"
+
+#include "dawn_native/MetalBackend.h"
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/DawnHelpers.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOSurface/IOSurface.h>
+
+namespace {
+
+    void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef key, int32_t value) {
+        CFNumberRef number = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value);
+        CFDictionaryAddValue(dictionary, key, number);
+        CFRelease(number);
+    }
+
+    class ScopedIOSurfaceRef {
+      public:
+        ScopedIOSurfaceRef() : mSurface(nullptr) {
+        }
+        explicit ScopedIOSurfaceRef(IOSurfaceRef surface) : mSurface(surface) {
+        }
+
+        ~ScopedIOSurfaceRef() {
+            if (mSurface != nullptr) {
+                CFRelease(mSurface);
+                mSurface = nullptr;
+            }
+        }
+
+        IOSurfaceRef get() const {
+            return mSurface;
+        }
+
+        ScopedIOSurfaceRef(ScopedIOSurfaceRef&& other) {
+            if (mSurface != nullptr) {
+                CFRelease(mSurface);
+            }
+            mSurface = other.mSurface;
+            other.mSurface = nullptr;
+        }
+
+        ScopedIOSurfaceRef& operator=(ScopedIOSurfaceRef&& other) {
+            if (mSurface != nullptr) {
+                CFRelease(mSurface);
+            }
+            mSurface = other.mSurface;
+            other.mSurface = nullptr;
+
+            return *this;
+        }
+
+        ScopedIOSurfaceRef(const ScopedIOSurfaceRef&) = delete;
+        ScopedIOSurfaceRef& operator=(const ScopedIOSurfaceRef&) = delete;
+
+      private:
+        IOSurfaceRef mSurface = nullptr;
+    };
+
+    ScopedIOSurfaceRef CreateSinglePlaneIOSurface(uint32_t width,
+                                                  uint32_t height,
+                                                  uint32_t format,
+                                                  uint32_t bytesPerElement) {
+        CFMutableDictionaryRef dict =
+            CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+                                      &kCFTypeDictionaryValueCallBacks);
+        AddIntegerValue(dict, kIOSurfaceWidth, width);
+        AddIntegerValue(dict, kIOSurfaceHeight, height);
+        AddIntegerValue(dict, kIOSurfacePixelFormat, format);
+        AddIntegerValue(dict, kIOSurfaceBytesPerElement, bytesPerElement);
+
+        IOSurfaceRef ioSurface = IOSurfaceCreate(dict);
+        EXPECT_NE(nullptr, ioSurface);
+        CFRelease(dict);
+
+        return ScopedIOSurfaceRef(ioSurface);
+    }
+
+    class IOSurfaceTestBase : public DawnTest {
+      public:
+        dawn::Texture WrapIOSurface(const dawn::TextureDescriptor* descriptor,
+                                    IOSurfaceRef ioSurface,
+                                    uint32_t plane) {
+            dawnTexture texture = dawn_native::metal::WrapIOSurface(
+                device.Get(), reinterpret_cast<const dawnTextureDescriptor*>(descriptor), ioSurface,
+                plane);
+            return dawn::Texture::Acquire(texture);
+        }
+    };
+
+}  // anonymous namespace
+
+// A small fixture used to initialize default data for the IOSurface validation tests.
+class IOSurfaceValidationTests : public IOSurfaceTestBase {
+  public:
+    IOSurfaceValidationTests() {
+        defaultIOSurface = CreateSinglePlaneIOSurface(10, 10, 'BGRA', 4);
+
+        descriptor.dimension = dawn::TextureDimension::e2D;
+        descriptor.format = dawn::TextureFormat::B8G8R8A8Unorm;
+        descriptor.size = {10, 10, 1};
+        descriptor.sampleCount = 1;
+        descriptor.arrayLayerCount = 1;
+        descriptor.mipLevelCount = 1;
+        descriptor.usage = dawn::TextureUsageBit::OutputAttachment;
+    }
+
+  protected:
+    dawn::TextureDescriptor descriptor;
+    ScopedIOSurfaceRef defaultIOSurface;
+};
+
+// Test a successful wrapping of an IOSurface in a texture
+TEST_P(IOSurfaceValidationTests, Success) {
+    dawn::Texture texture = WrapIOSurface(&descriptor, defaultIOSurface.get(), 0);
+    ASSERT_NE(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the texture descriptor is invalid
+TEST_P(IOSurfaceValidationTests, InvalidTextureDescriptor) {
+    descriptor.nextInChain = this;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the plane is too large
+TEST_P(IOSurfaceValidationTests, PlaneTooLarge) {
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 1));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor dimension isn't 2D
+// TODO(cwallez@chromium.org): Reenable when 1D or 3D textures are implemented
+TEST_P(IOSurfaceValidationTests, DISABLED_InvalidTextureDimension) {
+    descriptor.dimension = dawn::TextureDimension::e2D;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor mip level count isn't 1
+TEST_P(IOSurfaceValidationTests, InvalidMipLevelCount) {
+    descriptor.mipLevelCount = 2;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor array layer count isn't 1
+TEST_P(IOSurfaceValidationTests, InvalidArrayLayerCount) {
+    descriptor.arrayLayerCount = 2;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor sample count isn't 1
+TEST_P(IOSurfaceValidationTests, InvalidSampleCount) {
+    descriptor.sampleCount = 4;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor width doesn't match the surface's
+TEST_P(IOSurfaceValidationTests, InvalidWidth) {
+    descriptor.size.width = 11;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor height doesn't match the surface's
+TEST_P(IOSurfaceValidationTests, InvalidHeight) {
+    descriptor.size.height = 11;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor format isn't compatible with the IOSurface's
+TEST_P(IOSurfaceValidationTests, InvalidFormat) {
+    descriptor.format = dawn::TextureFormat::R8Unorm;
+
+    ASSERT_DEVICE_ERROR(dawn::Texture texture =
+                            WrapIOSurface(&descriptor, defaultIOSurface.get(), 0));
+    ASSERT_EQ(texture.Get(), nullptr);
+}
+
+// Fixture to test using IOSurfaces through different usages.
+class IOSurfaceUsageTests : public IOSurfaceTestBase {
+  public:
+    // Test that sampling a 1x1 works.
+    void DoSampleTest(IOSurfaceRef ioSurface,
+                      dawn::TextureFormat format,
+                      void* data,
+                      size_t dataSize,
+                      RGBA8 expectedColor) {
+        // Write the data to the IOSurface
+        IOSurfaceLock(ioSurface, 0, nullptr);
+        memcpy(IOSurfaceGetBaseAddress(ioSurface), data, dataSize);
+        IOSurfaceUnlock(ioSurface, 0, nullptr);
+
+        // The bindgroup containing the texture view for the ioSurface as well as the sampler.
+        dawn::BindGroupLayout bgl;
+        dawn::BindGroup bindGroup;
+        {
+            dawn::TextureDescriptor textureDescriptor;
+            textureDescriptor.dimension = dawn::TextureDimension::e2D;
+            textureDescriptor.format = format;
+            textureDescriptor.size = {1, 1, 1};
+            textureDescriptor.sampleCount = 1;
+            textureDescriptor.arrayLayerCount = 1;
+            textureDescriptor.mipLevelCount = 1;
+            textureDescriptor.usage = dawn::TextureUsageBit::Sampled;
+            dawn::Texture wrappingTexture = WrapIOSurface(&textureDescriptor, ioSurface, 0);
+
+            dawn::TextureView textureView = wrappingTexture.CreateDefaultTextureView();
+
+            dawn::SamplerDescriptor samplerDescriptor = utils::GetDefaultSamplerDescriptor();
+            dawn::Sampler sampler = device.CreateSampler(&samplerDescriptor);
+
+            bgl = utils::MakeBindGroupLayout(
+                device, {
+                            {0, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler},
+                            {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::SampledTexture},
+                        });
+
+            bindGroup = utils::MakeBindGroup(device, bgl, {{0, sampler}, {1, textureView}});
+        }
+
+        // The simplest texture sampling pipeline.
+        dawn::RenderPipeline pipeline;
+        {
+            dawn::ShaderModule vs = utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"(
+                #version 450
+                layout (location = 0) out vec2 o_texCoord;
+                void main() {
+                    const vec2 pos[6] = vec2[6](vec2(-2.f, -2.f),
+                                                vec2(-2.f,  2.f),
+                                                vec2( 2.f, -2.f),
+                                                vec2(-2.f,  2.f),
+                                                vec2( 2.f, -2.f),
+                                                vec2( 2.f,  2.f));
+                    const vec2 texCoord[6] = vec2[6](vec2(0.f, 0.f),
+                                                     vec2(0.f, 1.f),
+                                                     vec2(1.f, 0.f),
+                                                     vec2(0.f, 1.f),
+                                                     vec2(1.f, 0.f),
+                                                     vec2(1.f, 1.f));
+                    gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
+                    o_texCoord = texCoord[gl_VertexIndex];
+                }
+            )");
+            dawn::ShaderModule fs =
+                utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, R"(
+                #version 450
+                layout(set = 0, binding = 0) uniform sampler sampler0;
+                layout(set = 0, binding = 1) uniform texture2D texture0;
+                layout(location = 0) in vec2 texCoord;
+                layout(location = 0) out vec4 fragColor;
+
+                void main() {
+                    fragColor = texture(sampler2D(texture0, sampler0), texCoord);
+                }
+            )");
+
+            utils::ComboRenderPipelineDescriptor descriptor(device);
+            descriptor.cVertexStage.module = vs;
+            descriptor.cFragmentStage.module = fs;
+            descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl);
+            descriptor.cColorStates[0]->format = dawn::TextureFormat::R8G8B8A8Unorm;
+
+            pipeline = device.CreateRenderPipeline(&descriptor);
+        }
+
+        // Submit commands samping from the ioSurface and writing the result to renderPass.color
+        utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
+        dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+        {
+            dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+            pass.SetPipeline(pipeline);
+            pass.SetBindGroup(0, bindGroup);
+            pass.Draw(6, 1, 0, 0);
+            pass.EndPass();
+        }
+
+        dawn::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_PIXEL_RGBA8_EQ(expectedColor, renderPass.color, 0, 0);
+    }
+
+    // Test that clearing using BeginRenderPass writes correct data in the ioSurface.
+    void DoClearTest(IOSurfaceRef ioSurface,
+                     dawn::TextureFormat format,
+                     void* data,
+                     size_t dataSize) {
+        // Get a texture view for the ioSurface
+        dawn::TextureDescriptor textureDescriptor;
+        textureDescriptor.dimension = dawn::TextureDimension::e2D;
+        textureDescriptor.format = format;
+        textureDescriptor.size = {1, 1, 1};
+        textureDescriptor.sampleCount = 1;
+        textureDescriptor.arrayLayerCount = 1;
+        textureDescriptor.mipLevelCount = 1;
+        textureDescriptor.usage = dawn::TextureUsageBit::OutputAttachment;
+        dawn::Texture ioSurfaceTexture = WrapIOSurface(&textureDescriptor, ioSurface, 0);
+
+        dawn::TextureView ioSurfaceView = ioSurfaceTexture.CreateDefaultTextureView();
+
+        utils::ComboRenderPassDescriptor renderPassDescriptor({ioSurfaceView}, {});
+        renderPassDescriptor.cColorAttachmentsInfoPtr[0]->clearColor = {1 / 255.0f, 2 / 255.0f,
+                                                                        3 / 255.0f, 4 / 255.0f};
+
+        // Execute commands to clear the ioSurface
+        dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+        pass.EndPass();
+
+        dawn::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        // Use a fence to know that GPU rendering is finished.
+        // TODO(cwallez@chromium.org): IOSurfaceLock should wait for previous GPU use of the
+        // IOSurface to be completed but this appears to not be the case.
+        // Maybe it is because the Metal command buffer has been submitted but not "scheduled" yet?
+        dawn::FenceDescriptor fenceDescriptor;
+        fenceDescriptor.initialValue = 0u;
+        dawn::Fence fence = device.CreateFence(&fenceDescriptor);
+        queue.Signal(fence, 1);
+
+        while (fence.GetCompletedValue() < 1) {
+            WaitABit();
+        }
+
+        // Check the correct data was written
+        IOSurfaceLock(ioSurface, kIOSurfaceLockReadOnly, nullptr);
+        ASSERT_EQ(0, memcmp(IOSurfaceGetBaseAddress(ioSurface), data, dataSize));
+        IOSurfaceUnlock(ioSurface, kIOSurfaceLockReadOnly, nullptr);
+    }
+};
+
+// Test sampling from a R8 IOSurface
+TEST_P(IOSurfaceUsageTests, SampleFromR8IOSurface) {
+    ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, 'L008', 1);
+
+    uint8_t data = 0x01;
+    DoSampleTest(ioSurface.get(), dawn::TextureFormat::R8Unorm, &data, sizeof(data),
+                 RGBA8(1, 0, 0, 255));
+}
+
+// Test clearing a R8 IOSurface
+TEST_P(IOSurfaceUsageTests, ClearR8IOSurface) {
+    ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, 'L008', 1);
+
+    uint8_t data = 0x01;
+    DoClearTest(ioSurface.get(), dawn::TextureFormat::R8Unorm, &data, sizeof(data));
+}
+
+// Test sampling from a RG8 IOSurface
+TEST_P(IOSurfaceUsageTests, SampleFromRG8IOSurface) {
+    ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, '2C08', 2);
+
+    uint16_t data = 0x0102;  // Stored as (G, R)
+    DoSampleTest(ioSurface.get(), dawn::TextureFormat::R8G8Unorm, &data, sizeof(data),
+                 RGBA8(2, 1, 0, 255));
+}
+
+// Test clearing a RG8 IOSurface
+TEST_P(IOSurfaceUsageTests, ClearRG8IOSurface) {
+    ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, '2C08', 2);
+
+    uint16_t data = 0x0201;
+    DoClearTest(ioSurface.get(), dawn::TextureFormat::R8G8Unorm, &data, sizeof(data));
+}
+
+// Test sampling from a BGRA8 IOSurface
+TEST_P(IOSurfaceUsageTests, SampleFromBGRA8888IOSurface) {
+    ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, 'BGRA', 4);
+
+    uint32_t data = 0x01020304;  // Stored as (A, R, G, B)
+    DoSampleTest(ioSurface.get(), dawn::TextureFormat::B8G8R8A8Unorm, &data, sizeof(data),
+                 RGBA8(2, 3, 4, 1));
+}
+
+// Test clearing a BGRA8 IOSurface
+TEST_P(IOSurfaceUsageTests, ClearBGRA8IOSurface) {
+    ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, 'BGRA', 4);
+
+    uint32_t data = 0x04010203;
+    DoClearTest(ioSurface.get(), dawn::TextureFormat::B8G8R8A8Unorm, &data, sizeof(data));
+}
+
+DAWN_INSTANTIATE_TEST(IOSurfaceValidationTests, MetalBackend);
+DAWN_INSTANTIATE_TEST(IOSurfaceUsageTests, MetalBackend);