| // |
| // 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 <chrono> |
| #include <condition_variable> |
| #include <mutex> |
| |
| #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<uint64_t>(highPart & 0xFFFFFFFF) << 32) | |
| (static_cast<uint64_t>(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<MTLSharedEvent> 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<std::mutex> 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<std::condition_variable> cvRef = mCv; |
| std::shared_ptr<std::mutex> lockRef = mLock; |
| angle::ObjCPtr<MTLSharedEventListener> eventListener = |
| contextMtl->getDisplay()->getOrCreateSharedEventListener(); |
| [mMetalSharedEvent.get() notifyListener:eventListener |
| atValue:mSignalValue |
| block:^(id<MTLSharedEvent> sharedEvent, uint64_t value) { |
| std::unique_lock<std::mutex> 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<id<MTLSharedEvent>> mMetalSharedEvent; |
| uint64_t mSignalValue = 0; |
| |
| std::shared_ptr<std::condition_variable> mCv; |
| std::shared_ptr<std::mutex> 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<id<MTLEvent>> mMetalEvent; |
| uint64_t mEncodedCommandBufferSerial = 0; |
| }; |
| } // 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<mtl::EventSyncImpl> impl = std::make_unique<mtl::EventSyncImpl>(); |
| 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<mtl::EventSyncImpl> impl = std::make_unique<mtl::EventSyncImpl>(); |
| 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<mtl::EventSyncImpl> impl = std::make_unique<mtl::EventSyncImpl>(); |
| 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<MTLSharedEvent>)reinterpret_cast<void *>( |
| 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<mtl::SharedEventSyncImpl> impl = |
| std::make_unique<mtl::SharedEventSyncImpl>(); |
| 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; |
| } |
| |
| default: |
| UNREACHABLE(); |
| return egl::Error(EGL_BAD_ALLOC); |
| } |
| |
| 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); |
| |
| bool flush = (flags & EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) != 0; |
| GLenum result = GL_NONE; |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| if (IsError(mSync->clientWait(contextMtl, flush, static_cast<uint64_t>(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<id<MTLSharedEvent>> copySharedEvent = mSharedEvent; |
| *result = reinterpret_cast<void *>(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 |