Skip to content

Commit ac0b7f5

Browse files
authored
feat: Add a DRM/KMS backend
This adds a DRM/KMS based backend to the system, as per #42. This system finds a CRTC and a connector, then uses that to create a frame buffer and a DUMB buffer that it can render to. There's much more to do, and is left as an exercise for anyone with a significant DRM-based use case to pick up and fix. Signed-off-by: John Nunley <[email protected]>
1 parent 2689cec commit ac0b7f5

File tree

7 files changed

+665
-3
lines changed

7 files changed

+665
-3
lines changed

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# Apple platforms
55
/src/cg.rs @madsmtm
66

7+
# DRM/KMS (no maintainer)
8+
/src/kms.rs
9+
710
# Redox
811
/src/orbital.rs @jackpot51
912

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ jobs:
4343
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
4444
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen" }
4545
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
46+
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" }
4647
- { target: x86_64-unknown-redox, os: ubuntu-latest, }
4748
- { target: x86_64-unknown-freebsd, os: ubuntu-latest, }
48-
- { target: x86_64-unknown-netbsd, os: ubuntu-latest, }
49+
- { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" }
4950
- { target: x86_64-apple-darwin, os: macos-latest, }
5051
- { target: wasm32-unknown-unknown, os: ubuntu-latest, }
5152
include:

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ name = "buffer_mut"
1717
harness = false
1818

1919
[features]
20-
default = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"]
20+
default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"]
21+
kms = ["bytemuck", "drm", "drm-sys", "nix"]
2122
wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"]
2223
wayland-dlopen = ["wayland-sys/dlopen"]
2324
x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"]
@@ -30,6 +31,8 @@ raw-window-handle = "0.5.0"
3031
[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies]
3132
as-raw-xcb-connection = { version = "1.0.0", optional = true }
3233
bytemuck = { version = "1.12.3", optional = true }
34+
drm = { version = "0.9.0", default-features = false, optional = true }
35+
drm-sys = { version = "0.4.0", default-features = false, optional = true }
3336
memmap2 = { version = "0.7.1", optional = true }
3437
nix = { version = "0.26.1", optional = true }
3538
tiny-xlib = { version = "0.2.1", optional = true }
@@ -76,6 +79,7 @@ redox_syscall = "0.3"
7679
cfg_aliases = "0.1.1"
7780

7881
[dev-dependencies]
82+
colorous = "1.0.12"
7983
criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] }
8084
instant = "0.1.12"
8185
winit = "0.28.1"
@@ -95,6 +99,9 @@ rayon = "1.5.1"
9599
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
96100
wasm-bindgen-test = "0.3"
97101

102+
[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dev-dependencies]
103+
rustix = { version = "0.38.8", features = ["event"] }
104+
98105
[workspace]
99106
members = [
100107
"run-wasm",

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
fn main() {
22
cfg_aliases::cfg_aliases! {
33
free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))) },
4+
kms_platform: { all(feature = "kms", free_unix, not(target_arch = "wasm32")) },
45
x11_platform: { all(feature = "x11", free_unix, not(target_arch = "wasm32")) },
56
wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) },
67
}

