xref: /aosp_15_r20/external/angle/src/tests/gl_tests/ImageTestMetal.mm (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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#include <gmock/gmock.h>
19#include <span>
20
21namespace angle
22{
23namespace
24{
25constexpr char kOESExt[]                      = "GL_OES_EGL_image";
26constexpr char kBaseExt[]                     = "EGL_KHR_image_base";
27constexpr char kDeviceMtlExt[]                = "EGL_ANGLE_device_metal";
28constexpr char kEGLMtlImageNativeTextureExt[] = "EGL_ANGLE_metal_texture_client_buffer";
29constexpr EGLint kDefaultAttribs[]            = {
30    EGL_NONE,
31};
32
33template <typename T>
34class ScopedMetalObjectRef : angle::NonCopyable
35{
36  public:
37    ScopedMetalObjectRef() = default;
38
39    explicit ScopedMetalObjectRef(T &&surface) : mObject(surface) {}
40
41    ~ScopedMetalObjectRef()
42    {
43        if (mObject)
44        {
45            release();
46            mObject = nil;
47        }
48    }
49
50    T get() const { return mObject; }
51
52    operator bool() const { return !!mObject; }
53
54    // auto cast to T
55    operator T() const { return mObject; }
56    ScopedMetalObjectRef(const ScopedMetalObjectRef &other)
57    {
58        if (mObject)
59        {
60            release();
61        }
62        mObject = other.mObject;
63    }
64
65    explicit ScopedMetalObjectRef(ScopedMetalObjectRef &&other)
66    {
67        if (mObject)
68        {
69            release();
70        }
71        mObject       = other.mObject;
72        other.mObject = nil;
73    }
74
75    ScopedMetalObjectRef &operator=(ScopedMetalObjectRef &&other)
76    {
77        if (mObject)
78        {
79            release();
80        }
81        mObject       = other.mObject;
82        other.mObject = nil;
83
84        return *this;
85    }
86
87    ScopedMetalObjectRef &operator=(const ScopedMetalObjectRef &other)
88    {
89        if (mObject)
90        {
91            release();
92        }
93        mObject = other.mObject;
94
95        return *this;
96    }
97
98  private:
99    void release()
100    {
101#if !__has_feature(objc_arc)
102        [mObject release];
103#endif
104    }
105
106    T mObject = nil;
107};
108
109using ScopedMetalTextureRef      = ScopedMetalObjectRef<id<MTLTexture>>;
110using ScopedMetalBufferRef       = ScopedMetalObjectRef<id<MTLBuffer>>;
111using ScopedMetalCommandQueueRef = ScopedMetalObjectRef<id<MTLCommandQueue>>;
112
113}  // anonymous namespace
114
115bool IsDepthOrStencil(MTLPixelFormat format)
116{
117    switch (format)
118    {
119        case MTLPixelFormatDepth16Unorm:
120        case MTLPixelFormatDepth32Float:
121        case MTLPixelFormatStencil8:
122        case MTLPixelFormatDepth24Unorm_Stencil8:
123        case MTLPixelFormatDepth32Float_Stencil8:
124        case MTLPixelFormatX32_Stencil8:
125        case MTLPixelFormatX24_Stencil8:
126            return true;
127
128        default:
129            return false;
130    }
131}
132
133ScopedMetalTextureRef CreateMetalTexture2D(id<MTLDevice> deviceMtl,
134                                           int width,
135                                           int height,
136                                           MTLPixelFormat format,
137                                           int arrayLength)
138{
139    @autoreleasepool
140    {
141        MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
142                                                                                        width:width
143                                                                                       height:width
144                                                                                    mipmapped:NO];
145        desc.usage                 = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
146        if (IsDepthOrStencil(format))
147        {
148            desc.storageMode = MTLStorageModePrivate;
149        }
150        if (arrayLength)
151        {
152            desc.arrayLength = arrayLength;
153            desc.textureType = MTLTextureType2DArray;
154        }
155        ScopedMetalTextureRef re([deviceMtl newTextureWithDescriptor:desc]);
156        return re;
157    }
158}
159
160id<MTLSharedEvent> CreateMetalSharedEvent(id<MTLDevice> deviceMtl)
161{
162    id<MTLSharedEvent> sharedEvent = [deviceMtl newSharedEvent];
163    sharedEvent.label              = @"TestSharedEvent";
164    return sharedEvent;
165}
166
167EGLSync CreateEGLSyncFromMetalSharedEvent(EGLDisplay display,
168                                          id<MTLSharedEvent> sharedEvent,
169                                          uint64_t signalValue,
170                                          bool signaled)
171{
172    EGLAttrib signalValueHi            = signalValue >> 32;
173    EGLAttrib signalValueLo            = signalValue & 0xffffffff;
174    std::vector<EGLAttrib> syncAttribs = {
175        EGL_SYNC_METAL_SHARED_EVENT_OBJECT_ANGLE,          reinterpret_cast<EGLAttrib>(sharedEvent),
176        EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_HI_ANGLE, signalValueHi,
177        EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_LO_ANGLE, signalValueLo};
178
179    if (signaled)
180    {
181        syncAttribs.push_back(EGL_SYNC_CONDITION);
182        syncAttribs.push_back(EGL_SYNC_METAL_SHARED_EVENT_SIGNALED_ANGLE);
183    }
184
185    syncAttribs.push_back(EGL_NONE);
186
187    EGLSync syncWithSharedEvent =
188        eglCreateSync(display, EGL_SYNC_METAL_SHARED_EVENT_ANGLE, syncAttribs.data());
189    EXPECT_NE(syncWithSharedEvent, EGL_NO_SYNC);
190
191    return syncWithSharedEvent;
192}
193
194class ImageTestMetal : public ANGLETest<>
195{
196  protected:
197    ImageTestMetal()
198    {
199        setWindowWidth(128);
200        setWindowHeight(128);
201        setConfigRedBits(8);
202        setConfigGreenBits(8);
203        setConfigBlueBits(8);
204        setConfigAlphaBits(8);
205        setConfigDepthBits(24);
206    }
207
208    void testSetUp() override
209    {
210        constexpr char kVS[] = "precision highp float;\n"
211                               "attribute vec4 position;\n"
212                               "varying vec2 texcoord;\n"
213                               "\n"
214                               "void main()\n"
215                               "{\n"
216                               "    gl_Position = position;\n"
217                               "    texcoord = (position.xy * 0.5) + 0.5;\n"
218                               "    texcoord.y = 1.0 - texcoord.y;\n"
219                               "}\n";
220
221        constexpr char kTextureFS[] = "precision highp float;\n"
222                                      "uniform sampler2D tex;\n"
223                                      "varying vec2 texcoord;\n"
224                                      "\n"
225                                      "void main()\n"
226                                      "{\n"
227                                      "    gl_FragColor = texture2D(tex, texcoord);\n"
228                                      "}\n";
229
230        mTextureProgram = CompileProgram(kVS, kTextureFS);
231        if (mTextureProgram == 0)
232        {
233            FAIL() << "shader compilation failed.";
234        }
235
236        mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");
237
238        ASSERT_GL_NO_ERROR();
239    }
240
241    void testTearDown() override { glDeleteProgram(mTextureProgram); }
242
243    id<MTLDevice> getMtlDevice()
244    {
245        EGLAttrib angleDevice = 0;
246        EGLAttrib device      = 0;
247        EXPECT_EGL_TRUE(
248            eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
249
250        EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
251                                                EGL_METAL_DEVICE_ANGLE, &device));
252
253        return (__bridge id<MTLDevice>)reinterpret_cast<void *>(device);
254    }
255
256    ScopedMetalTextureRef createMtlTexture2D(int width, int height, MTLPixelFormat format)
257    {
258        id<MTLDevice> device = getMtlDevice();
259
260        return CreateMetalTexture2D(device, width, height, format, 0);
261    }
262
263    ScopedMetalTextureRef createMtlTexture2DArray(int width,
264                                                  int height,
265                                                  int arrayLength,
266                                                  MTLPixelFormat format)
267    {
268        id<MTLDevice> device = getMtlDevice();
269
270        return CreateMetalTexture2D(device, width, height, format, arrayLength);
271    }
272
273    void getTextureSliceBytes(id<MTLTexture> texture,
274                              unsigned bytesPerRow,
275                              MTLRegion region,
276                              unsigned mipmapLevel,
277                              unsigned slice,
278                              std::span<uint8_t> sliceImage)
279    {
280        @autoreleasepool
281        {
282            id<MTLDevice> device = texture.device;
283            ScopedMetalBufferRef readBuffer([device
284                newBufferWithLength:sliceImage.size()
285                            options:MTLResourceStorageModeShared]);
286            ScopedMetalCommandQueueRef commandQueue([device newCommandQueue]);
287            id<MTLCommandBuffer> commandBuffer    = [commandQueue commandBuffer];
288            id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
289            [blitEncoder copyFromTexture:texture
290                             sourceSlice:slice
291                             sourceLevel:mipmapLevel
292                            sourceOrigin:region.origin
293                              sourceSize:region.size
294                                toBuffer:readBuffer
295                       destinationOffset:0
296                  destinationBytesPerRow:bytesPerRow
297                destinationBytesPerImage:sliceImage.size()];
298            [blitEncoder endEncoding];
299            [commandBuffer commit];
300            [commandBuffer waitUntilCompleted];
301            memcpy(sliceImage.data(), readBuffer.get().contents, sliceImage.size());
302        }
303    }
304    void sourceMetalTarget2D_helper(GLubyte data[4],
305                                    const EGLint *attribs,
306                                    EGLImageKHR *imageOut,
307                                    GLuint *textureOut);
308
309    void verifyResultsTexture(GLuint texture,
310                              const GLubyte data[4],
311                              GLenum textureTarget,
312                              GLuint program,
313                              GLuint textureUniform)
314    {
315        // Draw a quad with the target texture
316        glUseProgram(program);
317        glBindTexture(textureTarget, texture);
318        glUniform1i(textureUniform, 0);
319
320        drawQuad(program, "position", 0.5f);
321
322        // Expect that the rendered quad has the same color as the source texture
323        EXPECT_PIXEL_NEAR(0, 0, data[0], data[1], data[2], data[3], 1.0);
324    }
325
326    void verifyResults2D(GLuint texture, const GLubyte data[4])
327    {
328        verifyResultsTexture(texture, data, GL_TEXTURE_2D, mTextureProgram,
329                             mTextureUniformLocation);
330    }
331
332    void drawColorQuad(GLColor color)
333    {
334        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
335        glUseProgram(program);
336        GLint colorUniformLocation =
337            glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
338        ASSERT_NE(colorUniformLocation, -1);
339        glUniform4fv(colorUniformLocation, 1, color.toNormalizedVector().data());
340        drawQuad(program, essl1_shaders::PositionAttrib(), 0);
341        glUseProgram(0);
342    }
343
344    bool hasDepth24Stencil8PixelFormat()
345    {
346        id<MTLDevice> device = getMtlDevice();
347        return device.depth24Stencil8PixelFormatSupported;
348    }
349
350    bool hasImageNativeMetalTextureExt() const
351    {
352        if (!IsMetal())
353        {
354            return false;
355        }
356        EGLAttrib angleDevice = 0;
357        eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice);
358        if (!angleDevice)
359        {
360            return false;
361        }
362        auto extensionString = static_cast<const char *>(
363            eglQueryDeviceStringEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice), EGL_EXTENSIONS));
364        if (strstr(extensionString, kDeviceMtlExt) == nullptr)
365        {
366            return false;
367        }
368        return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
369                                            kEGLMtlImageNativeTextureExt);
370    }
371
372    bool hasOESExt() const { return IsGLExtensionEnabled(kOESExt); }
373
374    bool hasBaseExt() const
375    {
376        return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), kBaseExt);
377    }
378
379    GLuint mTextureProgram;
380    GLint mTextureUniformLocation;
381};
382
383void ImageTestMetal::sourceMetalTarget2D_helper(GLubyte data[4],
384                                                const EGLint *attribs,
385                                                EGLImageKHR *imageOut,
386                                                GLuint *textureOut)
387{
388    EGLWindow *window = getEGLWindow();
389
390    // Create MTLTexture
391    ScopedMetalTextureRef textureMtl = createMtlTexture2D(1, 1, MTLPixelFormatRGBA8Unorm);
392
393    // Create image
394    EGLImageKHR image =
395        eglCreateImageKHR(window->getDisplay(), EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
396                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
397    ASSERT_EGL_SUCCESS();
398
399    // Write the data to the MTLTexture
400    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
401                  mipmapLevel:0
402                        slice:0
403                    withBytes:data
404                  bytesPerRow:4
405                bytesPerImage:0];
406
407    // Create a texture target to bind the egl image
408    GLuint target;
409    glGenTextures(1, &target);
410    glBindTexture(GL_TEXTURE_2D, target);
411    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
412
413    *imageOut   = image;
414    *textureOut = target;
415}
416
417// Testing source metal EGL image, target 2D texture
418TEST_P(ImageTestMetal, SourceMetalTarget2D)
419{
420    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
421    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
422
423    EGLWindow *window = getEGLWindow();
424
425    // Create the Image
426    EGLImageKHR image;
427    GLuint texTarget;
428    GLubyte data[4] = {7, 51, 197, 231};
429    sourceMetalTarget2D_helper(data, kDefaultAttribs, &image, &texTarget);
430
431    // Use texture target bound to egl image as source and render to framebuffer
432    // Verify that data in framebuffer matches that in the egl image
433    verifyResults2D(texTarget, data);
434
435    // Clean up
436    eglDestroyImageKHR(window->getDisplay(), image);
437    glDeleteTextures(1, &texTarget);
438}
439
440// Create source metal EGL image, target 2D texture, then trigger texture respecification.
441TEST_P(ImageTestMetal, SourceMetal2DTargetTextureRespecifySize)
442{
443    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
444    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
445
446    EGLWindow *window = getEGLWindow();
447
448    // Create the Image
449    EGLImageKHR image;
450    GLuint texTarget;
451    GLubyte data[4] = {7, 51, 197, 231};
452    sourceMetalTarget2D_helper(data, kDefaultAttribs, &image, &texTarget);
453
454    // Use texture target bound to egl image as source and render to framebuffer
455    // Verify that data in framebuffer matches that in the egl image
456    verifyResults2D(texTarget, data);
457
458    // Respecify texture size and verify results
459    std::array<GLubyte, 16> referenceColor;
460    referenceColor.fill(127);
461    glBindTexture(GL_TEXTURE_2D, texTarget);
462    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
463                 referenceColor.data());
464    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
465                 referenceColor.data());
466    ASSERT_GL_NO_ERROR();
467
468    // Expect that the target texture has the reference color values
469    verifyResults2D(texTarget, referenceColor.data());
470
471    // Clean up
472    eglDestroyImageKHR(window->getDisplay(), image);
473    glDeleteTextures(1, &texTarget);
474}
475
476// Tests that OpenGL can sample from a texture bound with Metal texture slice.
477TEST_P(ImageTestMetal, SourceMetalTarget2DArray)
478{
479    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
480    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
481    ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 3, MTLPixelFormatRGBA8Unorm);
482
483    GLubyte data0[4] = {93, 83, 75, 128};
484    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
485                  mipmapLevel:0
486                        slice:0
487                    withBytes:data0
488                  bytesPerRow:4
489                bytesPerImage:4];
490    GLubyte data1[4] = {7, 51, 197, 231};
491    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
492                  mipmapLevel:0
493                        slice:1
494                    withBytes:data1
495                  bytesPerRow:4
496                bytesPerImage:4];
497    GLubyte data2[4] = {33, 51, 44, 33};
498    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
499                  mipmapLevel:0
500                        slice:2
501                    withBytes:data2
502                  bytesPerRow:4
503                bytesPerImage:4];
504
505    EGLDisplay display = getEGLWindow()->getDisplay();
506    EGLImageKHR image0 =
507        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
508                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), nullptr);
509    ASSERT_EGL_SUCCESS();
510    const EGLint attribs1[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 1, EGL_NONE};
511    EGLImageKHR image1 =
512        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
513                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs1);
514    ASSERT_EGL_SUCCESS();
515    const EGLint attribs2[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 2, EGL_NONE};
516    EGLImageKHR image2 =
517        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
518                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs2);
519    ASSERT_EGL_SUCCESS();
520
521    GLTexture targetTexture;
522    glBindTexture(GL_TEXTURE_2D, targetTexture);
523    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image0);
524    verifyResults2D(targetTexture, data0);
525    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image1);
526    verifyResults2D(targetTexture, data1);
527    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image2);
528    verifyResults2D(targetTexture, data2);
529    eglDestroyImageKHR(display, image0);
530    eglDestroyImageKHR(display, image1);
531    eglDestroyImageKHR(display, image2);
532    EXPECT_GL_NO_ERROR();
533    EXPECT_EGL_SUCCESS();
534}
535
536// Test that bound slice to EGLImage is not affected by releasing the source texture.
537TEST_P(ImageTestMetal, SourceMetalTarget2DArrayReleasedSourceOk)
538{
539    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
540    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
541    ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 3, MTLPixelFormatRGBA8Unorm);
542
543    GLubyte data1[4] = {7, 51, 197, 231};
544    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
545                  mipmapLevel:0
546                        slice:1
547                    withBytes:data1
548                  bytesPerRow:4
549                bytesPerImage:4];
550
551    EGLDisplay display      = getEGLWindow()->getDisplay();
552    const EGLint attribs1[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 1, EGL_NONE};
553    EGLImageKHR image1 =
554        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
555                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs1);
556    ASSERT_EGL_SUCCESS();
557    // This is being tested: release the source texture but the slice keeps working.
558    textureMtl = {};
559    GLTexture targetTexture;
560    glBindTexture(GL_TEXTURE_2D, targetTexture);
561    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image1);
562    verifyResults2D(targetTexture, data1);
563    eglDestroyImageKHR(display, image1);
564    EXPECT_GL_NO_ERROR();
565    EXPECT_EGL_SUCCESS();
566}
567
568// Tests that OpenGL can draw to a texture bound with Metal texture.
569TEST_P(ImageTestMetal, DrawMetalTarget2D)
570{
571    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
572    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
573
574    EGLDisplay display = getEGLWindow()->getDisplay();
575
576    ScopedMetalTextureRef textureMtl = createMtlTexture2D(1, 1, MTLPixelFormatRGBA8Unorm);
577    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
578                  mipmapLevel:0
579                        slice:0
580                    withBytes:GLColor::red.data()
581                  bytesPerRow:4
582                bytesPerImage:4];
583
584    EGLImageKHR image =
585        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
586                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), nullptr);
587    ASSERT_EGL_SUCCESS();
588
589    GLTexture texture;
590    glBindTexture(GL_TEXTURE_2D, texture);
591    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
592
593    GLFramebuffer targetFbo;
594    glBindFramebuffer(GL_FRAMEBUFFER, targetFbo);
595    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
596    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
597
598    glViewport(0, 0, 1, 1);
599    drawColorQuad(GLColor::magenta);
600    EXPECT_GL_NO_ERROR();
601    eglDestroyImageKHR(display, image);
602    eglWaitUntilWorkScheduledANGLE(display);
603    EXPECT_GL_NO_ERROR();
604    EXPECT_EGL_SUCCESS();
605
606    GLColor result;
607    getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 0, {result.data(), 4});
608    EXPECT_EQ(result, GLColor::magenta);
609}
610
611// Tests that OpenGL can draw to a texture bound with Metal texture slice.
612TEST_P(ImageTestMetal, DrawMetalTarget2DArray)
613{
614    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
615    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
616
617    EGLDisplay display = getEGLWindow()->getDisplay();
618
619    ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 2, MTLPixelFormatRGBA8Unorm);
620    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
621                  mipmapLevel:0
622                        slice:0
623                    withBytes:GLColor::red.data()
624                  bytesPerRow:4
625                bytesPerImage:4];
626    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
627                  mipmapLevel:0
628                        slice:1
629                    withBytes:GLColor::red.data()
630                  bytesPerRow:4
631                bytesPerImage:4];
632
633    const EGLint attribs[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 1, EGL_NONE};
634    EGLImageKHR image =
635        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
636                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
637    ASSERT_EGL_SUCCESS();
638
639    GLTexture texture;
640    glBindTexture(GL_TEXTURE_2D, texture);
641    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
642    EXPECT_GL_NO_ERROR();
643    GLFramebuffer targetFbo;
644    glBindFramebuffer(GL_FRAMEBUFFER, targetFbo);
645    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
646    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
647
648    glViewport(0, 0, 1, 1);
649    drawColorQuad(GLColor::magenta);
650    EXPECT_GL_NO_ERROR();
651    eglDestroyImageKHR(display, image);
652    eglWaitUntilWorkScheduledANGLE(display);
653    EXPECT_GL_NO_ERROR();
654    EXPECT_EGL_SUCCESS();
655
656    GLColor result;
657    getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 1, {result.data(), 4});
658    EXPECT_EQ(result, GLColor::magenta);
659}
660
661// Tests that OpenGL can blit to a texture bound with Metal texture slice.
662TEST_P(ImageTestMetal, BlitMetalTarget2DArray)
663{
664    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
665    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
666
667    EGLDisplay display = getEGLWindow()->getDisplay();
668
669    GLTexture colorBuffer;
670    glBindTexture(GL_TEXTURE_2D, colorBuffer);
671    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
672    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
673    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
674    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, GLColor::green.data());
675    glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
676                    GLColor::yellow.data());
677    EXPECT_GL_NO_ERROR();
678
679    GLFramebuffer sourceFbo;
680    glBindFramebuffer(GL_FRAMEBUFFER, sourceFbo);
681    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuffer, 0);
682    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
683
684    ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 2, MTLPixelFormatRGBA8Unorm);
685    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
686                  mipmapLevel:0
687                        slice:0
688                    withBytes:GLColor::red.data()
689                  bytesPerRow:4
690                bytesPerImage:4];
691    [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
692                  mipmapLevel:0
693                        slice:1
694                    withBytes:GLColor::red.data()
695                  bytesPerRow:4
696                bytesPerImage:4];
697
698    for (int slice = 0; slice < 2; ++slice)
699    {
700        const EGLint attribs[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, slice, EGL_NONE};
701        EGLImageKHR image =
702            eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
703                              reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
704        ASSERT_EGL_SUCCESS();
705
706        GLTexture texture;
707        glBindTexture(GL_TEXTURE_2D, texture);
708        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
709        glBindFramebuffer(GL_FRAMEBUFFER, 0);
710        verifyResults2D(texture, GLColor::red.data());
711        EXPECT_GL_NO_ERROR();
712
713        GLFramebuffer targetFbo;
714        glBindFramebuffer(GL_FRAMEBUFFER, targetFbo);
715        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
716        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
717
718        glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, sourceFbo);
719        glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, targetFbo);
720        glBlitFramebufferANGLE(slice, 0, slice + 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
721        EXPECT_GL_NO_ERROR();
722
723        glBindFramebuffer(GL_FRAMEBUFFER, 0);
724        verifyResults2D(texture, slice == 0 ? GLColor::green.data() : GLColor::yellow.data());
725        eglDestroyImageKHR(display, image);
726        EXPECT_GL_NO_ERROR();
727        EXPECT_EGL_SUCCESS();
728    }
729    eglWaitUntilWorkScheduledANGLE(display);
730    EXPECT_EGL_SUCCESS();
731
732    GLColor result;
733    getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 0, {result.data(), 4});
734    EXPECT_EQ(result, GLColor::green);
735    getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 1, {result.data(), 4});
736    EXPECT_EQ(result, GLColor::yellow);
737}
738
739// Tests that OpenGL can override the internal format for a texture bound with
740// Metal texture.
741TEST_P(ImageTestMetal, OverrideMetalTextureInternalFormat)
742{
743    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
744    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
745    ANGLE_SKIP_TEST_IF(hasDepth24Stencil8PixelFormat());
746
747    EGLDisplay display = getEGLWindow()->getDisplay();
748
749    // On iOS devices, GL_DEPTH24_STENCIL8 is unavailable and is interally converted into
750    // GL_DEPTH32F_STENCIL8. This tests the ability to attach MTLPixelFormatDepth32Float_Stencil8
751    // and have GL treat it as GL_DEPTH24_STENCIL8 instead of GL_DEPTH32F_STENCIL8.
752    ScopedMetalTextureRef textureMtl =
753        createMtlTexture2DArray(1, 1, 1, MTLPixelFormatDepth32Float_Stencil8);
754    const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_DEPTH24_STENCIL8, EGL_NONE};
755    EGLImageKHR image =
756        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
757                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
758    EXPECT_EGL_SUCCESS();
759    EXPECT_NE(image, nullptr);
760}
761
762// Tests that OpenGL can override the internal format for a texture bound with
763// Metal texture and that rendering to the texture is successful.
764TEST_P(ImageTestMetal, RenderingTest)
765{
766    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
767    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
768    ANGLE_SKIP_TEST_IF(hasDepth24Stencil8PixelFormat());
769
770    EGLDisplay display = getEGLWindow()->getDisplay();
771
772    const int bufferSize = 32;
773    ScopedMetalTextureRef textureMtl =
774        createMtlTexture2DArray(bufferSize, bufferSize, 1, MTLPixelFormatDepth32Float_Stencil8);
775    const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_DEPTH24_STENCIL8, EGL_NONE};
776    EGLImageKHR image =
777        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
778                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
779    EXPECT_EGL_SUCCESS();
780    EXPECT_NE(image, nullptr);
781
782    GLRenderbuffer colorBuffer;
783    glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
784    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, bufferSize, bufferSize);
785    EXPECT_GL_NO_ERROR();
786
787    GLRenderbuffer depthStencilBuffer;
788    glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
789    glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
790    EXPECT_GL_NO_ERROR();
791
792    GLFramebuffer fb;
793    glBindFramebuffer(GL_FRAMEBUFFER, fb);
794    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer);
795    if (getClientMajorVersion() >= 3)
796    {
797        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
798                                  depthStencilBuffer);
799    }
800    else
801    {
802        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
803                                  depthStencilBuffer);
804        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
805                                  depthStencilBuffer);
806    }
807
808    EXPECT_GL_NO_ERROR();
809    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
810
811    glClearColor(1.f, 0.f, 0.f, 1.f);
812    glClearDepthf(1.f);
813    glClearStencil(0x55);
814    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
815    EXPECT_GL_NO_ERROR();
816
817    glEnable(GL_DEPTH_TEST);
818    glDepthFunc(GL_LESS);
819
820    glEnable(GL_STENCIL_TEST);
821    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
822    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
823    glStencilMask(0xFF);
824
825    // Draw green.
826    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
827    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.95f);
828
829    // Verify that green was drawn.
830    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
831    EXPECT_PIXEL_COLOR_EQ(0, bufferSize - 1, GLColor::green);
832    EXPECT_PIXEL_COLOR_EQ(bufferSize - 1, 0, GLColor::green);
833    EXPECT_PIXEL_COLOR_EQ(bufferSize - 1, bufferSize - 1, GLColor::green);
834
835    eglDestroyImageKHR(display, image);
836}
837
838// Tests that OpenGL override the with a bad internal format for a texture bound
839// with Metal texture fails.
840TEST_P(ImageTestMetal, OverrideMetalTextureInternalFormatBadFormat)
841{
842    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
843    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
844
845    EGLDisplay display = getEGLWindow()->getDisplay();
846
847    // On iOS devices, GL_DEPTH24_STENCIL8 is unavailable and is interally converted into
848    // GL_DEPTH32F_STENCIL8. This tests the ability to attach MTLPixelFormatDepth32Float_Stencil8
849    // and have GL treat it as GL_DEPTH24_STENCIL8 instead of GL_DEPTH32F_STENCIL8.
850    ScopedMetalTextureRef textureMtl =
851        createMtlTexture2DArray(1, 1, 1, MTLPixelFormatDepth32Float_Stencil8);
852    const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_TRIANGLES, EGL_NONE};
853    EGLImageKHR image =
854        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
855                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
856    EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
857    EXPECT_EQ(image, nullptr);
858}
859
860// Tests that OpenGL override the with an incompatible internal format for a texture bound
861// with Metal texture fails.
862TEST_P(ImageTestMetal, OverrideMetalTextureInternalFormatIncompatibleFormat)
863{
864    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
865    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
866
867    EGLDisplay display = getEGLWindow()->getDisplay();
868
869    // On iOS devices, GL_DEPTH24_STENCIL8 is unavailable and is interally converted into
870    // GL_DEPTH32F_STENCIL8. This tests the ability to attach MTLPixelFormatDepth32Float_Stencil8
871    // and have GL treat it as GL_DEPTH24_STENCIL8 instead of GL_DEPTH32F_STENCIL8.
872    ScopedMetalTextureRef textureMtl =
873        createMtlTexture2DArray(1, 1, 1, MTLPixelFormatDepth32Float_Stencil8);
874    const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_RGBA8, EGL_NONE};
875    EGLImageKHR image =
876        eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
877                          reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
878    EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
879    EXPECT_EQ(image, nullptr);
880}
881
882// Test this scenario:
883// Metal and GL share the same MTL texture (called texture1).
884// GL Context draws to texture1:
885// - draw.
886// - upload texture2
887// - draw using the texture2 as source.
888// - place a sync object.
889//
890// Metal reads the texture1:
891// - wait for the shared event sync object.
892// - copy the texture1 to a buffer.
893// - The buffer should contain color from texture2 after being uploaded.
894//
895// Previously this would cause a bug in Metal backend because texture upload would
896// create a new blit encoder in a middle of a render pass and the command buffer would mistrack the
897// ongoing render encoder. Thus making the metal sync object being placed incorrectly.
898TEST_P(ImageTestMetal, SharedEventSyncWhenThereIsTextureUploadBetweenDraws)
899{
900    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
901    ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
902
903    EGLDisplay display1 = getEGLWindow()->getDisplay();
904
905    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
906    ANGLE_SKIP_TEST_IF(
907        !IsEGLDisplayExtensionEnabled(display1, "EGL_ANGLE_metal_shared_event_sync"));
908
909    @autoreleasepool
910    {
911        // Create MTLTexture
912        constexpr int kSharedTextureSize = 1024;
913        ScopedMetalTextureRef textureMtl =
914            createMtlTexture2D(kSharedTextureSize, kSharedTextureSize, MTLPixelFormatR32Sint);
915
916        // Create SharedEvent
917        id<MTLDevice> deviceMtl           = getMtlDevice();
918        id<MTLSharedEvent> sharedEventMtl = CreateMetalSharedEvent(deviceMtl);
919
920        // -------------------------- Metal ---------------------------
921        // Create a buffer on Metal to store the final value.
922        ScopedMetalBufferRef dstBuffer(
923            [deviceMtl newBufferWithLength:sizeof(float) options:MTLResourceStorageModeShared]);
924        ScopedMetalCommandQueueRef commandQueue([deviceMtl newCommandQueue]);
925        id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
926
927        // Wait for drawing on GL context to finish on server side.
928        // Note: we issue a wait even before the draw calls are issued on GL context.
929        // GL context will issue a signaling later (see below).
930        constexpr uint64_t kSignalValue = 0xff;
931        [commandBuffer encodeWaitForEvent:sharedEventMtl value:kSignalValue];
932
933        // Copy a pixel from texture1 to dstBuffer
934        id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
935        [blitEncoder copyFromTexture:textureMtl
936                         sourceSlice:0
937                         sourceLevel:0
938                        sourceOrigin:MTLOriginMake(kSharedTextureSize - 1, kSharedTextureSize - 2,
939                                                   0)
940                          sourceSize:MTLSizeMake(1, 1, 1)
941                            toBuffer:dstBuffer
942                   destinationOffset:0
943              destinationBytesPerRow:sizeof(float) * kSharedTextureSize
944            destinationBytesPerImage:0];
945        [blitEncoder endEncoding];
946        [commandBuffer commit];
947
948        // -------------------------- GL context ---------------------------
949        constexpr int kNumValues = 1000;
950        // A deliberately slow shader that reads a texture many times then write
951        // the sum value to an ouput varible.
952        constexpr char kFS[] = R"(#version 300 es
953out highp ivec4 outColor;
954
955uniform highp isampler2D u_valuesTex;
956
957void main()
958{
959    highp int value = 0;
960    for (int i = 0; i < 1000; ++i) {
961        highp float uCoords = (float(i) + 0.5) / float(1000);
962        value += textureLod(u_valuesTex, vec2(uCoords, 0.0), 0.0).r;
963    }
964
965    outColor = ivec4(value);
966})";
967
968        ANGLE_GL_PROGRAM(complexProgram, essl3_shaders::vs::Simple(), kFS);
969        GLint valuesTexLocation = glGetUniformLocation(complexProgram, "u_valuesTex");
970        ASSERT_NE(valuesTexLocation, -1);
971
972        // Create the shared texture from MTLTexture.
973        EGLImageKHR image =
974            eglCreateImageKHR(display1, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
975                              reinterpret_cast<EGLClientBuffer>(textureMtl.get()), kDefaultAttribs);
976        EXPECT_EGL_SUCCESS();
977        GLTexture texture1;
978        glBindTexture(GL_TEXTURE_2D, texture1);
979        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
980        EXPECT_GL_ERROR(GL_NO_ERROR);
981        glViewport(0, 0, kSharedTextureSize, kSharedTextureSize);
982
983        // Create texture holding multiple values to be accumulated in shader.
984        GLTexture texture2;
985        glBindTexture(GL_TEXTURE_2D, texture2);
986        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
987        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
988        glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, kNumValues, 1, 0, GL_RED_INTEGER, GL_INT, nullptr);
989        glFlush();
990        EXPECT_GL_ERROR(GL_NO_ERROR);
991
992        // Using GL context to draw to the texture1
993        glBindTexture(GL_TEXTURE_2D, texture1);
994        GLFramebuffer framebuffer1;
995        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1);
996        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
997
998        // First draw with initial color
999        {
1000            constexpr char kSimpleFS[] = R"(#version 300 es
1001out highp ivec4 outColor;
1002
1003void main()
1004{
1005    outColor = ivec4(1);
1006})";
1007            ANGLE_GL_PROGRAM(colorProgram, angle::essl3_shaders::vs::Simple(), kSimpleFS);
1008            drawQuad(colorProgram, angle::essl3_shaders::PositionAttrib(), 0.5f);
1009        }
1010
1011        // Upload the texture2
1012        std::vector<int32_t> values(kNumValues);
1013        for (size_t i = 0; i < values.size(); ++i)
1014        {
1015            values[i] = i;
1016        }
1017        glBindTexture(GL_TEXTURE_2D, texture2);
1018        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kNumValues, 1, GL_RED_INTEGER, GL_INT,
1019                        values.data());
1020
1021        // 2nd draw call draw the texture2 to texture1.
1022        glUseProgram(complexProgram);
1023        drawQuad(complexProgram, angle::essl1_shaders::PositionAttrib(), 0.5f);
1024
1025        // Place a sync object on GL context's commands stream.
1026        EGLSync syncGL = CreateEGLSyncFromMetalSharedEvent(display1, sharedEventMtl, kSignalValue,
1027                                                           /*signaled=*/false);
1028        glFlush();
1029
1030        // -------------------------- Metal ---------------------------
1031        [commandBuffer waitUntilCompleted];
1032
1033        // Read dstBuffer
1034        const int32_t kExpectedSum = kNumValues * (kNumValues - 1) / 2;
1035        int32_t *mappedInts        = static_cast<int32_t *>(dstBuffer.get().contents);
1036        EXPECT_EQ(mappedInts[0], kExpectedSum);
1037
1038        eglDestroySync(display1, syncGL);
1039        eglDestroyImage(display1, image);
1040
1041    }  // @autoreleasepool
1042}
1043
1044class ImageClearTestMetal : public ImageTestMetal
1045{
1046  protected:
1047    ImageClearTestMetal() : ImageTestMetal() {}
1048
1049    void RunUnsizedClearTest(MTLPixelFormat format)
1050    {
1051        ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
1052        ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
1053
1054        EGLWindow *window  = getEGLWindow();
1055        EGLDisplay display = window->getDisplay();
1056
1057        window->makeCurrent();
1058
1059        const GLint bufferSize = 32;
1060        ScopedMetalTextureRef textureMtl =
1061            createMtlTexture2DArray(bufferSize, bufferSize, 1, format);
1062        EXPECT_TRUE(textureMtl);
1063
1064        EGLint internalFormat = GL_NONE;
1065        switch (format)
1066        {
1067            case MTLPixelFormatR8Unorm:
1068            case MTLPixelFormatR16Unorm:
1069                internalFormat = GL_RED_EXT;
1070                break;
1071            case MTLPixelFormatRG8Unorm:
1072            case MTLPixelFormatRG16Unorm:
1073                internalFormat = GL_RG_EXT;
1074                break;
1075            case MTLPixelFormatRGBA8Unorm:
1076            case MTLPixelFormatRGBA16Float:
1077            case MTLPixelFormatRGB10A2Unorm:
1078                internalFormat = GL_RGBA;
1079                break;
1080            case MTLPixelFormatRGBA8Unorm_sRGB:
1081                internalFormat = GL_SRGB_ALPHA_EXT;
1082                break;
1083            case MTLPixelFormatBGRA8Unorm:
1084                internalFormat = GL_BGRA_EXT;
1085                break;
1086            default:
1087                break;
1088        }
1089
1090        const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, internalFormat, EGL_NONE};
1091
1092        EGLImageKHR image =
1093            eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
1094                              reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
1095        ASSERT_EGL_SUCCESS();
1096        ASSERT_NE(image, EGL_NO_IMAGE_KHR);
1097
1098        GLTexture texture;
1099        glBindTexture(GL_TEXTURE_2D, texture);
1100        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1101        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1102        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1103        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1104        EXPECT_GL_NO_ERROR();
1105
1106        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
1107        EXPECT_GL_NO_ERROR();
1108
1109        GLFramebuffer fbo;
1110        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1111        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1112        EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER),
1113                  static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
1114        EXPECT_GL_NO_ERROR();
1115
1116        glViewport(0, 0, static_cast<GLsizei>(bufferSize), static_cast<GLsizei>(bufferSize));
1117        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
1118        glClear(GL_COLOR_BUFFER_BIT);
1119        ASSERT_GL_NO_ERROR();
1120
1121        if (format == MTLPixelFormatRGBA16Float)
1122        {
1123            EXPECT_PIXEL_32F_EQ(bufferSize / 2, bufferSize / 2, 1.0f, 1.0f, 1.0f, 1.0f);
1124        }
1125        else if (format == MTLPixelFormatR16Unorm)
1126        {
1127            EXPECT_PIXEL_16_NEAR(bufferSize / 2, bufferSize / 2, 65535, 0, 0, 65535, 0);
1128        }
1129        else if (format == MTLPixelFormatRG16Unorm)
1130        {
1131            EXPECT_PIXEL_16_NEAR(bufferSize / 2, bufferSize / 2, 65535, 65535, 0, 65535, 0);
1132        }
1133        else
1134        {
1135            GLuint readColor[4] = {0, 0, 0, 255};
1136            switch (format)
1137            {
1138                case MTLPixelFormatR8Unorm:
1139                    readColor[0] = 255;
1140                    break;
1141                case MTLPixelFormatRG8Unorm:
1142                    readColor[0] = readColor[1] = 255;
1143                    break;
1144                case MTLPixelFormatRGBA8Unorm:
1145                case MTLPixelFormatRGB10A2Unorm:
1146                case MTLPixelFormatRGBA16Float:
1147                case MTLPixelFormatRGBA8Unorm_sRGB:
1148                case MTLPixelFormatBGRA8Unorm:
1149                    readColor[0] = readColor[1] = readColor[2] = 255;
1150                    break;
1151                default:
1152                    break;
1153            }
1154            // Read back as GL_UNSIGNED_BYTE even though the texture might have more than 8bpc.
1155            EXPECT_PIXEL_EQ(bufferSize / 2, bufferSize / 2, readColor[0], readColor[1],
1156                            readColor[2], readColor[3]);
1157        }
1158    }
1159};
1160
1161TEST_P(ImageClearTestMetal, ClearUnsizedRGBA8)
1162{
1163    RunUnsizedClearTest(MTLPixelFormatRGBA8Unorm);
1164}
1165
1166TEST_P(ImageClearTestMetal, ClearUnsizedsRGBA8)
1167{
1168    RunUnsizedClearTest(MTLPixelFormatRGBA8Unorm_sRGB);
1169}
1170
1171TEST_P(ImageClearTestMetal, ClearUnsizedBGRA8)
1172{
1173    RunUnsizedClearTest(MTLPixelFormatBGRA8Unorm);
1174}
1175
1176TEST_P(ImageClearTestMetal, ClearUnsizedR8)
1177{
1178    RunUnsizedClearTest(MTLPixelFormatR8Unorm);
1179}
1180
1181TEST_P(ImageClearTestMetal, ClearUnsizedRG8)
1182{
1183    RunUnsizedClearTest(MTLPixelFormatRG8Unorm);
1184}
1185
1186TEST_P(ImageClearTestMetal, ClearUnsizedRGB10A2)
1187{
1188    RunUnsizedClearTest(MTLPixelFormatRGB10A2Unorm);
1189}
1190
1191TEST_P(ImageClearTestMetal, ClearUnsizedRGBAF16)
1192{
1193    RunUnsizedClearTest(MTLPixelFormatRGBA16Float);
1194}
1195
1196TEST_P(ImageClearTestMetal, ClearUnsizedR16)
1197{
1198    RunUnsizedClearTest(MTLPixelFormatR16Unorm);
1199}
1200
1201TEST_P(ImageClearTestMetal, ClearUnsizedRG16)
1202{
1203    RunUnsizedClearTest(MTLPixelFormatRG16Unorm);
1204}
1205
1206// Use this to select which configurations (e.g. which renderer, which GLES major version) these
1207// tests should be run against.
1208ANGLE_INSTANTIATE_TEST(ImageTestMetal, ES2_METAL(), ES3_METAL());
1209ANGLE_INSTANTIATE_TEST(ImageClearTestMetal, ES2_METAL(), ES3_METAL());
1210GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageTestMetal);
1211GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageClearTestMetal);
1212}  // namespace angle
1213