1 // Copyright 2021 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "tests/DawnTest.h"
16
17 #include "common/DynamicLib.h"
18 #include "dawn_native/OpenGLBackend.h"
19 #include "dawn_native/opengl/DeviceGL.h"
20 #include "utils/ComboRenderPipelineDescriptor.h"
21 #include "utils/WGPUHelpers.h"
22
23 #include <EGL/egl.h>
24
25 namespace {
26
27 class EGLFunctions {
28 public:
EGLFunctions()29 EGLFunctions() {
30 #ifdef DAWN_PLATFORM_WINDOWS
31 const char* eglLib = "libEGL.dll";
32 #else
33 const char* eglLib = "libEGL.so";
34 #endif
35 EXPECT_TRUE(mlibEGL.Open(eglLib));
36 CreateImage = reinterpret_cast<PFNEGLCREATEIMAGEPROC>(LoadProc("eglCreateImage"));
37 DestroyImage = reinterpret_cast<PFNEGLDESTROYIMAGEPROC>(LoadProc("eglDestroyImage"));
38 GetCurrentContext =
39 reinterpret_cast<PFNEGLGETCURRENTCONTEXTPROC>(LoadProc("eglGetCurrentContext"));
40 GetCurrentDisplay =
41 reinterpret_cast<PFNEGLGETCURRENTDISPLAYPROC>(LoadProc("eglGetCurrentDisplay"));
42 }
43
44 private:
LoadProc(const char * name)45 void* LoadProc(const char* name) {
46 void* proc = mlibEGL.GetProc(name);
47 EXPECT_NE(proc, nullptr);
48 return proc;
49 }
50
51 public:
52 PFNEGLCREATEIMAGEPROC CreateImage;
53 PFNEGLDESTROYIMAGEPROC DestroyImage;
54 PFNEGLGETCURRENTCONTEXTPROC GetCurrentContext;
55 PFNEGLGETCURRENTDISPLAYPROC GetCurrentDisplay;
56
57 private:
58 DynamicLib mlibEGL;
59 };
60
61 class ScopedEGLImage {
62 public:
ScopedEGLImage(PFNEGLDESTROYIMAGEPROC destroyImage,PFNGLDELETETEXTURESPROC deleteTextures,EGLDisplay display,EGLImage image,GLuint texture)63 ScopedEGLImage(PFNEGLDESTROYIMAGEPROC destroyImage,
64 PFNGLDELETETEXTURESPROC deleteTextures,
65 EGLDisplay display,
66 EGLImage image,
67 GLuint texture)
68 : mDestroyImage(destroyImage),
69 mDeleteTextures(deleteTextures),
70 mDisplay(display),
71 mImage(image),
72 mTexture(texture) {
73 }
74
ScopedEGLImage(ScopedEGLImage && other)75 ScopedEGLImage(ScopedEGLImage&& other) {
76 if (mImage != nullptr) {
77 mDestroyImage(mDisplay, mImage);
78 }
79 if (mTexture != 0) {
80 mDeleteTextures(1, &mTexture);
81 }
82 mDestroyImage = std::move(other.mDestroyImage);
83 mDeleteTextures = std::move(other.mDeleteTextures);
84 mDisplay = std::move(other.mDisplay);
85 mImage = std::move(other.mImage);
86 mTexture = std::move(other.mTexture);
87 }
88
~ScopedEGLImage()89 ~ScopedEGLImage() {
90 if (mTexture != 0) {
91 mDeleteTextures(1, &mTexture);
92 }
93 if (mImage != nullptr) {
94 mDestroyImage(mDisplay, mImage);
95 }
96 }
97
getImage() const98 EGLImage getImage() const {
99 return mImage;
100 }
101
getTexture() const102 GLuint getTexture() const {
103 return mTexture;
104 }
105
106 private:
107 PFNEGLDESTROYIMAGEPROC mDestroyImage = nullptr;
108 PFNGLDELETETEXTURESPROC mDeleteTextures = nullptr;
109 EGLDisplay mDisplay = nullptr;
110 EGLImage mImage = nullptr;
111 GLuint mTexture = 0;
112 };
113
114 } // anonymous namespace
115
116 class EGLImageTestBase : public DawnTest {
117 protected:
GetRequiredFeatures()118 std::vector<const char*> GetRequiredFeatures() override {
119 return {"dawn-internal-usages"};
120 }
121
122 public:
CreateEGLImage(uint32_t width,uint32_t height,GLenum internalFormat,GLenum format,GLenum type,void * data,size_t size)123 ScopedEGLImage CreateEGLImage(uint32_t width,
124 uint32_t height,
125 GLenum internalFormat,
126 GLenum format,
127 GLenum type,
128 void* data,
129 size_t size) {
130 dawn_native::opengl::Device* openglDevice =
131 dawn_native::opengl::ToBackend(dawn_native::FromAPI(device.Get()));
132 const dawn_native::opengl::OpenGLFunctions& gl = openglDevice->gl;
133 GLuint tex;
134 gl.GenTextures(1, &tex);
135 gl.BindTexture(GL_TEXTURE_2D, tex);
136 gl.TexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data);
137 EGLAttrib attribs[1] = {EGL_NONE};
138 EGLClientBuffer buffer = reinterpret_cast<EGLClientBuffer>(tex);
139 EGLDisplay dpy = egl.GetCurrentDisplay();
140 EGLContext ctx = egl.GetCurrentContext();
141 EGLImage eglImage = egl.CreateImage(dpy, ctx, EGL_GL_TEXTURE_2D, buffer, attribs);
142 EXPECT_NE(nullptr, eglImage);
143
144 return ScopedEGLImage(egl.DestroyImage, gl.DeleteTextures, dpy, eglImage, tex);
145 }
WrapEGLImage(const wgpu::TextureDescriptor * descriptor,EGLImage eglImage)146 wgpu::Texture WrapEGLImage(const wgpu::TextureDescriptor* descriptor, EGLImage eglImage) {
147 dawn_native::opengl::ExternalImageDescriptorEGLImage externDesc;
148 externDesc.cTextureDescriptor = reinterpret_cast<const WGPUTextureDescriptor*>(descriptor);
149 externDesc.image = eglImage;
150 WGPUTexture texture = dawn_native::opengl::WrapExternalEGLImage(device.Get(), &externDesc);
151 return wgpu::Texture::Acquire(texture);
152 }
153 EGLFunctions egl;
154 };
155
156 // A small fixture used to initialize default data for the EGLImage validation tests.
157 // These tests are skipped if the harness is using the wire.
158 class EGLImageValidationTests : public EGLImageTestBase {
159 public:
EGLImageValidationTests()160 EGLImageValidationTests() {
161 descriptor.dimension = wgpu::TextureDimension::e2D;
162 descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
163 descriptor.size = {10, 10, 1};
164 descriptor.sampleCount = 1;
165 descriptor.mipLevelCount = 1;
166 descriptor.usage = wgpu::TextureUsage::RenderAttachment;
167 }
168
CreateDefaultEGLImage()169 ScopedEGLImage CreateDefaultEGLImage() {
170 return CreateEGLImage(10, 10, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, 0);
171 }
172
173 protected:
174 wgpu::TextureDescriptor descriptor;
175 };
176
177 // Test a successful wrapping of an EGLImage in a texture
TEST_P(EGLImageValidationTests,Success)178 TEST_P(EGLImageValidationTests, Success) {
179 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
180 ScopedEGLImage image = CreateDefaultEGLImage();
181 wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage());
182 ASSERT_NE(texture.Get(), nullptr);
183 }
184
185 // Test a successful wrapping of an EGLImage in a texture with DawnTextureInternalUsageDescriptor
TEST_P(EGLImageValidationTests,SuccessWithInternalUsageDescriptor)186 TEST_P(EGLImageValidationTests, SuccessWithInternalUsageDescriptor) {
187 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
188 wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
189 descriptor.nextInChain = &internalDesc;
190 internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
191 internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor;
192
193 ScopedEGLImage image = CreateDefaultEGLImage();
194 wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage());
195 ASSERT_NE(texture.Get(), nullptr);
196 }
197
198 // Test an error occurs if an invalid sType is the nextInChain
TEST_P(EGLImageValidationTests,InvalidTextureDescriptor)199 TEST_P(EGLImageValidationTests, InvalidTextureDescriptor) {
200 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
201
202 wgpu::ChainedStruct chainedDescriptor;
203 chainedDescriptor.sType = wgpu::SType::SurfaceDescriptorFromWindowsSwapChainPanel;
204 descriptor.nextInChain = &chainedDescriptor;
205
206 ScopedEGLImage image = CreateDefaultEGLImage();
207 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
208
209 ASSERT_EQ(texture.Get(), nullptr);
210 }
211
212 // Test an error occurs if the descriptor dimension isn't 2D
TEST_P(EGLImageValidationTests,InvalidTextureDimension)213 TEST_P(EGLImageValidationTests, InvalidTextureDimension) {
214 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
215 descriptor.dimension = wgpu::TextureDimension::e3D;
216
217 ScopedEGLImage image = CreateDefaultEGLImage();
218 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
219
220 ASSERT_EQ(texture.Get(), nullptr);
221 }
222
223 // Test an error occurs if the texture usage is not RenderAttachment
TEST_P(EGLImageValidationTests,InvalidTextureUsage)224 TEST_P(EGLImageValidationTests, InvalidTextureUsage) {
225 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
226 descriptor.usage = wgpu::TextureUsage::TextureBinding;
227
228 ScopedEGLImage image = CreateDefaultEGLImage();
229 wgpu::Texture texture;
230 ASSERT_DEVICE_ERROR(texture = WrapEGLImage(&descriptor, image.getImage()));
231
232 ASSERT_EQ(texture.Get(), nullptr);
233 descriptor.usage = wgpu::TextureUsage::StorageBinding;
234
235 ASSERT_DEVICE_ERROR(texture = WrapEGLImage(&descriptor, image.getImage()));
236
237 ASSERT_EQ(texture.Get(), nullptr);
238 }
239
240 // Test an error occurs if the descriptor mip level count isn't 1
TEST_P(EGLImageValidationTests,InvalidMipLevelCount)241 TEST_P(EGLImageValidationTests, InvalidMipLevelCount) {
242 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
243 descriptor.mipLevelCount = 2;
244
245 ScopedEGLImage image = CreateDefaultEGLImage();
246 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
247 ASSERT_EQ(texture.Get(), nullptr);
248 }
249
250 // Test an error occurs if the descriptor depth isn't 1
TEST_P(EGLImageValidationTests,InvalidDepth)251 TEST_P(EGLImageValidationTests, InvalidDepth) {
252 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
253 descriptor.size.depthOrArrayLayers = 2;
254
255 ScopedEGLImage image = CreateDefaultEGLImage();
256 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
257 ASSERT_EQ(texture.Get(), nullptr);
258 }
259
260 // Test an error occurs if the descriptor sample count isn't 1
TEST_P(EGLImageValidationTests,InvalidSampleCount)261 TEST_P(EGLImageValidationTests, InvalidSampleCount) {
262 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
263 descriptor.sampleCount = 4;
264
265 ScopedEGLImage image = CreateDefaultEGLImage();
266 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
267 ASSERT_EQ(texture.Get(), nullptr);
268 }
269
270 // Test an error occurs if the descriptor width doesn't match the surface's
TEST_P(EGLImageValidationTests,InvalidWidth)271 TEST_P(EGLImageValidationTests, InvalidWidth) {
272 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
273 descriptor.size.width = 11;
274
275 ScopedEGLImage image = CreateDefaultEGLImage();
276 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
277 ASSERT_EQ(texture.Get(), nullptr);
278 }
279
280 // Test an error occurs if the descriptor height doesn't match the surface's
TEST_P(EGLImageValidationTests,InvalidHeight)281 TEST_P(EGLImageValidationTests, InvalidHeight) {
282 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
283 descriptor.size.height = 11;
284
285 ScopedEGLImage image = CreateDefaultEGLImage();
286 ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapEGLImage(&descriptor, image.getImage()));
287 ASSERT_EQ(texture.Get(), nullptr);
288 }
289
290 // Fixture to test using EGLImages through different usages.
291 // These tests are skipped if the harness is using the wire.
292 class EGLImageUsageTests : public EGLImageTestBase {
293 public:
294 // Test that clearing using BeginRenderPass writes correct data in the eglImage.
DoClearTest(EGLImage eglImage,GLuint texture,wgpu::TextureFormat format,GLenum glFormat,GLenum glType,void * data,size_t dataSize)295 void DoClearTest(EGLImage eglImage,
296 GLuint texture,
297 wgpu::TextureFormat format,
298 GLenum glFormat,
299 GLenum glType,
300 void* data,
301 size_t dataSize) {
302 dawn_native::opengl::Device* openglDevice =
303 dawn_native::opengl::ToBackend(dawn_native::FromAPI(device.Get()));
304 const dawn_native::opengl::OpenGLFunctions& gl = openglDevice->gl;
305
306 // Get a texture view for the eglImage
307 wgpu::TextureDescriptor textureDescriptor;
308 textureDescriptor.dimension = wgpu::TextureDimension::e2D;
309 textureDescriptor.format = format;
310 textureDescriptor.size = {1, 1, 1};
311 textureDescriptor.sampleCount = 1;
312 textureDescriptor.mipLevelCount = 1;
313 textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
314
315 wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
316 textureDescriptor.nextInChain = &internalDesc;
317 internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
318 internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor;
319
320 wgpu::Texture eglImageTexture = WrapEGLImage(&textureDescriptor, eglImage);
321 ASSERT_NE(eglImageTexture, nullptr);
322
323 wgpu::TextureView eglImageView = eglImageTexture.CreateView();
324
325 utils::ComboRenderPassDescriptor renderPassDescriptor({eglImageView}, {});
326 renderPassDescriptor.cColorAttachments[0].clearColor = {1 / 255.0f, 2 / 255.0f, 3 / 255.0f,
327 4 / 255.0f};
328
329 // Execute commands to clear the eglImage
330 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
331 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
332 pass.EndPass();
333
334 wgpu::CommandBuffer commands = encoder.Finish();
335 queue.Submit(1, &commands);
336
337 // Check the correct data was written
338 std::vector<uint8_t> result(dataSize);
339 GLuint fbo;
340 gl.GenFramebuffers(1, &fbo);
341 gl.BindFramebuffer(GL_FRAMEBUFFER, fbo);
342 gl.FramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
343 0);
344 gl.ReadPixels(0, 0, 1, 1, glFormat, glType, result.data());
345 gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
346 gl.DeleteFramebuffers(1, &fbo);
347 ASSERT_EQ(0, memcmp(result.data(), data, dataSize));
348 }
349 };
350
351 // Test clearing a R8 EGLImage
TEST_P(EGLImageUsageTests,ClearR8EGLImage)352 TEST_P(EGLImageUsageTests, ClearR8EGLImage) {
353 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
354 ScopedEGLImage eglImage = CreateEGLImage(1, 1, GL_R8, GL_RED, GL_UNSIGNED_BYTE, nullptr, 0);
355
356 uint8_t data = 0x01;
357 DoClearTest(eglImage.getImage(), eglImage.getTexture(), wgpu::TextureFormat::R8Unorm, GL_RED,
358 GL_UNSIGNED_BYTE, &data, sizeof(data));
359 }
360
361 // Test clearing a RG8 EGLImage
TEST_P(EGLImageUsageTests,ClearRG8EGLImage)362 TEST_P(EGLImageUsageTests, ClearRG8EGLImage) {
363 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
364 ScopedEGLImage eglImage = CreateEGLImage(1, 1, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, nullptr, 0);
365
366 uint16_t data = 0x0201;
367 DoClearTest(eglImage.getImage(), eglImage.getTexture(), wgpu::TextureFormat::RG8Unorm, GL_RG,
368 GL_UNSIGNED_BYTE, &data, sizeof(data));
369 }
370
371 // Test clearing an RGBA8 EGLImage
TEST_P(EGLImageUsageTests,ClearRGBA8EGLImage)372 TEST_P(EGLImageUsageTests, ClearRGBA8EGLImage) {
373 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
374 ScopedEGLImage eglImage = CreateEGLImage(1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, 0);
375
376 uint32_t data = 0x04030201;
377 DoClearTest(eglImage.getImage(), eglImage.getTexture(), wgpu::TextureFormat::RGBA8Unorm,
378 GL_RGBA, GL_UNSIGNED_BYTE, &data, sizeof(data));
379 }
380
381 DAWN_INSTANTIATE_TEST(EGLImageValidationTests, OpenGLESBackend());
382 DAWN_INSTANTIATE_TEST(EGLImageUsageTests, OpenGLESBackend());
383