// // Copyright (c) 2020 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. // // SyncMtl: // Defines the class interface for SyncMtl, implementing SyncImpl. // #include "libANGLE/renderer/metal/SyncMtl.h" #include #include #include #include "common/debug.h" #include "libANGLE/Context.h" #include "libANGLE/Display.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h" namespace rx { namespace mtl { static uint64_t UnpackSignalValue(EGLAttrib highPart, EGLAttrib lowPart) { return (static_cast(highPart & 0xFFFFFFFF) << 32) | (static_cast(lowPart & 0xFFFFFFFF)); } static constexpr uint64_t kNanosecondsPerDay = 86400000000000; static uint64_t SanitizeTimeout(uint64_t timeout) { // Passing EGL_FOREVER_KHR overflows std::chrono::nanoseconds. return std::min(timeout, kNanosecondsPerDay); } class SyncImpl { public: virtual ~SyncImpl() {} virtual angle::Result clientWait(ContextMtl *contextMtl, bool flushCommands, uint64_t timeout, GLenum *outResult) = 0; virtual angle::Result serverWait(ContextMtl *contextMtl) = 0; virtual angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) = 0; }; // SharedEvent is only available on iOS 12.0+ or mac 10.14+ class SharedEventSyncImpl : public SyncImpl { public: SharedEventSyncImpl() : mCv(new std::condition_variable()), mLock(new std::mutex()) {} ~SharedEventSyncImpl() override {} angle::Result set(ContextMtl *contextMtl, id sharedEvent, uint64_t signalValue, bool enqueueEvent) { mMetalSharedEvent = std::move(sharedEvent); mSignalValue = signalValue; if (enqueueEvent) { contextMtl->queueEventSignal(mMetalSharedEvent, mSignalValue); } return angle::Result::Continue; } angle::Result clientWait(ContextMtl *contextMtl, bool flushCommands, uint64_t timeout, GLenum *outResult) override { std::unique_lock lg(*mLock); if (mMetalSharedEvent.get().signaledValue >= mSignalValue) { *outResult = GL_ALREADY_SIGNALED; return angle::Result::Continue; } if (flushCommands) { contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled); } if (timeout == 0) { *outResult = GL_TIMEOUT_EXPIRED; return angle::Result::Continue; } // Create references to mutex and condition variable since they might be released in // onDestroy(), but the callback might still not be fired yet. std::shared_ptr cvRef = mCv; std::shared_ptr lockRef = mLock; angle::ObjCPtr eventListener = contextMtl->getDisplay()->getOrCreateSharedEventListener(); [mMetalSharedEvent.get() notifyListener:eventListener atValue:mSignalValue block:^(id sharedEvent, uint64_t value) { std::unique_lock localLock(*lockRef); cvRef->notify_one(); }]; if (!mCv->wait_for(lg, std::chrono::nanoseconds(SanitizeTimeout(timeout)), [this] { return mMetalSharedEvent.get().signaledValue >= mSignalValue; })) { *outResult = GL_TIMEOUT_EXPIRED; return angle::Result::Continue; } ASSERT(mMetalSharedEvent.get().signaledValue >= mSignalValue); *outResult = GL_CONDITION_SATISFIED; return angle::Result::Continue; } angle::Result serverWait(ContextMtl *contextMtl) override { contextMtl->serverWaitEvent(mMetalSharedEvent, mSignalValue); return angle::Result::Continue; } angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) override { *signaled = mMetalSharedEvent.get().signaledValue >= mSignalValue; return angle::Result::Continue; } private: angle::ObjCPtr> mMetalSharedEvent; uint64_t mSignalValue = 0; std::shared_ptr mCv; std::shared_ptr mLock; }; class EventSyncImpl : public SyncImpl { private: // MTLEvent starts with a value of 0, use 1 to signal it. static constexpr uint64_t kEventSignalValue = 1; public: ~EventSyncImpl() override {} angle::Result set(ContextMtl *contextMtl) { ANGLE_MTL_OBJC_SCOPE { mMetalEvent = contextMtl->getMetalDevice().newEvent(); } mEncodedCommandBufferSerial = contextMtl->queueEventSignal(mMetalEvent, kEventSignalValue); return angle::Result::Continue; } angle::Result clientWait(ContextMtl *contextMtl, bool flushCommands, uint64_t timeout, GLenum *outResult) override { DisplayMtl *display = contextMtl->getDisplay(); if (display->cmdQueue().isSerialCompleted(mEncodedCommandBufferSerial)) { *outResult = GL_ALREADY_SIGNALED; return angle::Result::Continue; } if (flushCommands) { contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled); } if (timeout == 0 || !display->cmdQueue().waitUntilSerialCompleted( mEncodedCommandBufferSerial, SanitizeTimeout(timeout))) { *outResult = GL_TIMEOUT_EXPIRED; return angle::Result::Continue; } *outResult = GL_CONDITION_SATISFIED; return angle::Result::Continue; } angle::Result serverWait(ContextMtl *contextMtl) override { contextMtl->serverWaitEvent(mMetalEvent, kEventSignalValue); return angle::Result::Continue; } angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) override { *signaled = displayMtl->cmdQueue().isSerialCompleted(mEncodedCommandBufferSerial); return angle::Result::Continue; } private: angle::ObjCPtr> mMetalEvent; uint64_t mEncodedCommandBufferSerial = 0; }; class CommandsScheduledSyncImpl : public SyncImpl { public: CommandsScheduledSyncImpl() = default; ~CommandsScheduledSyncImpl() override = default; angle::Result set(ContextMtl *contextMtl) { contextMtl->addCommandBufferScheduledCallback([state = mState] { std::lock_guard lock(state->mutex); state->signaled = true; state->cv.notify_one(); }); contextMtl->flushCommandBuffer(mtl::NoWait); return angle::Result::Continue; } angle::Result clientWait(ContextMtl *contextMtl, bool flushCommands, uint64_t timeout, GLenum *outResult) override { std::unique_lock lock(mState->mutex); if (mState->signaled) { *outResult = GL_ALREADY_SIGNALED; return angle::Result::Continue; } if (timeout == 0) { *outResult = GL_TIMEOUT_EXPIRED; return angle::Result::Continue; } if (!mState->cv.wait_for(lock, std::chrono::nanoseconds(SanitizeTimeout(timeout)), [state = mState] { return state->signaled; })) { *outResult = GL_TIMEOUT_EXPIRED; return angle::Result::Continue; } ASSERT(mState->signaled); *outResult = GL_CONDITION_SATISFIED; return angle::Result::Continue; } angle::Result serverWait(ContextMtl *contextMtl) override { // Not supported for this sync type. return angle::Result::Continue; } angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) override { std::lock_guard lock(mState->mutex); *signaled = mState->signaled; return angle::Result::Continue; } private: struct State { std::condition_variable cv; std::mutex mutex; bool signaled = false; }; std::shared_ptr mState{std::make_shared()}; }; } // namespace mtl // FenceNVMtl implementation FenceNVMtl::FenceNVMtl() : FenceNVImpl() {} FenceNVMtl::~FenceNVMtl() {} void FenceNVMtl::onDestroy(const gl::Context *context) { mSync.reset(); } angle::Result FenceNVMtl::set(const gl::Context *context, GLenum condition) { ASSERT(condition == GL_ALL_COMPLETED_NV); ContextMtl *contextMtl = mtl::GetImpl(context); std::unique_ptr impl = std::make_unique(); ANGLE_TRY(impl->set(contextMtl)); mSync = std::move(impl); return angle::Result::Continue; } angle::Result FenceNVMtl::test(const gl::Context *context, GLboolean *outFinished) { ContextMtl *contextMtl = mtl::GetImpl(context); bool signaled = false; ANGLE_TRY(mSync->getStatus(contextMtl->getDisplay(), &signaled)); *outFinished = signaled ? GL_TRUE : GL_FALSE; return angle::Result::Continue; } angle::Result FenceNVMtl::finish(const gl::Context *context) { ContextMtl *contextMtl = mtl::GetImpl(context); GLenum result = GL_NONE; ANGLE_TRY(mSync->clientWait(contextMtl, true, mtl::kNanosecondsPerDay, &result)); ASSERT(result != GL_WAIT_FAILED); return angle::Result::Continue; } // SyncMtl implementation SyncMtl::SyncMtl() : SyncImpl() {} SyncMtl::~SyncMtl() {} void SyncMtl::onDestroy(const gl::Context *context) { mSync.reset(); } angle::Result SyncMtl::set(const gl::Context *context, GLenum condition, GLbitfield flags) { ASSERT(condition == GL_SYNC_GPU_COMMANDS_COMPLETE); ASSERT(flags == 0); ContextMtl *contextMtl = mtl::GetImpl(context); std::unique_ptr impl = std::make_unique(); ANGLE_TRY(impl->set(contextMtl)); mSync = std::move(impl); return angle::Result::Continue; } angle::Result SyncMtl::clientWait(const gl::Context *context, GLbitfield flags, GLuint64 timeout, GLenum *outResult) { ASSERT((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) == 0); ContextMtl *contextMtl = mtl::GetImpl(context); bool flush = (flags & GL_SYNC_FLUSH_COMMANDS_BIT) != 0; return mSync->clientWait(contextMtl, flush, timeout, outResult); } angle::Result SyncMtl::serverWait(const gl::Context *context, GLbitfield flags, GLuint64 timeout) { ASSERT(flags == 0); ASSERT(timeout == GL_TIMEOUT_IGNORED); ContextMtl *contextMtl = mtl::GetImpl(context); return mSync->serverWait(contextMtl); } angle::Result SyncMtl::getStatus(const gl::Context *context, GLint *outResult) { ContextMtl *contextMtl = mtl::GetImpl(context); bool signaled = false; ANGLE_TRY(mSync->getStatus(contextMtl->getDisplay(), &signaled)); *outResult = signaled ? GL_SIGNALED : GL_UNSIGNALED; return angle::Result::Continue; } // EGLSyncMtl implementation EGLSyncMtl::EGLSyncMtl() : EGLSyncImpl() {} EGLSyncMtl::~EGLSyncMtl() {} void EGLSyncMtl::onDestroy(const egl::Display *display) { mSync.reset(); mSharedEvent = nil; } egl::Error EGLSyncMtl::initialize(const egl::Display *display, const gl::Context *context, EGLenum type, const egl::AttributeMap &attribs) { ASSERT(context != nullptr); ContextMtl *contextMtl = mtl::GetImpl(context); switch (type) { case EGL_SYNC_FENCE_KHR: { std::unique_ptr impl = std::make_unique(); if (IsError(impl->set(contextMtl))) { return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object"); } mSync = std::move(impl); break; } case EGL_SYNC_METAL_SHARED_EVENT_ANGLE: { mSharedEvent = (__bridge id)reinterpret_cast( attribs.get(EGL_SYNC_METAL_SHARED_EVENT_OBJECT_ANGLE, 0)); if (!mSharedEvent) { mSharedEvent = contextMtl->getMetalDevice().newSharedEvent(); } uint64_t signalValue = 0; if (attribs.contains(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_HI_ANGLE) || attribs.contains(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_LO_ANGLE)) { signalValue = mtl::UnpackSignalValue( attribs.get(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_HI_ANGLE, 0), attribs.get(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_LO_ANGLE, 0)); } else { signalValue = mSharedEvent.get().signaledValue + 1; } // If the condition is anything other than EGL_SYNC_METAL_SHARED_EVENT_SIGNALED_ANGLE, // we enque the event created/provided. // TODO: Could this be changed to `mSharedEvent != nullptr`? Do we ever create an event // but not want to enqueue it? bool enqueue = attribs.getAsInt(EGL_SYNC_CONDITION, 0) != EGL_SYNC_METAL_SHARED_EVENT_SIGNALED_ANGLE; std::unique_ptr impl = std::make_unique(); if (IsError(impl->set(contextMtl, mSharedEvent, signalValue, enqueue))) { return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object"); } mSync = std::move(impl); break; } case EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE: { auto impl = std::make_unique(); if (IsError(impl->set(contextMtl))) { return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object"); } mSync = std::move(impl); break; } default: UNREACHABLE(); return egl::Error(EGL_BAD_ALLOC); } mType = type; return egl::NoError(); } egl::Error EGLSyncMtl::clientWait(const egl::Display *display, const gl::Context *context, EGLint flags, EGLTime timeout, EGLint *outResult) { ASSERT((flags & ~EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) == 0); // Allow null context only for EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE. if (mType != EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE && !context) { return egl::Error(EGL_BAD_CONTEXT); } bool flush = (flags & EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) != 0; GLenum result = GL_NONE; ContextMtl *contextMtl = context ? mtl::GetImpl(context) : nullptr; if (IsError(mSync->clientWait(contextMtl, flush, static_cast(timeout), &result))) { return egl::Error(EGL_BAD_ALLOC); } switch (result) { case GL_ALREADY_SIGNALED: // fall through. EGL doesn't differentiate between event being already set, or set // before timeout. case GL_CONDITION_SATISFIED: *outResult = EGL_CONDITION_SATISFIED_KHR; return egl::NoError(); case GL_TIMEOUT_EXPIRED: *outResult = EGL_TIMEOUT_EXPIRED_KHR; return egl::NoError(); default: UNREACHABLE(); *outResult = EGL_FALSE; return egl::Error(EGL_BAD_ALLOC); } } egl::Error EGLSyncMtl::serverWait(const egl::Display *display, const gl::Context *context, EGLint flags) { // Server wait requires a valid bound context. ASSERT(context); // No flags are currently implemented. ASSERT(flags == 0); ContextMtl *contextMtl = mtl::GetImpl(context); if (IsError(mSync->serverWait(contextMtl))) { return egl::Error(EGL_BAD_ALLOC); } return egl::NoError(); } egl::Error EGLSyncMtl::getStatus(const egl::Display *display, EGLint *outStatus) { DisplayMtl *displayMtl = mtl::GetImpl(display); bool signaled = false; if (IsError(mSync->getStatus(displayMtl, &signaled))) { return egl::Error(EGL_BAD_ALLOC); } *outStatus = signaled ? EGL_SIGNALED_KHR : EGL_UNSIGNALED_KHR; return egl::NoError(); } egl::Error EGLSyncMtl::copyMetalSharedEventANGLE(const egl::Display *display, void **result) const { ASSERT(mSharedEvent != nil); angle::ObjCPtr> copySharedEvent = mSharedEvent; *result = reinterpret_cast(copySharedEvent.leakObject()); return egl::NoError(); } egl::Error EGLSyncMtl::dupNativeFenceFD(const egl::Display *display, EGLint *result) const { UNREACHABLE(); return egl::Error(EGL_BAD_DISPLAY); } } // namespace rx