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