//
// Copyright 2016 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.
//
// SyncVk.cpp:
//    Implements the class methods for SyncVk.
//

#include "libANGLE/renderer/vulkan/SyncVk.h"

#include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/DisplayVk.h"

#if !defined(ANGLE_PLATFORM_WINDOWS)
#    include <poll.h>
#    include <unistd.h>
#else
#    include <io.h>
#endif

namespace
{
// Wait for file descriptor to be signaled
VkResult SyncWaitFd(int fd, uint64_t timeoutNs, VkResult timeoutResult = VK_TIMEOUT)
{
#if !defined(ANGLE_PLATFORM_WINDOWS)
    struct pollfd fds;
    int ret;

    // Convert nanoseconds to milliseconds
    int timeoutMs = static_cast<int>(timeoutNs / 1000000);
    // If timeoutNs was non-zero but less than one millisecond, make it a millisecond.
    if (timeoutNs > 0 && timeoutNs < 1000000)
    {
        timeoutMs = 1;
    }

    ASSERT(fd >= 0);

    fds.fd     = fd;
    fds.events = POLLIN;

    do
    {
        ret = poll(&fds, 1, timeoutMs);
        if (ret > 0)
        {
            if (fds.revents & (POLLERR | POLLNVAL))
            {
                return VK_ERROR_UNKNOWN;
            }
            return VK_SUCCESS;
        }
        else if (ret == 0)
        {
            return timeoutResult;
        }
    } while (ret == -1 && (errno == EINTR || errno == EAGAIN));

    return VK_ERROR_UNKNOWN;
#else
    UNREACHABLE();
    return VK_ERROR_UNKNOWN;
#endif
}

// Map VkResult to GLenum
void MapVkResultToGlenum(VkResult vkResult, angle::Result angleResult, void *outResult)
{
    GLenum *glEnumOut = static_cast<GLenum *>(outResult);
    ASSERT(glEnumOut);

    if (angleResult != angle::Result::Continue)
    {
        *glEnumOut = GL_WAIT_FAILED;
        return;
    }

    switch (vkResult)
    {
        case VK_EVENT_SET:
            *glEnumOut = GL_ALREADY_SIGNALED;
            break;
        case VK_SUCCESS:
            *glEnumOut = GL_CONDITION_SATISFIED;
            break;
        case VK_TIMEOUT:
            *glEnumOut = GL_TIMEOUT_EXPIRED;
            break;
        default:
            *glEnumOut = GL_WAIT_FAILED;
            break;
    }
}

// Map VkResult to EGLint
void MapVkResultToEglint(VkResult result, angle::Result angleResult, void *outResult)
{
    EGLint *eglIntOut = static_cast<EGLint *>(outResult);
    ASSERT(eglIntOut);

    if (angleResult != angle::Result::Continue)
    {
        *eglIntOut = EGL_FALSE;
        return;
    }

    switch (result)
    {
        case VK_EVENT_SET:
            // fall through.  EGL doesn't differentiate between event being already set, or set
            // before timeout.
        case VK_SUCCESS:
            *eglIntOut = EGL_CONDITION_SATISFIED_KHR;
            break;
        case VK_TIMEOUT:
            *eglIntOut = EGL_TIMEOUT_EXPIRED_KHR;
            break;
        default:
            *eglIntOut = EGL_FALSE;
            break;
    }
}

}  // anonymous namespace

namespace rx
{
namespace vk
{
SyncHelper::SyncHelper() {}

SyncHelper::~SyncHelper() {}

void SyncHelper::releaseToRenderer(Renderer *renderer) {}

angle::Result SyncHelper::initialize(ContextVk *contextVk, SyncFenceScope scope)
{
    ASSERT(!mUse.valid());
    return contextVk->onSyncObjectInit(this, scope);
}

angle::Result SyncHelper::prepareForClientWait(ErrorContext *context,
                                               ContextVk *contextVk,
                                               bool flushCommands,
                                               uint64_t timeout,
                                               VkResult *resultOut)
{
    // If the event is already set, don't wait
    bool alreadySignaled = false;
    ANGLE_TRY(getStatus(context, contextVk, &alreadySignaled));
    if (alreadySignaled)
    {
        *resultOut = VK_EVENT_SET;
        return angle::Result::Continue;
    }

    // If timeout is zero, there's no need to wait, so return timeout already.
    if (timeout == 0)
    {
        *resultOut = VK_TIMEOUT;
        return angle::Result::Continue;
    }

    // Submit commands if requested
    if (flushCommands && contextVk)
    {
        ANGLE_TRY(contextVk->flushCommandsAndEndRenderPassIfDeferredSyncInit(
            RenderPassClosureReason::SyncObjectClientWait));
    }

    *resultOut = VK_INCOMPLETE;
    return angle::Result::Continue;
}

angle::Result SyncHelper::clientWait(ErrorContext *context,
                                     ContextVk *contextVk,
                                     bool flushCommands,
                                     uint64_t timeout,
                                     MapVkResultToApiType mappingFunction,
                                     void *resultOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "SyncHelper::clientWait");

