• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2// Copyright 2021 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6// ImageTestMetal:
7//   Tests the correctness of eglImage with native Metal texture extensions.
8//
9
10#include "test_utils/ANGLETest.h"
11
12#include "common/mathutil.h"
13#include "test_utils/gl_raii.h"
14#include "util/EGLWindow.h"
15
16#include <CoreFoundation/CoreFoundation.h>
17#include <Metal/Metal.h>
18
19namespace angle
20{
21namespace
22{
23constexpr char kOESExt[]                      = "GL_OES_EGL_image";
24constexpr char kBaseExt[]                     = "EGL_KHR_image_base";
25constexpr char kDeviceMtlExt[]                = "EGL_ANGLE_device_metal";
26constexpr char kEGLMtlImageNativeTextureExt[] = "EGL_ANGLE_metal_texture_client_buffer";
27constexpr EGLint kDefaultAttribs[]            = {
28    EGL_NONE,
29};
30}  // anonymous namespace
31
32class ScopeMetalTextureRef : angle::NonCopyable
33{
34  public:
35    explicit ScopeMetalTextureRef(id<MTLTexture> &&surface) : mSurface(surface) {}
36
37    ~ScopeMetalTextureRef()
38    {
39        if (mSurface)
40        {
41            release();
42            mSurface = nullptr;
43        }
44    }
45
46    id<MTLTexture> get() const { return mSurface; }
47
48    // auto cast to MTLTexture
49    operator id<MTLTexture>() const { return mSurface; }
50    ScopeMetalTextureRef(const ScopeMetalTextureRef &other)
51    {
52        if (mSurface)
53        {
54            release();
55        }
56        mSurface = other.mSurface;
57    }
58
59    explicit ScopeMetalTextureRef(ScopeMetalTextureRef &&other)
60    {
61        if (mSurface)
62        {
63            release();
64        }
65        mSurface       = other.mSurface;
66        other.mSurface = nil;
67    }
68
69    ScopeMetalTextureRef &operator=(ScopeMetalTextureRef &&other)
70    {
71        if (mSurface)
72        {
73            release();
74        }
75        mSurface       = other.mSurface;
76        other.mSurface = nil;
77
78        return *this;
79    }
80
81    ScopeMetalTextureRef &operator=(const ScopeMetalTextureRef &other)
82    {
83        if (mSurface)
84        {
85            release();
86        }
87        mSurface = other.mSurface;
88
89        return *this;
90    }
91
92  private:
93    void release()
94    {
95#if !__has_feature(objc_arc)
96        [mSurface release];
97#endif
98    }
99
100    id<MTLTexture> mSurface = nil;
101};
102
103ScopeMetalTextureRef CreateMetalTexture2D(id<MTLDevice> deviceMtl,
104                                          int width,
105                                          int height,
106                                          MTLPixelFormat format)
107{
108    @autoreleasepool
109    {
110        MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
111                                                                                        width:width
112                                                                                       height:width
113                                                                                    mipmapped:NO];
114        desc.usage                 = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
115
116        ScopeMetalTextureRef re([deviceMtl newTextureWithDescriptor:desc]);
117        return re;
118    }
119}
120
121class ImageTestMetal : public ANGLETest
122{
123  protected:
124    ImageTestMetal()
125    {
126        setWindowWidth(128);
127        setWindowHeight(128);
128        setConfigRedBits(8);
129        setConfigGreenBits(8);
130        setConfigBlueBits(8);
131        setConfigAlphaBits(8);
132        setConfigDepthBits(24);
133    }
134
135    void testSetUp() override
136    {
137        constexpr char kVS[] = "precision highp float;\n"
138                               "attribute vec4 position;\n"
139                               "varying vec2 texcoord;\n"
140                               "\n"
141                               "void main()\n"
142                               "{\n"
143                               "    gl_Position = position;\n"
144                               "    texcoord = (position.xy * 0.5) + 0.5;\n"
145                               "    texcoord.y = 1.0 - texcoord.y;\n"
146                               "}\n";
147
148        constexpr char kTextureFS[] = "precision highp float;\n"
149                                      "uniform sampler2D tex;\n"
150                                      "varying vec2 texcoord;\n"
151                                      "\n"
152                                      "void main()\n"
153                                      "{\n"
154                                      "    gl_FragColor = texture2D(tex, texcoord);\n"
155                                      "}\n";
156
157        mTextureProgram = CompileProgram(kVS, kTextureFS);
158        if (mTextureProgram == 0)
159        {
160            FAIL() << "shader compilation failed.";
161        }
162
163        mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");
164
165        ASSERT_GL_NO_ERROR();
166    }
167
168    void testTearDown() override { glDeleteProgram(mTextureProgram); }
169
170    id<MTLDevice> getMtlDevice()
171    {
172        EGLAttrib angleDevice = 0;
173        EGLAttrib device      = 0;
174        EXPECT_EGL_TRUE(
175            eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
176
177        EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
178                                                EGL_METAL_DEVICE_ANGLE, &device));
179
180        return (__bridge id<MTLDevice>)reinterpret_cast<void *>(device);
181    }
182
183    ScopeMetalTextureRef createMtlTexture2D(int width, int height, MTLPixelFormat format)
184    {
185        id<MTLDevice> device = getMtlDevice();
186
187        return CreateMetalTexture2D(device, width, height, format);
188    }
189
190    void sourceMetalTarget2D_helper(GLubyte data[4],
191                                    const EGLint *attribs,
192                                    EGLImageKHR *imageOut,
193                                    GLuint *textureOut);
194
195    void verifyResultsTexture(GLuint texture,
196                              GLubyte data[4],
197                              GLenum textureTarget,
198                              GLuint program,
199                              GLuint textureUniform)
200    {
201        // Draw a quad with the target texture
202        glUseProgram(program);
203        glBindTexture(textureTarget, texture);
204        glUniform1i(textureUniform, 0);
205
206        drawQuad(program, "position", 0.5f);
207
208        // Expect that the rendered quad has the same color as the source texture
209        EXPECT_PIXEL_NEAR(0, 0, data[0], data[1], data[2], data[3], 1.0);
210    }
211
212    void verifyResults2D(GLuint texture, GLubyte data[4])
213    {
214        verifyResultsTexture(texture, data, GL_TEXTURE_2D, mTextureProgram,
215                             mTextureUniformLocation);
216    }
217
218    template <typename destType, typename sourcetype>
219    destType reinterpretHelper(sourcetype source)
220    {
221        static_assert(sizeof(destType) == sizeof(size_t),
222                      "destType should be the same size as a size_t");
223        size_t sourceSizeT = static_cast<size_t>(source);
224        return reinterpret_cast<destType>(sourceSizeT);
225    }
226
227    bool hasImageNativeMetalTextureExt() const
228    {
229        if (!IsMetal())
230        {
231            return false;
232        }
233        EGLAttrib angleDevice = 0;
234        eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice);
235        if (!angleDevice)
236        {
237            return false;
238        }
239        auto extensionString = static_cast<const char *>(
240            eglQueryDeviceStringEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice), EGL_EXTENSIONS));
241        if (strstr(extensionString, kDeviceMtlExt) == nullptr)
242        {
243            return false;
244        }
245        return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
246                                            kEGLMtlImageNativeTextureExt);
247    }
248
249    bool hasOESExt() const { return IsGLExtensionEnabled(kOESExt); }
250
251    bool hasBaseExt() const
252    {
253        return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), kBaseExt);
254    }
255
256    GLuint mTextureProgram;
257    GLint mTextureUniformLocation;
258};
259
260void ImageTestMetal::sourceMetalTarget2D_helper(GLubyte data[4],
261                                                const EGLint *attribs,
262                                                EGLImageKHR *imageOut,
263                                                GLuint *textureOut)
264{
265    EGLWindow *window = getEGLWindow();
266
267    // Create MTLTexture
268    ScopeMetalTextureRef textureMtl = createMtlTexture2D(1, 1, MTLPixelFormatRGBA8Unorm);
269
270    // Create image
271    EGLImageKHR image =
272        eglCreateImageKHR(window->getDisplay(), EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
273                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
274    ASSERT_EGL_SUCCESS();
275
276    // Write the data to the MTLTexture
277    [textureMtl.get() replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
278                        mipmapLevel:0
279                              slice:0
280                          withBytes:data
281                        bytesPerRow:4
282                      bytesPerImage:0];
283
284    // Create a texture target to bind the egl image
285    GLuint target;
286    glGenTextures(1, &target);
287    glBindTexture(GL_TEXTURE_2D, target);
288    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
289
290    *imageOut   = image;
291    *textureOut = target;
292}
293
294// Testing source metal EGL image, target 2D texture
295TEST_P(ImageTestMetal, SourceMetalTarget2D)
296{
297    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
298    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
299
300    EGLWindow *window = getEGLWindow();
301
302    // Create the Image
303    EGLImageKHR image;
304    GLuint texTarget;
305    GLubyte data[4] = {7, 51, 197, 231};
306    sourceMetalTarget2D_helper(data, kDefaultAttribs, &image, &texTarget);
307
308    // Use texture target bound to egl image as source and render to framebuffer
309    // Verify that data in framebuffer matches that in the egl image
310    verifyResults2D(texTarget, data);
311
312    // Clean up
313    eglDestroyImageKHR(window->getDisplay(), image);
314    glDeleteTextures(1, &texTarget);
315}
316
317// Create source metal EGL image, target 2D texture, then trigger texture respecification.
318TEST_P(ImageTestMetal, SourceMetal2DTargetTextureRespecifySize)
319{
320    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
321    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
322
323    EGLWindow *window = getEGLWindow();
324
325    // Create the Image
326    EGLImageKHR image;
327    GLuint texTarget;
328    GLubyte data[4] = {7, 51, 197, 231};
329    sourceMetalTarget2D_helper(data, kDefaultAttribs, &image, &texTarget);
330
331    // Use texture target bound to egl image as source and render to framebuffer
332    // Verify that data in framebuffer matches that in the egl image
333    verifyResults2D(texTarget, data);
334
335    // Respecify texture size and verify results
336    std::array<GLubyte, 16> referenceColor;
337    referenceColor.fill(127);
338    glBindTexture(GL_TEXTURE_2D, texTarget);
339    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
340                 referenceColor.data());
341    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
342                 referenceColor.data());
343    ASSERT_GL_NO_ERROR();
344
345    // Expect that the target texture has the reference color values
346    verifyResults2D(texTarget, referenceColor.data());
347
348    // Clean up
349    eglDestroyImageKHR(window->getDisplay(), image);
350    glDeleteTextures(1, &texTarget);
351}
352
353// Use this to select which configurations (e.g. which renderer, which GLES major version) these
354// tests should be run against.
355ANGLE_INSTANTIATE_TEST(ImageTestMetal, ES2_METAL(), ES3_METAL());
356
357GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageTestMetal);
358}  // namespace angle
359