Display Manager
Display Manager
Overview
The DisplayManager is responsible for discovering, configuring, and managing all physical displays connected to the system via DRM/KMS. It provides a thread-safe API for multi-monitor setups, handles hotplug events, manages core-allocated swapchain buffers, and supports Variable Refresh Rate (VRR).
Key Responsibilities:
- Discover connected displays via DRM/KMS
- Allocate and manage CRTC resources
- Provide atomic snapshot API for display configurations
- Handle display hotplug (connect/disconnect)
- Support VRR (Variable Refresh Rate / Adaptive Sync)
- Allocate core-managed swapchain buffers (DMA-BUF export from Vulkan)
- O(1) CRTC ID lookup for page flip handling
- Thread-safe callback-based iteration API
DisplayState Structure
DisplayState represents a single physical monitor and contains all state needed for rendering and display management.
struct DisplayState {
uint32_t display_id{UINT32_MAX}; // Unique identifier
AtomicKMSOutput output; // DRM atomic commit interface
DisplayMode mode; // Resolution, refresh rate, VRR
DisplayBufferState buffer_state; // Triple buffer management
DisplayRuntimeState runtime; // RenderLoop tracking
std::atomic<bool> is_active{true}; // On/off state
std::atomic<uint64_t> frame_counter{0}; // Frame counter
};
Key Properties:
- Non-copyable, non-movable:
DisplayBufferStatecontains atomics and mutex - RAII-managed: Vulkan resources automatically cleaned up on destruction
- Thread-safe: Uses atomics for state tracking
DisplayState Hierarchy
DisplayMode
Contains physical display properties from DRM/KMS:
struct DisplayMode {
uint32_t width{0}; // Display width in pixels
uint32_t height{0}; // Display height in pixels
uint32_t refresh_rate{0}; // Refresh rate in Hz
uint32_t connector_id{0}; // DRM connector ID
uint32_t crtc_id{0}; // DRM CRTC ID
bool vrr_capable{false}; // Hardware supports VRR
bool vrr_enabled{false}; // VRR is currently active
};
DisplayConfig
Runtime configuration exposed to plugins via get_display_configs():
struct DisplayConfig {
uint32_t display_id;
DisplayRole role; // Primary or Secondary
DisplayLayoutMode layout_mode; // Mirror or Extend
uint32_t width;
uint32_t height;
uint32_t refresh_rate;
int32_t position_x; // Virtual desktop position
int32_t position_y;
bool vrr_capable;
bool vrr_enabled;
};
DisplayLayoutMode
Controls how multiple displays are arranged:
enum class DisplayLayoutMode : uint32_t {
Mirror = 0, // All displays show the same content
Extend = 1, // Displays extend the virtual desktop horizontally
};
Multi-Monitor Layout (Extend Mode)
In Mirror Mode:
- All displays have
position_x=0, position_y=0 - All displays render identical content
DisplayRole
Identifies the primary vs secondary display:
enum class DisplayRole : uint32_t {
Primary = 0, // First display (display_id == 0)
Secondary = 1, // All subsequent displays
};
Usage:
- The primary display receives special treatment for global UI elements
- Display 0 is always considered primary
- Used by plugins for window placement decisions
Hotplug Handling
The rescan_displays() method handles display connect/disconnect events:
auto rescan_displays() -> Result<size_t>;
Hotplug Process:
- Discovers all currently connected connectors
- Removes displays whose connector is no longer connected
- Adds newly connected displays (allocates CRTC, creates DisplayState)
- Updates CRTC ID lookup map
- Returns new display count
Thread Safety:
- Acquires exclusive write lock during entire operation
- Prevents race conditions with render loop
VRR (Variable Refresh Rate) Support
VRR (aka Adaptive Sync / FreeSync) allows the display to synchronize to the actual frame delivery time, reducing tearing and stuttering.
Detection
auto check_vrr_support(uint32_t connector_id) const -> bool;
Queries DRM properties for VRR capability:
"vrr_capable"on connector"adaptive_sync"on connector"VRR_CAPABLE"on connector
Enabling
auto try_enable_vrr(uint32_t connector_id, uint32_t crtc_id) const -> bool;
Attempts to enable VRR by checking for:
"VRR_ENABLED"on CRTC (AMDGPU)"vrr_enabled"on CRTC or connector"freesync"property
Mode Selection:
- VRR-capable modes (60Hz+) are prioritized over EDID preferred modes
- Scoring system:
VRR bonus (1000000000000) + refresh_rate + preference_bonus
VRR Logging
Display 0 initialized: 1920x1080 @ 144Hz [VRR] (connector=42, crtc=50)
The [VRR] marker indicates VRR is enabled for that display.
Thread Safety
DisplayManager uses std::shared_mutex for read-write locking:
Read Lock (Shared Lock)
Multiple readers can access displays concurrently:
std::shared_lock lock(mutex_);
Used by:
get_display()get_display_by_crtc_id()get_display_count()for_each_display()get_display_configs()refresh_cursor_buffers()
Write Lock (Exclusive Lock)
Only one writer, no concurrent readers:
std::unique_lock lock(mutex_);
Used by:
init()rescan_displays()- Destructor (display cleanup)
Callback-Based Iteration
CRITICAL: for_each_display() is the ONLY safe way to iterate displays:
manager->for_each_display([](DisplayState& display) {
// Display reference is valid for this callback only
process_display(display);
});
Why callback-based?
- Prevents iterator invalidation
- No dangling references
- Lock held for entire iteration duration
Anti-pattern (UNSAFE):
// DON'T DO THIS - Race condition!
auto display = manager->get_display(0);
// Lock released here, display may be invalidated
use_display(display); // Potential use-after-free
CRTC ID Lookup
The page flip handler receives crtc_id directly from the kernel and needs O(1) lookup to find the corresponding DisplayState.
std::unordered_map<uint32_t, DisplayState*> crtc_id_to_display_;
Usage:
auto display = get_display_by_crtc_id(crtc_id);
if (display) {
display->runtime.commit_pending.store(false);
}
Updated on:
- Display initialization (
init()) - Hotplug (
rescan_displays())
Swapchain Buffer Allocation
The core manages triple-buffered swapchains for each display using Vulkan with DMA-BUF export.
CoreBuffer
Represents a single swapchain buffer:
struct CoreBuffer {
core::utils::UniqueFd fd; // DMA-BUF file descriptor
uint32_t width, height; // Dimensions
uint32_t stride; // Row pitch in bytes
uint32_t format; // DRM_FORMAT_XBGR8888
uint64_t modifier; // Format modifier (0 = linear)
uint32_t id; // Buffer index (0, 1, 2)
// Vulkan handles (RAII-managed)
std::unique_ptr<vk::raii::DeviceMemory> vk_memory;
std::unique_ptr<vk::raii::Image> vk_image;
};
Allocation Process
auto allocate_swapchain_buffers(DisplayState& display) -> Result<void>;
Steps:
- Create Vulkan image with external memory (
VK_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME) - Allocate device memory with DMA-BUF export capability
- Bind image to memory
- Query actual row pitch from LINEAR tiling
- Export memory as DMA-BUF file descriptor
- Store in
swapchain_buffersarray
Key Details:
- Uses linear tiling for DMA-BUF compatibility
- Format:
VK_FORMAT_R8G8B8A8_UNORM(matchesDRM_FORMAT_XBGR8888) - Triple buffering: 3 buffers per display
- RAII handles cleanup automatically
DisplayBufferState
Consolidates all buffer state for a display:
class DisplayBufferState {
static constexpr size_t TRIPLE_BUFFER_COUNT = 3;
std::array<BufferSlot, TRIPLE_BUFFER_COUNT> slots;
std::array<CoreBuffer, TRIPLE_BUFFER_COUNT> swapchain_buffers;
std::atomic<uint32_t> write_index{0}; // Next slot to acquire
std::atomic<uint32_t> current_slot{0}; // Currently displayed
std::atomic<uint32_t> next_slot{0}; // Queued for next frame
std::mutex mutex;
};
Cursor Support
The DisplayManager integrates with CursorService for hardware cursor rendering:
auto set_cursor_provider(const CursorProvider* provider) -> void;
auto refresh_cursor_buffers() -> void;
Flow:
- Plugin registers cursor provider via
core_register_cursor_provider() - DisplayManager stores the provider
- On cursor shape change,
refresh_cursor_buffers()is called - Each display’s cursor buffer is updated via
AtomicKMSOutput
Initialization Sequence
auto init(int drm_fd) -> Result<size_t>;
Steps:
- Initialize Vulkan context (for swapchain allocation)
- Initialize CursorService (Hyprcursor theme loading)
- Discover all connected connectors
- For each connector:
- Allocate CRTC
- Create DisplayState (select best mode, check VRR)
- Initialize AtomicKMSOutput
- Allocate swapchain buffers (DMA-BUF)
- Add to displays vector and CRTC lookup map
- Return display count
Vulkan Context (DisplayManager-Managed)
The DisplayManager maintains its own Vulkan context, independent of plugin contexts:
std::unique_ptr<vk::raii::Context> vk_context_;
std::unique_ptr<vk::raii::Instance> vk_instance_;
std::unique_ptr<vk::raii::PhysicalDevice> vk_physical_device_;
std::unique_ptr<vk::raii::Device> vk_device_;
std::unique_ptr<vk::raii::Queue> vk_queue_;
Extensions Required:
VK_KHR_external_memory_capabilitiesVK_KHR_external_semaphore_capabilitiesVK_KHR_external_memory_fdVK_KHR_external_semaphore_fdVK_EXT_external_memory_dma_buf(preferred)
Destruction Order
CRITICAL: Displays must be destroyed BEFORE Vulkan context cleanup:
~DisplayManager() {
{
std::unique_lock lock(mutex_);
displays_.clear(); // CoreBuffer RAII members destroyed first
}
// Then vk_device_, vk_instance_, vk_context_ destroyed
}
This ensures Vulkan device is still valid when swapchain_buffers are destroyed.
API Summary
| Method | Lock Type | Purpose |
|---|---|---|
init(drm_fd) | Write | Initialize manager, discover displays |
rescan_displays() | Write | Handle hotplug, add/remove displays |
for_each_display(cb) | Read | Iterate active displays |
get_display_configs() | Read | Get display configurations snapshot |
get_display(id) | Read | Get display by ID (immediate use only) |
get_display_by_crtc_id(id) | Read | Get display by CRTC (page flip handler) |
get_display_count() | Read | Get number of displays |
set_cursor_provider(p) | None | Set cursor callback |
refresh_cursor_buffers() | Read | Update cursor on all displays |
allocate_swapchain_buffers(d) | None | Allocate core-managed buffers |