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>
This commit is contained in:
parent
2dfb3f01e7
commit
0cdf9e09c4
9
BUILD.gn
9
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) {
|
||||
|
|
|
@ -51,6 +51,10 @@ namespace dawn_native { namespace metal {
|
|||
|
||||
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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<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
|
||||
|
|
|
@ -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> mtlTexture);
|
||||
Texture(Device* device,
|
||||
const TextureDescriptor* descriptor,
|
||||
IOSurfaceRef ioSurface,
|
||||
uint32_t plane);
|
||||
~Texture();
|
||||
|
||||
id<MTLTexture> GetMTLTexture();
|
||||
|
|
|
@ -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 @@ namespace dawn_native { namespace metal {
|
|||
|
||||
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 @@ 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];
|
||||
}
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
Loading…
Reference in New Issue