diff options
| author | PancakeTAS <pancake@mgnet.work> | 2026-06-23 21:12:55 +0200 |
|---|---|---|
| committer | PancakeTAS <pancake@mgnet.work> | 2026-06-23 21:24:11 +0200 |
| commit | 5f3f95c67d86cf82ae32d6821b283724eba8b293 (patch) | |
| tree | c4b48c98bbef45512712059af966b79ae2bd1c2e | |
| parent | Significantly reduce line count and code duplication (diff) | |
Implement GEM objects and dumb buffers
Diffstat (limited to '')
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | include/drm++/gem.hpp | 184 | ||||
| -rw-r--r-- | src/gem.cpp | 134 |
3 files changed, 319 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 497f394..a23eddd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ project(drm++ include(cmake/Diagnostics.cmake) add_library(drm++ SHARED + "src/gem.cpp" "src/ioctl.cpp" "src/syncobject.cpp") diff --git a/include/drm++/gem.hpp b/include/drm++/gem.hpp new file mode 100644 index 0000000..1dfe1ba --- /dev/null +++ b/include/drm++/gem.hpp @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: MIT */ + +#pragma once + +#include "drm++/helper.hpp" + +namespace drm::gem { + +/// A GEM object wrapping memory. +/// @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. + /// + /// @param close Close fd after import (regardless of success) + Object(int fd, int dmabuf_fd, bool close = true); + + /// Flags for exporting fds + enum class ExportFlags : u32 { + None = 0, + ReadWrite = 1 << 0, //!< Allow read/write access (drivers may ignore this) + CloseOnExec = 1 << 1, //!< Close fd on execve() + }; + + /// Obtain a DMA-BUF file descriptor for the GEM object, incrementing the reference count. + /// Requires DRM_PRIME_CAP_EXPORT. + /// + /// An export can fail for various driver-specific reasons, including lack of support on this + /// specific GEM object. + /// + /// @param flags Flags to set on the exported file descriptor. + [[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); + + // Private access + GETTER(fd) + GETTER(handle) + + // Move constructor/operator + Object(Object&& other) noexcept : m_fd(other.m_fd), m_handle(other.m_handle) { + other.m_fd = -1; // invalidate other + } + + Object& operator=(Object&& other) noexcept { + if (this != &other) { + this->destruct(); + this->m_handle = other.m_handle; + this->m_fd = other.m_fd; + other.m_fd = -1; // invalidate other + } + + return *this; + } + + // Copy constructor/operator + Object(const Object& other) = delete; + Object& operator=(const Object& other) = delete; + + // Destructor + ~Object() noexcept { + this->destruct(); + } + +private: + int m_fd; // indicates object validity (>= 0) + u32 m_handle; + + void destruct() noexcept; +}; + +/// A dumb buffer GEM object. +/// 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, + /// will return the same pointer if already mapped. + /// @throws std::system_error on mmap failure + [[nodiscard]] void* map(); + + // Private access + GETTER(width) + GETTER(height) + GETTER(bpp) + GETTER(pitch) + GETTER(size) + + // Move constructor/operator + DumbBuffer(DumbBuffer&& other) noexcept + : Object(std::move(other)), // NOLINTBEGIN (use after move) + m_width(other.m_width), m_height(other.m_height), m_bpp(other.m_bpp), + m_pitch(other.m_pitch), m_size(other.m_size), + m_map(other.m_map) { + other.m_map = nullptr; // invalidate other + } // NOLINTEND + + DumbBuffer& operator=(DumbBuffer&& other) noexcept { + if (this != &other) { + this->destruct(); + Object::operator=(std::move(other)); // NOLINTBEGIN (use after move) + this->m_width = other.m_width; + this->m_height = other.m_height; + this->m_bpp = other.m_bpp; + this->m_pitch = other.m_pitch; + this->m_size = other.m_size; + this->m_map = other.m_map; + other.m_map = nullptr; // invalidate other + } // NOLINTEND + + return *this; + } + + // Copy constructor/operator + DumbBuffer(const DumbBuffer& other) = delete; + DumbBuffer& operator=(const DumbBuffer& other) = delete; + + // Destructor + ~DumbBuffer() noexcept { + this->destruct(); + } + +private: + u32 m_width; + u32 m_height; + u32 m_bpp; + u32 m_pitch; + u64 m_size; + void* m_map{nullptr}; // indicate mapping validity + + void destruct() noexcept; +}; + +} diff --git a/src/gem.cpp b/src/gem.cpp new file mode 100644 index 0000000..4b81703 --- /dev/null +++ b/src/gem.cpp @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: MIT */ + +#include "drm++/gem.hpp" +#include "drm++/helper.hpp" +#include "drm++/ioctl.hpp" + +#include <cerrno> +#include <system_error> + +#include <drm.h> +#include <drm_mode.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + +using namespace drm; +using namespace drm::gem; + +/* GEM object class */ + +Object::Object(int fd, int dmabuf_fd, bool close) : m_fd(fd) { + try { + drm_prime_handle args{ + .fd = dmabuf_fd + }; + ioctl::perform(this->m_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, args); + + this->m_handle = args.handle; + } catch (...) { + if (close) { + ::close(dmabuf_fd); + } + + throw; + } + + if (close) { + ::close(dmabuf_fd); + } +} + +int Object::exportFd(ExportFlags flags) const { + drm_prime_handle args{ + .handle = this->m_handle, + .flags = static_cast<u32>(flags) + }; + ioctl::perform(this->m_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, args); + + return args.fd; +} + +void Object::changeHandle(u32 handle) { + drm_gem_close args{ + .handle = this->m_handle + }; + ioctl::perform(this->m_fd, DRM_IOCTL_GEM_CLOSE, args); + + this->m_handle = handle; +} + +void Object::destruct() noexcept { + if (this->m_fd < 0) { + return; + } + + drm_gem_close args{ + .handle = this->m_handle + }; + try { + ioctl::perform(this->m_fd, DRM_IOCTL_GEM_CLOSE, args); + } catch(...) { + (void) 0; // not much we can do about the leak + } + + this->m_fd = -1; +} + +/* Dumb buffer class */ + +DumbBuffer::DumbBuffer(int fd, u32 width, u32 height, u32 bpp) + : Object(fd, -1), m_width(width), m_height(height), m_bpp(bpp) { + drm_mode_create_dumb args{ + .height = height, + .width = width, + .bpp = bpp + }; + ioctl::perform(this->m_fd, DRM_IOCTL_MODE_CREATE_DUMB, args); + + this->m_handle = args.handle; + this->m_pitch = args.pitch; + this->m_size = args.size; +} + +void* DumbBuffer::map() { + if (this->m_map != nullptr) { + return this->m_map; + } + + drm_mode_map_dumb args{ + .handle = this->m_handle + }; + ioctl::perform(this->m_fd, DRM_IOCTL_MODE_MAP_DUMB, args); + + void* map{::mmap(nullptr, this->m_size, PROT_READ | PROT_WRITE, MAP_SHARED, + this->m_fd, static_cast<off_t>(args.offset))}; + if (map == MAP_FAILED) { + throw std::system_error(errno, std::generic_category(), "mmap failed"); + } + + this->m_map = map; + return this->m_map; +} + +void DumbBuffer::destruct() noexcept { + if (this->m_fd < 0) { + return; + } + + if (this->m_map != nullptr) { + ::munmap(this->m_map, this->m_size); + this->m_map = nullptr; + } + + drm_mode_destroy_dumb args{ + .handle = this->m_handle + }; + try { + ioctl::perform(this->m_fd, DRM_IOCTL_MODE_DESTROY_DUMB, args); + } catch(...) { + (void) 0; // not much we can do about the leak + } + + this->m_fd = -1; +} |
