• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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