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>
This commit is contained in:
Stephen White 2021-06-03 16:19:16 +00:00 committed by Dawn LUCI CQ
parent b3c371031c
commit b5652c75d2
9 changed files with 454 additions and 1 deletions

View File

@ -37,6 +37,7 @@ using GLhalf = unsigned short;
using GLint64 = khronos_int64_t;
using GLuint64 = khronos_uint64_t;
using GLsync = struct __GLsync*;
using GLeglImageOES = void*;
using GLDEBUGPROC = void(KHRONOS_APIENTRY*)(GLenum source,
GLenum type,
GLuint id,

View File

@ -176,6 +176,65 @@ namespace dawn_native { namespace opengl {
mFencesInFlight.emplace(sync, GetLastSubmittedCommandSerial());
}
MaybeError Device::ValidateEGLImageCanBeWrapped(const TextureDescriptor* descriptor,
::EGLImage image) {
if (descriptor->dimension != wgpu::TextureDimension::e2D) {
return DAWN_VALIDATION_ERROR("EGLImage texture must be 2D");
}
if (descriptor->usage & (wgpu::TextureUsage::Sampled | wgpu::TextureUsage::Storage)) {
return DAWN_VALIDATION_ERROR("EGLImage texture cannot have sampled or storage usage");
}
if (descriptor->mipLevelCount != 1) {
return DAWN_VALIDATION_ERROR("EGLImage mip level count must be 1");
}
if (descriptor->size.depthOrArrayLayers != 1) {
return DAWN_VALIDATION_ERROR("EGLImage array layer count must be 1");
}
if (descriptor->sampleCount != 1) {
return DAWN_VALIDATION_ERROR("EGLImage sample count must be 1");
}
return {};
}
TextureBase* Device::CreateTextureWrappingEGLImage(const ExternalImageDescriptor* descriptor,
::EGLImage image) {
const TextureDescriptor* textureDescriptor =
reinterpret_cast<const TextureDescriptor*>(descriptor->cTextureDescriptor);
if (ConsumedError(ValidateTextureDescriptor(this, textureDescriptor))) {
return nullptr;
}
if (ConsumedError(ValidateEGLImageCanBeWrapped(textureDescriptor, image))) {
return nullptr;
}
GLuint tex;
gl.GenTextures(1, &tex);
gl.BindTexture(GL_TEXTURE_2D, tex);
gl.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
GLint width, height, internalFormat;
gl.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
gl.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
gl.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
if (textureDescriptor->size.width != static_cast<uint32_t>(width) ||
textureDescriptor->size.height != static_cast<uint32_t>(height) ||
textureDescriptor->size.depthOrArrayLayers != 1) {
ConsumedError(DAWN_VALIDATION_ERROR("EGLImage size doesn't match descriptor"));
gl.DeleteTextures(1, &tex);
return nullptr;
}
// TODO(dawn:803): Validate the OpenGL texture format from the EGLImage against the format
// in the passed-in TextureDescriptor.
return new Texture(this, textureDescriptor, tex, TextureBase::TextureState::OwnedInternal);
}
MaybeError Device::TickImpl() {
return {};
}

View File

@ -31,6 +31,8 @@
# include "common/windows_with_undefs.h"
#endif
typedef void* EGLImage;
namespace dawn_native { namespace opengl {
class Device : public DeviceBase {
@ -49,6 +51,11 @@ namespace dawn_native { namespace opengl {
void SubmitFenceSync();
MaybeError ValidateEGLImageCanBeWrapped(const TextureDescriptor* descriptor,
::EGLImage image);
TextureBase* CreateTextureWrappingEGLImage(const ExternalImageDescriptor* descriptor,
::EGLImage image);
ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer(
CommandEncoder* encoder,
const CommandBufferDescriptor* descriptor) override;

View File

@ -50,4 +50,15 @@ namespace dawn_native { namespace opengl {
return static_cast<WGPUTextureFormat>(impl->GetPreferredFormat());
}
ExternalImageDescriptorEGLImage::ExternalImageDescriptorEGLImage()
: ExternalImageDescriptor(ExternalImageType::EGLImage) {
}
WGPUTexture WrapExternalEGLImage(WGPUDevice cDevice,
const ExternalImageDescriptorEGLImage* descriptor) {
Device* device = reinterpret_cast<Device*>(cDevice);
TextureBase* texture = device->CreateTextureWrappingEGLImage(descriptor, descriptor->image);
return reinterpret_cast<WGPUTexture>(texture);
}
}} // namespace dawn_native::opengl

View File

@ -17,6 +17,7 @@
"supported_extensions": [
"GL_EXT_texture_compression_s3tc",
"GL_EXT_texture_compression_s3tc_srgb"
"GL_EXT_texture_compression_s3tc_srgb",
"GL_OES_EGL_image"
]
}

View File

@ -215,6 +215,7 @@ namespace dawn_native {
DmaBuf,
IOSurface,
DXGISharedHandle,
EGLImage,
};
// Common properties of external images

View File

@ -15,6 +15,8 @@
#ifndef DAWNNATIVE_OPENGLBACKEND_H_
#define DAWNNATIVE_OPENGLBACKEND_H_
typedef void* EGLImage;
#include <dawn/dawn_wsi.h>
#include <dawn_native/DawnNative.h>
@ -38,6 +40,16 @@ namespace dawn_native { namespace opengl {
DAWN_NATIVE_EXPORT WGPUTextureFormat
GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain);
struct DAWN_NATIVE_EXPORT ExternalImageDescriptorEGLImage : ExternalImageDescriptor {
public:
ExternalImageDescriptorEGLImage();
::EGLImage image;
};
DAWN_NATIVE_EXPORT WGPUTexture
WrapExternalEGLImage(WGPUDevice device, const ExternalImageDescriptorEGLImage* descriptor);
}} // namespace dawn_native::opengl
#endif // DAWNNATIVE_OPENGLBACKEND_H_

View File

@ -449,6 +449,11 @@ source_set("dawn_white_box_tests_sources") {
deps += [ "${dawn_root}/src/utils:dawn_glfw" ]
}
if (dawn_enable_opengles) {
sources += [ "white_box/EGLImageWrappingTests.cpp" ]
deps += [ "//third_party/angle:libEGL" ]
}
libs = []
}

View File

@ -0,0 +1,356 @@
// 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());