dawn-cmake/src/tests/white_box/EGLImageWrappingTests.cpp
Stephen White b5652c75d2 Implement EGLImage external texture API for GL.
Tests are based on the IOSurfaceWrappingTests.
Sampling tests are not implemented, since they would require
support for the samplerExternalOES sampler type in WGSL.

Bug: chromium:1205155
Change-Id: Icc114eaf6efaee93f1b8486e615f0fd307f23080
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/50201
Commit-Queue: Stephen White <senorblanco@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
2021-06-03 16:19:16 +00:00

357 lines
13 KiB
C++

// Copyright 2021 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 "common/DynamicLib.h"
#include "dawn_native/OpenGLBackend.h"
#include "dawn_native/opengl/DeviceGL.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
#include <EGL/egl.h>
namespace {
class EGLFunctions {
public:
EGLFunctions() {
#ifdef DAWN_PLATFORM_WINDOWS
const char* eglLib = "libEGL.dll";
#else
const char* eglLib = "libEGL.so";
#endif
EXPECT_TRUE(mlibEGL.Open(eglLib));
CreateImage = reinterpret_cast<PFNEGLCREATEIMAGEPROC>(LoadProc("eglCreateImage"));
DestroyImage = reinterpret_cast<PFNEGLDESTROYIMAGEPROC>(LoadProc("eglDestroyImage"));
GetCurrentContext =
reinterpret_cast<PFNEGLGETCURRENTCONTEXTPROC>(LoadProc("eglGetCurrentContext"));
GetCurrentDisplay =
reinterpret_cast<PFNEGLGETCURRENTDISPLAYPROC>(LoadProc("eglGetCurrentDisplay"));
}
private:
void* LoadProc(const char* name) {
void* proc = mlibEGL.GetProc(name);
EXPECT_NE(proc, nullptr);
return proc;
}
public:
PFNEGLCREATEIMAGEPROC CreateImage;
PFNEGLDESTROYIMAGEPROC DestroyImage;
PFNEGLGETCURRENTCONTEXTPROC GetCurrentContext;
PFNEGLGETCURRENTDISPLAYPROC GetCurrentDisplay;
private:
DynamicLib mlibEGL;
};
class ScopedEGLImage {
public:
ScopedEGLImage(PFNEGLDESTROYIMAGEPROC destroyImage,
PFNGLDELETETEXTURESPROC deleteTextures,
EGLDisplay display,
EGLImage image,
GLuint texture)
: mDestroyImage(destroyImage),
mDeleteTextures(deleteTextures),
mDisplay(display),
mImage(image),
mTexture(texture) {
}
ScopedEGLImage(ScopedEGLImage&& other) {
if (mImage != nullptr) {
mDestroyImage(mDisplay, mImage);
}
if (mTexture != 0) {
mDeleteTextures(1, &mTexture);
}
mDestroyImage = std::move(other.mDestroyImage);
mDeleteTextures = std::move(other.mDeleteTextures);
mDisplay = std::move(other.mDisplay);
mImage = std::move(other.mImage);
mTexture = std::move(other.mTexture);
}
~ScopedEGLImage() {
if (mTexture != 0) {
mDeleteTextures(1, &mTexture);
}
if (mImage != nullptr) {
mDestroyImage(mDisplay, mImage);
}
}
EGLImage getImage() const {
return mImage;
}
GLuint getTexture() const {
return mTexture;
}
private:
PFNEGLDESTROYIMAGEPROC mDestroyImage = nullptr;
PFNGLDELETETEXTURESPROC mDeleteTextures = nullptr;
EGLDisplay mDisplay = nullptr;
EGLImage mImage = nullptr;
GLuint mTexture = 0;
};
} // anonymous namespace
class EGLImageTestBase : public DawnTest {
public:
ScopedEGLImage CreateEGLImage(uint32_t width,
uint32_t height,
GLenum internalFormat,
GLenum format,
GLenum type,
void* data,
size_t size) {
dawn_native::opengl::Device* openglDevice =
reinterpret_cast<dawn_native::opengl::Device*>(device.Get());
const dawn_native::opengl::OpenGLFunctions& gl = openglDevice->gl;
GLuint tex;
gl.GenTextures(1, &tex);
gl.BindTexture(GL_TEXTURE_2D, tex);
gl.TexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data);
EGLAttrib attribs[1] = {EGL_NONE};
EGLClientBuffer buffer = reinterpret_cast<EGLClientBuffer>(tex);
EGLDisplay dpy = egl.GetCurrentDisplay();
EGLContext ctx = egl.GetCurrentContext();
EGLImage eglImage = egl.CreateImage(dpy, ctx, EGL_GL_TEXTURE_2D, buffer, attribs);
EXPECT_NE(nullptr, eglImage);
return ScopedEGLImage(egl.DestroyImage, gl.DeleteTextures, dpy, eglImage, tex);
}
wgpu::Texture WrapEGLImage(const wgpu::TextureDescriptor* descriptor, EGLImage eglImage) {
dawn_native::opengl::ExternalImageDescriptorEGLImage externDesc;
externDesc.cTextureDescriptor = reinterpret_cast<const WGPUTextureDescriptor*>(descriptor);
externDesc.image = eglImage;
WGPUTexture texture = dawn_native::opengl::WrapExternalEGLImage(device.Get(), &externDesc);
return wgpu::Texture::Acquire(texture);
}
EGLFunctions egl;
};
// A small fixture used to initialize default data for the EGLImage validation tests.
// These tests are skipped if the harness is using the wire.
class EGLImageValidationTests : public EGLImageTestBase {
public:
EGLImageValidationTests() {
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.size = {10, 10, 1};
descriptor.sampleCount = 1;
descriptor.mipLevelCount = 1;
descriptor.usage = wgpu::TextureUsage::RenderAttachment;
}
ScopedEGLImage CreateDefaultEGLImage() {
return CreateEGLImage(10, 10, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, 0);
}
protected:
wgpu::TextureDescriptor descriptor;
};
// Test a successful wrapping of an EGLImage in a texture
TEST_P(EGLImageValidationTests, Success) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
ScopedEGLImage image = CreateDefaultEGLImage();
wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage());
ASSERT_NE(texture.Get(), nullptr);
}
// Test an error occurs if the texture descriptor is invalid
TEST_P(EGLImageValidationTests, InvalidTextureDescriptor) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
wgpu::ChainedStruct chainedDescriptor;
descriptor.nextInChain = &chainedDescriptor;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the descriptor dimension isn't 2D
TEST_P(EGLImageValidationTests, InvalidTextureDimension) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.dimension = wgpu::TextureDimension::e3D;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the texture usage is not RenderAttachment
TEST_P(EGLImageValidationTests, InvalidTextureUsage) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.usage = wgpu::TextureUsage::Sampled;
ScopedEGLImage image = CreateDefaultEGLImage();
wgpu::Texture texture;
ASSERT_DEVICE_ERROR(texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
descriptor.usage = wgpu::TextureUsage::Storage;
ASSERT_DEVICE_ERROR(texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the descriptor mip level count isn't 1
TEST_P(EGLImageValidationTests, InvalidMipLevelCount) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.mipLevelCount = 2;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the descriptor depth isn't 1
TEST_P(EGLImageValidationTests, InvalidDepth) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.size.depthOrArrayLayers = 2;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the descriptor sample count isn't 1
TEST_P(EGLImageValidationTests, InvalidSampleCount) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.sampleCount = 4;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the descriptor width doesn't match the surface's
TEST_P(EGLImageValidationTests, InvalidWidth) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.size.width = 11;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if the descriptor height doesn't match the surface's
TEST_P(EGLImageValidationTests, InvalidHeight) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
descriptor.size.height = 11;
ScopedEGLImage image = CreateDefaultEGLImage();
ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
ASSERT_EQ(texture.Get(), nullptr);
}
// Fixture to test using EGLImages through different usages.
// These tests are skipped if the harness is using the wire.
class EGLImageUsageTests : public EGLImageTestBase {
public:
// Test that clearing using BeginRenderPass writes correct data in the eglImage.
void DoClearTest(EGLImage eglImage,
GLuint texture,
wgpu::TextureFormat format,
GLenum glFormat,
GLenum glType,
void* data,
size_t dataSize) {
dawn_native::opengl::Device* openglDevice =
reinterpret_cast<dawn_native::opengl::Device*>(device.Get());
const dawn_native::opengl::OpenGLFunctions& gl = openglDevice->gl;
// Get a texture view for the eglImage
wgpu::TextureDescriptor textureDescriptor;
textureDescriptor.dimension = wgpu::TextureDimension::e2D;
textureDescriptor.format = format;
textureDescriptor.size = {1, 1, 1};
textureDescriptor.sampleCount = 1;
textureDescriptor.mipLevelCount = 1;
textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
wgpu::Texture eglImageTexture = WrapEGLImage(&textureDescriptor, eglImage);
ASSERT_NE(eglImageTexture, nullptr);
wgpu::TextureView eglImageView = eglImageTexture.CreateView();
utils::ComboRenderPassDescriptor renderPassDescriptor({eglImageView}, {});
renderPassDescriptor.cColorAttachments[0].clearColor = {1 / 255.0f, 2 / 255.0f, 3 / 255.0f,
4 / 255.0f};
// Execute commands to clear the eglImage
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
pass.EndPass();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Check the correct data was written
std::vector<uint8_t> result(dataSize);
GLuint fbo;
gl.GenFramebuffers(1, &fbo);
gl.BindFramebuffer(GL_FRAMEBUFFER, fbo);
gl.FramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
0);
gl.ReadPixels(0, 0, 1, 1, glFormat, glType, result.data());
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffers(1, &fbo);
ASSERT_EQ(0, memcmp(result.data(), data, dataSize));
}
};
// Test clearing a R8 EGLImage
TEST_P(EGLImageUsageTests, ClearR8EGLImage) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
ScopedEGLImage eglImage = CreateEGLImage(1, 1, GL_R8, GL_RED, GL_UNSIGNED_BYTE, nullptr, 0);
uint8_t data = 0x01;
DoClearTest(eglImage.getImage(), eglImage.getTexture(), wgpu::TextureFormat::R8Unorm, GL_RED,
GL_UNSIGNED_BYTE, &data, sizeof(data));
}
// Test clearing a RG8 EGLImage
TEST_P(EGLImageUsageTests, ClearRG8EGLImage) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
ScopedEGLImage eglImage = CreateEGLImage(1, 1, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, nullptr, 0);
uint16_t data = 0x0201;
DoClearTest(eglImage.getImage(), eglImage.getTexture(), wgpu::TextureFormat::RG8Unorm, GL_RG,
GL_UNSIGNED_BYTE, &data, sizeof(data));
}
// Test clearing an RGBA8 EGLImage
TEST_P(EGLImageUsageTests, ClearRGBA8EGLImage) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
ScopedEGLImage eglImage = CreateEGLImage(1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, 0);
uint32_t data = 0x04030201;
DoClearTest(eglImage.getImage(), eglImage.getTexture(), wgpu::TextureFormat::RGBA8Unorm,
GL_RGBA, GL_UNSIGNED_BYTE, &data, sizeof(data));
}
DAWN_INSTANTIATE_TEST(EGLImageValidationTests, OpenGLESBackend());
DAWN_INSTANTIATE_TEST(EGLImageUsageTests, OpenGLESBackend());