diff options
| author | PancakeTAS <pancake@mgnet.work> | 2026-05-18 10:45:34 +0200 |
|---|---|---|
| committer | PancakeTAS <pancake@mgnet.work> | 2026-05-18 10:45:34 +0200 |
| commit | baf77bec541ef02659153a59118801c8002d7fdc (patch) | |
| tree | a27986a1c66d2093c92c33638f6d044d36a7741d | |
| parent | Initial commit (diff) | |
Implement (non-timeline) sync objects
| -rw-r--r-- | src/syncobject.cpp | 156 | ||||
| -rw-r--r-- | src/syncobject.hpp | 178 |
2 files changed, 334 insertions, 0 deletions
diff --git a/src/syncobject.cpp b/src/syncobject.cpp new file mode 100644 index 0000000..59c06fd --- /dev/null +++ b/src/syncobject.cpp @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: MIT */ + +#include "syncobject.hpp" +#include "priv/ioctl.hpp" + +#include <cstdint> +#include <optional> +#include <vector> + +#include <unistd.h> + +using namespace drm; + +SyncObject::SyncObject(int fd, bool signal) : m_fd(fd) { + drm_syncobj_create args{ + .flags = signal ? DRM_SYNCOBJ_CREATE_SIGNALED : 0U + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_CREATE, args); + + this->m_handle = args.handle; +} + +SyncObject::SyncObject(int fd, int syncobj_fd, bool close) : m_fd(fd) { + try { + drm_syncobj_handle args{ + .fd = syncobj_fd + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, args); + + this->m_handle = args.handle; + } catch (...) { + if (close) { + ::close(syncobj_fd); + } + + throw; + } + + if (close) { + ::close(syncobj_fd); + } +} + +int SyncObject::exportFd() const { + drm_syncobj_handle args{ + .handle = this->m_handle, + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, args); + + return args.fd; +} + +void SyncObject::importSyncFile(int syncfile_fd, bool close) const { + try { + drm_syncobj_handle args{ + .handle = this->m_handle, + .flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, + .fd = syncfile_fd + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, args); + } catch (...) { + if (close) { + ::close(syncfile_fd); + } + + throw; + } + + if (close) { + ::close(syncfile_fd); + } +} + +int SyncObject::exportSyncFile() const { + drm_syncobj_handle args{ + .handle = this->m_handle, + .flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, args); + + return args.fd; +} + +void SyncObject::signal(int fd, const std::vector<uint32_t>& objects) { + if (objects.empty()) { + return; + } + + drm_syncobj_array args{ + .handles = reinterpret_cast<uint64_t>(objects.data()), + .count_handles = static_cast<uint32_t>(objects.size()) + }; + ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_SIGNAL, args); +} + +void SyncObject::reset(int fd, const std::vector<uint32_t>& objects) { + if (objects.empty()) { + return; + } + + drm_syncobj_array args{ + .handles = reinterpret_cast<uint64_t>(objects.data()), + .count_handles = static_cast<uint32_t>(objects.size()) + }; + ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_RESET, args); +} + +uint32_t SyncObject::wait( + int fd, + const std::vector<uint32_t>& objects, + int64_t timeout, + bool waitAll, + bool waitEmpty, + bool waitAvailable, + std::optional<uint64_t> deadlineHint +) { + if (objects.empty()) { + return 0; + } + + if (waitAvailable) { + waitEmpty = false; + } + + drm_syncobj_wait args{ + .handles = reinterpret_cast<uint64_t>(objects.data()), + .timeout_nsec = timeout, + .count_handles = static_cast<uint32_t>(objects.size()), + .flags = + (waitAll ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL : 0U) | + (waitEmpty ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT : 0U) | + (waitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U) | + (deadlineHint.has_value() ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE : 0U), + .deadline_nsec = deadlineHint.value_or(0) + }; + ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_WAIT, args); + + return args.first_signaled; +} + +void SyncObject::destruct() noexcept { + if (this->m_fd < 0) { + return; + } + + drm_syncobj_destroy args{ + .handle = this->m_handle + }; + try { + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_DESTROY, args); + } catch(...) { + (void) 0; // not much we can do about the leak + } + + this->m_fd = -1; +} diff --git a/src/syncobject.hpp b/src/syncobject.hpp new file mode 100644 index 0000000..cf30078 --- /dev/null +++ b/src/syncobject.hpp @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: MIT */ + +#pragma once + +#include <cstdint> +#include <optional> +#include <vector> + +namespace drm { + + /// + /// A DRM synchronization object is a container which can hold one DRM fence. + /// + /// Their primary use-case is to implement Vulkan fences & semaphores, however they can + /// be used across ordinary processes as well. + /// + /// A sync object is reference counted, therefore destroying a sync object does not + /// invalidate all exported opaque file descriptors. + /// + /// Ordinary use cases for sync objects include: + /// - Creating two sync objects in separate processes, exporting a sync object fd into + /// the other process. Then signaling/polling the shared sync object. + /// - Exporting a Vulkan fence/semaphore as a sync file and importing it into a sync object. + /// It is then possible to wait for a signal from Vulkan inside of the DRM subsystem. + /// + /// Timeline synchronization objects extend the single DRM fence to a list of fences, where + /// fences are identified via 64-bit unsigned integer. + /// + /// https://www.kernel.org/doc/html/v6.19/gpu/drm-mm.html#drm-sync-objects + /// + class SyncObject { + public: + /// + /// Create a new DRM sync object + /// + /// By default, the sync object will not have a DRM fence emplaced. + /// + /// @param fd DRM node file descriptor + /// @param signal Emplace a signaled fence on creation + /// @throws drm::ioctl::Exception on failure + /// + SyncObject(int fd, bool signal = false); + + /// + /// Import a DRM sync object from a file descriptor + /// + /// @param fd DRM node file descriptor + /// @param syncobj_fd Sync object file descriptor + /// @param close Close the file descriptor after import (regardless of success) + /// @throws drm::ioctl::Exception on failure + /// + SyncObject(int fd, int syncobj_fd, bool close = true); + + /// + /// Export a reference to the sync object + /// + /// @throws drm::ioctl::Exception on failure + /// @returns Exported file descriptor. + /// + [[nodiscard]] int exportFd() const; + + /// + /// Import a sync file (DRM fence) into the sync object + /// + /// @param syncfile_fd File descriptor to import + /// @param close Close the file descriptor after import (regardless of success) + /// @throws drm::ioctl::Exception on failure + /// + void importSyncFile(int syncfile_fd, bool close = true) const; + + /// + /// Export a sync file from the DRM fence within the sync object + /// + /// Any subsequent modifications to the sync object are not applied to + /// the exported sync file. + /// + /// @throws drm::ioctl::Exception on failure + /// @returns Exported file descriptor + /// + [[nodiscard]] int exportSyncFile() const; + + /// + /// Emplace signaled fences into a list of sync objects + /// + /// @param fd DRM node file descriptor + /// @param objects List of sync object handles + /// @throws drm::ioctl::Exception on failure + /// + static void signal(int fd, const std::vector<uint32_t>& objects); + + /// + /// Remove the DRM fence from a list of sync objects + /// + /// @param fd DRM node file descriptor + /// @param objects List of sync object handles + /// @throws drm::ioctl::Exception on failure + /// + static void reset(int fd, const std::vector<uint32_t>& objects); + + /// How to handle empty sync objects when waiting + enum class EmptyFlags { + /// Return -EINVAL if any sync object is empty + Throw, + /// Block until a fen + }; + + /// + /// Wait for a list of sync objects to be signaled + /// + /// If waitEmpty or waitAvailable is not set, any empty sync object will result in + /// an error. + /// + /// If waitAvailable is set, the function will return as soon as a fence is emplaced into + /// all sync objects, as opposed to waiting for the fence to be signaled. This option + /// should not be used together with waitEmpty. + /// + /// @param fd DRM node file descriptor + /// @param objects List of sync object handles + /// @param timeout Absolute timeout in nanoseconds, or zero for polling + /// @param waitAll Wait for all sync objects, as opposed to a single one + /// @param waitEmpty Wait for sync objects, which do not yet have a fence emplaced + /// @param waitAvailable Only wait for a fence to be available, as opposed to signaled. + /// @param deadlineHint Set a CLOCK_MONOTONIC deadline hint in nanoseconds on all fences + /// @throws drm::ioctl::Exception on failure + /// @return Handle which was signaled first, when waitAll is false. + /// + [[nodiscard]] + static uint32_t wait( + int fd, + const std::vector<uint32_t>& objects, + int64_t timeout = 0, + bool waitAll = false, + bool waitEmpty = false, + bool waitAvailable = false, + std::optional<uint64_t> deadlineHint = std::nullopt + ); + + // Into handle + operator uint32_t() const { return this->m_handle; } + + // Move constructor/operator + SyncObject(SyncObject&& other) noexcept : m_fd(other.m_fd), m_handle(other.m_handle) { + other.m_fd = -1; // invalidate other + } + + SyncObject& operator=(SyncObject&& other) noexcept { + if (this != &other) { + this->destruct(); + m_handle = other.m_handle; + m_fd = other.m_fd; + other.m_fd = -1; // invalidate other + } + + return *this; + } + + // Copy constructor/operator + SyncObject(const SyncObject& other) + : SyncObject(other.m_fd, other.exportFd(), true) {} + + SyncObject& operator=(const SyncObject& other) { + if (this != &other) { + const int fd = other.exportFd(); + *this = SyncObject(other.m_fd, fd, true); + } + + return *this; + } + + // Destructor + ~SyncObject(); + private: + int m_fd; // indicates object validity (>= 0) + uint32_t m_handle; + + void destruct() noexcept; + }; +} |