examples/drm.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
//! Example of using softbuffer with drm-rs.
2+
3+
#[cfg(kms_platform)]
4+
mod imple {
5+
use drm::control::{connector, Device as CtrlDevice, Event, ModeTypeFlags, PlaneType};
6+
use drm::Device;
7+
8+
use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle};
9+
use softbuffer::{Context, Surface};
10+
11+
use std::num::NonZeroU32;
12+
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
13+
use std::path::Path;
14+
use std::time::{Duration, Instant};
15+
16+
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
17+
// Open a new device.
18+
let device = Card::find()?;
19+
20+
// Create the softbuffer context.
21+
let context = unsafe {
22+
Context::from_raw({
23+
let mut handle = DrmDisplayHandle::empty();
24+
handle.fd = device.as_fd().as_raw_fd();
25+
handle.into()
26+
})
27+
}?;
28+
29+
// Get the DRM handles.
30+
let handles = device.resource_handles()?;
31+
32+
// Get the list of connectors and CRTCs.
33+
let connectors = handles
34+
.connectors()
35+
.iter()
36+
.map(|&con| device.get_connector(con, true))
37+
.collect::<Result<Vec<_>, _>>()?;
38+
let crtcs = handles
39+
.crtcs()
40+
.iter()
41+
.map(|&crtc| device.get_crtc(crtc))
42+
.collect::<Result<Vec<_>, _>>()?;
43+
44+
// Find a connected crtc.
45+
let con = connectors
46+
.iter()
47+
.find(|con| con.state() == connector::State::Connected)
48+
.ok_or("No connected connectors")?;
49+
50+
// Get the first CRTC.
51+
let crtc = crtcs.first().ok_or("No CRTCs")?;
52+
53+
// Find a mode to use.
54+
let mode = con
55+
.modes()
56+
.iter()
57+
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
58+
.or_else(|| con.modes().first())
59+
.ok_or("No modes")?;
60+
61+
// Look for a primary plane compatible with our CRTC.
62+
let planes = device.plane_handles()?;
63+
let planes = planes
64+
.iter()
65+
.filter(|&&plane| {
66+
device.get_plane(plane).map_or(false, |plane| {
67+
let crtcs = handles.filter_crtcs(plane.possible_crtcs());
68+
crtcs.contains(&crtc.handle())
69+
})
70+
})
71+
.collect::<Vec<_>>();
72+
73+
// Find the first primary plane or take the first one period.
74+
let plane = planes
75+
.iter()
76+
.find(|&&&plane| {
77+
if let Ok(props) = device.get_properties(plane) {
78+
let (ids, vals) = props.as_props_and_values();
79+
for (&id, &val) in ids.iter().zip(vals.iter()) {
80+
if let Ok(info) = device.get_property(id) {
81+
if info.name().to_str().map_or(false, |x| x == "type") {
82+
return val == PlaneType::Primary as u32 as u64;
83+
}
84+
}
85+
}
86+
}
87+
88+
false
89+
})
90+
.or(planes.first())
91+
.ok_or("No planes")?;
92+
93+
// Create the surface on top of this plane.
94+
// Note: This requires root on DRM/KMS.
95+
let mut surface = unsafe {
96+
Surface::from_raw(&context, {
97+
let mut handle = DrmWindowHandle::empty();
98+
handle.plane = (**plane).into();
99+
handle.into()
100+
})
101+
}?;
102+
103+
// Resize the surface.
104+
let (width, height) = mode.size();
105+
surface.resize(
106+
NonZeroU32::new(width as u32).unwrap(),
107+
NonZeroU32::new(height as u32).unwrap(),
108+
)?;
109+
110+
// Start drawing to it.
111+
let start = Instant::now();
112+
let mut tick = 0;
113+
while Instant::now().duration_since(start) < Duration::from_secs(2) {
114+
tick += 1;
115+
println!("Drawing tick {tick}");
116+
117+
// Start drawing.
118+
let mut buffer = surface.buffer_mut()?;
119+
draw_to_buffer(&mut buffer, tick);
120+
buffer.present()?;
121+
122+
// Wait for the page flip to happen.
123+
rustix::event::poll(
124+
&mut [rustix::event::PollFd::new(
125+
&device,
126+
rustix::event::PollFlags::IN,
127+
)],
128+
-1,
129+
)?;
130+
131+
// Receive the events.
132+
let events = device.receive_events()?;
133+
println!("Got some events...");
134+
for event in events {
135+
match event {
136+
Event::PageFlip(_) => {
137+
println!("Page flip event.");
138+
}
139+
Event::Vblank(_) => {
140+
println!("Vblank event.");
141+
}
142+
_ => {
143+
println!("Unknown event.");
144+
}
145+
}
146+
}
147+
}
148+
149+
Ok(())
150+
}
151+
152+
fn draw_to_buffer(buf: &mut [u32], tick: usize) {
153+
let scale = colorous::SINEBOW;
154+
let mut i = (tick as f64) / 20.0;
155+
while i > 1.0 {
156+
i -= 1.0;
157+
}
158+
159+
let color = scale.eval_continuous(i);
160+
let pixel = (color.r as u32) << 16 | (color.g as u32) << 8 | (color.b as u32);
161+
buf.fill(pixel);
162+
}
163+
164+
struct Card(std::fs::File);
165+
166+
impl Card {
167+
fn find() -> Result<Card, Box<dyn std::error::Error>> {
168+
for i in 0..10 {
169+
let path = format!("/dev/dri/card{i}");
170+
let device = Card::open(path)?;
171+
172+
// Only use it if it has connectors.
173+
let handles = match device.resource_handles() {
174+
Ok(handles) => handles,
175+
Err(_) => continue,
176+
};
177+
178+
if handles
179+
.connectors
180+
.iter()
181+
.filter_map(|c| device.get_connector(*c, false).ok())
182+
.any(|c| c.state() == connector::State::Connected)
183+
{
184+
return Ok(device);
185+
}
186+
}
187+
188+
Err("No DRM device found".into())
189+
}
190+
191+
fn open(path: impl AsRef<Path>) -> Result<Card, Box<dyn std::error::Error>> {
192+
let file = std::fs::OpenOptions::new()
193+
.read(true)
194+
.write(true)
195+
.open(path)?;
196+
Ok(Card(file))
197+
}
198+
}
199+
200+
impl AsFd for Card {
201+
fn as_fd(&self) -> BorrowedFd<'_> {
202+
self.0.as_fd()
203+
}
204+
}
205+
206+
impl Device for Card {}
207+
impl CtrlDevice for Card {}
208+
}
209+
210+
#[cfg(not(kms_platform))]
211+
mod imple {
212+
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
213+
eprintln!("This example requires the `kms` feature.");
214+
Ok(())
215+
}
216+
}
217+
218+
fn main() -> Result<(), Box<dyn std::error::Error>> {
219+
imple::entry()
220+
}

0 commit comments

Comments
 (0)