    VkResult status = VK_INCOMPLETE;
    ANGLE_TRY(prepareForClientWait(context, contextVk, flushCommands, timeout, &status));

    if (status != VK_INCOMPLETE)
    {
        mappingFunction(status, angle::Result::Continue, resultOut);
        return angle::Result::Continue;
    }

    Renderer *renderer = context->getRenderer();

    // If we need to perform a CPU wait don't set the resultOut parameter passed into the
    // method, instead set the parameter passed into the unlocked tail call.
    auto clientWaitUnlocked = [renderer, context, mappingFunction, use = mUse,
                               timeout](void *resultOut) {
        ANGLE_TRACE_EVENT0("gpu.angle", "SyncHelper::clientWait block (unlocked)");

        VkResult status = VK_INCOMPLETE;
        angle::Result angleResult =
            renderer->waitForResourceUseToFinishWithUserTimeout(context, use, timeout, &status);
        // Note: resultOut may be nullptr through the glFinishFenceNV path, which does not have a
        // return value.
        if (resultOut != nullptr)
        {
            mappingFunction(status, angleResult, resultOut);
        }
    };

    // Schedule the wait to be run at the tail of the current call.
    egl::Display::GetCurrentThreadUnlockedTailCall()->add(clientWaitUnlocked);
    return angle::Result::Continue;
}

angle::Result SyncHelper::finish(ContextVk *contextVk)
{
    GLenum result;
    return clientWait(contextVk, contextVk, true, UINT64_MAX, MapVkResultToGlenum, &result);
}

angle::Result SyncHelper::serverWait(ContextVk *contextVk)
{
    // If already signaled, no need to wait
    bool alreadySignaled = false;
    ANGLE_TRY(getStatus(contextVk, contextVk, &alreadySignaled));
    if (alreadySignaled)
    {
        return angle::Result::Continue;
    }

    // Every resource already tracks its usage and issues the appropriate barriers, so there's
    // really nothing to do here.  An execution barrier is issued to strictly satisfy what the
    // application asked for.
    vk::OutsideRenderPassCommandBuffer *commandBuffer;
    ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
    commandBuffer->pipelineBarrier(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                                   VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 0,
                                   nullptr);
    return angle::Result::Continue;
}

angle::Result SyncHelper::getStatus(ErrorContext *context, ContextVk *contextVk, bool *signaledOut)
{
    ASSERT(context);
    if (!context->getFeatures().disableSubmitCommandsOnSyncStatusCheckForTesting.enabled)
    {
        // Submit commands if it was deferred on the context that issued the sync object
        ANGLE_TRY(submitSyncIfDeferred(contextVk, RenderPassClosureReason::SyncObjectClientWait));
    }

    ASSERT(mUse.valid());
    Renderer *renderer = context->getRenderer();
    if (renderer->hasResourceUseFinished(mUse))
    {
        *signaledOut = true;
    }
    else
    {
        // Check completed commands once before returning, perhaps the serial is actually already
        // finished.
        // We don't call checkCompletedCommandsAndCleanup() to cleanup finished commands immediately
        // if isAsyncCommandBufferResetAndGarbageCleanupEnabled feature is turned off.
        // Because when that feature is turned off, vkResetCommandBuffer() is called in cleanup
        // step, and it must take the CommandPoolAccess::mCmdPoolMutex lock, see details in
        // CommandPoolAccess::collectPrimaryCommandBuffer. This means the cleanup step can
        // be blocked by command buffer recording if another thread calls
        // CommandPoolAccess::flushRenderPassCommands(), which is against EGL spec where
        // eglClientWaitSync() should return immediately with timeout == 0.
        if (renderer->getFeatures().asyncGarbageCleanup.enabled)
        {
            ANGLE_TRY(renderer->checkCompletedCommandsAndCleanup(context));
        }
        else
        {
            ANGLE_TRY(renderer->checkCompletedCommands(context));
        }

        *signaledOut = renderer->hasResourceUseFinished(mUse);
    }
    return angle::Result::Continue;
}

angle::Result SyncHelper::submitSyncIfDeferred(ContextVk *contextVk, RenderPassClosureReason reason)
{
    if (contextVk == nullptr)
    {
        return angle::Result::Continue;
    }

    if (contextVk->getRenderer()->hasResourceUseSubmitted(mUse))
    {
        return angle::Result::Continue;
    }

    // The submission of a sync object may be deferred to allow further optimizations to an open
    // render pass before a submission happens for another reason.  If the sync object is being
    // waited on by the current context, the application must have used GL_SYNC_FLUSH_COMMANDS_BIT.
    // However, when waited on by other contexts, the application must have ensured the original
    // context is flushed.  Due to deferred flushes, a glFlush is not sufficient to guarantee this.
    //
    // Deferring the submission is restricted to non-EGL sync objects, so it's sufficient to ensure
    // that the contexts in the share group issue their deferred flushes.
    for (auto context : contextVk->getShareGroup()->getContexts())
    {
        ContextVk *sharedContextVk = vk::GetImpl(context.second);
        if (sharedContextVk->hasUnsubmittedUse(mUse))
        {
            ANGLE_TRY(sharedContextVk->flushCommandsAndEndRenderPassIfDeferredSyncInit(reason));
            break;
        }
    }
    // Note mUse could still be invalid here if it is inserted on a fresh created context, i.e.,
    // fence is tracking nothing and is finished when inserted..
    ASSERT(contextVk->getRenderer()->hasResourceUseSubmitted(mUse));

    return angle::Result::Continue;
}

ExternalFence::ExternalFence()
    : mDevice(VK_NULL_HANDLE), mFenceFdStatus(VK_INCOMPLETE), mFenceFd(kInvalidFenceFd)
{}

ExternalFence::~ExternalFence()
{
    if (mDevice != VK_NULL_HANDLE)
    {
        mFence.destroy(mDevice);
    }

    if (mFenceFd != kInvalidFenceFd)
    {
        close(mFenceFd);
    }
}

VkResult ExternalFence::init(VkDevice device, const VkFenceCreateInfo &createInfo)
{
    ASSERT(device != VK_NULL_HANDLE);
    ASSERT(mFenceFdStatus == VK_INCOMPLETE && mFenceFd == kInvalidFenceFd);
    ASSERT(mDevice == VK_NULL_HANDLE);
    mDevice = device;
    return mFence.init(device, createInfo);
}

void ExternalFence::init(int fenceFd)
{
    ASSERT(fenceFd != kInvalidFenceFd);
    ASSERT(mFenceFdStatus == VK_INCOMPLETE && mFenceFd == kInvalidFenceFd);
    mFenceFdStatus = VK_SUCCESS;
    mFenceFd       = fenceFd;
}

VkResult ExternalFence::getStatus(VkDevice device) const
{
    if (mFenceFdStatus == VK_SUCCESS)
    {
        if (mFenceFd == kInvalidFenceFd)
        {
            return VK_SUCCESS;
        }
        return SyncWaitFd(mFenceFd, 0, VK_NOT_READY);
    }
    return mFence.getStatus(device);
}

VkResult ExternalFence::wait(VkDevice device, uint64_t timeout) const
{
    if (mFenceFdStatus == VK_SUCCESS)
    {
        if (mFenceFd == kInvalidFenceFd)
        {
            return VK_SUCCESS;
        }
        return SyncWaitFd(mFenceFd, timeout);
    }
    return mFence.wait(device, timeout);
}

void ExternalFence::exportFd(VkDevice device, const VkFenceGetFdInfoKHR &fenceGetFdInfo)
{
    ASSERT(mFenceFdStatus == VK_INCOMPLETE && mFenceFd == kInvalidFenceFd);
    mFenceFdStatus = mFence.exportFd(device, fenceGetFdInfo, &mFenceFd);
    ASSERT(mFenceFdStatus != VK_INCOMPLETE);
}

SyncHelperNativeFence::SyncHelperNativeFence()
{
    mExternalFence = std::make_shared<ExternalFence>();
}

SyncHelperNativeFence::~SyncHelperNativeFence() {}

void SyncHelperNativeFence::releaseToRenderer(Renderer *renderer)
{
    mExternalFence.reset();
}

angle::Result SyncHelperNativeFence::initializeWithFd(ContextVk *contextVk, int inFd)
{
    ASSERT(inFd >= kInvalidFenceFd);

    // If valid FD provided by application - import it to fence.
    if (inFd > kInvalidFenceFd)
    {
        // File descriptor ownership: EGL_ANDROID_native_fence_sync
        // Whenever a file descriptor is passed into or returned from an
        // EGL call in this extension, ownership of that file descriptor is
        // transferred. The recipient of the file descriptor must close it when it is
        // no longer needed, and the provider of the file descriptor must dup it
        // before providing it if they require continued use of the native fence.
        mExternalFence->init(inFd);
        return angle::Result::Continue;
    }

    Renderer *renderer = contextVk->getRenderer();
    VkDevice device    = renderer->getDevice();

    VkExportFenceCreateInfo exportCreateInfo = {};
    exportCreateInfo.sType                   = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO;
    exportCreateInfo.pNext                   = nullptr;
    exportCreateInfo.handleTypes             = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR;

    // Create fenceInfo base.
    VkFenceCreateInfo fenceCreateInfo = {};
    fenceCreateInfo.sType             = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenceCreateInfo.flags             = 0;
    fenceCreateInfo.pNext             = &exportCreateInfo;

    // Initialize/create a VkFence handle
    ANGLE_VK_TRY(contextVk, mExternalFence->init(device, fenceCreateInfo));

    // invalid FD provided by application - create one with fence.
    /*
      Spec: "When a fence sync object is created or when an EGL native fence sync
      object is created with the EGL_SYNC_NATIVE_FENCE_FD_ANDROID attribute set to
      EGL_NO_NATIVE_FENCE_FD_ANDROID, eglCreateSyncKHR also inserts a fence command
      into the command stream of the bound client API's current context and associates it
      with the newly created sync object.
    */
    // Flush current pending set of commands providing the fence...
    ANGLE_TRY(contextVk->flushAndSubmitCommands(nullptr, &mExternalFence,
                                                QueueSubmitReason::SyncObjectWithFdInit));

    ANGLE_VK_TRY(contextVk, mExternalFence->getFenceFdStatus());

    return angle::Result::Continue;
}

angle::Result SyncHelperNativeFence::prepareForClientWait(ErrorContext *context,
                                                          ContextVk *contextVk,
                                                          bool flushCommands,
                                                          uint64_t timeout,
                                                          VkResult *resultOut)
{
    // If already signaled, don't wait
    bool alreadySignaled = false;
    ANGLE_TRY(getStatus(context, contextVk, &alreadySignaled));
    if (alreadySignaled)
    {
        *resultOut = VK_SUCCESS;
        return angle::Result::Continue;
    }

    // If timeout is zero, there's no need to wait, so return timeout already.
    if (timeout == 0)
    {
        *resultOut = VK_TIMEOUT;
        return angle::Result::Continue;
    }

    if (flushCommands && contextVk)
    {
        ANGLE_TRY(contextVk->flushAndSubmitCommands(nullptr, nullptr,
                                                    QueueSubmitReason::SyncObjectClientWait));
    }

    *resultOut = VK_INCOMPLETE;
    return angle::Result::Continue;
}

angle::Result SyncHelperNativeFence::clientWait(ErrorContext *context,
                                                ContextVk *contextVk,
                                                bool flushCommands,
                                                uint64_t timeout,
                                                MapVkResultToApiType mappingFunction,
                                                void *resultOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "SyncHelperNativeFence::clientWait");

