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