Edit

kc3-lang/angle/src/tests/gl_tests/ImageTestMetal.mm

Branch :

  • Show log

    Commit

  • Author : Le Hoang Quyen
    Date : 2021-04-11 14:55:19
    Hash : f2aa9d5d
    Message : Reland: Metal: Support importing external metal textures This relands I4d4a88cfbb77d8b7508b787c7fec44073d3b11b0. Fixes: - uninstantiated ImageTestMetal error - failed ImageTest.ANGLEExtensionAvailability on ARM mac. Bug: angleproject:5763 Bug: angleproject:5814 Change-Id: I906fe52baefd6be3c6e00f594795bd527df01616 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2820178 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Le Hoang Quyen <le.hoang.q@gmail.com>

  • src/tests/gl_tests/ImageTestMetal.mm
  • //
    // Copyright 2021 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    // ImageTestMetal:
    //   Tests the correctness of eglImage with native Metal texture extensions.
    //
    
    #include "test_utils/ANGLETest.h"
    
    #include "common/mathutil.h"
    #include "test_utils/gl_raii.h"
    #include "util/EGLWindow.h"
    
    #include <CoreFoundation/CoreFoundation.h>
    #include <Metal/Metal.h>
    
    namespace angle
    {
    namespace
    {
    constexpr char kOESExt[]                      = "GL_OES_EGL_image";
    constexpr char kBaseExt[]                     = "EGL_KHR_image_base";
    constexpr char kDeviceMtlExt[]                = "EGL_ANGLE_device_metal";
    constexpr char kEGLMtlImageNativeTextureExt[] = "EGL_ANGLE_metal_texture_client_buffer";
    constexpr EGLint kDefaultAttribs[]            = {
        EGL_NONE,
    };
    }  // anonymous namespace
    
    class ScopeMetalTextureRef : angle::NonCopyable
    {
      public:
        explicit ScopeMetalTextureRef(id<MTLTexture> surface) : mSurface(surface) {}
    
        ~ScopeMetalTextureRef()
        {
            if (mSurface)
            {
                release();
                mSurface = nullptr;
            }
        }
    
        id<MTLTexture> get() const { return mSurface; }
    
        // auto cast to MTLTexture
        operator id<MTLTexture>() const { return mSurface; }
        ScopeMetalTextureRef(const ScopeMetalTextureRef &other)
        {
            if (mSurface)
            {
                release();
            }
            mSurface = other.mSurface;
        }
    
        explicit ScopeMetalTextureRef(ScopeMetalTextureRef &&other)
        {
            if (mSurface)
            {
                release();
            }
            mSurface       = other.mSurface;
            other.mSurface = nil;
        }
    
        ScopeMetalTextureRef &operator=(ScopeMetalTextureRef &&other)
        {
            if (mSurface)
            {
                release();
            }
            mSurface       = other.mSurface;
            other.mSurface = nil;
    
            return *this;
        }
    
        ScopeMetalTextureRef &operator=(const ScopeMetalTextureRef &other)
        {
            if (mSurface)
            {
                release();
            }
            mSurface = other.mSurface;
    
            return *this;
        }
    
      private:
        void release()
        {
    #if !__has_feature(objc_arc)
            [mSurface release];
    #endif
        }
    
        id<MTLTexture> mSurface = nil;
    };
    
    ScopeMetalTextureRef CreateMetalTexture2D(id<MTLDevice> deviceMtl,
                                              int width,
                                              int height,
                                              MTLPixelFormat format)
    {
        @autoreleasepool
        {
            MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
                                                                                            width:width
                                                                                           height:width
                                                                                        mipmapped:NO];
            desc.usage                 = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
    
            id<MTLTexture> texture = [deviceMtl newTextureWithDescriptor:desc];
    
            ScopeMetalTextureRef re(texture);
            return re;
        }
    }
    
    class ImageTestMetal : public ANGLETest
    {
      protected:
        ImageTestMetal()
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
            setConfigDepthBits(24);
        }
    
        void testSetUp() override
        {
            constexpr char kVS[] = "precision highp float;\n"
                                   "attribute vec4 position;\n"
                                   "varying vec2 texcoord;\n"
                                   "\n"
                                   "void main()\n"
                                   "{\n"
                                   "    gl_Position = position;\n"
                                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                                   "    texcoord.y = 1.0 - texcoord.y;\n"
                                   "}\n";
    
            constexpr char kTextureFS[] = "precision highp float;\n"
                                          "uniform sampler2D tex;\n"
                                          "varying vec2 texcoord;\n"
                                          "\n"
                                          "void main()\n"
                                          "{\n"
                                          "    gl_FragColor = texture2D(tex, texcoord);\n"
                                          "}\n";
    
            mTextureProgram = CompileProgram(kVS, kTextureFS);
            if (mTextureProgram == 0)
            {
                FAIL() << "shader compilation failed.";
            }
    
            mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override { glDeleteProgram(mTextureProgram); }
    
        id<MTLDevice> getMtlDevice()
        {
            EGLAttrib angleDevice = 0;
            EGLAttrib device      = 0;
            EXPECT_EGL_TRUE(
                eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
    
            EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
                                                    EGL_METAL_DEVICE_ANGLE, &device));
    
            return (__bridge id<MTLDevice>)reinterpret_cast<void *>(device);
        }
    
        ScopeMetalTextureRef createMtlTexture2D(int width, int height, MTLPixelFormat format)
        {
            id<MTLDevice> device = getMtlDevice();
    
            return CreateMetalTexture2D(device, width, height, format);
        }
    
        void sourceMetalTarget2D_helper(GLubyte data[4],
                                        const EGLint *attribs,
                                        EGLImageKHR *imageOut,
                                        GLuint *textureOut);
    
        void verifyResultsTexture(GLuint texture,
                                  GLubyte data[4],
                                  GLenum textureTarget,
                                  GLuint program,
                                  GLuint textureUniform)
        {
            // Draw a quad with the target texture
            glUseProgram(program);
            glBindTexture(textureTarget, texture);
            glUniform1i(textureUniform, 0);
    
            drawQuad(program, "position", 0.5f);
    
            // Expect that the rendered quad has the same color as the source texture
            EXPECT_PIXEL_NEAR(0, 0, data[0], data[1], data[2], data[3], 1.0);
        }
    
        void verifyResults2D(GLuint texture, GLubyte data[4])
        {
            verifyResultsTexture(texture, data, GL_TEXTURE_2D, mTextureProgram,
                                 mTextureUniformLocation);
        }
    
        template <typename destType, typename sourcetype>
        destType reinterpretHelper(sourcetype source)
        {
            static_assert(sizeof(destType) == sizeof(size_t),
                          "destType should be the same size as a size_t");
            size_t sourceSizeT = static_cast<size_t>(source);
            return reinterpret_cast<destType>(sourceSizeT);
        }
    
        bool hasImageNativeMetalTextureExt() const
        {
            if (!IsMetal())
            {
                return false;
            }
            EGLAttrib angleDevice = 0;
            eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice);
            if (!angleDevice)
            {
                return false;
            }
            auto extensionString = static_cast<const char *>(
                eglQueryDeviceStringEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice), EGL_EXTENSIONS));
            if (strstr(extensionString, kDeviceMtlExt) == nullptr)
            {
                return false;
            }
            return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
                                                kEGLMtlImageNativeTextureExt);
        }
    
        bool hasOESExt() const { return IsGLExtensionEnabled(kOESExt); }
    
        bool hasBaseExt() const
        {
            return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), kBaseExt);
        }
    
        GLuint mTextureProgram;
        GLint mTextureUniformLocation;
    };
    
    void ImageTestMetal::sourceMetalTarget2D_helper(GLubyte data[4],
                                                    const EGLint *attribs,
                                                    EGLImageKHR *imageOut,
                                                    GLuint *textureOut)
    {
        EGLWindow *window = getEGLWindow();
    
        // Create MTLTexture
        ScopeMetalTextureRef textureMtl = createMtlTexture2D(1, 1, MTLPixelFormatRGBA8Unorm);
    
        // Create image
        EGLImageKHR image =
            eglCreateImageKHR(window->getDisplay(), EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE,
                              reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs);
        ASSERT_EGL_SUCCESS();
    
        // Write the data to the MTLTexture
        [textureMtl.get() replaceRegion:MTLRegionMake2D(0, 0, 1, 1)
                            mipmapLevel:0
                                  slice:0
                              withBytes:data
                            bytesPerRow:4
                          bytesPerImage:0];
    
        // Create a texture target to bind the egl image
        GLuint target;
        glGenTextures(1, &target);
        glBindTexture(GL_TEXTURE_2D, target);
        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
    
        *imageOut   = image;
        *textureOut = target;
    }
    
    // Testing source metal EGL image, target 2D texture
    TEST_P(ImageTestMetal, SourceMetalTarget2D)
    {
        ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
        ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
    
        EGLWindow *window = getEGLWindow();
    
        // Create the Image
        EGLImageKHR image;
        GLuint texTarget;
        GLubyte data[4] = {7, 51, 197, 231};
        sourceMetalTarget2D_helper(data, kDefaultAttribs, &image, &texTarget);
    
        // Use texture target bound to egl image as source and render to framebuffer
        // Verify that data in framebuffer matches that in the egl image
        verifyResults2D(texTarget, data);
    
        // Clean up
        eglDestroyImageKHR(window->getDisplay(), image);
        glDeleteTextures(1, &texTarget);
    }
    
    // Create source metal EGL image, target 2D texture, then trigger texture respecification.
    TEST_P(ImageTestMetal, SourceMetal2DTargetTextureRespecifySize)
    {
        ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt());
        ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt());
    
        EGLWindow *window = getEGLWindow();
    
        // Create the Image
        EGLImageKHR image;
        GLuint texTarget;
        GLubyte data[4] = {7, 51, 197, 231};
        sourceMetalTarget2D_helper(data, kDefaultAttribs, &image, &texTarget);
    
        // Use texture target bound to egl image as source and render to framebuffer
        // Verify that data in framebuffer matches that in the egl image
        verifyResults2D(texTarget, data);
    
        // Respecify texture size and verify results
        std::array<GLubyte, 16> referenceColor;
        referenceColor.fill(127);
        glBindTexture(GL_TEXTURE_2D, texTarget);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     referenceColor.data());
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     referenceColor.data());
        ASSERT_GL_NO_ERROR();
    
        // Expect that the target texture has the reference color values
        verifyResults2D(texTarget, referenceColor.data());
    
        // Clean up
        eglDestroyImageKHR(window->getDisplay(), image);
        glDeleteTextures(1, &texTarget);
    }
    
    // Use this to select which configurations (e.g. which renderer, which GLES major version) these
    // tests should be run against.
    ANGLE_INSTANTIATE_TEST(ImageTestMetal, ES2_METAL(), ES3_METAL());
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageTestMetal);
    }  // namespace angle