diff --git a/BUILD.gn b/BUILD.gn index 1bdeb3e862..13c130e8ae 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -564,6 +564,7 @@ source_set("libdawn_native_sources") { "Metal.framework", "Cocoa.framework", "IOKit.framework", + "IOSurface.framework", ] sources += [ "src/dawn_native/metal/BackendMTL.h", @@ -1018,6 +1019,14 @@ test("dawn_end2end_tests") { "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 6969b5d5c4..9bcbf2f577 100644 --- a/src/dawn_native/metal/DeviceMTL.h +++ b/src/dawn_native/metal/DeviceMTL.h @@ -51,6 +51,10 @@ namespace dawn_native { namespace metal { MapRequestTracker* GetMapTracker() const; + TextureBase* CreateTextureWrappingIOSurface(const TextureDescriptor* descriptor, + IOSurfaceRef ioSurface, + uint32_t plane); + ResultOrError> 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 3037e6f4a0..3a5fa1a9f1 100644 --- a/src/dawn_native/metal/DeviceMTL.mm +++ b/src/dawn_native/metal/DeviceMTL.mm @@ -204,4 +204,16 @@ namespace dawn_native { namespace metal { 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 d8e3eaa46d..7f6274740f 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 @@ namespace dawn_native { namespace metal { return device->GetMTLDevice(); } + dawnTexture WrapIOSurface(dawnDevice cDevice, + const dawnTextureDescriptor* cDescriptor, + IOSurfaceRef ioSurface, + uint32_t plane) { + Device* device = reinterpret_cast(cDevice); + const TextureDescriptor* descriptor = + reinterpret_cast(cDescriptor); + TextureBase* texture = device->CreateTextureWrappingIOSurface(descriptor, ioSurface, plane); + return reinterpret_cast(texture); + } + }} // namespace dawn_native::metal diff --git a/src/dawn_native/metal/TextureMTL.h b/src/dawn_native/metal/TextureMTL.h index c002f569db..51951f67ac 100644 --- a/src/dawn_native/metal/TextureMTL.h +++ b/src/dawn_native/metal/TextureMTL.h @@ -24,11 +24,19 @@ namespace dawn_native { namespace metal { 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); + Texture(Device* device, + const TextureDescriptor* descriptor, + IOSurfaceRef ioSurface, + uint32_t plane); ~Texture(); id GetMTLTexture(); diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm index c438a40483..607b00e1a6 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 + 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 @@ namespace dawn_native { namespace metal { return false; } + + ResultOrError 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) @@ -143,6 +205,18 @@ namespace dawn_native { namespace metal { [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 fdca226142..ae8b58aa36 100644 --- a/src/include/dawn_native/MetalBackend.h +++ b/src/include/dawn_native/MetalBackend.h @@ -18,10 +18,24 @@ #include #include -#import +struct __IOSurface; +typedef __IOSurface* IOSurfaceRef; + +#ifdef __OBJC__ +# import +#endif //__OBJC__ namespace dawn_native { namespace metal { - DAWN_NATIVE_EXPORT id 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 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 0000000000..8c41fd356f --- /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 +#include + +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(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);