    VkResult status = VK_INCOMPLETE;
    ANGLE_TRY(prepareForClientWait(context, contextVk, flushCommands, timeout, &status));

    if (status != VK_INCOMPLETE)
    {
        mappingFunction(status, angle::Result::Continue, resultOut);
        return angle::Result::Continue;
    }

    Renderer *renderer = context->getRenderer();

    auto clientWaitUnlocked = [device = renderer->getDevice(), fence = mExternalFence,
                               mappingFunction, timeout](void *resultOut) {
        ANGLE_TRACE_EVENT0("gpu.angle", "SyncHelperNativeFence::clientWait block (unlocked)");
        ASSERT(resultOut);

        VkResult status = fence->wait(device, timeout);
        mappingFunction(status, angle::Result::Continue, resultOut);
    };

    egl::Display::GetCurrentThreadUnlockedTailCall()->add(clientWaitUnlocked);
    return angle::Result::Continue;
}

angle::Result SyncHelperNativeFence::serverWait(ContextVk *contextVk)
{
    Renderer *renderer = contextVk->getRenderer();

    // If already signaled, no need to wait
    bool alreadySignaled = false;
    ANGLE_TRY(getStatus(contextVk, contextVk, &alreadySignaled));
    if (alreadySignaled)
    {
        return angle::Result::Continue;
    }

    VkDevice device = renderer->getDevice();
    DeviceScoped<Semaphore> waitSemaphore(device);
    // Wait semaphore for next vkQueueSubmit().
    // Create a Semaphore with imported fenceFd.
    ANGLE_VK_TRY(contextVk, waitSemaphore.get().init(device, VK_SEMAPHORE_TYPE_BINARY));

    VkImportSemaphoreFdInfoKHR importFdInfo = {};
    importFdInfo.sType                      = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
    importFdInfo.semaphore                  = waitSemaphore.get().getHandle();
    importFdInfo.flags                      = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR;
    importFdInfo.handleType                 = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR;
    importFdInfo.fd                         = dup(mExternalFence->getFenceFd());
    ANGLE_VK_CHECK(contextVk, importFdInfo.fd >= 0, VK_ERROR_OUT_OF_HOST_MEMORY);
    ANGLE_VK_TRY(contextVk, waitSemaphore.get().importFd(device, importFdInfo));

    // Add semaphore to next submit job.
    contextVk->addWaitSemaphore(waitSemaphore.get().getHandle(),
                                VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
    contextVk->addGarbage(&waitSemaphore.get());  // This releases the handle.
    return angle::Result::Continue;
}

angle::Result SyncHelperNativeFence::getStatus(ErrorContext *context,
                                               ContextVk *contextVk,
                                               bool *signaledOut)
{
    VkResult result = mExternalFence->getStatus(context->getDevice());
    if (result != VK_NOT_READY)
    {
        ANGLE_VK_TRY(context, result);
    }
    *signaledOut = (result == VK_SUCCESS);
    return angle::Result::Continue;
}

angle::Result SyncHelperNativeFence::dupNativeFenceFD(ErrorContext *context, int *fdOut) const
{
    if (mExternalFence->getFenceFd() == kInvalidFenceFd)
    {
        return angle::Result::Stop;
    }

    *fdOut = dup(mExternalFence->getFenceFd());
    ANGLE_VK_CHECK(context, *fdOut >= 0, VK_ERROR_OUT_OF_HOST_MEMORY);

    return angle::Result::Continue;
}

}  // namespace vk

