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/+/41408
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Yilong Li <liyl@google.com>
Commit-Queue: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Vulkan/BUILD.gn b/src/Vulkan/BUILD.gn
index 880ba73..8ba0b9b 100644
--- a/src/Vulkan/BUILD.gn
+++ b/src/Vulkan/BUILD.gn
@@ -101,6 +101,10 @@
"VkDeviceMemoryExternalLinux.hpp",
"VkSemaphoreExternalLinux.hpp",
]
+ } else if (is_mac) {
+ sources += [
+ "VkDeviceMemoryExternalMac.hpp",
+ ]
} else if (is_fuchsia) {
sources += [ "VkSemaphoreExternalFuchsia.hpp" ]
}
diff --git a/src/Vulkan/VkConfig.hpp b/src/Vulkan/VkConfig.hpp
index 5838736..e5518e8 100644
--- a/src/Vulkan/VkConfig.hpp
+++ b/src/Vulkan/VkConfig.hpp
@@ -92,6 +92,9 @@
#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
diff --git a/src/Vulkan/VkDeviceMemory.cpp b/src/Vulkan/VkDeviceMemory.cpp
index 5f3ac19..9921328 100644
--- a/src/Vulkan/VkDeviceMemory.cpp
+++ b/src/Vulkan/VkDeviceMemory.cpp
@@ -17,6 +17,7 @@
#include "VkDevice.hpp"
#include "VkDeviceMemoryExternalBase.hpp"
#include "VkImage.hpp"
+#include "VkStringify.hpp"
#include "VkConfig.hpp"
@@ -182,7 +183,65 @@
};
#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!"
diff --git a/src/Vulkan/VkDeviceMemoryExternalLinux.hpp b/src/Vulkan/VkDeviceMemoryExternalLinux.hpp
index b4f22fc..af69b2f 100644
--- a/src/Vulkan/VkDeviceMemoryExternalLinux.hpp
+++ b/src/Vulkan/VkDeviceMemoryExternalLinux.hpp
@@ -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 @@
private:
LinuxMemFd memfd;
- AllocateInfo allocateInfo;
+ OpaqueFdAllocateInfo allocateInfo;
};
diff --git a/src/Vulkan/VkDeviceMemoryExternalMac.hpp b/src/Vulkan/VkDeviceMemoryExternalMac.hpp
new file mode 100644
index 0000000..bcd06c5
--- /dev/null
+++ b/src/Vulkan/VkDeviceMemoryExternalMac.hpp
@@ -0,0 +1,197 @@
+// 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;
+};