summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--include/drm++/drm.hpp195
-rw-r--r--include/drm++/gem.hpp114
-rw-r--r--include/drm++/ioctl.hpp11
-rw-r--r--include/drm++/syncobject.hpp51
4 files changed, 321 insertions, 50 deletions
diff --git a/include/drm++/drm.hpp b/include/drm++/drm.hpp
new file mode 100644
index 0000000..f128930
--- /dev/null
+++ b/include/drm++/drm.hpp
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: MIT */
+
+#pragma once
+
+///
+/// * Direct Rendering Manager
+///
+/// The Direct Rendering Manager (DRM) is a Linux subsystem that provides an interface
+/// for GPU drivers. The interface is split into common DRM core and driver-specific components.
+///
+/// This library provides a C++ wrapper around the core DRM interface, not including
+/// any driver-specific ioctls. This library also focuses on modern DRM and intentionally
+/// does not provide wrappers for legacy features (which are a no-op on most modern
+/// GPU drivers anyways).
+///
+/// The kernel exposes a DRM device as a device file, usually located at `/dev/dri/cardX`, and all
+/// ioctls are performed on a file descriptor to this device.
+///
+/// * Authentication
+///
+/// Access to `/dev/dri/cardX` is restricted to the DRM master, or any client that has been
+/// granted access. While not all ioctls are restricted, client should use `/dev/dri/renderDXXX`
+/// for purely unauthenticated access (such as simple buffer management, compute, etc).
+///
+/// Acquiring DRM master happens automatically when the first process opens `/dev/dri/cardX`, but
+/// can be explicitly requested as well. DRM master is exclusive, and only one process can be
+/// DRM master at a time.
+///
+/// If another client wishes to use restricted ioctls, it must send its magic token to
+/// the DRM master, which can authenticate the file descriptor.
+///
+/// * Graphics Execution Manager
+///
+/// The Graphics Execution Manager (GEM) provides an API for memory management in video memory,
+/// however in core DRM its functionality is limited to a minimal set of features.
+///
+/// A memory object living in video memory is called a GEM object and is identified by a handle.
+/// In modern (core) DRM, GEM objects are usually only imported from DMA-BUFs allocated
+/// through other APIs (such as GBM, Vulkan, etc.). This is known as "PRIME".
+///
+/// However, most drivers will also support "dumb buffers", which are simple linear buffers held
+/// in either system memory or video memory, but always accessible through the CPU.
+///
+/// * Kernel Mode Setting
+///
+/// (TODO)
+///
+/// * Sync Objects
+///
+/// Sync objects are a relatively recent addition to DRM and are primarily used by the
+/// Vulkan WSI and Wayland for explicit synchronization.
+///
+/// A sync object can hold one or more DRM fences, which are a kernel object that can be signaled
+/// by the GPU when a certain operation has been completed. Fences inside sync objects can be
+/// exported to file descriptors called "sync files", which can be used by other APIs (such as
+/// Vulkan) to synchronize with the GPU.
+///
+/// They can also be used purely CPU-side for synchronization between processes.
+///
+/// * Legacy features
+///
+/// As said previously, this library intentionally does not provide wrappers for legacy features,
+/// however it is a good idea to be aware of their existence. This list is ordered from most
+/// relevant to least relevant.
+///
+/// 1. The single most relevant feature is legacy modesetting. This was the original way to set
+/// display modes, manage CRTCs, etc. and has been superseded all the way back in 2015 by the
+/// far superior atomic modesetting, which addresses several severe flaws in the original design.
+/// However only recently (around ~2023) has atomic modesetting been stable enough in
+/// drivers (specifically NVIDIA) to be used everywhere. Nowdays it is required for explicit
+/// synchronization and therefore this library chooses to only support atomic modesetting.
+///
+/// 2. Prior to PRIME, sharing GEM objects across processes was done via "flink" and "open". Each
+/// GEM object could be given a unique name which other processes could then open. Due to the
+/// security implications of this, it should never be used unless legacy API require it.
+///
+/// 3. Next to GEM there is also the Translation Table Maps (TTM) memory manager. The user-facing
+/// API was deemed way too complex, as it tried to unify various memory constellations into a
+/// single interface. While some drivers still use TTM internally, GEM acts as a common interface
+/// and should be used instead (see https://docs.kernel.org/gpu/drm-mm.html).
+///
+/// 4. In the distant past, GPU context management, draw areas, AGP maintenance and GPU locking
+/// were all handled by userspace (also known as userspace mode settings / UMS). In v6.8-rc1
+/// these were finally retired from the kernel codebase entirely, although they haven't had
+/// an implementation in decades anyways (see 184dcdc).
+///
+/// * Further reading
+///
+/// Unfortunately documentation for DRM is kind of all over the place. Crucial information is
+/// often hidden in individual commit messages and requires reading kernel code to understand.
+///
+/// A good starting point is the wikipedia page:
+/// https://en.wikipedia.org/wiki/Direct_Rendering_Manager
+///
+/// For more in-depth information, the kernel documentation is a good resource:
+/// https://www.kernel.org/doc/html/latest/gpu/index.html
+///
+
+///
+/// * API Usage
+///
+/// Using the C++ API is fairly straightforward.
+///
+/// Components of the DRM API are split into their own header files, each in their own namespace.
+///
+/// Each header will have a large comment block at the top explaining the concept of the
+/// component and giving essential information about its usage, such that individual methods
+/// aren't cluttered with documentation.
+///
+/// Most methods utilize ioctls and will throw drm::ioctl::Exception on failure. This is always
+/// documented in the class comment block and applies for all methods unless otherwise specified
+/// or annotated with `noexcept`.
+///
+/// Should a DRM capability be required for a certain feature, this will also be documented in the
+/// class comment block, unless only a single method requires it, in which case it will be
+/// documented in the method accordingly.
+///
+/// Most classes cannot be copied, however there are exceptions (such as SyncObject) in which a
+/// copy constructor/operator is provided. Please note that these likely utilize ioctls and
+/// may throw an exception on failure.
+///
+/// Occasionally strictly non-virtual inheritance is used to minimize code duplication and
+/// macros and type definitions are used to shorten getters and integer types.
+///
+/// * Requirements
+///
+/// The majority of the DRM API requires a Linux kernel version of at least (TODO), and individual
+/// components requiring an even newer version are documented accordingly (FIXME).
+///
+/// This library requires a C++ compiler capable of C++20 or newer.
+///
+
+#include "drm++/helper.hpp"
+
+namespace drm {
+
+///
+/// DRM device node (e.g. `/dev/dri/cardX`).
+///
+/// @throws drm::ioctl::Exception on failure
+///
+class Device {
+public:
+ /// Wrap around an existing file descriptor, taking ownership and closing it on destruction.
+ /// @throws std::invalid_argument if fd is invalid
+ Device(int fd);
+
+ /// Attempt to acquire DRM master on the device. This will fail if another process is
+ /// already DRM master.
+ void acquireMaster() const;
+
+ /// Release DRM master on the device.
+ void releaseMaster() const;
+
+ /// Authenticate a file descriptor via magic token, allowing for access to
+ /// restricted ioctls (such as mode setting).
+ void authenticate(u32 magic) const;
+
+ // Private access
+ GETTER(fd)
+ GETTER(magic)
+
+ // Move constructor/operator
+ Device(Device&& other) noexcept : m_fd(other.m_fd), m_magic(other.m_magic) {
+ other.m_fd = -1; // invalidate other
+ }
+
+ Device& operator=(Device&& other) noexcept {
+ if (this != &other) {
+ this->destruct();
+ this->m_fd = other.m_fd;
+ this->m_magic = other.m_magic;
+ other.m_fd = -1; // invalidate other
+ }
+
+ return *this;
+ }
+
+ // Copy constructor/operator
+ Device(const Device& other) = delete;
+ Device& operator=(const Device& other) = delete;
+
+ // Destructor
+ ~Device() noexcept {
+ this->destruct();
+ }
+
+private:
+ int m_fd; // indicates device validity (>= 0)
+ u32 m_magic;
+
+ void destruct() noexcept;
+};
+
+}
diff --git a/include/drm++/gem.hpp b/include/drm++/gem.hpp
index 1dfe1ba..4a02e55 100644
--- a/include/drm++/gem.hpp
+++ b/include/drm++/gem.hpp
@@ -2,19 +2,82 @@
#pragma once
+///
+/// * GEM Objects
+///
+/// A GEM object is a reference-counted kernel object wrapping a memory allocation of any kind,
+/// identified by one or more handles.
+///
+/// In core DRM there are 2 ways to create a GEM object:
+/// - Import a DMA-BUF via PRIME
+/// - Create a linear dumb buffer
+///
+/// Upon importing a DMA-BUF, a new handle to the underlying GEM object is created and the
+/// reference count is increased. The file descriptor itself also holds a reference and must
+/// therefore be closed.
+///
+/// When creating a dumb buffer, there are no guarantees about the underlying memory. It may be
+/// in system memory or GPU memory and there are no guarantees about the performance of read/write
+/// operations. A dumb buffer can however always be mapped into CPU-accessible memory.
+///
+/// * Dumb Buffers
+///
+/// A dumb buffer is a primitive DRM-native driver independent GEM object, wrapping around a linear
+/// memory allocation.
+///
+/// The only creation parameters are width, height and bpp (bits per pixel / color mode). The
+/// bpp parameter also specifies the DRM formats this buffer can be used with. The table below
+/// can serve as a reference, however there are no guarantees that a format is compatible.
+///
+/// Most drivers will support DRM_FORMAT_XRGB8888 with 32 bits per pixel.
+///
+/// +-----+------------------------+------------------------+
+/// | BPP | Framebuffer format | Compatible formats |
+/// +=====+========================+========================+
+/// | 32 | * DRM_FORMAT_XRGB8888 | * DRM_FORMAT_BGRX8888 |
+/// | | | * DRM_FORMAT_RGBX8888 |
+/// | | | * DRM_FORMAT_XBGR8888 |
+/// +-----+------------------------+------------------------+
+/// | 24 | * DRM_FORMAT_RGB888 | * DRM_FORMAT_BGR888 |
+/// +-----+------------------------+------------------------+
+/// | 16 | * DRM_FORMAT_RGB565 | * DRM_FORMAT_BGR565 |
+/// +-----+------------------------+------------------------+
+/// | 15 | * DRM_FORMAT_XRGB1555 | * DRM_FORMAT_BGRX1555 |
+/// | | | * DRM_FORMAT_RGBX1555 |
+/// | | | * DRM_FORMAT_XBGR1555 |
+/// ------+------------------------+------------------------+
+/// | 8 | * DRM_FORMAT_C8 | * DRM_FORMAT_D8 |
+/// | | | * DRM_FORMAT_R8 |
+/// +-----+------------------------+------------------------+
+/// | 4 | * DRM_FORMAT_C4 | * DRM_FORMAT_D4 |
+/// | | | * DRM_FORMAT_R4 |
+/// +-----+------------------------+------------------------+
+/// | 2 | * DRM_FORMAT_C2 | * DRM_FORMAT_D2 |
+/// | | | * DRM_FORMAT_R2 |
+/// +-----+------------------------+------------------------+
+/// | 1 | * DRM_FORMAT_C1 | * DRM_FORMAT_D1 |
+/// | | | * DRM_FORMAT_R1 |
+/// +-----+------------------------+------------------------+
+///
+/// This table is not exhaustive, but it is not recommended to use any other values for bpp.
+///
+
#include "drm++/helper.hpp"
namespace drm::gem {
-/// A GEM object wrapping memory.
+///
+/// GEM object wrapping a memory allocation
+///
/// @throws drm::ioctl::Exception on failure
+///
class Object {
friend class DumbBuffer;
public:
/// Import a DMA-BUF file descriptor into a GEM object.
/// Requires DRM_PRIME_CAP_IMPORT.
///
- /// An import can fail for various driver-specific reasons.
+ /// An import can fail for various driver-specific reasons, especially for foreign DMA-BUFs.
///
/// @param close Close fd after import (regardless of success)
Object(int fd, int dmabuf_fd, bool close = true);
@@ -36,11 +99,6 @@ public:
[[nodiscard]] int exportFd(ExportFlags flags = ExportFlags::None) const;
/// Change the handle of the GEM object.
- ///
- /// SAFETY: This is a dangerous operation, as the previous handle will be invalidated. There is
- /// no guarantee that importing a DMA-BUF file descriptor will return a new handle, so other
- /// instances of this class on the same file descriptor may become invalid. Use with caution.
- ///
/// @param handle An unused GEM handle to change into.
void changeHandle(u32 handle);
@@ -77,51 +135,19 @@ private:
int m_fd; // indicates object validity (>= 0)
u32 m_handle;
+ Object(int fd) : m_fd(fd), m_handle(0) {}
void destruct() noexcept;
};
-/// A dumb buffer GEM object.
+///
+/// Dumb buffer wrapping a linear memory allocation.
/// Requires DRM_CAP_DUMB_BUFFER.
+///
/// @throws drm::ioctl::Exception on failure
+///
class DumbBuffer : public Object {
public:
/// Create a new dumb buffer.
- ///
- /// The bits per pixel (bpp) also indicates the format and compatible formats
- /// with similar pixel layouts. All buffers are strictly linear.
- ///
- /// +-----+------------------------+------------------------+
- /// | BPP | Framebuffer format | Compatible formats |
- /// +=====+========================+========================+
- /// | 32 | * DRM_FORMAT_XRGB8888 | * DRM_FORMAT_BGRX8888 |
- /// | | | * DRM_FORMAT_RGBX8888 |
- /// | | | * DRM_FORMAT_XBGR8888 |
- /// +-----+------------------------+------------------------+
- /// | 24 | * DRM_FORMAT_RGB888 | * DRM_FORMAT_BGR888 |
- /// +-----+------------------------+------------------------+
- /// | 16 | * DRM_FORMAT_RGB565 | * DRM_FORMAT_BGR565 |
- /// +-----+------------------------+------------------------+
- /// | 15 | * DRM_FORMAT_XRGB1555 | * DRM_FORMAT_BGRX1555 |
- /// | | | * DRM_FORMAT_RGBX1555 |
- /// | | | * DRM_FORMAT_XBGR1555 |
- /// ------+------------------------+------------------------+
- /// | 8 | * DRM_FORMAT_C8 | * DRM_FORMAT_D8 |
- /// | | | * DRM_FORMAT_R8 |
- /// +-----+------------------------+------------------------+
- /// | 4 | * DRM_FORMAT_C4 | * DRM_FORMAT_D4 |
- /// | | | * DRM_FORMAT_R4 |
- /// +-----+------------------------+------------------------+
- /// | 2 | * DRM_FORMAT_C2 | * DRM_FORMAT_D2 |
- /// | | | * DRM_FORMAT_R2 |
- /// +-----+------------------------+------------------------+
- /// | 1 | * DRM_FORMAT_C1 | * DRM_FORMAT_D1 |
- /// | | | * DRM_FORMAT_R1 |
- /// +-----+------------------------+------------------------+
- ///
- /// Table is not exhaustive, however other bpp values should only be used for legacy purposes.
- ///
- /// Support for all bits per pixel is optional and successful creation of a dumb buffer does
- /// not guarantee that all related formats are compatible.
DumbBuffer(int fd, u32 width, u32 height, u32 bpp);
/// Map the dumb buffer into userspace memory. May be called multiple times,
diff --git a/include/drm++/ioctl.hpp b/include/drm++/ioctl.hpp
index 1a68dae..29b08ae 100644
--- a/include/drm++/ioctl.hpp
+++ b/include/drm++/ioctl.hpp
@@ -46,8 +46,15 @@ private:
/// Perform an ioctl() call.
/// @throws drm::ioctl::Exception on failure
+inline void perform(int fd, unsigned long op) {
+ if (::ioctl(fd, op, nullptr) < 0)
+ throw Exception(fd, op);
+}
+
+/// Perform an ioctl() call.
+/// @throws drm::ioctl::Exception on failure
template<typename T>
-T perform(int fd, unsigned long op) {
+inline T perform(int fd, unsigned long op) {
T data{};
if (::ioctl(fd, op, &data) < 0)
@@ -59,7 +66,7 @@ T perform(int fd, unsigned long op) {
/// Perform an ioctl() call.
/// @throws drm::ioctl::Exception on failure
template<typename T>
-void perform(int fd, unsigned long op, T& data) {
+inline void perform(int fd, unsigned long op, T& data) {
if (::ioctl(fd, op, &data) < 0)
throw Exception(fd, op);
}
diff --git a/include/drm++/syncobject.hpp b/include/drm++/syncobject.hpp
index f530c49..9ecbf17 100644
--- a/include/drm++/syncobject.hpp
+++ b/include/drm++/syncobject.hpp
@@ -2,6 +2,39 @@
#pragma once
+///
+/// * Sync Object
+///
+/// A sync object is a reference-counted kernel object which can hold a DRM fence. A fence can
+/// either be signaled or unsignaled, but a sync object can also hold no fence at all.
+///
+/// Fences are typically imported from a sync file, which has been exported from an API such as
+/// Vulkan, which will signal the fence when a certain operation has been completed.
+///
+/// In core DRM, it is possible to emplace a trivially signaled fence into a sync object, or
+/// remove ("reset") a fence from a sync object. It is also possible to copy a fence from another
+/// sync object.
+///
+/// Fences can be imported and exported from a sync object via sync files, but it is also possible
+/// to export a reference to the sync object itself, which can be imported by another process.
+///
+/// Finally, sync objects can be waited on, performing a CPU-side wait. It is also possible
+/// to merely wait for a sync object to be emplaced with a fence, without waiting for it to be
+/// signaled. During a wait, it is possible to set a deadline hint on the fence, described by
+/// the kernel as "to provide the fence signaler with an appropriate sense of urgency".
+///
+/// * Timeline Sync Object
+///
+/// A timeline sync object is a sync object which can hold multiple fences, each identified by a
+/// 64-bit unsigned integer "point". The point should be monotonically increasing, as this
+/// is what other APIs are designed with (e.g. Vulkan).
+///
+/// While the kernel does not differentiate between a (binary) sync object and a timeline
+/// sync object, there exists a clear distinction and mixing ioctls can lead to undefined
+/// behavior. This library protects against this by providing separate classes for each type
+/// of sync object.
+///
+
#include "drm++/helper.hpp"
#include <optional>
@@ -12,7 +45,12 @@ namespace drm::syncobj {
class SyncObject;
class TimelineSyncObject;
-/// Common base class for SyncObject and TimelineSyncObject.
+///
+/// Common base class for sync objects.
+/// Requires DRM_CAP_SYNCOBJ.
+///
+/// @throws drm::ioctl::Exception on failure
+///
class SyncObjectBase {
friend class SyncObject;
friend class TimelineSyncObject;
@@ -79,9 +117,12 @@ enum class WaitMode : u8 {
WaitAvailable //!< Wait for empty sync objects to be filled, but do not wait for signaling
};
-/// A (binary) synchronization object is a reference-counted container which can hold a DRM fence.
+///
+/// A (binary) synchronization object.
/// Requires DRM_CAP_SYNCOBJ.
+///
/// @throws drm::ioctl::Exception on failure
+///
class SyncObject : public SyncObjectBase {
public:
using SyncObjectBase::SyncObjectBase;
@@ -119,10 +160,12 @@ public:
void registerEventFd(int eventfd_fd, bool waitAvailable = false) const;
};
-/// A timeline synchronization object can hold multiple DRM fences, identified via a
-/// monotonically increasing 64-bit unsigned integer "point".
+///
+/// A timeline synchronization object.
/// Requires DRM_CAP_SYNCOBJ_TIMELINE.
+///
/// @throws drm::ioctl::Exception on failure
+///
class TimelineSyncObject : public SyncObjectBase {
public:
using SyncObjectBase::SyncObjectBase;