blob: 1472d69262883e42749b97d33761fae5a622c6a4 [file] [log] [blame]
//
// 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