1// 2// Copyright (c) 2020 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// SyncMtl: 7// Defines the class interface for SyncMtl, implementing SyncImpl. 8// 9 10#include "libANGLE/renderer/metal/SyncMtl.h" 11 12#include <chrono> 13#include <condition_variable> 14#include <mutex> 15 16#include "common/debug.h" 17#include "libANGLE/Context.h" 18#include "libANGLE/Display.h" 19#include "libANGLE/renderer/metal/ContextMtl.h" 20#include "libANGLE/renderer/metal/DisplayMtl.h" 21 22namespace rx 23{ 24namespace mtl 25{ 26 27static uint64_t UnpackSignalValue(EGLAttrib highPart, EGLAttrib lowPart) 28{ 29 return (static_cast<uint64_t>(highPart & 0xFFFFFFFF) << 32) | 30 (static_cast<uint64_t>(lowPart & 0xFFFFFFFF)); 31} 32 33static constexpr uint64_t kNanosecondsPerDay = 86400000000000; 34static uint64_t SanitizeTimeout(uint64_t timeout) 35{ 36 // Passing EGL_FOREVER_KHR overflows std::chrono::nanoseconds. 37 return std::min(timeout, kNanosecondsPerDay); 38} 39 40class SyncImpl 41{ 42 public: 43 virtual ~SyncImpl() {} 44 45 virtual angle::Result clientWait(ContextMtl *contextMtl, 46 bool flushCommands, 47 uint64_t timeout, 48 GLenum *outResult) = 0; 49 virtual angle::Result serverWait(ContextMtl *contextMtl) = 0; 50 virtual angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) = 0; 51}; 52 53// SharedEvent is only available on iOS 12.0+ or mac 10.14+ 54#if ANGLE_MTL_EVENT_AVAILABLE 55class SharedEventSyncImpl : public SyncImpl 56{ 57 public: 58 SharedEventSyncImpl() : mCv(new std::condition_variable()), mLock(new std::mutex()) {} 59 60 ~SharedEventSyncImpl() override {} 61 62 angle::Result set(ContextMtl *contextMtl, 63 id<MTLSharedEvent> sharedEvent, 64 uint64_t signalValue, 65 bool enqueueEvent) 66 { 67 mMetalSharedEvent.retainAssign(sharedEvent); 68 mSignalValue = signalValue; 69 70 if (enqueueEvent) 71 { 72 contextMtl->queueEventSignal(mMetalSharedEvent, mSignalValue); 73 } 74 75 return angle::Result::Continue; 76 } 77 78 angle::Result clientWait(ContextMtl *contextMtl, 79 bool flushCommands, 80 uint64_t timeout, 81 GLenum *outResult) override 82 { 83 std::unique_lock<std::mutex> lg(*mLock); 84 if (mMetalSharedEvent.get().signaledValue >= mSignalValue) 85 { 86 *outResult = GL_ALREADY_SIGNALED; 87 return angle::Result::Continue; 88 } 89 if (flushCommands) 90 { 91 contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled); 92 } 93 94 if (timeout == 0) 95 { 96 *outResult = GL_TIMEOUT_EXPIRED; 97 return angle::Result::Continue; 98 } 99 100 // Create references to mutex and condition variable since they might be released in 101 // onDestroy(), but the callback might still not be fired yet. 102 std::shared_ptr<std::condition_variable> cvRef = mCv; 103 std::shared_ptr<std::mutex> lockRef = mLock; 104 AutoObjCObj<MTLSharedEventListener> eventListener = 105 contextMtl->getDisplay()->getOrCreateSharedEventListener(); 106 [mMetalSharedEvent.get() notifyListener:eventListener 107 atValue:mSignalValue 108 block:^(id<MTLSharedEvent> sharedEvent, uint64_t value) { 109 std::unique_lock<std::mutex> localLock(*lockRef); 110 cvRef->notify_one(); 111 }]; 112 113 if (!mCv->wait_for(lg, std::chrono::nanoseconds(SanitizeTimeout(timeout)), [this] { 114 return mMetalSharedEvent.get().signaledValue >= mSignalValue; 115 })) 116 { 117 *outResult = GL_TIMEOUT_EXPIRED; 118 return angle::Result::Continue; 119 } 120 121 ASSERT(mMetalSharedEvent.get().signaledValue >= mSignalValue); 122 *outResult = GL_CONDITION_SATISFIED; 123 124 return angle::Result::Continue; 125 } 126 127 angle::Result serverWait(ContextMtl *contextMtl) override 128 { 129 contextMtl->serverWaitEvent(mMetalSharedEvent, mSignalValue); 130 return angle::Result::Continue; 131 } 132 133 angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) override 134 { 135 *signaled = mMetalSharedEvent.get().signaledValue >= mSignalValue; 136 return angle::Result::Continue; 137 } 138 139 private: 140 AutoObjCPtr<id<MTLSharedEvent>> mMetalSharedEvent; 141 uint64_t mSignalValue = 0; 142 143 std::shared_ptr<std::condition_variable> mCv; 144 std::shared_ptr<std::mutex> mLock; 145}; 146#endif // ANGLE_MTL_EVENT_AVAILABLE 147 148class EventSyncImpl : public SyncImpl 149{ 150 private: 151 // MTLEvent starts with a value of 0, use 1 to signal it. 152 static constexpr uint64_t kEventSignalValue = 1; 153 154 public: 155 ~EventSyncImpl() override {} 156 157 angle::Result set(ContextMtl *contextMtl) 158 { 159 ANGLE_MTL_OBJC_SCOPE 160 { 161 mMetalEvent = contextMtl->getMetalDevice().newEvent(); 162 } 163 164 mEncodedCommandBufferSerial = contextMtl->queueEventSignal(mMetalEvent, kEventSignalValue); 165 return angle::Result::Continue; 166 } 167 168 angle::Result clientWait(ContextMtl *contextMtl, 169 bool flushCommands, 170 uint64_t timeout, 171 GLenum *outResult) override 172 { 173 DisplayMtl *display = contextMtl->getDisplay(); 174 175 if (display->cmdQueue().isSerialCompleted(mEncodedCommandBufferSerial)) 176 { 177 *outResult = GL_ALREADY_SIGNALED; 178 return angle::Result::Continue; 179 } 180 181 if (flushCommands) 182 { 183 contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled); 184 } 185 186 if (timeout == 0 || !display->cmdQueue().waitUntilSerialCompleted( 187 mEncodedCommandBufferSerial, SanitizeTimeout(timeout))) 188 { 189 *outResult = GL_TIMEOUT_EXPIRED; 190 return angle::Result::Continue; 191 } 192 193 *outResult = GL_CONDITION_SATISFIED; 194 return angle::Result::Continue; 195 } 196 197 angle::Result serverWait(ContextMtl *contextMtl) override 198 { 199 contextMtl->serverWaitEvent(mMetalEvent, kEventSignalValue); 200 return angle::Result::Continue; 201 } 202 203 angle::Result getStatus(DisplayMtl *displayMtl, bool *signaled) override 204 { 205 *signaled = displayMtl->cmdQueue().isSerialCompleted(mEncodedCommandBufferSerial); 206 return angle::Result::Continue; 207 } 208 209 private: 210 AutoObjCPtr<id<MTLEvent>> mMetalEvent; 211 uint64_t mEncodedCommandBufferSerial = 0; 212}; 213} // namespace mtl 214 215// FenceNVMtl implementation 216FenceNVMtl::FenceNVMtl() : FenceNVImpl() {} 217 218FenceNVMtl::~FenceNVMtl() {} 219 220void FenceNVMtl::onDestroy(const gl::Context *context) 221{ 222 mSync.reset(); 223} 224 225angle::Result FenceNVMtl::set(const gl::Context *context, GLenum condition) 226{ 227#if ANGLE_MTL_EVENT_AVAILABLE 228 ASSERT(condition == GL_ALL_COMPLETED_NV); 229 ContextMtl *contextMtl = mtl::GetImpl(context); 230 231 std::unique_ptr<mtl::EventSyncImpl> impl = std::make_unique<mtl::EventSyncImpl>(); 232 ANGLE_TRY(impl->set(contextMtl)); 233 mSync = std::move(impl); 234 235 return angle::Result::Continue; 236#else 237 UNREACHABLE(); 238 return angle::Result::Stop; 239#endif // ANGLE_MTL_EVENT_AVAILABLE 240} 241 242angle::Result FenceNVMtl::test(const gl::Context *context, GLboolean *outFinished) 243{ 244 ContextMtl *contextMtl = mtl::GetImpl(context); 245 246 bool signaled = false; 247 ANGLE_TRY(mSync->getStatus(contextMtl->getDisplay(), &signaled)); 248 249 *outFinished = signaled ? GL_TRUE : GL_FALSE; 250 return angle::Result::Continue; 251} 252 253angle::Result FenceNVMtl::finish(const gl::Context *context) 254{ 255 ContextMtl *contextMtl = mtl::GetImpl(context); 256 GLenum result = GL_NONE; 257 ANGLE_TRY(mSync->clientWait(contextMtl, true, mtl::kNanosecondsPerDay, &result)); 258 ASSERT(result != GL_WAIT_FAILED); 259 return angle::Result::Continue; 260} 261 262// SyncMtl implementation 263SyncMtl::SyncMtl() : SyncImpl() {} 264 265SyncMtl::~SyncMtl() {} 266 267void SyncMtl::onDestroy(const gl::Context *context) 268{ 269 mSync.reset(); 270} 271 272angle::Result SyncMtl::set(const gl::Context *context, GLenum condition, GLbitfield flags) 273{ 274#if ANGLE_MTL_EVENT_AVAILABLE 275 ASSERT(condition == GL_SYNC_GPU_COMMANDS_COMPLETE); 276 ASSERT(flags == 0); 277 278 ContextMtl *contextMtl = mtl::GetImpl(context); 279 std::unique_ptr<mtl::EventSyncImpl> impl = std::make_unique<mtl::EventSyncImpl>(); 280 ANGLE_TRY(impl->set(contextMtl)); 281 mSync = std::move(impl); 282 283 return angle::Result::Continue; 284#else 285 UNREACHABLE(); 286 return angle::Result::Stop; 287#endif // ANGLE_MTL_EVENT_AVAILABLE 288} 289 290angle::Result SyncMtl::clientWait(const gl::Context *context, 291 GLbitfield flags, 292 GLuint64 timeout, 293 GLenum *outResult) 294{ 295 ASSERT((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) == 0); 296 297 ContextMtl *contextMtl = mtl::GetImpl(context); 298 bool flush = (flags & GL_SYNC_FLUSH_COMMANDS_BIT) != 0; 299 return mSync->clientWait(contextMtl, flush, timeout, outResult); 300} 301 302angle::Result SyncMtl::serverWait(const gl::Context *context, GLbitfield flags, GLuint64 timeout) 303{ 304 ASSERT(flags == 0); 305 ASSERT(timeout == GL_TIMEOUT_IGNORED); 306 307 ContextMtl *contextMtl = mtl::GetImpl(context); 308 return mSync->serverWait(contextMtl); 309} 310 311angle::Result SyncMtl::getStatus(const gl::Context *context, GLint *outResult) 312{ 313 ContextMtl *contextMtl = mtl::GetImpl(context); 314 315 bool signaled = false; 316 ANGLE_TRY(mSync->getStatus(contextMtl->getDisplay(), &signaled)); 317 318 *outResult = signaled ? GL_SIGNALED : GL_UNSIGNALED; 319 return angle::Result::Continue; 320} 321 322// EGLSyncMtl implementation 323EGLSyncMtl::EGLSyncMtl() : EGLSyncImpl() {} 324 325EGLSyncMtl::~EGLSyncMtl() {} 326 327void EGLSyncMtl::onDestroy(const egl::Display *display) 328{ 329 mSync.reset(); 330 mSharedEvent = nil; 331} 332 333egl::Error EGLSyncMtl::initialize(const egl::Display *display, 334 const gl::Context *context, 335 EGLenum type, 336 const egl::AttributeMap &attribs) 337{ 338 ASSERT(context != nullptr); 339 340 ContextMtl *contextMtl = mtl::GetImpl(context); 341 switch (type) 342 { 343#if ANGLE_MTL_EVENT_AVAILABLE 344 case EGL_SYNC_FENCE_KHR: 345 { 346 std::unique_ptr<mtl::EventSyncImpl> impl = std::make_unique<mtl::EventSyncImpl>(); 347 if (IsError(impl->set(contextMtl))) 348 { 349 return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object"); 350 } 351 mSync = std::move(impl); 352 353 break; 354 } 355 356 case EGL_SYNC_METAL_SHARED_EVENT_ANGLE: 357 { 358 mSharedEvent.retainAssign((__bridge id<MTLSharedEvent>)reinterpret_cast<void *>( 359 attribs.get(EGL_SYNC_METAL_SHARED_EVENT_OBJECT_ANGLE, 0))); 360 if (!mSharedEvent) 361 { 362 mSharedEvent = contextMtl->getMetalDevice().newSharedEvent(); 363 } 364 365 uint64_t signalValue = 0; 366 if (attribs.contains(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_HI_ANGLE) || 367 attribs.contains(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_LO_ANGLE)) 368 { 369 signalValue = mtl::UnpackSignalValue( 370 attribs.get(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_HI_ANGLE, 0), 371 attribs.get(EGL_SYNC_METAL_SHARED_EVENT_SIGNAL_VALUE_LO_ANGLE, 0)); 372 } 373 else 374 { 375 signalValue = mSharedEvent.get().signaledValue + 1; 376 } 377 378 // If the condition is anything other than EGL_SYNC_METAL_SHARED_EVENT_SIGNALED_ANGLE, 379 // we enque the event created/provided. 380 // TODO: Could this be changed to `mSharedEvent != nullptr`? Do we ever create an event 381 // but not want to enqueue it? 382 bool enqueue = attribs.getAsInt(EGL_SYNC_CONDITION, 0) != 383 EGL_SYNC_METAL_SHARED_EVENT_SIGNALED_ANGLE; 384 385 std::unique_ptr<mtl::SharedEventSyncImpl> impl = 386 std::make_unique<mtl::SharedEventSyncImpl>(); 387 if (IsError(impl->set(contextMtl, mSharedEvent, signalValue, enqueue))) 388 { 389 return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object"); 390 } 391 mSync = std::move(impl); 392 break; 393 } 394#endif // ANGLE_MTL_EVENT_AVAILABLE 395 396 default: 397 UNREACHABLE(); 398 return egl::Error(EGL_BAD_ALLOC); 399 } 400 401 return egl::NoError(); 402} 403 404egl::Error EGLSyncMtl::clientWait(const egl::Display *display, 405 const gl::Context *context, 406 EGLint flags, 407 EGLTime timeout, 408 EGLint *outResult) 409{ 410 ASSERT((flags & ~EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) == 0); 411 412 bool flush = (flags & EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) != 0; 413 GLenum result = GL_NONE; 414 ContextMtl *contextMtl = mtl::GetImpl(context); 415 if (IsError(mSync->clientWait(contextMtl, flush, static_cast<uint64_t>(timeout), &result))) 416 { 417 return egl::Error(EGL_BAD_ALLOC); 418 } 419 420 switch (result) 421 { 422 case GL_ALREADY_SIGNALED: 423 // fall through. EGL doesn't differentiate between event being already set, or set 424 // before timeout. 425 case GL_CONDITION_SATISFIED: 426 *outResult = EGL_CONDITION_SATISFIED_KHR; 427 return egl::NoError(); 428 429 case GL_TIMEOUT_EXPIRED: 430 *outResult = EGL_TIMEOUT_EXPIRED_KHR; 431 return egl::NoError(); 432 433 default: 434 UNREACHABLE(); 435 *outResult = EGL_FALSE; 436 return egl::Error(EGL_BAD_ALLOC); 437 } 438} 439 440egl::Error EGLSyncMtl::serverWait(const egl::Display *display, 441 const gl::Context *context, 442 EGLint flags) 443{ 444 // Server wait requires a valid bound context. 445 ASSERT(context); 446 447 // No flags are currently implemented. 448 ASSERT(flags == 0); 449 450 ContextMtl *contextMtl = mtl::GetImpl(context); 451 if (IsError(mSync->serverWait(contextMtl))) 452 { 453 return egl::Error(EGL_BAD_ALLOC); 454 } 455 456 return egl::NoError(); 457} 458 459egl::Error EGLSyncMtl::getStatus(const egl::Display *display, EGLint *outStatus) 460{ 461 DisplayMtl *displayMtl = mtl::GetImpl(display); 462 bool signaled = false; 463 if (IsError(mSync->getStatus(displayMtl, &signaled))) 464 { 465 return egl::Error(EGL_BAD_ALLOC); 466 } 467 468 *outStatus = signaled ? EGL_SIGNALED_KHR : EGL_UNSIGNALED_KHR; 469 return egl::NoError(); 470} 471 472egl::Error EGLSyncMtl::copyMetalSharedEventANGLE(const egl::Display *display, void **result) const 473{ 474 ASSERT(mSharedEvent != nil); 475 476 mtl::AutoObjCPtr<id<MTLSharedEvent>> copySharedEvent = mSharedEvent; 477 *result = reinterpret_cast<void *>(copySharedEvent.leakObject()); 478 479 return egl::NoError(); 480} 481 482egl::Error EGLSyncMtl::dupNativeFenceFD(const egl::Display *display, EGLint *result) const 483{ 484 UNREACHABLE(); 485 return egl::EglBadDisplay(); 486} 487 488} // namespace rx 489