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 <dev@notgull.net>
This commit is contained in:
parent
2689cec2ca
commit
ac0b7f5e14
7 changed files with 665 additions and 3 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
|
@ -4,6 +4,9 @@
|
||||||
# Apple platforms
|
# Apple platforms
|
||||||
/src/cg.rs @madsmtm
|
/src/cg.rs @madsmtm
|
||||||
|
|
||||||
|
# DRM/KMS (no maintainer)
|
||||||
|
/src/kms.rs
|
||||||
|
|
||||||
# Redox
|
# Redox
|
||||||
/src/orbital.rs @jackpot51
|
/src/orbital.rs @jackpot51
|
||||||
|
|
||||||
|
|
|
||||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -43,9 +43,10 @@ jobs:
|
||||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
- { 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: "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: "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-redox, os: ubuntu-latest, }
|
||||||
- { target: x86_64-unknown-freebsd, 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: x86_64-apple-darwin, os: macos-latest, }
|
||||||
- { target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
- { target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||||
include:
|
include:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ name = "buffer_mut"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[features]
|
[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 = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"]
|
||||||
wayland-dlopen = ["wayland-sys/dlopen"]
|
wayland-dlopen = ["wayland-sys/dlopen"]
|
||||||
x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"]
|
x11 = ["as-raw-xcb-connection", "bytemuck", "nix", "tiny-xlib", "x11rb"]
|
||||||
|
|
@ -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]
|
[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 }
|
as-raw-xcb-connection = { version = "1.0.0", optional = true }
|
||||||
bytemuck = { version = "1.12.3", 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 }
|
memmap2 = { version = "0.7.1", optional = true }
|
||||||
nix = { version = "0.26.1", optional = true }
|
nix = { version = "0.26.1", optional = true }
|
||||||
tiny-xlib = { version = "0.2.1", optional = true }
|
tiny-xlib = { version = "0.2.1", optional = true }
|
||||||
|
|
@ -76,6 +79,7 @@ redox_syscall = "0.3"
|
||||||
cfg_aliases = "0.1.1"
|
cfg_aliases = "0.1.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
colorous = "1.0.12"
|
||||||
criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] }
|
criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] }
|
||||||
instant = "0.1.12"
|
instant = "0.1.12"
|
||||||
winit = "0.28.1"
|
winit = "0.28.1"
|
||||||
|
|
@ -95,6 +99,9 @@ rayon = "1.5.1"
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3"
|
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]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"run-wasm",
|
"run-wasm",
|
||||||
|
|
|
||||||
1
build.rs
1
build.rs
|
|
@ -1,6 +1,7 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
cfg_aliases::cfg_aliases! {
|
cfg_aliases::cfg_aliases! {
|
||||||
free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))) },
|
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")) },
|
x11_platform: { all(feature = "x11", free_unix, not(target_arch = "wasm32")) },
|
||||||
wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) },
|
wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
220
examples/drm.rs
Normal file
220
examples/drm.rs
Normal file
|
|
@ -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.
|
||||||
|
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() {
|
||||||
|
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.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
imple::entry()
|
||||||
|
}
|
||||||
409
src/kms.rs
Normal file
409
src/kms.rs
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
//! Backend for DRM/KMS for raw rendering directly to the screen.
|
||||||
|
//!
|
||||||
|
//! This strategy uses dumb buffers for rendering.
|
||||||
|
|
||||||
|
use drm::buffer::{Buffer, DrmFourcc};
|
||||||
|
use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};
|
||||||
|
use drm::control::{connector, crtc, framebuffer, plane, Device as CtrlDevice, PageFlipFlags};
|
||||||
|
use drm::Device;
|
||||||
|
|
||||||
|
use raw_window_handle::{DrmDisplayHandle, DrmWindowHandle};
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::error::{SoftBufferError, SwResultExt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct KmsDisplayImpl {
|
||||||
|
/// The underlying raw device file descriptor.
|
||||||
|
///
|
||||||
|
/// Once rwh v0.6 support is merged, this an be made safe. Until then,
|
||||||
|
/// we use this hacky workaround, since this FD's validity is guaranteed by
|
||||||
|
/// the unsafe constructor.
|
||||||
|
fd: BorrowedFd<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFd for KmsDisplayImpl {
|
||||||
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||||
|
self.fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device for KmsDisplayImpl {}
|
||||||
|
impl CtrlDevice for KmsDisplayImpl {}
|
||||||
|
|
||||||
|
impl KmsDisplayImpl {
|
||||||
|
/// SAFETY: The underlying fd must not outlive the display.
|
||||||
|
pub(crate) unsafe fn new(handle: DrmDisplayHandle) -> Result<KmsDisplayImpl, SoftBufferError> {
|
||||||
|
let fd = handle.fd;
|
||||||
|
if fd == -1 {
|
||||||
|
return Err(SoftBufferError::IncompleteDisplayHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Invariants guaranteed by the user.
|
||||||
|
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
|
||||||
|
|
||||||
|
Ok(KmsDisplayImpl { fd })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All the necessary types for the Drm/Kms backend.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct KmsImpl {
|
||||||
|
/// The display implementation.
|
||||||
|
display: Rc<KmsDisplayImpl>,
|
||||||
|
|
||||||
|
/// The connectors to use.
|
||||||
|
connectors: Vec<connector::Handle>,
|
||||||
|
|
||||||
|
/// The CRTC to render to.
|
||||||
|
crtc: crtc::Info,
|
||||||
|
|
||||||
|
/// The dumb buffer we're using as a buffer.
|
||||||
|
buffer: Option<Buffers>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Buffers {
|
||||||
|
/// The involved set of buffers.
|
||||||
|
buffers: [SharedBuffer; 2],
|
||||||
|
|
||||||
|
/// Whether to use the first buffer or the second buffer as the front buffer.
|
||||||
|
first_is_front: bool,
|
||||||
|
|
||||||
|
/// A buffer full of zeroes.
|
||||||
|
zeroes: Box<[u32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The buffer implementation.
|
||||||
|
pub(crate) struct BufferImpl<'a> {
|
||||||
|
/// The mapping of the dump buffer.
|
||||||
|
mapping: DumbMapping<'a>,
|
||||||
|
|
||||||
|
/// The framebuffer object of the current front buffer.
|
||||||
|
front_fb: framebuffer::Handle,
|
||||||
|
|
||||||
|
/// The CRTC handle.
|
||||||
|
crtc_handle: crtc::Handle,
|
||||||
|
|
||||||
|
/// This is used to change the front buffer.
|
||||||
|
first_is_front: &'a mut bool,
|
||||||
|
|
||||||
|
/// Buffer full of zeroes.
|
||||||
|
zeroes: &'a [u32],
|
||||||
|
|
||||||
|
/// The current size.
|
||||||
|
size: (NonZeroU32, NonZeroU32),
|
||||||
|
|
||||||
|
/// The display implementation.
|
||||||
|
display: &'a KmsDisplayImpl,
|
||||||
|
|
||||||
|
/// Age of the front buffer.
|
||||||
|
front_age: &'a mut u8,
|
||||||
|
|
||||||
|
/// Age of the back buffer.
|
||||||
|
back_age: &'a mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The combined frame buffer and dumb buffer.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SharedBuffer {
|
||||||
|
/// The frame buffer.
|
||||||
|
fb: framebuffer::Handle,
|
||||||
|
|
||||||
|
/// The dumb buffer.
|
||||||
|
db: DumbBuffer,
|
||||||
|
|
||||||
|
/// The age of this buffer.
|
||||||
|
age: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KmsImpl {
|
||||||
|
/// Create a new KMS backend.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The plane must be valid for the lifetime of the backend.
|
||||||
|
pub(crate) unsafe fn new(
|
||||||
|
window_handle: DrmWindowHandle,
|
||||||
|
display: Rc<KmsDisplayImpl>,
|
||||||
|
) -> Result<Self, SoftBufferError> {
|
||||||
|
log::trace!("new: window_handle={:X}", window_handle.plane);
|
||||||
|
|
||||||
|
// Make sure that the window handle is valid.
|
||||||
|
let plane_handle = match NonZeroU32::new(window_handle.plane) {
|
||||||
|
Some(handle) => plane::Handle::from(handle),
|
||||||
|
None => return Err(SoftBufferError::IncompleteWindowHandle),
|
||||||
|
};
|
||||||
|
|
||||||
|
let plane_info = display
|
||||||
|
.get_plane(plane_handle)
|
||||||
|
.swbuf_err("failed to get plane info")?;
|
||||||
|
let handles = display
|
||||||
|
.resource_handles()
|
||||||
|
.swbuf_err("failed to get resource handles")?;
|
||||||
|
|
||||||
|
// Use either the attached CRTC or the primary CRTC.
|
||||||
|
let crtc = {
|
||||||
|
let handle = match plane_info.crtc() {
|
||||||
|
Some(crtc) => crtc,
|
||||||
|
None => {
|
||||||
|
log::warn!("no CRTC attached to plane, falling back to primary CRTC");
|
||||||
|
handles
|
||||||
|
.filter_crtcs(plane_info.possible_crtcs())
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
.swbuf_err("failed to find a primary CRTC")?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get info about the CRTC.
|
||||||
|
display
|
||||||
|
.get_crtc(handle)
|
||||||
|
.swbuf_err("failed to get CRTC info")?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Figure out all of the encoders that are attached to this CRTC.
|
||||||
|
let encoders = handles
|
||||||
|
.encoders
|
||||||
|
.iter()
|
||||||
|
.flat_map(|handle| display.get_encoder(*handle))
|
||||||
|
.filter(|encoder| encoder.crtc() == Some(crtc.handle()))
|
||||||
|
.map(|encoder| encoder.handle())
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
// Get a list of every connector that the CRTC is connected to via encoders.
|
||||||
|
let connectors = handles
|
||||||
|
.connectors
|
||||||
|
.iter()
|
||||||
|
.flat_map(|handle| display.get_connector(*handle, false))
|
||||||
|
.filter(|connector| {
|
||||||
|
connector
|
||||||
|
.current_encoder()
|
||||||
|
.map_or(false, |encoder| encoders.contains(&encoder))
|
||||||
|
})
|
||||||
|
.map(|info| info.handle())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
crtc,
|
||||||
|
connectors,
|
||||||
|
display,
|
||||||
|
buffer: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resize the internal buffer to the given size.
|
||||||
|
pub(crate) fn resize(
|
||||||
|
&mut self,
|
||||||
|
width: NonZeroU32,
|
||||||
|
height: NonZeroU32,
|
||||||
|
) -> Result<(), SoftBufferError> {
|
||||||
|
// Don't resize if we don't have to.
|
||||||
|
if let Some(buffer) = &self.buffer {
|
||||||
|
let (buffer_width, buffer_height) = buffer.size();
|
||||||
|
if buffer_width == width && buffer_height == height {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new buffer set.
|
||||||
|
let front_buffer = SharedBuffer::new(&self.display, width, height)?;
|
||||||
|
let back_buffer = SharedBuffer::new(&self.display, width, height)?;
|
||||||
|
|
||||||
|
self.buffer = Some(Buffers {
|
||||||
|
first_is_front: true,
|
||||||
|
buffers: [front_buffer, back_buffer],
|
||||||
|
zeroes: vec![0; width.get() as usize * height.get() as usize].into_boxed_slice(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the buffer from the window.
|
||||||
|
pub(crate) fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
|
||||||
|
// TODO: Implement this!
|
||||||
|
Err(SoftBufferError::Unimplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the buffer.
|
||||||
|
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl<'_>, SoftBufferError> {
|
||||||
|
// Map the dumb buffer.
|
||||||
|
let set = self
|
||||||
|
.buffer
|
||||||
|
.as_mut()
|
||||||
|
.expect("Must set size of surface before calling `buffer_mut()`");
|
||||||
|
|
||||||
|
let size = set.size();
|
||||||
|
|
||||||
|
let [first_buffer, second_buffer] = &mut set.buffers;
|
||||||
|
let (front_buffer, back_buffer) = if set.first_is_front {
|
||||||
|
(first_buffer, second_buffer)
|
||||||
|
} else {
|
||||||
|
(second_buffer, first_buffer)
|
||||||
|
};
|
||||||
|
|
||||||
|
let front_fb = front_buffer.fb;
|
||||||
|
let front_age = &mut front_buffer.age;
|
||||||
|
let back_age = &mut back_buffer.age;
|
||||||
|
|
||||||
|
let mapping = self
|
||||||
|
.display
|
||||||
|
.map_dumb_buffer(&mut front_buffer.db)
|
||||||
|
.swbuf_err("failed to map dumb buffer")?;
|
||||||
|
|
||||||
|
Ok(BufferImpl {
|
||||||
|
mapping,
|
||||||
|
size,
|
||||||
|
first_is_front: &mut set.first_is_front,
|
||||||
|
front_fb,
|
||||||
|
crtc_handle: self.crtc.handle(),
|
||||||
|
display: &self.display,
|
||||||
|
zeroes: &set.zeroes,
|
||||||
|
front_age,
|
||||||
|
back_age,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for KmsImpl {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Map the CRTC to the information that was there before.
|
||||||
|
self.display
|
||||||
|
.set_crtc(
|
||||||
|
self.crtc.handle(),
|
||||||
|
self.crtc.framebuffer(),
|
||||||
|
self.crtc.position(),
|
||||||
|
&self.connectors,
|
||||||
|
self.crtc.mode(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferImpl<'_> {
|
||||||
|
#[inline]
|
||||||
|
pub fn pixels(&self) -> &[u32] {
|
||||||
|
// drm-rs doesn't let us have the immutable reference... so just use a bunch of zeroes.
|
||||||
|
// TODO: There has to be a better way of doing this!
|
||||||
|
self.zeroes
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn pixels_mut(&mut self) -> &mut [u32] {
|
||||||
|
bytemuck::cast_slice_mut(self.mapping.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn age(&self) -> u8 {
|
||||||
|
*self.front_age
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn present_with_damage(self, damage: &[crate::Rect]) -> Result<(), SoftBufferError> {
|
||||||
|
let rectangles = damage
|
||||||
|
.iter()
|
||||||
|
.map(|&rect| {
|
||||||
|
let err = || SoftBufferError::DamageOutOfRange { rect };
|
||||||
|
Ok(drm_sys::drm_clip_rect {
|
||||||
|
x1: rect.x.try_into().map_err(|_| err())?,
|
||||||
|
y1: rect.y.try_into().map_err(|_| err())?,
|
||||||
|
x2: rect
|
||||||
|
.x
|
||||||
|
.checked_add(rect.width.get())
|
||||||
|
.and_then(|x| x.try_into().ok())
|
||||||
|
.ok_or_else(err)?,
|
||||||
|
y2: rect
|
||||||
|
.y
|
||||||
|
.checked_add(rect.height.get())
|
||||||
|
.and_then(|y| y.try_into().ok())
|
||||||
|
.ok_or_else(err)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
// Dirty the framebuffer with out damage rectangles.
|
||||||
|
//
|
||||||
|
// Some drivers don't support this, so we just ignore the `ENOSYS` error.
|
||||||
|
// TODO: It would be nice to not have to heap-allocate the above rectangles if we know that
|
||||||
|
// this is going to fail. Low hanging fruit PR: add a flag that's set to false if this
|
||||||
|
// returns `ENOSYS` and check that before allocating the above and running this.
|
||||||
|
match self.display.dirty_framebuffer(self.front_fb, &rectangles) {
|
||||||
|
Ok(())
|
||||||
|
| Err(drm::SystemError::Unknown {
|
||||||
|
errno: nix::errno::Errno::ENOSYS,
|
||||||
|
}) => {}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(SoftBufferError::PlatformError(
|
||||||
|
Some("failed to dirty framebuffer".into()),
|
||||||
|
Some(e.into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the buffers.
|
||||||
|
// TODO: Use atomic commits here!
|
||||||
|
self.display
|
||||||
|
.page_flip(self.crtc_handle, self.front_fb, PageFlipFlags::EVENT, None)
|
||||||
|
.swbuf_err("failed to page flip")?;
|
||||||
|
|
||||||
|
// Flip the front and back buffers.
|
||||||
|
*self.first_is_front = !*self.first_is_front;
|
||||||
|
|
||||||
|
// Set the ages.
|
||||||
|
*self.front_age = 1;
|
||||||
|
if *self.back_age != 0 {
|
||||||
|
*self.back_age += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn present(self) -> Result<(), SoftBufferError> {
|
||||||
|
let (width, height) = self.size;
|
||||||
|
self.present_with_damage(&[crate::Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedBuffer {
|
||||||
|
/// Create a new buffer set.
|
||||||
|
pub(crate) fn new(
|
||||||
|
display: &KmsDisplayImpl,
|
||||||
|
width: NonZeroU32,
|
||||||
|
height: NonZeroU32,
|
||||||
|
) -> Result<Self, SoftBufferError> {
|
||||||
|
let db = display
|
||||||
|
.create_dumb_buffer((width.get(), height.get()), DrmFourcc::Xrgb8888, 32)
|
||||||
|
.swbuf_err("failed to create dumb buffer")?;
|
||||||
|
let fb = display
|
||||||
|
.add_framebuffer(&db, 24, 32)
|
||||||
|
.swbuf_err("failed to add framebuffer")?;
|
||||||
|
|
||||||
|
Ok(SharedBuffer { fb, db, age: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of this buffer.
|
||||||
|
pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) {
|
||||||
|
let (width, height) = self.db.size();
|
||||||
|
|
||||||
|
NonZeroU32::new(width)
|
||||||
|
.and_then(|width| NonZeroU32::new(height).map(|height| (width, height)))
|
||||||
|
.expect("buffer size is zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buffers {
|
||||||
|
/// Get the size of this buffer.
|
||||||
|
pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) {
|
||||||
|
self.buffers[0].size()
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/lib.rs
23
src/lib.rs
|
|
@ -10,6 +10,8 @@ extern crate core;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod cg;
|
mod cg;
|
||||||
|
#[cfg(kms_platform)]
|
||||||
|
mod kms;
|
||||||
#[cfg(target_os = "redox")]
|
#[cfg(target_os = "redox")]
|
||||||
mod orbital;
|
mod orbital;
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
|
|
@ -27,7 +29,7 @@ mod util;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
#[cfg(any(wayland_platform, x11_platform))]
|
#[cfg(any(wayland_platform, x11_platform, kms_platform))]
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub use error::SoftBufferError;
|
pub use error::SoftBufferError;
|
||||||
|
|
@ -73,6 +75,7 @@ macro_rules! make_dispatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)] // it's boxed anyways
|
||||||
enum SurfaceDispatch {
|
enum SurfaceDispatch {
|
||||||
$(
|
$(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
|
|
@ -174,6 +177,8 @@ make_dispatch! {
|
||||||
X11(Rc<x11::X11DisplayImpl>, x11::X11Impl, x11::BufferImpl<'a>),
|
X11(Rc<x11::X11DisplayImpl>, x11::X11Impl, x11::BufferImpl<'a>),
|
||||||
#[cfg(wayland_platform)]
|
#[cfg(wayland_platform)]
|
||||||
Wayland(Rc<wayland::WaylandDisplayImpl>, wayland::WaylandImpl, wayland::BufferImpl<'a>),
|
Wayland(Rc<wayland::WaylandDisplayImpl>, wayland::WaylandImpl, wayland::BufferImpl<'a>),
|
||||||
|
#[cfg(kms_platform)]
|
||||||
|
Kms(Rc<kms::KmsDisplayImpl>, kms::KmsImpl, kms::BufferImpl<'a>),
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
Win32((), win32::Win32Impl, win32::BufferImpl<'a>),
|
Win32((), win32::Win32Impl, win32::BufferImpl<'a>),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
@ -213,6 +218,10 @@ impl Context {
|
||||||
RawDisplayHandle::Wayland(wayland_handle) => unsafe {
|
RawDisplayHandle::Wayland(wayland_handle) => unsafe {
|
||||||
ContextDispatch::Wayland(Rc::new(wayland::WaylandDisplayImpl::new(wayland_handle)?))
|
ContextDispatch::Wayland(Rc::new(wayland::WaylandDisplayImpl::new(wayland_handle)?))
|
||||||
},
|
},
|
||||||
|
#[cfg(kms_platform)]
|
||||||
|
RawDisplayHandle::Drm(drm_handle) => unsafe {
|
||||||
|
ContextDispatch::Kms(Rc::new(kms::KmsDisplayImpl::new(drm_handle)?))
|
||||||
|
},
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()),
|
RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
@ -303,6 +312,12 @@ impl Surface {
|
||||||
) => SurfaceDispatch::Wayland(unsafe {
|
) => SurfaceDispatch::Wayland(unsafe {
|
||||||
wayland::WaylandImpl::new(wayland_window_handle, wayland_display_impl.clone())?
|
wayland::WaylandImpl::new(wayland_window_handle, wayland_display_impl.clone())?
|
||||||
}),
|
}),
|
||||||
|
#[cfg(kms_platform)]
|
||||||
|
(ContextDispatch::Kms(kms_display_impl), RawWindowHandle::Drm(drm_window_handle)) => {
|
||||||
|
SurfaceDispatch::Kms(unsafe {
|
||||||
|
kms::KmsImpl::new(drm_window_handle, kms_display_impl.clone())?
|
||||||
|
})
|
||||||
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
(ContextDispatch::Win32(()), RawWindowHandle::Win32(win32_handle)) => {
|
(ContextDispatch::Win32(()), RawWindowHandle::Win32(win32_handle)) => {
|
||||||
SurfaceDispatch::Win32(unsafe { win32::Win32Impl::new(&win32_handle)? })
|
SurfaceDispatch::Win32(unsafe { win32::Win32Impl::new(&win32_handle)? })
|
||||||
|
|
@ -361,6 +376,12 @@ impl Surface {
|
||||||
/// Return a [`Buffer`] that the next frame should be rendered into. The size must
|
/// Return a [`Buffer`] that the next frame should be rendered into. The size must
|
||||||
/// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or
|
/// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or
|
||||||
/// may contain a previous frame. Call [`Buffer::age`] to determine this.
|
/// may contain a previous frame. Call [`Buffer::age`] to determine this.
|
||||||
|
///
|
||||||
|
/// ## Platform Dependent Behavior
|
||||||
|
///
|
||||||
|
/// - On DRM/KMS, there is no reliable and sound way to wait for the page flip to happen from within
|
||||||
|
/// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before
|
||||||
|
/// sending another frame.
|
||||||
pub fn buffer_mut(&mut self) -> Result<Buffer, SoftBufferError> {
|
pub fn buffer_mut(&mut self) -> Result<Buffer, SoftBufferError> {
|
||||||
Ok(Buffer {
|
Ok(Buffer {
|
||||||
buffer_impl: self.surface_impl.buffer_mut()?,
|
buffer_impl: self.surface_impl.buffer_mut()?,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue