Commit 7c4d0a0f by David 'Digit' Turner Committed by David Turner

[vulkan] Implement Linux-based external semaphore

Add external semaphore support for Linux and Android based on a process-shared pthread mutex + condition variable stored in a shared memory region backed by memfd_create(). This takes care of waiting for external semaphores in a background thread when invoked from a Yarn fiber. Test: dEQP-VK.api.external.semaphore.opaque_fd* Bug: b/140421726 Change-Id: Ifa74c807d3e33914e5a37dd96650c312246c8e4f Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/35939Reviewed-by: 's avatarBen Clayton <bclayton@google.com> Reviewed-by: 's avatarChris Forbes <chrisforbes@google.com> Tested-by: 's avatarDavid Turner <digit@google.com> Kokoro-Presubmit: David Turner <digit@google.com> Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
parent fb632b91
......@@ -83,6 +83,15 @@ swiftshader_source_set("swiftshader_libvulkan_headers") {
"VkShaderModule.hpp",
"VulkanPlatform.h",
]
if (is_linux || is_android) {
sources += [
"VkSemaphoreExternalLinux.hpp",
]
} else {
sources += [
"VkSemaphoreExternalNone.hpp",
]
}
}
swiftshader_shared_library("swiftshader_libvulkan") {
......
......@@ -84,4 +84,8 @@ constexpr int SUBPIXEL_PRECISION_MASK = 0xFFFFFFFF >> (32 - SUBPIXEL_PRECISION_B
}
#if VK_USE_PLATFORM_XLIB_KHR || VK_USE_PLATFORM_ANDROID_KHR
#define SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD 1
#endif
#endif // VK_CONFIG_HPP_
......@@ -334,6 +334,17 @@ static const std::vector<std::pair<const char*, std::unordered_map<std::string,
}
},
#endif
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
// VK_KHR_external_semaphore_fd
{
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
{
MAKE_VULKAN_DEVICE_ENTRY(vkGetSemaphoreFdKHR),
MAKE_VULKAN_DEVICE_ENTRY(vkImportSemaphoreFdKHR),
}
},
#endif
};
#undef MAKE_VULKAN_DEVICE_ENTRY
......@@ -443,4 +454,4 @@ extern "C" hwvulkan_module_t HAL_MODULE_INFO_SYM =
}
};
#endif
\ No newline at end of file
#endif
......@@ -357,6 +357,15 @@ void PhysicalDevice::getProperties(const VkPhysicalDeviceExternalFenceInfo* pExt
void PhysicalDevice::getProperties(const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties) const
{
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
if (pExternalSemaphoreInfo->handleType == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)
{
pExternalSemaphoreProperties->compatibleHandleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
pExternalSemaphoreProperties->exportFromImportedHandleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
pExternalSemaphoreProperties->externalSemaphoreFeatures = VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT;
return;
}
#endif
pExternalSemaphoreProperties->compatibleHandleTypes = 0;
pExternalSemaphoreProperties->exportFromImportedHandleTypes = 0;
pExternalSemaphoreProperties->externalSemaphoreFeatures = 0;
......
......@@ -14,8 +14,21 @@
#include "VkSemaphore.hpp"
#include "VkConfig.h"
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
#include "VkSemaphoreExternalLinux.hpp"
#else
#include "VkSemaphoreExternalNone.hpp"
#endif
#include "marl/blockingcall.h"
#include "marl/conditionvariable.h"
#include <functional>
#include <memory>
#include <mutex>
#include <utility>
namespace vk
{
......@@ -24,17 +37,115 @@ namespace vk
class Semaphore::Impl
{
public:
Impl() = default;
// Create a new instance. The external instance will be allocated only
// the pCreateInfo->pNext chain indicates it needs to be exported.
Impl(const VkSemaphoreCreateInfo* pCreateInfo) {
bool exportSemaphore = false;
for (const auto* nextInfo = reinterpret_cast<const VkBaseInStructure*>(pCreateInfo->pNext);
nextInfo != nullptr; nextInfo = nextInfo->pNext)
{
if (nextInfo->sType == VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO)
{
const auto* exportInfo = reinterpret_cast<const VkExportSemaphoreCreateInfo *>(nextInfo);
if (exportInfo->handleTypes != External::kExternalSemaphoreHandleType)
{
UNIMPLEMENTED("exportInfo->handleTypes");
}
exportSemaphore = true;
break;
}
}
if (exportSemaphore)
{
allocateExternalNoInit();
external->init();
}
}
~Impl() {
deallocateExternal();
}
// Deallocate the External semaphore if any.
void deallocateExternal()
{
if (external)
{
external->~External();
external = nullptr;
}
}
// Allocate the external semaphore.
// Note that this does not allocate the internal resource, which must be
// performed by calling external->init(), or importing one using
// a platform-specific external->importXXX(...) method.
void allocateExternalNoInit()
{
external = new (externalStorage) External();
}
void wait()
{
if (external)
{
if (!external->tryWait())
{
// Dispatch the external wait to a background thread.
// Even if this creates a new thread on each
// call, it is assumed that this is negligible
// compared with the actual semaphore wait()
// operation.
marl::blocking_call([this](){
external->wait();
});
}
// If the import was temporary, reset the semaphore to its
// permanent state by getting rid of |external|.
// See "6.4.5. Importing Semaphore Payloads" in Vulkan 1.1 spec.
if (temporaryImport)
{
deallocateExternal();
temporaryImport = false;
}
}
else
{
waitInternal();
}
}
void signal()
{
if (external)
{
// Assumes that signalling an external semaphore is non-blocking,
// so it can be performed directly either from a fiber or thread.
external->signal();
}
else
{
signalInternal();
}
}
private:
// Necessary to make ::importXXX() and ::exportXXX() simpler.
friend Semaphore;
void waitInternal()
{
// Wait on the marl condition variable only.
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [this]{ return this->signaled; });
signaled = false; // Vulkan requires resetting after waiting.
}
void signal()
void signalInternal()
{
// Signal the marl condition variable only.
std::unique_lock<std::mutex> lock(mutex);
if (!signaled)
{
......@@ -43,15 +154,23 @@ public:
}
}
private:
// Implementation of a non-external semaphore based on Marl.
std::mutex mutex;
marl::ConditionVariable condition;
bool signaled = false;
// Optional external semaphore data might be referenced and stored here.
External* external = nullptr;
// Set to true if |external| comes from a temporary import.
bool temporaryImport = false;
alignas(External) char externalStorage[sizeof(External)];
};
Semaphore::Semaphore(const VkSemaphoreCreateInfo* pCreateInfo, void* mem)
{
impl = new (mem) Impl();
impl = new (mem) Impl(pCreateInfo);
}
void Semaphore::destroy(const VkAllocationCallbacks* pAllocator)
......@@ -75,4 +194,36 @@ void Semaphore::signal()
impl->signal();
}
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
VkResult Semaphore::importFd(int fd, bool temporaryImport)
{
std::unique_lock<std::mutex> lock(impl->mutex);
if (!impl->external)
{
impl->allocateExternalNoInit();
}
VkResult result = impl->external->importFd(fd);
if (result != VK_SUCCESS)
{
impl->deallocateExternal();
}
else
{
impl->temporaryImport = temporaryImport;
}
return result;
}
VkResult Semaphore::exportFd(int* pFd) const
{
std::unique_lock<std::mutex> lock(impl->mutex);
if (!impl->external)
{
TRACE("Cannot export non-external semaphore");
return VK_ERROR_INVALID_EXTERNAL_HANDLE;
}
return impl->external->exportFd(pFd);
}
#endif // SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
} // namespace vk
......@@ -15,6 +15,7 @@
#ifndef VK_SEMAPHORE_HPP_
#define VK_SEMAPHORE_HPP_
#include "VkConfig.h"
#include "VkObject.hpp"
namespace vk
......@@ -38,7 +39,13 @@ public:
void signal();
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
VkResult importFd(int fd, bool temporaryImport);
VkResult exportFd(int* pFd) const;
#endif
private:
class External;
class Impl;
Impl* impl = nullptr;
};
......
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef VK_SEMAPHORE_EXTERNAL_LINUX_H_
#define VK_SEMAPHORE_EXTERNAL_LINUX_H_
#include "System/Linux/MemFd.hpp"
#include "System/Memory.hpp"
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <sys/mman.h>
// An external semaphore implementation for Linux, that uses memfd-backed
// shared memory regions as the underlying implementation. The region contains
// a single SharedSemaphore instance, which is a reference-counted semaphore
// implementation based on a pthread process-shared mutex + condition variable
// pair.
//
// This implementation works on any Linux system with at least kernel 3.17
// (which should be sufficient for any not-so-recent Android system) and doesn't
// require special libraries installed on the system.
//
// NOTE: This is not interoperable with other Linux ICDs that use Linux kernel
// sync file objects (which correspond to
// VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) instead.
//
namespace linux
{
// A process-shared semaphore implementation that can be stored in
// a process-shared memory region. It also includes a reference count to
// ensure it is only destroyed when the last reference to it is dropped.
class SharedSemaphore
{
public:
SharedSemaphore()
{
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&mutex, &mattr);
pthread_mutexattr_destroy(&mattr);
pthread_condattr_t cattr;
pthread_condattr_init(&cattr);
pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&cond, &cattr);
pthread_condattr_destroy(&cattr);
}
~SharedSemaphore()
{
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
}
// Increment reference count.
void addRef()
{
pthread_mutex_lock(&mutex);
ref_count++;
pthread_mutex_unlock(&mutex);
}
// Decrement reference count and returns true iff it reaches 0.
bool deref()
{
pthread_mutex_lock(&mutex);
bool result = (--ref_count == 0);
pthread_mutex_unlock(&mutex);
return result;
}
void wait()
{
pthread_mutex_lock(&mutex);
while (!signaled)
{
pthread_cond_wait(&cond, &mutex);
}
// From Vulkan 1.1.119 spec, section 6.4.2:
// Unlike fences or events, the act of waiting for a semaphore also
// unsignals that semaphore.
signaled = false;
pthread_mutex_unlock(&mutex);
}
// Just like wait() but never blocks. Returns true if the semaphore
// was signaled (and reset by the function), or false otherwise.
// Used to avoid using a background thread for waiting in the case
// where the semaphore is already signaled.
bool tryWait()
{
pthread_mutex_lock(&mutex);
bool result = signaled;
if (result)
{
signaled = false;
}
pthread_mutex_unlock(&mutex);
return result;
}
void signal()
{
pthread_mutex_lock(&mutex);
signaled = true;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
private:
pthread_mutex_t mutex;
pthread_cond_t cond;
int ref_count = 1;
bool signaled = false;
};
} // namespace linux
namespace vk
{
class Semaphore::External {
public:
// The type of external semaphore handle types supported by this implementation.
static const VkExternalSemaphoreHandleTypeFlags kExternalSemaphoreHandleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
// Default constructor. Note that one should call either init() or
// importFd() before any call to wait() or signal().
External() = default;
~External() { close(); }
// Initialize instance by creating a new shared memory region.
void init()
{
// Allocate or import the region's file descriptor.
const size_t size = sw::memoryPageSize();
// To be exportable, the PosixSemaphore must be stored in a shared
// memory region.
static int counter = 0;
char name[40];
snprintf(name, sizeof(name), "SwiftShader.Semaphore.%d", ++counter);
if (!memfd.allocate(name, size))
{
ABORT("memfd.allocate() returned %s", strerror(errno));
}
mapRegion(size, true);
}
// Import an existing semaphore through its file descriptor.
VkResult importFd(int fd)
{
close();
memfd.importFd(fd);
mapRegion(sw::memoryPageSize(), false);
return VK_SUCCESS;
}
// Export the current semaphore as a duplicated file descriptor to the same
// region. This can be consumed by importFd() running in a different
// process.
VkResult exportFd(int* pFd) const
{
int fd = memfd.exportFd();
if (fd < 0)
{
return VK_ERROR_INVALID_EXTERNAL_HANDLE;
}
*pFd = fd;
return VK_SUCCESS;
}
void wait()
{
semaphore->wait();
}
bool tryWait()
{
return semaphore->tryWait();
}
void signal()
{
semaphore->signal();
}
private:
// Unmap the semaphore if needed and close its file descriptor.
void close()
{
if (semaphore)
{
if (semaphore->deref())
{
semaphore->~SharedSemaphore();
}
memfd.unmap(semaphore, sw::memoryPageSize());
memfd.close();
semaphore = nullptr;
}
}
// Remap the shared region and setup the semaphore or increment its reference count.
void mapRegion(size_t size, bool needInitialization)
{
// Map the region into memory and point the semaphore to it.
void* addr = memfd.mapReadWrite(0, size);
if (!addr)
{
ABORT("mmap() failed: %s", strerror(errno));
}
semaphore = reinterpret_cast<linux::SharedSemaphore *>(addr);
if (needInitialization)
{
new (semaphore) linux::SharedSemaphore();
}
else
{
semaphore->addRef();
}
}
linux::MemFd memfd;
linux::SharedSemaphore* semaphore = nullptr;
};
} // namespace vk
#endif // VK_SEMAPHORE_EXTERNAL_LINUX_H_
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef VK_SEMAPHORE_EXTERNAL_NONE_H_
#define VK_SEMAPHORE_EXTERNAL_NONE_H_
namespace vk
{
// Empty external sempahore implementation.
class Semaphore::External {
public:
// The type of external semaphore handle types supported by this implementation.
static const VkExternalSemaphoreHandleTypeFlags kExternalSemaphoreHandleType = 0;
void init() {}
void wait() {}
bool tryWait() { return true; }
void signal() {}
private:
int dummy;
};
} // namespace vk
#endif // VK_SEMAPHORE_EXTERNAL_NONE_H_
......@@ -228,6 +228,9 @@ static const VkExtensionProperties deviceExtensionProperties[] =
// (from KHR_swapchain v70) to vkBindImageMemory2.
{ VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME, 7 },
#endif
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
{ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_SPEC_VERSION },
#endif
};
VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance)
......@@ -945,9 +948,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateSemaphore(VkDevice device, const VkSemaph
TRACE("(VkDevice device = %p, const VkSemaphoreCreateInfo* pCreateInfo = %p, const VkAllocationCallbacks* pAllocator = %p, VkSemaphore* pSemaphore = %p)",
device, pCreateInfo, pAllocator, pSemaphore);
if(pCreateInfo->pNext || pCreateInfo->flags)
if(pCreateInfo->flags)
{
UNIMPLEMENTED("pCreateInfo->pNext || pCreateInfo->flags");
UNIMPLEMENTED("pCreateInfo->flags");
}
return vk::Semaphore::Create(pAllocator, pCreateInfo, pSemaphore);
......@@ -961,6 +964,35 @@ VKAPI_ATTR void VKAPI_CALL vkDestroySemaphore(VkDevice device, VkSemaphore semap
vk::destroy(semaphore, pAllocator);
}
#if SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreFdKHR(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd)
{
TRACE("(VkDevice device = %p, const VkSemaphoreGetFdInfoKHR* pGetFdInfo = %p, int* pFd = %p)",
device, static_cast<const void*>(pGetFdInfo), static_cast<void*>(pFd));
if (pGetFdInfo->handleType != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)
{
UNIMPLEMENTED("pGetFdInfo->handleType");
}
return vk::Cast(pGetFdInfo->semaphore)->exportFd(pFd);
}
VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreFdKHR(VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreInfo)
{
TRACE("(VkDevice device = %p, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreInfo = %p",
device, static_cast<const void*>(pImportSemaphoreInfo));
if (pImportSemaphoreInfo->handleType != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)
{
UNIMPLEMENTED("pImportSemaphoreInfo->handleType");
}
bool temporaryImport = (pImportSemaphoreInfo->flags & VK_SEMAPHORE_IMPORT_TEMPORARY_BIT) != 0;
return vk::Cast(pImportSemaphoreInfo->semaphore)->importFd(pImportSemaphoreInfo->fd, temporaryImport);
}
#endif // SWIFTSHADER_EXTERNAL_SEMAPHORE_LINUX_MEMFD
VKAPI_ATTR VkResult VKAPI_CALL vkCreateEvent(VkDevice device, const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent)
{
TRACE("(VkDevice device = %p, const VkEventCreateInfo* pCreateInfo = %p, const VkAllocationCallbacks* pAllocator = %p, VkEvent* pEvent = %p)",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment