Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a DRM/KMS backend #135

Merged
merged 15 commits into from
Aug 12, 2023
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# Apple platforms
/src/cg.rs @madsmtm

# DRM/KMS (no maintainer)
/src/kms.rs

# Redox
/src/orbital.rs @jackpot51

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ jobs:
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen" }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" }
- { target: x86_64-unknown-redox, os: ubuntu-latest, }
- { target: x86_64-unknown-freebsd, os: ubuntu-latest, }
- { target: x86_64-unknown-netbsd, os: ubuntu-latest, }
- { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: wasm32-unknown-unknown, os: ubuntu-latest, }
include:
Expand Down
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ name = "buffer_mut"
harness = false

[features]
default = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"]
default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"]
kms = ["bytemuck", "drm", "drm-sys", "nix"]
wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"]
wayland-dlopen = ["wayland-sys/dlopen"]
x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"]
Expand All @@ -30,6 +31,8 @@ raw-window-handle = "0.5.0"
[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies]
as-raw-xcb-connection = { version = "1.0.0", optional = true }
bytemuck = { version = "1.12.3", optional = true }
drm = { version = "0.9.0", default-features = false, optional = true }
drm-sys = { version = "0.4.0", default-features = false, optional = true }
memmap2 = { version = "0.7.1", optional = true }
nix = { version = "0.26.1", optional = true }
tiny-xlib = { version = "0.2.1", optional = true }
Expand Down Expand Up @@ -76,6 +79,7 @@ redox_syscall = "0.3"
cfg_aliases = "0.1.1"

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

[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dev-dependencies]
rustix = { version = "0.38.8", features = ["event"] }

[workspace]
members = [
"run-wasm",
Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
fn main() {
cfg_aliases::cfg_aliases! {
free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))) },
kms_platform: { all(feature = "kms", free_unix, not(target_arch = "wasm32")) },
x11_platform: { all(feature = "x11", free_unix, not(target_arch = "wasm32")) },
wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) },
}
Expand Down
220 changes: 220 additions & 0 deletions examples/drm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
//! Example of using softbuffer with drm-rs.

#[cfg(kms_platform)]
mod imple {
use drm::control::{connector, Device as CtrlDevice, Event, ModeTypeFlags, PlaneType};
use drm::Device;

use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle};
use softbuffer::{Context, Surface};

use std::num::NonZeroU32;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
use std::path::Path;
use std::time::{Duration, Instant};

pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
// Open a new device.
let device = Card::find()?;

// Create the softbuffer context.
let context = unsafe {
Context::from_raw({
let mut handle = DrmDisplayHandle::empty();
handle.fd = device.as_fd().as_raw_fd();
handle.into()
})
}?;

// Get the DRM handles.
let handles = device.resource_handles()?;

// Get the list of connectors and CRTCs.
let connectors = handles
.connectors()
.iter()
.map(|&con| device.get_connector(con, true))
.collect::<Result<Vec<_>, _>>()?;
let crtcs = handles
.crtcs()
.iter()
.map(|&crtc| device.get_crtc(crtc))
.collect::<Result<Vec<_>, _>>()?;

// Find a connected crtc.
let con = connectors
.iter()
.find(|con| con.state() == connector::State::Connected)
.ok_or("No connected connectors")?;

// Get the first CRTC.
let crtc = crtcs.first().ok_or("No CRTCs")?;

// Find a mode to use.
let mode = con
.modes()
.iter()
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
.or_else(|| con.modes().first())
.ok_or("No modes")?;

// Look for a primary plane compatible with our CRTC.
let planes = device.plane_handles()?;
let planes = planes
.iter()
.filter(|&&plane| {
device.get_plane(plane).map_or(false, |plane| {
let crtcs = handles.filter_crtcs(plane.possible_crtcs());
crtcs.contains(&crtc.handle())
})
})
.collect::<Vec<_>>();

// Find the first primary plane or take the first one period.
let plane = planes
.iter()
.find(|&&&plane| {
if let Ok(props) = device.get_properties(plane) {
let (ids, vals) = props.as_props_and_values();
for (&id, &val) in ids.iter().zip(vals.iter()) {
if let Ok(info) = device.get_property(id) {
if info.name().to_str().map_or(false, |x| x == "type") {
return val == PlaneType::Primary as u32 as u64;
}
}
}
}

false
})
.or(planes.first())
.ok_or("No planes")?;

// Create the surface on top of this plane.
// Note: This requires root on DRM/KMS.
let mut surface = unsafe {
Surface::from_raw(&context, {
let mut handle = DrmWindowHandle::empty();
handle.plane = (**plane).into();
handle.into()
})
}?;

// Resize the surface.
let (width, height) = mode.size();
surface.resize(
NonZeroU32::new(width as u32).unwrap(),
NonZeroU32::new(height as u32).unwrap(),
)?;

// Start drawing to it.
let start = Instant::now();
let mut tick = 0;
while Instant::now().duration_since(start) < Duration::from_secs(2) {
tick += 1;
println!("Drawing tick {tick}");

// Start drawing.
let mut buffer = surface.buffer_mut()?;
draw_to_buffer(&mut buffer, tick);
buffer.present()?;

// Wait for the page flip to happen.
notgull marked this conversation as resolved.
Show resolved Hide resolved
rustix::event::poll(
&mut [rustix::event::PollFd::new(
&device,
rustix::event::PollFlags::IN,
)],
-1,
)?;

// Receive the events.
let events = device.receive_events()?;
println!("Got some events...");
for event in events {
match event {
Event::PageFlip(_) => {
println!("Page flip event.");
}
Event::Vblank(_) => {
println!("Vblank event.");
}
_ => {
println!("Unknown event.");
}
}
}
}

Ok(())
}

fn draw_to_buffer(buf: &mut [u32], tick: usize) {
let scale = colorous::SINEBOW;
let mut i = (tick as f64) / 20.0;
while i > 1.0 {
i -= 1.0;
}

let color = scale.eval_continuous(i);
let pixel = (color.r as u32) << 16 | (color.g as u32) << 8 | (color.b as u32);
buf.fill(pixel);
}

struct Card(std::fs::File);

impl Card {
fn find() -> Result<Card, Box<dyn std::error::Error>> {
for i in 0..10 {
let path = format!("/dev/dri/card{i}");
let device = Card::open(path)?;

// Only use it if it has connectors.
let handles = match device.resource_handles() {
notgull marked this conversation as resolved.
Show resolved Hide resolved
Ok(handles) => handles,
Err(_) => continue,
};

if handles
.connectors
.iter()
.filter_map(|c| device.get_connector(*c, false).ok())
.any(|c| c.state() == connector::State::Connected)
{
return Ok(device);
}
}

Err("No DRM device found".into())
}

fn open(path: impl AsRef<Path>) -> Result<Card, Box<dyn std::error::Error>> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path)?;
Ok(Card(file))
}
}

impl AsFd for Card {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}

impl Device for Card {}
impl CtrlDevice for Card {}
}

#[cfg(not(kms_platform))]
mod imple {
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("This example requires the `kms` feature.");
notgull marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
imple::entry()
}
Loading