blob: 482855d482d939ab4618efb4af27a64a64a6b75a [file] [log] [blame] [edit]
// Copyright 2025 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.
//
// EGLSyncTestCommandsScheduled:
// Tests pertaining to EGL_ANGLE_metal_commands_scheduled_sync extension.
//
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include <gtest/gtest.h>
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include <Metal/Metal.h>
#include <thread>
using namespace angle;
class EGLSyncTestMetalCommandsScheduled : public ANGLETest<>
{
protected:
bool hasCommandsScheduledSyncExtension() const
{
return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANGLE_metal_commands_scheduled_sync");
}
void waitForCommandsScheduled(EGLDisplay display, EGLSync sync)
{
// Don't wait forever to make sure the test terminates
constexpr GLuint64 kTimeout = 1000'000'000ul; // 1 second
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSync(display, sync, EGL_SYNC_FLUSH_COMMANDS_BIT, kTimeout));
EGLAttrib value = 0;
EXPECT_EGL_TRUE(eglGetSyncAttrib(display, sync, EGL_SYNC_STATUS, &value));
EXPECT_EQ(value, EGL_SIGNALED);
}
void recordCommands()
{
// Create work to do
ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
}
};
// Test that a sync object is signaled after commands are scheduled.
TEST_P(EGLSyncTestMetalCommandsScheduled, SingleThread)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
waitForCommandsScheduled(display, sync);
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that a sync object can be waited on from another thread.
TEST_P(EGLSyncTestMetalCommandsScheduled, MultiThread)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
std::thread thread([&]() { waitForCommandsScheduled(display, sync); });
thread.join();
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that a sync object can be waited on after the work is done.
TEST_P(EGLSyncTestMetalCommandsScheduled, AfterFinish)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
glFinish();
waitForCommandsScheduled(display, sync);
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that a sync object is signaled even if no work is done.
TEST_P(EGLSyncTestMetalCommandsScheduled, NoWork)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
waitForCommandsScheduled(display, sync);
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that there's no crash if a sync object is destroyed before its command buffer is scheduled.
TEST_P(EGLSyncTestMetalCommandsScheduled, DestroyBeforeScheduled)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync1 = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync1, EGL_NO_SYNC);
// Create and destroy another sync object immediately - it will add a callback to the commands
// scheduled handler, but the callback shouldn't crash even after the sync object is destroyed.
EGLSync sync2 = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync2, EGL_NO_SYNC);
EXPECT_EGL_TRUE(eglDestroySync(display, sync2));
waitForCommandsScheduled(display, sync1);
EXPECT_EGL_TRUE(eglDestroySync(display, sync1));
}
ANGLE_INSTANTIATE_TEST(EGLSyncTestMetalCommandsScheduled, ES2_METAL(), ES3_METAL());
// This test suite is not instantiated on non-Metal backends and OSes.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLSyncTestMetalCommandsScheduled);