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