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);