xref: /aosp_15_r20/external/angle/src/libANGLE/renderer/metal/SyncMtl.mm (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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