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