SyncVk::SyncVk() : SyncImpl() {}

SyncVk::~SyncVk() {}

void SyncVk::onDestroy(const gl::Context *context)
{
    mSyncHelper.releaseToRenderer(vk::GetImpl(context)->getRenderer());
}

angle::Result SyncVk::set(const gl::Context *context, GLenum condition, GLbitfield flags)
{
    ASSERT(condition == GL_SYNC_GPU_COMMANDS_COMPLETE);
    ASSERT(flags == 0);

    return mSyncHelper.initialize(vk::GetImpl(context), SyncFenceScope::CurrentContextToShareGroup);
}

angle::Result SyncVk::clientWait(const gl::Context *context,
                                 GLbitfield flags,
                                 GLuint64 timeout,
                                 GLenum *outResult)
{
    ContextVk *contextVk = vk::GetImpl(context);

    ASSERT((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) == 0);

    bool flush = (flags & GL_SYNC_FLUSH_COMMANDS_BIT) != 0;

    return mSyncHelper.clientWait(contextVk, contextVk, flush, static_cast<uint64_t>(timeout),
                                  MapVkResultToGlenum, outResult);
}

angle::Result SyncVk::serverWait(const gl::Context *context, GLbitfield flags, GLuint64 timeout)
{
    ASSERT(flags == 0);
    ASSERT(timeout == GL_TIMEOUT_IGNORED);

    ContextVk *contextVk = vk::GetImpl(context);
    return mSyncHelper.serverWait(contextVk);
}

