| // |
| // 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> |
| #include <gmock/gmock.h> |
| #include <span> |
| |
| 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, |
| }; |
| |
| template <typename T> |
| class ScopedMetalObjectRef : angle::NonCopyable |
| { |
| public: |
| ScopedMetalObjectRef() = default; |
| |
| explicit ScopedMetalObjectRef(T &&surface) : mObject(surface) {} |
| |
| ~ScopedMetalObjectRef() |
| { |
| if (mObject) |
| { |
| release(); |
| mObject = nil; |
| } |
| } |
| |
| T get() const { return mObject; } |
| |
| operator bool() const { return !!mObject; } |
| |
| // auto cast to T |
| operator T() const { return mObject; } |
| ScopedMetalObjectRef(const ScopedMetalObjectRef &other) |
| { |
| if (mObject) |
| { |
| release(); |
| } |
| mObject = other.mObject; |
| } |
| |
| explicit ScopedMetalObjectRef(ScopedMetalObjectRef &&other) |
| { |
| if (mObject) |
| { |
| release(); |
| } |
| mObject = other.mObject; |
| other.mObject = nil; |
| } |
| |
| ScopedMetalObjectRef &operator=(ScopedMetalObjectRef &&other) |
| { |
| if (mObject) |
| { |
| release(); |
| } |
| mObject = other.mObject; |
| other.mObject = nil; |
| |
| return *this; |
| } |
| |
| ScopedMetalObjectRef &operator=(const ScopedMetalObjectRef &other) |
| { |
| if (mObject) |
| { |
| release(); |
| } |
| mObject = other.mObject; |
| |
| return *this; |
| } |
| |
| private: |
| void release() |
| { |
| #if !__has_feature(objc_arc) |
| [mObject release]; |
| #endif |
| } |
| |
| T mObject = nil; |
| }; |
| |
| using ScopedMetalTextureRef = ScopedMetalObjectRef<id<MTLTexture>>; |
| using ScopedMetalBufferRef = ScopedMetalObjectRef<id<MTLBuffer>>; |
| using ScopedMetalCommandQueueRef = ScopedMetalObjectRef<id<MTLCommandQueue>>; |
| |
| } // anonymous namespace |
| |
| bool IsDepthOrStencil(MTLPixelFormat format) |
| { |
| switch (format) |
| { |
| case MTLPixelFormatDepth16Unorm: |
| case MTLPixelFormatDepth32Float: |
| case MTLPixelFormatStencil8: |
| case MTLPixelFormatDepth24Unorm_Stencil8: |
| case MTLPixelFormatDepth32Float_Stencil8: |
| case MTLPixelFormatX32_Stencil8: |
| case MTLPixelFormatX24_Stencil8: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| ScopedMetalTextureRef CreateMetalTexture2D(id<MTLDevice> deviceMtl, |
| int width, |
| int height, |
| MTLPixelFormat format, |
| int arrayLength) |
| { |
| @autoreleasepool |
| { |
| MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format |
| width:width |
| height:width |
| mipmapped:NO]; |
| desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; |
| if (IsDepthOrStencil(format)) |
| { |
| desc.storageMode = MTLStorageModePrivate; |
| } |
| if (arrayLength) |
| { |
| desc.arrayLength = arrayLength; |
| desc.textureType = MTLTextureType2DArray; |
| } |
| ScopedMetalTextureRef re([deviceMtl newTextureWithDescriptor:desc]); |
| return re; |
| } |
| } |
| |
| id<MTLSharedEvent> CreateMetalSharedEvent(id<MTLDevice> deviceMtl) |
| { |
| id<MTLSharedEvent> sharedEvent = [deviceMtl newSharedEvent]; |
| sharedEvent.label = @"TestSharedEvent"; |
| return sharedEvent; |
| } |
| |
| EGLSync CreateEGLSyncFromMetalSharedEvent(EGLDisplay display, |
| id<MTLSharedEvent> sharedEvent, |
| uint64_t signalValue, |
| bool signaled) |
| { |
| EGLAttrib signalValueHi = signalValue >> 32; |
| EGLAttrib signalValueLo = signalValue & 0xffffffff; |
| std::vector<EGLAttrib> syncAttribs = { |
| EGL_SYNC_METAL_SHARED_EVENT_OBJECT_ANGLE, reinterpret_cast<EGLAttrib>(sharedEvent), |
| EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_HI_ANGLE, signalValueHi, |
| EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_LO_ANGLE, signalValueLo}; |
| |
| if (signaled) |
| { |
| syncAttribs.push_back(EGL_SYNC_CONDITION); |
| syncAttribs.push_back(EGL_SYNC_METAL_SHARED_EVENT_SIGNALED_ANGLE); |
| } |
| |
| syncAttribs.push_back(EGL_NONE); |
| |
| EGLSync syncWithSharedEvent = |
| eglCreateSync(display, EGL_SYNC_METAL_SHARED_EVENT_ANGLE, syncAttribs.data()); |
| EXPECT_NE(syncWithSharedEvent, EGL_NO_SYNC); |
| |
| return syncWithSharedEvent; |
| } |
| |
| 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); |
| } |
| |
| ScopedMetalTextureRef createMtlTexture2D(int width, int height, MTLPixelFormat format) |
| { |
| id<MTLDevice> device = getMtlDevice(); |
| |
| return CreateMetalTexture2D(device, width, height, format, 0); |
| } |
| |
| ScopedMetalTextureRef createMtlTexture2DArray(int width, |
| int height, |
| int arrayLength, |
| MTLPixelFormat format) |
| { |
| id<MTLDevice> device = getMtlDevice(); |
| |
| return CreateMetalTexture2D(device, width, height, format, arrayLength); |
| } |
| |
| void getTextureSliceBytes(id<MTLTexture> texture, |
| unsigned bytesPerRow, |
| MTLRegion region, |
| unsigned mipmapLevel, |
| unsigned slice, |
| std::span<uint8_t> sliceImage) |
| { |
| @autoreleasepool |
| { |
| id<MTLDevice> device = texture.device; |
| ScopedMetalBufferRef readBuffer([device |
| newBufferWithLength:sliceImage.size() |
| options:MTLResourceStorageModeShared]); |
| ScopedMetalCommandQueueRef commandQueue([device newCommandQueue]); |
| id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer]; |
| id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder]; |
| [blitEncoder copyFromTexture:texture |
| sourceSlice:slice |
| sourceLevel:mipmapLevel |
| sourceOrigin:region.origin |
| sourceSize:region.size |
| toBuffer:readBuffer |
| destinationOffset:0 |
| destinationBytesPerRow:bytesPerRow |
| destinationBytesPerImage:sliceImage.size()]; |
| [blitEncoder endEncoding]; |
| [commandBuffer commit]; |
| [commandBuffer waitUntilCompleted]; |
| memcpy(sliceImage.data(), readBuffer.get().contents, sliceImage.size()); |
| } |
| } |
| void sourceMetalTarget2D_helper(GLubyte data[4], |
| const EGLint *attribs, |
| EGLImageKHR *imageOut, |
| GLuint *textureOut); |
| |
| void verifyResultsTexture(GLuint texture, |
| const 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, const GLubyte data[4]) |
| { |
| verifyResultsTexture(texture, data, GL_TEXTURE_2D, mTextureProgram, |
| mTextureUniformLocation); |
| } |
| |
| void drawColorQuad(GLColor color) |
| { |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor()); |
| glUseProgram(program); |
| GLint colorUniformLocation = |
| glGetUniformLocation(program, angle::essl1_shaders::ColorUniform()); |
| ASSERT_NE(colorUniformLocation, -1); |
| glUniform4fv(colorUniformLocation, 1, color.toNormalizedVector().data()); |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0); |
| glUseProgram(0); |
| } |
| |
| bool hasDepth24Stencil8PixelFormat() |
| { |
| id<MTLDevice> device = getMtlDevice(); |
| return device.depth24Stencil8PixelFormatSupported; |
| } |
| |
| 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 |
| ScopedMetalTextureRef 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 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; |
| } |
| |
| // Test that trying to set renderbuffer storage without a renderbuffer is an error. |
| TEST_P(ImageTestMetal, RenderbufferStorageNoRenderbufferIsError) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| const int bufferSize = 32; |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2DArray(bufferSize, bufferSize, 1, MTLPixelFormatDepth32Float_Stencil8); |
| const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_DEPTH24_STENCIL8, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| EXPECT_EGL_SUCCESS(); |
| EXPECT_NE(image, nullptr); |
| |
| glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| GLRenderbuffer depthStencilBuffer; |
| glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer); |
| glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // 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); |
| } |
| |
| // Tests that OpenGL can sample from a texture bound with Metal texture slice. |
| TEST_P(ImageTestMetal, SourceMetalTarget2DArray) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 3, MTLPixelFormatRGBA8Unorm); |
| |
| GLubyte data0[4] = {93, 83, 75, 128}; |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:0 |
| withBytes:data0 |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| GLubyte data1[4] = {7, 51, 197, 231}; |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:1 |
| withBytes:data1 |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| GLubyte data2[4] = {33, 51, 44, 33}; |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:2 |
| withBytes:data2 |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| EGLImageKHR image0 = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), nullptr); |
| ASSERT_EGL_SUCCESS(); |
| const EGLint attribs1[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 1, EGL_NONE}; |
| EGLImageKHR image1 = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs1); |
| ASSERT_EGL_SUCCESS(); |
| const EGLint attribs2[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 2, EGL_NONE}; |
| EGLImageKHR image2 = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs2); |
| ASSERT_EGL_SUCCESS(); |
| |
| GLTexture targetTexture; |
| glBindTexture(GL_TEXTURE_2D, targetTexture); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image0); |
| verifyResults2D(targetTexture, data0); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image1); |
| verifyResults2D(targetTexture, data1); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image2); |
| verifyResults2D(targetTexture, data2); |
| eglDestroyImageKHR(display, image0); |
| eglDestroyImageKHR(display, image1); |
| eglDestroyImageKHR(display, image2); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EGL_SUCCESS(); |
| } |
| |
| // Test that bound slice to EGLImage is not affected by releasing the source texture. |
| TEST_P(ImageTestMetal, SourceMetalTarget2DArrayReleasedSourceOk) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 3, MTLPixelFormatRGBA8Unorm); |
| |
| GLubyte data1[4] = {7, 51, 197, 231}; |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:1 |
| withBytes:data1 |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| const EGLint attribs1[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 1, EGL_NONE}; |
| EGLImageKHR image1 = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs1); |
| ASSERT_EGL_SUCCESS(); |
| // This is being tested: release the source texture but the slice keeps working. |
| textureMtl = {}; |
| GLTexture targetTexture; |
| glBindTexture(GL_TEXTURE_2D, targetTexture); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image1); |
| verifyResults2D(targetTexture, data1); |
| eglDestroyImageKHR(display, image1); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EGL_SUCCESS(); |
| } |
| |
| // Tests that OpenGL can draw to a texture bound with Metal texture. |
| TEST_P(ImageTestMetal, DrawMetalTarget2D) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| ScopedMetalTextureRef textureMtl = createMtlTexture2D(1, 1, MTLPixelFormatRGBA8Unorm); |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:0 |
| withBytes:GLColor::red.data() |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), nullptr); |
| ASSERT_EGL_SUCCESS(); |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| |
| GLFramebuffer targetFbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, targetFbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| glViewport(0, 0, 1, 1); |
| drawColorQuad(GLColor::magenta); |
| EXPECT_GL_NO_ERROR(); |
| eglDestroyImageKHR(display, image); |
| eglWaitUntilWorkScheduledANGLE(display); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EGL_SUCCESS(); |
| |
| GLColor result; |
| getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 0, {result.data(), 4}); |
| EXPECT_EQ(result, GLColor::magenta); |
| } |
| |
| // Tests that OpenGL can draw to a texture bound with Metal texture slice. |
| TEST_P(ImageTestMetal, DrawMetalTarget2DArray) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 2, MTLPixelFormatRGBA8Unorm); |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:0 |
| withBytes:GLColor::red.data() |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:1 |
| withBytes:GLColor::red.data() |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| |
| const EGLint attribs[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, 1, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| ASSERT_EGL_SUCCESS(); |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| EXPECT_GL_NO_ERROR(); |
| GLFramebuffer targetFbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, targetFbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| glViewport(0, 0, 1, 1); |
| drawColorQuad(GLColor::magenta); |
| EXPECT_GL_NO_ERROR(); |
| eglDestroyImageKHR(display, image); |
| eglWaitUntilWorkScheduledANGLE(display); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EGL_SUCCESS(); |
| |
| GLColor result; |
| getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 1, {result.data(), 4}); |
| EXPECT_EQ(result, GLColor::magenta); |
| } |
| |
| // Tests that OpenGL can blit to a texture bound with Metal texture slice. |
| TEST_P(ImageTestMetal, BlitMetalTarget2DArray) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| GLTexture colorBuffer; |
| glBindTexture(GL_TEXTURE_2D, colorBuffer); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, GLColor::green.data()); |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, |
| GLColor::yellow.data()); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLFramebuffer sourceFbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, sourceFbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuffer, 0); |
| EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| ScopedMetalTextureRef textureMtl = createMtlTexture2DArray(1, 1, 2, MTLPixelFormatRGBA8Unorm); |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:0 |
| withBytes:GLColor::red.data() |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| [textureMtl replaceRegion:MTLRegionMake2D(0, 0, 1, 1) |
| mipmapLevel:0 |
| slice:1 |
| withBytes:GLColor::red.data() |
| bytesPerRow:4 |
| bytesPerImage:4]; |
| |
| for (int slice = 0; slice < 2; ++slice) |
| { |
| const EGLint attribs[] = {EGL_METAL_TEXTURE_ARRAY_SLICE_ANGLE, slice, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| ASSERT_EGL_SUCCESS(); |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| verifyResults2D(texture, GLColor::red.data()); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLFramebuffer targetFbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, targetFbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, sourceFbo); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, targetFbo); |
| glBlitFramebufferANGLE(slice, 0, slice + 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| EXPECT_GL_NO_ERROR(); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| verifyResults2D(texture, slice == 0 ? GLColor::green.data() : GLColor::yellow.data()); |
| eglDestroyImageKHR(display, image); |
| EXPECT_GL_NO_ERROR(); |
| EXPECT_EGL_SUCCESS(); |
| } |
| eglWaitUntilWorkScheduledANGLE(display); |
| EXPECT_EGL_SUCCESS(); |
| |
| GLColor result; |
| getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 0, {result.data(), 4}); |
| EXPECT_EQ(result, GLColor::green); |
| getTextureSliceBytes(textureMtl, 4, MTLRegionMake2D(0, 0, 1, 1), 0, 1, {result.data(), 4}); |
| EXPECT_EQ(result, GLColor::yellow); |
| } |
| |
| // Tests that OpenGL can override the internal format for a texture bound with |
| // Metal texture. |
| TEST_P(ImageTestMetal, OverrideMetalTextureInternalFormat) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| ANGLE_SKIP_TEST_IF(hasDepth24Stencil8PixelFormat()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| // On iOS devices, GL_DEPTH24_STENCIL8 is unavailable and is interally converted into |
| // GL_DEPTH32F_STENCIL8. This tests the ability to attach MTLPixelFormatDepth32Float_Stencil8 |
| // and have GL treat it as GL_DEPTH24_STENCIL8 instead of GL_DEPTH32F_STENCIL8. |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2DArray(1, 1, 1, MTLPixelFormatDepth32Float_Stencil8); |
| const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_DEPTH24_STENCIL8, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| EXPECT_EGL_SUCCESS(); |
| EXPECT_NE(image, nullptr); |
| } |
| |
| // Tests that OpenGL can override the internal format for a texture bound with |
| // Metal texture and that rendering to the texture is successful. |
| TEST_P(ImageTestMetal, RenderingTest) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| ANGLE_SKIP_TEST_IF(hasDepth24Stencil8PixelFormat()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| const int bufferSize = 32; |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2DArray(bufferSize, bufferSize, 1, MTLPixelFormatDepth32Float_Stencil8); |
| const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_DEPTH24_STENCIL8, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| EXPECT_EGL_SUCCESS(); |
| EXPECT_NE(image, nullptr); |
| |
| GLRenderbuffer colorBuffer; |
| glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer); |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, bufferSize, bufferSize); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLRenderbuffer depthStencilBuffer; |
| glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer); |
| glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLFramebuffer fb; |
| glBindFramebuffer(GL_FRAMEBUFFER, fb); |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer); |
| if (getClientMajorVersion() >= 3) |
| { |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, |
| depthStencilBuffer); |
| } |
| else |
| { |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, |
| depthStencilBuffer); |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, |
| depthStencilBuffer); |
| } |
| |
| EXPECT_GL_NO_ERROR(); |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); |
| |
| glClearColor(1.f, 0.f, 0.f, 1.f); |
| glClearDepthf(1.f); |
| glClearStencil(0x55); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
| EXPECT_GL_NO_ERROR(); |
| |
| glEnable(GL_DEPTH_TEST); |
| glDepthFunc(GL_LESS); |
| |
| glEnable(GL_STENCIL_TEST); |
| glStencilFunc(GL_EQUAL, 0x55, 0xFF); |
| glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); |
| glStencilMask(0xFF); |
| |
| // Draw green. |
| ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); |
| drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.95f); |
| |
| // Verify that green was drawn. |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(0, bufferSize - 1, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(bufferSize - 1, 0, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(bufferSize - 1, bufferSize - 1, GLColor::green); |
| |
| eglDestroyImageKHR(display, image); |
| } |
| |
| // Tests that OpenGL override the with a bad internal format for a texture bound |
| // with Metal texture fails. |
| TEST_P(ImageTestMetal, OverrideMetalTextureInternalFormatBadFormat) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| // On iOS devices, GL_DEPTH24_STENCIL8 is unavailable and is interally converted into |
| // GL_DEPTH32F_STENCIL8. This tests the ability to attach MTLPixelFormatDepth32Float_Stencil8 |
| // and have GL treat it as GL_DEPTH24_STENCIL8 instead of GL_DEPTH32F_STENCIL8. |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2DArray(1, 1, 1, MTLPixelFormatDepth32Float_Stencil8); |
| const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_TRIANGLES, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE); |
| EXPECT_EQ(image, nullptr); |
| } |
| |
| // Tests that OpenGL override the with an incompatible internal format for a texture bound |
| // with Metal texture fails. |
| TEST_P(ImageTestMetal, OverrideMetalTextureInternalFormatIncompatibleFormat) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display = getEGLWindow()->getDisplay(); |
| |
| // On iOS devices, GL_DEPTH24_STENCIL8 is unavailable and is interally converted into |
| // GL_DEPTH32F_STENCIL8. This tests the ability to attach MTLPixelFormatDepth32Float_Stencil8 |
| // and have GL treat it as GL_DEPTH24_STENCIL8 instead of GL_DEPTH32F_STENCIL8. |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2DArray(1, 1, 1, MTLPixelFormatDepth32Float_Stencil8); |
| const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_RGBA8, EGL_NONE}; |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE); |
| EXPECT_EQ(image, nullptr); |
| } |
| |
| // Test this scenario: |
| // Metal and GL share the same MTL texture (called texture1). |
| // GL Context draws to texture1: |
| // - draw. |
| // - upload texture2 |
| // - draw using the texture2 as source. |
| // - place a sync object. |
| // |
| // Metal reads the texture1: |
| // - wait for the shared event sync object. |
| // - copy the texture1 to a buffer. |
| // - The buffer should contain color from texture2 after being uploaded. |
| // |
| // Previously this would cause a bug in Metal backend because texture upload would |
| // create a new blit encoder in a middle of a render pass and the command buffer would mistrack the |
| // ongoing render encoder. Thus making the metal sync object being placed incorrectly. |
| TEST_P(ImageTestMetal, SharedEventSyncWhenThereIsTextureUploadBetweenDraws) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLDisplay display1 = getEGLWindow()->getDisplay(); |
| |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); |
| ANGLE_SKIP_TEST_IF( |
| !IsEGLDisplayExtensionEnabled(display1, "EGL_ANGLE_metal_shared_event_sync")); |
| |
| @autoreleasepool |
| { |
| // Create MTLTexture |
| constexpr int kSharedTextureSize = 1024; |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2D(kSharedTextureSize, kSharedTextureSize, MTLPixelFormatR32Sint); |
| |
| // Create SharedEvent |
| id<MTLDevice> deviceMtl = getMtlDevice(); |
| id<MTLSharedEvent> sharedEventMtl = CreateMetalSharedEvent(deviceMtl); |
| |
| // -------------------------- Metal --------------------------- |
| // Create a buffer on Metal to store the final value. |
| ScopedMetalBufferRef dstBuffer( |
| [deviceMtl newBufferWithLength:sizeof(float) options:MTLResourceStorageModeShared]); |
| ScopedMetalCommandQueueRef commandQueue([deviceMtl newCommandQueue]); |
| id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer]; |
| |
| // Wait for drawing on GL context to finish on server side. |
| // Note: we issue a wait even before the draw calls are issued on GL context. |
| // GL context will issue a signaling later (see below). |
| constexpr uint64_t kSignalValue = 0xff; |
| [commandBuffer encodeWaitForEvent:sharedEventMtl value:kSignalValue]; |
| |
| // Copy a pixel from texture1 to dstBuffer |
| id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder]; |
| [blitEncoder copyFromTexture:textureMtl |
| sourceSlice:0 |
| sourceLevel:0 |
| sourceOrigin:MTLOriginMake(kSharedTextureSize - 1, kSharedTextureSize - 2, |
| 0) |
| sourceSize:MTLSizeMake(1, 1, 1) |
| toBuffer:dstBuffer |
| destinationOffset:0 |
| destinationBytesPerRow:sizeof(float) * kSharedTextureSize |
| destinationBytesPerImage:0]; |
| [blitEncoder endEncoding]; |
| [commandBuffer commit]; |
| |
| // -------------------------- GL context --------------------------- |
| constexpr int kNumValues = 1000; |
| // A deliberately slow shader that reads a texture many times then write |
| // the sum value to an ouput varible. |
| constexpr char kFS[] = R"(#version 300 es |
| out highp ivec4 outColor; |
| |
| uniform highp isampler2D u_valuesTex; |
| |
| void main() |
| { |
| highp int value = 0; |
| for (int i = 0; i < 1000; ++i) { |
| highp float uCoords = (float(i) + 0.5) / float(1000); |
| value += textureLod(u_valuesTex, vec2(uCoords, 0.0), 0.0).r; |
| } |
| |
| outColor = ivec4(value); |
| })"; |
| |
| ANGLE_GL_PROGRAM(complexProgram, essl3_shaders::vs::Simple(), kFS); |
| GLint valuesTexLocation = glGetUniformLocation(complexProgram, "u_valuesTex"); |
| ASSERT_NE(valuesTexLocation, -1); |
| |
| // Create the shared texture from MTLTexture. |
| EGLImageKHR image = |
| eglCreateImageKHR(display1, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), kDefaultAttribs); |
| EXPECT_EGL_SUCCESS(); |
| GLTexture texture1; |
| glBindTexture(GL_TEXTURE_2D, texture1); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| EXPECT_GL_ERROR(GL_NO_ERROR); |
| glViewport(0, 0, kSharedTextureSize, kSharedTextureSize); |
| |
| // Create texture holding multiple values to be accumulated in shader. |
| GLTexture texture2; |
| glBindTexture(GL_TEXTURE_2D, texture2); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, kNumValues, 1, 0, GL_RED_INTEGER, GL_INT, nullptr); |
| glFlush(); |
| EXPECT_GL_ERROR(GL_NO_ERROR); |
| |
| // Using GL context to draw to the texture1 |
| glBindTexture(GL_TEXTURE_2D, texture1); |
| GLFramebuffer framebuffer1; |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer1); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0); |
| |
| // First draw with initial color |
| { |
| constexpr char kSimpleFS[] = R"(#version 300 es |
| out highp ivec4 outColor; |
| |
| void main() |
| { |
| outColor = ivec4(1); |
| })"; |
| ANGLE_GL_PROGRAM(colorProgram, angle::essl3_shaders::vs::Simple(), kSimpleFS); |
| drawQuad(colorProgram, angle::essl3_shaders::PositionAttrib(), 0.5f); |
| } |
| |
| // Upload the texture2 |
| std::vector<int32_t> values(kNumValues); |
| for (size_t i = 0; i < values.size(); ++i) |
| { |
| values[i] = i; |
| } |
| glBindTexture(GL_TEXTURE_2D, texture2); |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kNumValues, 1, GL_RED_INTEGER, GL_INT, |
| values.data()); |
| |
| // 2nd draw call draw the texture2 to texture1. |
| glUseProgram(complexProgram); |
| drawQuad(complexProgram, angle::essl1_shaders::PositionAttrib(), 0.5f); |
| |
| // Place a sync object on GL context's commands stream. |
| EGLSync syncGL = CreateEGLSyncFromMetalSharedEvent(display1, sharedEventMtl, kSignalValue, |
| /*signaled=*/false); |
| glFlush(); |
| |
| // -------------------------- Metal --------------------------- |
| [commandBuffer waitUntilCompleted]; |
| |
| // Read dstBuffer |
| const int32_t kExpectedSum = kNumValues * (kNumValues - 1) / 2; |
| int32_t *mappedInts = static_cast<int32_t *>(dstBuffer.get().contents); |
| EXPECT_EQ(mappedInts[0], kExpectedSum); |
| |
| eglDestroySync(display1, syncGL); |
| eglDestroyImage(display1, image); |
| |
| } // @autoreleasepool |
| } |
| |
| class ImageClearTestMetal : public ImageTestMetal |
| { |
| protected: |
| ImageClearTestMetal() : ImageTestMetal() {} |
| |
| void RunUnsizedClearTest(MTLPixelFormat format) |
| { |
| ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt()); |
| ANGLE_SKIP_TEST_IF(!hasImageNativeMetalTextureExt()); |
| |
| EGLWindow *window = getEGLWindow(); |
| EGLDisplay display = window->getDisplay(); |
| |
| window->makeCurrent(); |
| |
| const GLint bufferSize = 32; |
| ScopedMetalTextureRef textureMtl = |
| createMtlTexture2DArray(bufferSize, bufferSize, 1, format); |
| EXPECT_TRUE(textureMtl); |
| |
| EGLint internalFormat = GL_NONE; |
| switch (format) |
| { |
| case MTLPixelFormatR8Unorm: |
| case MTLPixelFormatR16Unorm: |
| internalFormat = GL_RED_EXT; |
| break; |
| case MTLPixelFormatRG8Unorm: |
| case MTLPixelFormatRG16Unorm: |
| internalFormat = GL_RG_EXT; |
| break; |
| case MTLPixelFormatRGBA8Unorm: |
| case MTLPixelFormatRGBA16Float: |
| case MTLPixelFormatRGB10A2Unorm: |
| internalFormat = GL_RGBA; |
| break; |
| case MTLPixelFormatRGBA8Unorm_sRGB: |
| internalFormat = GL_SRGB_ALPHA_EXT; |
| break; |
| case MTLPixelFormatBGRA8Unorm: |
| internalFormat = GL_BGRA_EXT; |
| break; |
| default: |
| break; |
| } |
| |
| const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, internalFormat, EGL_NONE}; |
| |
| EGLImageKHR image = |
| eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, |
| reinterpret_cast<EGLClientBuffer>(textureMtl.get()), attribs); |
| ASSERT_EGL_SUCCESS(); |
| ASSERT_NE(image, EGL_NO_IMAGE_KHR); |
| |
| GLTexture texture; |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| EXPECT_GL_NO_ERROR(); |
| |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
| EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), |
| static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE)); |
| EXPECT_GL_NO_ERROR(); |
| |
| glViewport(0, 0, static_cast<GLsizei>(bufferSize), static_cast<GLsizei>(bufferSize)); |
| glClearColor(1.0f, 1.0f, 1.0f, 1.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| ASSERT_GL_NO_ERROR(); |
| |
| if (format == MTLPixelFormatRGBA16Float) |
| { |
| EXPECT_PIXEL_32F_EQ(bufferSize / 2, bufferSize / 2, 1.0f, 1.0f, 1.0f, 1.0f); |
| } |
| else if (format == MTLPixelFormatR16Unorm) |
| { |
| EXPECT_PIXEL_16_NEAR(bufferSize / 2, bufferSize / 2, 65535, 0, 0, 65535, 0); |
| } |
| else if (format == MTLPixelFormatRG16Unorm) |
| { |
| EXPECT_PIXEL_16_NEAR(bufferSize / 2, bufferSize / 2, 65535, 65535, 0, 65535, 0); |
| } |
| else |
| { |
| GLuint readColor[4] = {0, 0, 0, 255}; |
| switch (format) |
| { |
| case MTLPixelFormatR8Unorm: |
| readColor[0] = 255; |
| break; |
| case MTLPixelFormatRG8Unorm: |
| readColor[0] = readColor[1] = 255; |
| break; |
| case MTLPixelFormatRGBA8Unorm: |
| case MTLPixelFormatRGB10A2Unorm: |
| case MTLPixelFormatRGBA16Float: |
| case MTLPixelFormatRGBA8Unorm_sRGB: |
| case MTLPixelFormatBGRA8Unorm: |
| readColor[0] = readColor[1] = readColor[2] = 255; |
| break; |
| default: |
| break; |
| } |
| // Read back as GL_UNSIGNED_BYTE even though the texture might have more than 8bpc. |
| EXPECT_PIXEL_EQ(bufferSize / 2, bufferSize / 2, readColor[0], readColor[1], |
| readColor[2], readColor[3]); |
| } |
| } |
| }; |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedRGBA8) |
| { |
| RunUnsizedClearTest(MTLPixelFormatRGBA8Unorm); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedsRGBA8) |
| { |
| RunUnsizedClearTest(MTLPixelFormatRGBA8Unorm_sRGB); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedBGRA8) |
| { |
| RunUnsizedClearTest(MTLPixelFormatBGRA8Unorm); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedR8) |
| { |
| RunUnsizedClearTest(MTLPixelFormatR8Unorm); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedRG8) |
| { |
| RunUnsizedClearTest(MTLPixelFormatRG8Unorm); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedRGB10A2) |
| { |
| RunUnsizedClearTest(MTLPixelFormatRGB10A2Unorm); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedRGBAF16) |
| { |
| RunUnsizedClearTest(MTLPixelFormatRGBA16Float); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedR16) |
| { |
| RunUnsizedClearTest(MTLPixelFormatR16Unorm); |
| } |
| |
| TEST_P(ImageClearTestMetal, ClearUnsizedRG16) |
| { |
| RunUnsizedClearTest(MTLPixelFormatRG16Unorm); |
| } |
| |
| // 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()); |
| ANGLE_INSTANTIATE_TEST(ImageClearTestMetal, ES2_METAL(), ES3_METAL()); |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageTestMetal); |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageClearTestMetal); |
| } // namespace angle |