// // 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 # include #else # include #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(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(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(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(Context *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(Context *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(Context *context, ContextVk *contextVk, bool *signaledOut) { // 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 isAsyncCommandBufferResetAndGarbageCleanupEnabled 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 when // eglClientWaitSync() should return immediately with timeout == 0. if (renderer->isAsyncCommandBufferResetAndGarbageCleanupEnabled()) { 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) { return SyncWaitFd(mFenceFd, 0, VK_NOT_READY); } return mFence.getStatus(device); } VkResult ExternalFence::wait(VkDevice device, uint64_t timeout) const { if (mFenceFdStatus == 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(); } 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, RenderPassClosureReason::SyncObjectWithFdInit)); QueueSerial submitSerial = contextVk->getLastSubmittedQueueSerial(); // exportFd is exporting VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR type handle which // obeys copy semantics. This means that the fence must already be signaled or the work to // signal it is in the graphics pipeline at the time we export the fd. Thus we need to // call waitForQueueSerialToBeSubmittedToDevice() here. ANGLE_TRY(renderer->waitForQueueSerialToBeSubmittedToDevice(contextVk, submitSerial)); ANGLE_VK_TRY(contextVk, mExternalFence->getFenceFdStatus()); return angle::Result::Continue; } angle::Result SyncHelperNativeFence::prepareForClientWait(Context *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, RenderPassClosureReason::SyncObjectClientWait)); } *resultOut = VK_INCOMPLETE; return angle::Result::Continue; } angle::Result SyncHelperNativeFence::clientWait(Context *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 waitSemaphore(device); // Wait semaphore for next vkQueueSubmit(). // Create a Semaphore with imported fenceFd. ANGLE_VK_TRY(contextVk, waitSemaphore.get().init(device)); 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_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(Context *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(Context *context, int *fdOut) const { if (mExternalFence->getFenceFd() == kInvalidFenceFd) { return angle::Result::Stop; } *fdOut = dup(mExternalFence->getFenceFd()); 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(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(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