angle::Result SyncVk::getStatus(const gl::Context *context, GLint *outResult)
{
    ContextVk *contextVk = vk::GetImpl(context);
    bool signaled        = false;
    ANGLE_TRY(mSyncHelper.getStatus(contextVk, contextVk, &signaled));

    *outResult = signaled ? GL_SIGNALED : GL_UNSIGNALED;
    return angle::Result::Continue;
}

EGLSyncVk::EGLSyncVk() : EGLSyncImpl(), mSyncHelper(nullptr) {}

EGLSyncVk::~EGLSyncVk() {}

void EGLSyncVk::onDestroy(const egl::Display *display)
{
    mSyncHelper->releaseToRenderer(vk::GetImpl(display)->getRenderer());
}

egl::Error EGLSyncVk::initialize(const egl::Display *display,
                                 const gl::Context *context,
                                 EGLenum type,
                                 const egl::AttributeMap &attribs)
{
    ASSERT(context != nullptr);

    switch (type)
    {
        case EGL_SYNC_FENCE_KHR:
        case EGL_SYNC_GLOBAL_FENCE_ANGLE:
        {
            vk::SyncHelper *syncHelper = new vk::SyncHelper();
            mSyncHelper.reset(syncHelper);
            const SyncFenceScope scope = type == EGL_SYNC_GLOBAL_FENCE_ANGLE
                                             ? SyncFenceScope::AllContextsToAllContexts
                                             : SyncFenceScope::CurrentContextToAllContexts;
            if (syncHelper->initialize(vk::GetImpl(context), scope) == angle::Result::Stop)
            {
                return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object");
            }
            return egl::NoError();
        }
        case EGL_SYNC_NATIVE_FENCE_ANDROID:
        {
            vk::SyncHelperNativeFence *syncHelper = new vk::SyncHelperNativeFence();
            mSyncHelper.reset(syncHelper);
            EGLint nativeFenceFd =
                attribs.getAsInt(EGL_SYNC_NATIVE_FENCE_FD_ANDROID, EGL_NO_NATIVE_FENCE_FD_ANDROID);
            return angle::ToEGL(syncHelper->initializeWithFd(vk::GetImpl(context), nativeFenceFd),
                                EGL_BAD_ALLOC);
        }
        default:
            UNREACHABLE();
            return egl::Error(EGL_BAD_ALLOC);
    }
}

egl::Error EGLSyncVk::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;

    ContextVk *contextVk = context != nullptr && flush ? vk::GetImpl(context) : nullptr;
    if (mSyncHelper->clientWait(vk::GetImpl(display), contextVk, flush,
                                static_cast<uint64_t>(timeout), MapVkResultToEglint,
                                outResult) == angle::Result::Stop)
    {
        return egl::Error(EGL_BAD_ALLOC);
    }

    return egl::NoError();
}

egl::Error EGLSyncVk::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);

    ContextVk *contextVk = vk::GetImpl(context);
    return angle::ToEGL(mSyncHelper->serverWait(contextVk), EGL_BAD_ALLOC);
}

egl::Error EGLSyncVk::getStatus(const egl::Display *display, EGLint *outStatus)
{
    bool signaled = false;
    if (mSyncHelper->getStatus(vk::GetImpl(display), nullptr, &signaled) == angle::Result::Stop)
    {
        return egl::Error(EGL_BAD_ALLOC);
    }

    *outStatus = signaled ? EGL_SIGNALED_KHR : EGL_UNSIGNALED_KHR;
    return egl::NoError();
}

egl::Error EGLSyncVk::dupNativeFenceFD(const egl::Display *display, EGLint *fdOut) const
{
    return angle::ToEGL(mSyncHelper->dupNativeFenceFD(vk::GetImpl(display), fdOut),
                        EGL_BAD_PARAMETER);
}

}  // namespace rx
