Skip to content

EasyDRM - A minimal, GLFW-like Rust framework for direct rendering on Linux using DRM/KMS + GBM + EGL.

Notifications You must be signed in to change notification settings

everything-os/easydrm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EasyDRM — Minimal DRM/KMS Framework

EasyDRM is a GLFW-inspired abstraction over DRM/KMS, GBM, and EGL/OpenGL that lets you build fullscreen Linux applications without a compositor (no X11, no Wayland). It owns the low-level plumbing—monitor discovery, events, page flips, fences, and atomic commits—so you can focus on your render loop while staying in total control of timing.

Highlights

  • Single-threaded & explicit: you drive the loop, EasyDRM provides blocking events and render surfaces.
  • Multi-monitor aware: every monitor gets an isolated GL/EGL/GBM context, framebuffer, and fence.
  • Deterministic swap orchestration: swap_buffers() walks the monitors you rendered and issues atomic commits in a predictable order.
  • Refresh-rate aware: monitors are grouped by refresh rate for introspection and future scheduling tweaks.
  • 3-state mode management: default_mode, requested_mode, and current_mode minimize expensive modesets and handle TTY focus loss.
  • Robust fences: GPU→DRM synchronization prevents tearing and leaks by cleaning sync objects every frame.

Core Concept

use easydrm::EasyDRM;

let mut easydrm = EasyDRM::init_empty().expect("GPU available");

loop {
    for monitor in easydrm.monitors_mut() {
        if monitor.can_render() {     // 1) Draw only to ready monitors
            monitor.make_current().unwrap();
            render_frame(monitor);
        }
    }
    easydrm.swap_buffers().unwrap();  // 2) Commit each monitor that was drawn
    easydrm.poll_events().unwrap();   // 3) Block on DRM/input events
    if easydrm.should_update() {      // 4) Global logic tied to fastest refresh group
        update_simulation();
    }
}

EasyDRM feels like a game engine main loop: you control the cadence, EasyDRM keeps hardware state in sync.

Architecture Overview

Event & Rendering Flow

  1. poll_events() blocks on DRM, vblank, hotplug, and optional input fds via poll().
  2. can_render() turns true when the previous page flip + fence finished.
  3. make_current() activates the monitor’s GL/EGL context so you can issue draw calls.
  4. swap_buffers() iterates the monitors that were drawn and issues their atomic commits with the right fences.

Multi-Monitor Grouping

  • Monitors are grouped by refresh rate; the map is exposed so you can choose a cadence or diagnostics strategy.
  • The helper should_update() fires once every time the fastest refresh-rate group has committed, letting you run simulation at that cadence.
  • Every monitor tracks its own fence + framebuffer pair to keep scan-out safe.

Mode System (Default / Requested / Current)

Field Meaning When it changes
default_mode Optimal mode detected at init Never after initialization
requested_mode What the app wants (None = default) monitor.set_mode(...)
current_mode What DRM is actually using After successful atomic commit or clear_mode_state()

A modeset runs when requested_mode != current_mode, covering first boot, TTY focus loss, and user-driven mode switches.

Getting Started

Prerequisites

  • Linux environment with a DRM/KMS-capable GPU (running on a VT/TTY, not under X11/Wayland).
  • Permissions to open /dev/dri/card* (run as root or add the user to the video group).
  • Rust 1.84+ (edition 2024) and a modern Mesa/GBM/EGL stack.

Build

cargo build --release

Run the basic example

⚠️ Run from a VT (outside X/Wayland) to avoid fighting the system compositor.

cargo run --example basic

The example prints detected monitors, animates a color wipe, and keeps running until you Ctrl+C.

API Sketch

  • EasyDRM::init_empty() – initialize without a custom per-monitor context.
  • EasyDRM::init(|req| { /* create custom context using req.gl / req.get_proc_address */ }) – attach your own data per monitor.
  • EasyDRM::monitors() / monitors_mut() – iterate over monitor handles.
  • Monitor::make_current() – bind this monitor’s GL context and mark it as drawn.
  • Monitor::gl() – access generated GLES2 bindings.
  • Monitor::set_mode(Some(mode)) – request a specific DRM mode; None reverts to default_mode.
  • EasyDRM::swap_buffers() – walks monitors that were drawn and calls their atomic swap path.
  • EasyDRM::poll_events() – wait for page flips, hotplug, and optional input events.
  • EasyDRM::should_update() – returns true once per cycle when the fastest refresh-rate group has committed.

See examples/basic.rs and examples/custom_context.rs for end-to-end loops.

Design Principles

  • No automatic rendering—users decide exactly when and how to render.
  • No UI toolkit logic—just buffers, events, and explicit synchronization.
  • Works with any renderer (OpenGL, Skia, Clay, custom software) as long as it can target the provided GL context.
  • Deterministic timing: a single swap_buffers() call per loop orchestrates every commit that needs to happen.

Roadmap

  • ✅ Global render loop & commit model
  • ✅ Fence strategy plus refresh-rate grouping metadata
  • ✅ 3-state display mode system
  • ✅ Complete Monitor::swap_buffers() implementation
  • 🚧 Cursor plane API

About

EasyDRM - A minimal, GLFW-like Rust framework for direct rendering on Linux using DRM/KMS + GBM + EGL.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages