vulkan: Support VK_KHR_external_memory_fd on OS X.

This CL adds a Posix-based implementation for the VK_KHR_external_memory_fd Vulkan extension, using the SysV shm_open() / shm_unlink() API to create the shared memory region. The Linux backend still uses memfd-based regions which are easier to create, and because Android does not provide shm_open() intentionally [1]. [1] https://android.googlesource.com/platform/ndk/+/4e159d95ebf23b5f72bb707b0cb1518ef96b3d03/docs/system/libc/SYSV-IPC.TXT Bug: b/140419396 Tests: dEQP-VK.* Change-Id: Ibbb23c3af59e81f76e41a0e71281f6d1a8b07c01 Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/41408Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com> Tested-by: 's avatarYilong Li <liyl@google.com> Commit-Queue: Nicolas Capens <nicolascapens@google.com> Kokoro-Result: kokoro <noreply+kokoro@google.com>
parent 8da0f82b
......@@ -101,6 +101,10 @@ swiftshader_source_set("swiftshader_libvulkan_headers") {
"VkDeviceMemoryExternalLinux.hpp",
"VkSemaphoreExternalLinux.hpp",
]
} else if (is_mac) {
sources += [
"VkDeviceMemoryExternalMac.hpp",
]
} else if (is_fuchsia) {
sources += [ "VkSemaphoreExternalFuchsia.hpp" ]
}
......
......@@ -92,6 +92,9 @@ constexpr int SUBPIXEL_PRECISION_MASK = 0xFFFFFFFF >> (32 - SUBPIXEL_PRECISION_B
#elif defined(__ANDROID__)
# define SWIFTSHADER_EXTERNAL_SEMAPHORE_OPAQUE_FD 1
#endif
#if defined(__APPLE__)
# define SWIFTSHADER_EXTERNAL_MEMORY_OPAQUE_FD 1
#endif
constexpr VkDeviceSize MAX_MEMORY_ALLOCATION_SIZE = 0x40000000ull; // 0x40000000 = 1 GiB
......
......@@ -17,6 +17,7 @@
#include "VkDevice.hpp"
#include "VkDeviceMemoryExternalBase.hpp"
#include "VkImage.hpp"
#include "VkStringify.hpp"
#include "VkConfig.hpp"
......@@ -182,7 +183,65 @@ private:
};
#if SWIFTSHADER_EXTERNAL_MEMORY_OPAQUE_FD
# if defined(__linux__) && !defined(__ANDROID__)
// Helper struct to parse the VkMemoryAllocateInfo.pNext chain and
// extract relevant information related to the handle type supported
// by this DeviceMemory;:ExternalBase subclass.
struct OpaqueFdAllocateInfo
{
bool importFd = false;
bool exportFd = false;
int fd = -1;
OpaqueFdAllocateInfo() = default;
// Parse the VkMemoryAllocateInfo.pNext chain to initialize an OpaqueFdAllocateInfo.
OpaqueFdAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
{
const auto *createInfo = reinterpret_cast<const VkBaseInStructure *>(pAllocateInfo->pNext);
while(createInfo)
{
switch(createInfo->sType)
{
case VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR:
{
const auto *importInfo = reinterpret_cast<const VkImportMemoryFdInfoKHR *>(createInfo);
if(importInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
{
UNSUPPORTED("VkImportMemoryFdInfoKHR::handleType %d", int(importInfo->handleType));
}
importFd = true;
fd = importInfo->fd;
}
break;
case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO:
{
const auto *exportInfo = reinterpret_cast<const VkExportMemoryAllocateInfo *>(createInfo);
if(exportInfo->handleTypes != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
{
UNSUPPORTED("VkExportMemoryAllocateInfo::handleTypes %d", int(exportInfo->handleTypes));
}
exportFd = true;
}
break;
case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO:
// This can safely be ignored, as the Vulkan spec mentions:
// "If the pNext chain includes a VkMemoryDedicatedAllocateInfo structure, then that structure
// includes a handle of the sole buffer or image resource that the memory *can* be bound to."
break;
default:
WARN("VkMemoryAllocateInfo->pNext sType = %s", vk::Stringify(createInfo->sType).c_str());
}
createInfo = createInfo->pNext;
}
}
};
# if defined(__APPLE__)
# include "VkDeviceMemoryExternalMac.hpp"
# elif defined(__linux__) && !defined(__ANDROID__)
# include "VkDeviceMemoryExternalLinux.hpp"
# else
# error "Missing VK_KHR_external_memory_fd implementation for this platform!"
......
......@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "VkStringify.hpp"
#include "System/Debug.hpp"
#include "System/Linux/MemFd.hpp"
......@@ -24,67 +22,11 @@
class OpaqueFdExternalMemory : public vk::DeviceMemory::ExternalBase
{
public:
// Helper struct to parse the VkMemoryAllocateInfo.pNext chain and
// extract relevant information related to the handle type supported
// by this DeviceMemory;:ExternalBase subclass.
struct AllocateInfo
{
bool importFd = false;
bool exportFd = false;
int fd = -1;
AllocateInfo() = default;
// Parse the VkMemoryAllocateInfo.pNext chain to initialize an AllocateInfo.
AllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
{
const auto *createInfo = reinterpret_cast<const VkBaseInStructure *>(pAllocateInfo->pNext);
while(createInfo)
{
switch(createInfo->sType)
{
case VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR:
{
const auto *importInfo = reinterpret_cast<const VkImportMemoryFdInfoKHR *>(createInfo);
if(importInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
{
UNSUPPORTED("VkImportMemoryFdInfoKHR::handleType %d", int(importInfo->handleType));
}
importFd = true;
fd = importInfo->fd;
}
break;
case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO:
{
const auto *exportInfo = reinterpret_cast<const VkExportMemoryAllocateInfo *>(createInfo);
if(exportInfo->handleTypes != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
{
UNSUPPORTED("VkExportMemoryAllocateInfo::handleTypes %d", int(exportInfo->handleTypes));
}
exportFd = true;
}
break;
case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO:
// This can safely be ignored, as the Vulkan spec mentions:
// "If the pNext chain includes a VkMemoryDedicatedAllocateInfo structure, then that structure
// includes a handle of the sole buffer or image resource that the memory *can* be bound to."
break;
default:
WARN("VkMemoryAllocateInfo->pNext sType = %s", vk::Stringify(createInfo->sType).c_str());
}
createInfo = createInfo->pNext;
}
}
};
static const VkExternalMemoryHandleTypeFlagBits typeFlagBit = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
static bool SupportsAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
{
AllocateInfo info(pAllocateInfo);
OpaqueFdAllocateInfo info(pAllocateInfo);
return info.importFd || info.exportFd;
}
......@@ -152,5 +94,5 @@ public:
private:
LinuxMemFd memfd;
AllocateInfo allocateInfo;
OpaqueFdAllocateInfo allocateInfo;
};
// 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.
#include "System/Debug.hpp"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#ifndef __APPLE__
# error "This file is for macOS only!"
#endif // __APPLE__
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12
# include <mach/mach_time.h>
#endif // __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12
namespace {
struct timespec GetTime()
{
struct timespec tv;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12
clock_gettime(CLOCK_REALTIME, &tv);
#else
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
uint64_t time;
time = mach_absolute_time();
double convert_ratio = (double)timebase.numer / (double)timebase.denom;
uint64_t secs = (uint64_t)((double)time * convert_ratio / 1e-9);
uint64_t usecs = (uint64_t)((double)time * convert_ratio - secs * 1e9);
tv.tv_sec = secs;
tv.tv_nsec = usecs;
#endif
return tv;
}
} // namespace
// An implementation of OpaqueFdExternalMemory that relies on shm_open().
// Useful on OS X which do not have Linux memfd regions.
class OpaqueFdExternalMemory : public vk::DeviceMemory::ExternalBase
{
public:
static const VkExternalMemoryHandleTypeFlagBits typeFlagBit = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
static bool SupportsAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
{
OpaqueFdAllocateInfo info(pAllocateInfo);
return info.importFd || info.exportFd;
}
explicit OpaqueFdExternalMemory(const VkMemoryAllocateInfo *pAllocateInfo)
: allocateInfo(pAllocateInfo)
{
}
~OpaqueFdExternalMemory()
{
if(shm_fd_ >= 0)
{
::close(shm_fd_);
shm_fd_ = -1;
}
}
VkResult allocate(size_t size, void **pBuffer) override
{
if(allocateInfo.importFd)
{
shm_fd_ = allocateInfo.fd;
if(shm_fd_ < 0)
{
return VK_ERROR_INVALID_EXTERNAL_HANDLE;
}
}
else
{
ASSERT(allocateInfo.exportFd);
// Create shared memory region with shm_open() and a randomly-generated region name.
static const char kPrefix[] = "/SwiftShader-";
const size_t kPrefixSize = sizeof(kPrefix) - 1;
const size_t kRandomSize = 8;
char name[kPrefixSize + kRandomSize + 1u];
memcpy(name, kPrefix, kPrefixSize);
int fd = -1;
for(int tries = 0; tries < 6; ++tries)
{
struct timespec tv = GetTime();
uint64_t r = (uint64_t)tv.tv_sec + (uint64_t)tv.tv_nsec;
for(size_t pos = 0; pos < kRandomSize; ++pos, r /= 8)
{
name[kPrefixSize + pos] = '0' + (r % 8);
}
name[kPrefixSize + kRandomSize] = '\0';
fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
if(fd >= 0)
break;
if(errno != EEXIST)
{
TRACE("shm_open() failed with: %s", strerror(errno));
break;
}
}
// Unlink the name since it's not needed anymore.
if(fd >= 0)
{
if(shm_unlink(name) == -1)
{
TRACE("shm_unlink() failed with: %s", strerror(errno));
close(fd);
fd = -1;
}
}
// Ensure there is enough space.
if(fd >= 0 && size > 0)
{
if(::ftruncate(fd, size) < 0)
{
TRACE("ftruncate() failed with: %s", strerror(errno));
close(fd);
fd = -1;
}
}
if(fd < 0)
{
TRACE("Could not allocate shared memory region");
return VK_ERROR_OUT_OF_DEVICE_MEMORY;
}
shm_fd_ = fd;
}
void *addr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
shm_fd_, 0);
if(addr == MAP_FAILED)
{
return VK_ERROR_MEMORY_MAP_FAILED;
}
*pBuffer = addr;
return VK_SUCCESS;
}
void deallocate(void *buffer, size_t size) override
{
::munmap(buffer, size);
}
VkExternalMemoryHandleTypeFlagBits getFlagBit() const override
{
return typeFlagBit;
}
VkResult exportFd(int *pFd) const override
{
int fd = dup(shm_fd_);
if(fd < 0)
{
return VK_ERROR_INVALID_EXTERNAL_HANDLE;
}
// Set the clo-on-exec flag.
int flags = ::fcntl(fd, F_GETFD);
::fcntl(fd, F_SETFL, flags | FD_CLOEXEC);
*pFd = fd;
return VK_SUCCESS;
}
private:
int shm_fd_ = -1;
OpaqueFdAllocateInfo allocateInfo;
};
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