aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-01-03 18:16:20 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-01-03 18:16:20 +0400
commit5cd31e5730160f9162502d2388dc00400c7a87ae (patch)
treea718ad47e0554263042ae4eebd5e49af34346732 /src
parentde3fc2def0cf3683fff7994cd294d325e4f033b2 (diff)
downloadniri-5cd31e5730160f9162502d2388dc00400c7a87ae.tar.gz
niri-5cd31e5730160f9162502d2388dc00400c7a87ae.tar.bz2
niri-5cd31e5730160f9162502d2388dc00400c7a87ae.zip
Implement multi-GPU support
Rendering always happens on the primary GPU.
Diffstat (limited to 'src')
-rw-r--r--src/backend/mod.rs10
-rw-r--r--src/backend/tty.rs627
-rw-r--r--src/handlers/compositor.rs1
-rw-r--r--src/niri.rs103
-rw-r--r--src/render_helpers.rs39
-rw-r--r--src/screenshot_ui.rs29
6 files changed, 609 insertions, 200 deletions
diff --git a/src/backend/mod.rs b/src/backend/mod.rs
index 54b504ca..66b05f77 100644
--- a/src/backend/mod.rs
+++ b/src/backend/mod.rs
@@ -5,6 +5,7 @@ use std::time::Duration;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::output::Output;
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use crate::input::CompositorMod;
use crate::Niri;
@@ -102,6 +103,13 @@ impl Backend {
}
}
+ pub fn early_import(&mut self, surface: &WlSurface) {
+ match self {
+ Backend::Tty(tty) => tty.early_import(surface),
+ Backend::Winit(_) => (),
+ }
+ }
+
#[cfg_attr(not(feature = "dbus"), allow(unused))]
pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
match self {
@@ -116,7 +124,7 @@ impl Backend {
) -> Option<smithay::backend::allocator::gbm::GbmDevice<smithay::backend::drm::DrmDeviceFd>>
{
match self {
- Backend::Tty(tty) => tty.gbm_device(),
+ Backend::Tty(tty) => tty.primary_gbm_device(),
Backend::Winit(_) => None,
}
}
diff --git a/src/backend/tty.rs b/src/backend/tty.rs
index 34798008..067b3e3e 100644
--- a/src/backend/tty.rs
+++ b/src/backend/tty.rs
@@ -1,23 +1,27 @@
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
-use std::mem;
-use std::path::{Path, PathBuf};
+use std::path::Path;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Duration;
+use std::{io, mem};
use anyhow::{anyhow, Context};
use libc::dev_t;
-use smithay::backend::allocator::dmabuf::Dmabuf;
+use smithay::backend::allocator::dmabuf::{Dmabuf, DmabufAllocator};
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
-use smithay::backend::allocator::Fourcc;
+use smithay::backend::allocator::{Format, Fourcc};
use smithay::backend::drm::compositor::{DrmCompositor, PrimaryPlaneElement};
-use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventMetadata, DrmEventTime};
+use smithay::backend::drm::{
+ DrmDevice, DrmDeviceFd, DrmEvent, DrmEventMetadata, DrmEventTime, DrmNode, NodeType,
+};
use smithay::backend::egl::context::ContextPriority;
-use smithay::backend::egl::{EGLContext, EGLDisplay};
+use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay};
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
use smithay::backend::renderer::gles::{Capability, GlesRenderer, GlesTexture};
-use smithay::backend::renderer::{DebugFlags, ImportDma, ImportEgl};
+use smithay::backend::renderer::multigpu::gbm::GbmGlesBackend;
+use smithay::backend::renderer::multigpu::{GpuManager, MultiFrame, MultiRenderer};
+use smithay::backend::renderer::{DebugFlags, ImportDma, ImportEgl, Renderer};
use smithay::backend::session::libseat::LibSeatSession;
use smithay::backend::session::{Event as SessionEvent, Session};
use smithay::backend::udev::{self, UdevBackend, UdevEvent};
@@ -31,6 +35,7 @@ use smithay::reexports::drm::control::{
use smithay::reexports::input::Libinput;
use smithay::reexports::rustix::fs::OFlags;
use smithay::reexports::wayland_protocols;
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::DeviceFd;
use smithay::wayland::dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal};
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
@@ -41,6 +46,7 @@ use wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
use super::RenderResult;
use crate::config::Config;
use crate::niri::{RedrawState, State};
+use crate::render_helpers::AsGlesRenderer;
use crate::utils::get_monotonic_time;
use crate::Niri;
@@ -51,14 +57,42 @@ pub struct Tty {
session: LibSeatSession,
udev_dispatcher: Dispatcher<'static, UdevBackend, State>,
libinput: Libinput,
- primary_gpu_path: PathBuf,
+ gpu_manager: GpuManager<GbmGlesBackend<GlesRenderer>>,
+ // DRM node corresponding to the primary GPU. May or may not be the same as
+ // primary_render_node.
+ primary_node: DrmNode,
+ // DRM render node corresponding to the primary GPU.
+ primary_render_node: DrmNode,
+ // Devices indexed by DRM node (not necessarily the render node).
+ devices: HashMap<DrmNode, OutputDevice>,
// The dma-buf global corresponds to the output device (the primary GPU). It is only `Some()`
// if we have a device corresponding to the primary GPU.
dmabuf_global: Option<DmabufGlobal>,
- output_device: Option<OutputDevice>,
+ // The allocator for the primary GPU. It is only `Some()` if we have a device corresponding to
+ // the primary GPU.
+ primary_allocator: Option<DmabufAllocator<GbmAllocator<DrmDeviceFd>>>,
connectors: Arc<Mutex<HashMap<String, Output>>>,
}
+pub type TtyRenderer<'render, 'alloc> = MultiRenderer<
+ 'render,
+ 'render,
+ 'alloc,
+ GbmGlesBackend<GlesRenderer>,
+ GbmGlesBackend<GlesRenderer>,
+>;
+
+pub type TtyFrame<'render, 'alloc, 'frame> = MultiFrame<
+ 'render,
+ 'render,
+ 'alloc,
+ 'frame,
+ GbmGlesBackend<GlesRenderer>,
+ GbmGlesBackend<GlesRenderer>,
+>;
+
+pub type TtyRendererError<'render, 'alloc> = <TtyRenderer<'render, 'alloc> as Renderer>::Error;
+
type GbmDrmCompositor = DrmCompositor<
GbmAllocator<DrmDeviceFd>,
GbmDevice<DrmDeviceFd>,
@@ -67,9 +101,8 @@ type GbmDrmCompositor = DrmCompositor<
>;
struct OutputDevice {
- id: dev_t,
token: RegistrationToken,
- gles: GlesRenderer,
+ render_node: DrmNode,
drm_scanner: DrmScanner,
surfaces: HashMap<crtc::Handle, Surface>,
// SAFETY: drop after all the objects used with them are dropped.
@@ -80,14 +113,14 @@ struct OutputDevice {
#[derive(Debug, Clone, Copy)]
struct TtyOutputState {
- device_id: dev_t,
+ node: DrmNode,
crtc: crtc::Handle,
}
struct Surface {
name: String,
compositor: GbmDrmCompositor,
- dmabuf_feedback: DmabufFeedback,
+ dmabuf_feedback: Option<SurfaceDmabufFeedback>,
/// Tracy frame that goes from vblank to vblank.
vblank_frame: Option<tracy_client::Frame>,
/// Frame name for the VBlank frame.
@@ -99,6 +132,11 @@ struct Surface {
sequence_delta_plot_name: tracy_client::PlotName,
}
+pub struct SurfaceDmabufFeedback {
+ pub render: DmabufFeedback,
+ pub scanout: DmabufFeedback,
+}
+
impl Tty {
pub fn new(config: Rc<RefCell<Config>>, event_loop: LoopHandle<'static, State>) -> Self {
let (session, notifier) = LibSeatSession::new().unwrap();
@@ -129,16 +167,46 @@ impl Tty {
})
.unwrap();
+ let config_ = config.clone();
+ let create_renderer = move |display: &EGLDisplay| {
+ let color_transforms = config_
+ .borrow()
+ .debug
+ .enable_color_transformations_capability;
+
+ let egl_context = EGLContext::new_with_priority(display, ContextPriority::High)?;
+ let gles = if color_transforms {
+ unsafe { GlesRenderer::new(egl_context)? }
+ } else {
+ let capabilities = unsafe { GlesRenderer::supported_capabilities(&egl_context) }?
+ .into_iter()
+ .filter(|c| *c != Capability::ColorTransformations);
+ unsafe { GlesRenderer::with_capabilities(egl_context, capabilities)? }
+ };
+ Ok(gles)
+ };
+ let api = GbmGlesBackend::with_factory(Box::new(create_renderer));
+ let gpu_manager = GpuManager::new(api).unwrap();
+
let primary_gpu_path = udev::primary_gpu(&seat_name).unwrap().unwrap();
+ let primary_node = DrmNode::from_path(primary_gpu_path).unwrap();
+ let primary_render_node = primary_node
+ .node_with_type(NodeType::Render)
+ .unwrap()
+ .unwrap();
+ info!("using {} as the primary GPU", primary_render_node);
Self {
config,
session,
udev_dispatcher,
libinput,
- primary_gpu_path,
+ gpu_manager,
+ primary_node,
+ primary_render_node,
+ devices: HashMap::new(),
dmabuf_global: None,
- output_device: None,
+ primary_allocator: None,
connectors: Arc::new(Mutex::new(HashMap::new())),
}
}
@@ -193,8 +261,8 @@ impl Tty {
self.libinput.suspend();
- if let Some(output_device) = &self.output_device {
- output_device.drm.pause();
+ for device in self.devices.values() {
+ device.drm.pause();
}
}
SessionEvent::ActivateSession => {
@@ -204,69 +272,85 @@ impl Tty {
error!("error resuming libinput");
}
- if let Some(output_device) = &mut self.output_device {
- // We had an output device, check if it's been removed.
- let output_device_id = output_device.id;
- if !self
- .udev_dispatcher
- .as_source_ref()
- .device_list()
- .any(|(device_id, _)| device_id == output_device_id)
- {
- // The output device, if we had any, has been removed.
- self.device_removed(output_device_id, niri);
- } else {
- // It hasn't been removed, update its state as usual.
- output_device.drm.activate();
-
- // HACK: force reset the connectors to make resuming work across
- // sleep.
- let output_device = self.output_device.as_mut().unwrap();
- let crtcs: Vec<_> = output_device
- .drm_scanner
- .crtcs()
- .map(|(conn, crtc)| (conn.clone(), crtc))
- .collect();
- for (conn, crtc) in crtcs {
- self.connector_disconnected(niri, conn, crtc);
- }
+ let mut device_list = self
+ .udev_dispatcher
+ .as_source_ref()
+ .device_list()
+ .map(|(device_id, path)| (device_id, path.to_owned()))
+ .collect::<HashMap<_, _>>();
+
+ let removed_devices = self
+ .devices
+ .keys()
+ .filter(|node| !device_list.contains_key(&node.dev_id()))
+ .copied()
+ .collect::<Vec<_>>();
+
+ let remained_devices = self
+ .devices
+ .keys()
+ .filter(|node| device_list.contains_key(&node.dev_id()))
+ .copied()
+ .collect::<Vec<_>>();
+
+ // Remove removed devices.
+ for node in removed_devices {
+ device_list.remove(&node.dev_id());
+ self.device_removed(node.dev_id(), niri);
+ }
- let output_device = self.output_device.as_mut().unwrap();
- let _ = output_device
- .drm_scanner
- .scan_connectors(&output_device.drm);
- let crtcs: Vec<_> = output_device
- .drm_scanner
- .crtcs()
- .map(|(conn, crtc)| (conn.clone(), crtc))
- .collect();
- for (conn, crtc) in crtcs {
- if let Err(err) = self.connector_connected(niri, conn, crtc) {
- warn!("error connecting connector: {err:?}");
- }
+ // Update remained devices.
+ for node in remained_devices {
+ device_list.remove(&node.dev_id());
+
+ // It hasn't been removed, update its state as usual.
+ let device = &self.devices[&node];
+ device.drm.activate();
+
+ // HACK: force reset the connectors to make resuming work across sleep.
+ let device = &self.devices[&node];
+ let crtcs: Vec<_> = device
+ .drm_scanner
+ .crtcs()
+ .map(|(conn, crtc)| (conn.clone(), crtc))
+ .collect();
+ for (conn, crtc) in crtcs {
+ self.connector_disconnected(niri, node, conn, crtc);
+ }
+
+ let device = self.devices.get_mut(&node).unwrap();
+ let _ = device.drm_scanner.scan_connectors(&device.drm);
+ let crtcs: Vec<_> = device
+ .drm_scanner
+ .crtcs()
+ .map(|(conn, crtc)| (conn.clone(), crtc))
+ .collect();
+ for (conn, crtc) in crtcs {
+ if let Err(err) = self.connector_connected(niri, node, conn, crtc) {
+ warn!("error connecting connector: {err:?}");
}
+ }
- // // Refresh the connectors.
- // self.device_changed(output_device_id, niri);
+ // // Refresh the connectors.
+ // self.device_changed(node.dev_id(), niri);
- // // Refresh the state on unchanged connectors.
- // let output_device = self.output_device.as_mut().unwrap();
- // for drm_compositor in output_device.surfaces.values_mut() {
- // if let Err(err) = drm_compositor.surface().reset_state() {
- // warn!("error resetting DRM surface state: {err}");
- // }
- // drm_compositor.reset_buffers();
- // }
+ // // Refresh the state on unchanged connectors.
+ // let device = self.devices.get_mut(&node).unwrap();
+ // for surface in device.surfaces.values_mut() {
+ // let compositor = &mut surface.compositor;
+ // if let Err(err) = compositor.surface().reset_state() {
+ // warn!("error resetting DRM surface state: {err}");
+ // }
+ // compositor.reset_buffers();
+ // }
- // niri.queue_redraw_all();
- }
- } else {
- // We didn't have an output device, check if it's been added.
- let udev_dispatcher = self.udev_dispatcher.clone();
- for (device_id, path) in udev_dispatcher.as_source_ref().device_list() {
- if let Err(err) = self.device_added(device_id, path, niri) {
- warn!("error adding device: {err:?}");
- }
+ // niri.queue_redraw_all();
+ }
+
+ // Add new devices.
+ for (device_id, path) in device_list.into_iter() {
+ if let Err(err) = self.device_added(device_id, &path, niri) {
+ warn!("error adding device: {err:?}");
}
}
}
@@ -279,13 +363,9 @@ impl Tty {
path: &Path,
niri: &mut Niri,
) -> anyhow::Result<()> {
- if path != self.primary_gpu_path {
- debug!("skipping non-primary device {path:?}");
- return Ok(());
- }
+ debug!("device added: {device_id} {path:?}");
- debug!("adding device {path:?}");
- assert!(self.output_device.is_none());
+ let node = DrmNode::from_dev_id(device_id)?;
let open_flags = OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK;
let fd = self.session.open(path, open_flags)?;
@@ -295,24 +375,70 @@ impl Tty {
let gbm = GbmDevice::new(device_fd)?;
let display = EGLDisplay::new(gbm.clone())?;
- let egl_context = EGLContext::new_with_priority(&display, ContextPriority::High)?;
-
- // ColorTransformations is disabled by default as it makes rendering slightly slower.
- let mut gles = if self
- .config
- .borrow()
- .debug
- .enable_color_transformations_capability
- {
- unsafe { GlesRenderer::new(egl_context)? }
- } else {
- let capabilities = unsafe { GlesRenderer::supported_capabilities(&egl_context) }?
- .into_iter()
- .filter(|c| *c != Capability::ColorTransformations);
- unsafe { GlesRenderer::with_capabilities(egl_context, capabilities)? }
- };
-
- gles.bind_wl_display(&niri.display_handle)?;
+ let egl_device = EGLDevice::device_for_display(&display)?;
+
+ // HACK: There's an issue in Smithay where the display created by GpuManager will be the
+ // same as the one we just created here, so when ours is dropped at the end of the scope,
+ // it will also close the long-lived display in GpuManager. Thus, we need to drop ours
+ // beforehand.
+ drop(display);
+
+ let render_node = egl_device
+ .try_get_render_node()?
+ .context("no render node")?;
+ self.gpu_manager
+ .as_mut()
+ .add_node(render_node, gbm.clone())
+ .context("error adding render node to GPU manager")?;
+
+ if node == self.primary_node {
+ debug!("this is the primary node");
+
+ let mut renderer = self
+ .gpu_manager
+ .single_renderer(&render_node)
+ .context("error creating renderer")?;
+
+ renderer.bind_wl_display(&niri.display_handle)?;
+
+ // Create the dmabuf global.
+ let primary_formats = renderer.dmabuf_formats().collect::<HashSet<_>>();
+ let default_feedback =
+ DmabufFeedbackBuilder::new(render_node.dev_id(), primary_formats.clone())
+ .build()
+ .context("error building default dmabuf feedback")?;
+ let dmabuf_global = niri
+ .dmabuf_state
+ .create_global_with_default_feedback::<State>(
+ &niri.display_handle,
+ &default_feedback,
+ );
+ assert!(self.dmabuf_global.replace(dmabuf_global).is_none());
+
+ // Create the primary allocator.
+ let primary_allocator =
+ DmabufAllocator(GbmAllocator::new(gbm.clone(), GbmBufferFlags::RENDERING));
+ assert!(self.primary_allocator.replace(primary_allocator).is_none());
+
+ // Update the dmabuf feedbacks for all surfaces.
+ for device in self.devices.values_mut() {
+ for surface in device.surfaces.values_mut() {
+ match surface_dmabuf_feedback(
+ &surface.compositor,
+ primary_formats.clone(),
+ self.primary_render_node,
+ device.render_node,
+ ) {
+ Ok(feedback) => {
+ surface.dmabuf_feedback = Some(feedback);
+ }
+ Err(err) => {
+ warn!("error building dmabuf feedback: {err:?}");
+ }
+ }
+ }
+ }
+ }
let token = niri
.event_loop
@@ -321,30 +447,22 @@ impl Tty {
match event {
DrmEvent::VBlank(crtc) => {
let meta = meta.expect("VBlank events must have metadata");
- tty.on_vblank(&mut state.niri, crtc, meta);
+ tty.on_vblank(&mut state.niri, node, crtc, meta);
}
DrmEvent::Error(error) => error!("DRM error: {error}"),
};
})
.unwrap();
- let default_feedback = DmabufFeedbackBuilder::new(device_id, gles.dmabuf_formats())
- .build()
- .context("error building default dmabuf feedback")?;
- let dmabuf_global = niri
- .dmabuf_state
- .create_global_with_default_feedback::<State>(&niri.display_handle, &default_feedback);
-
- self.output_device = Some(OutputDevice {
- id: device_id,
+ let device = OutputDevice {
token,
+ render_node,
drm,
gbm,
- gles,
drm_scanner: DrmScanner::new(),
surfaces: HashMap::new(),
- });
- self.dmabuf_global = Some(dmabuf_global);
+ };
+ assert!(self.devices.insert(node, device).is_none());
self.device_changed(device_id, niri);
@@ -352,13 +470,17 @@ impl Tty {
}
fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) {
- let Some(device) = &mut self.output_device else {
+ debug!("device changed: {device_id}");
+
+ let Ok(node) = DrmNode::from_dev_id(device_id) else {
+ warn!("error creating DrmNode");
return;
};
- if device.id != device_id {
+
+ let Some(device) = self.devices.get_mut(&node) else {
+ warn!("no such device");
return;
- }
- debug!("output device changed");
+ };
for event in device.drm_scanner.scan_connectors(&device.drm) {
match event {
@@ -366,26 +488,31 @@ impl Tty {
connector,
crtc: Some(crtc),
} => {
- if let Err(err) = self.connector_connected(niri, connector, crtc) {
+ if let Err(err) = self.connector_connected(niri, node, connector, crtc) {
warn!("error connecting connector: {err:?}");
}
}
DrmScanEvent::Disconnected {
connector,
crtc: Some(crtc),
- } => self.connector_disconnected(niri, connector, crtc),
+ } => self.connector_disconnected(niri, node, connector, crtc),
_ => (),
}
}
}
fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) {
- let Some(device) = &mut self.output_device else {
+ debug!("device removed: {device_id}");
+
+ let Ok(node) = DrmNode::from_dev_id(device_id) else {
+ warn!("error creating DrmNode");
return;
};
- if device_id != device.id {
+
+ let Some(device) = self.devices.get_mut(&node) else {
+ warn!("no such device");
return;
- }
+ };
let crtcs: Vec<_> = device
.drm_scanner
@@ -394,34 +521,54 @@ impl Tty {
.collect();
for (connector, crtc) in crtcs {
- self.connector_disconnected(niri, connector, crtc);
+ self.connector_disconnected(niri, node, connector, crtc);
}
- let mut device = self.output_device.take().unwrap();
- device.gles.unbind_wl_display();
-
- let global = self.dmabuf_global.take().unwrap();
- niri.dmabuf_state
- .disable_global::<State>(&niri.display_handle, &global);
- niri.event_loop
- .insert_source(
- Timer::from_duration(Duration::from_secs(10)),
- move |_, _, state| {
- state
- .niri
- .dmabuf_state
- .destroy_global::<State>(&state.niri.display_handle, global);
- TimeoutAction::Drop
- },
- )
- .unwrap();
+ let device = self.devices.remove(&node).unwrap();
+
+ if node == self.primary_node {
+ match self.gpu_manager.single_renderer(&device.render_node) {
+ Ok(mut renderer) => renderer.unbind_wl_display(),
+ Err(err) => {
+ error!("error creating renderer during device removal: {err}");
+ }
+ }
+
+ // Disable and destroy the dmabuf global.
+ let global = self.dmabuf_global.take().unwrap();
+ niri.dmabuf_state
+ .disable_global::<State>(&niri.display_handle, &global);
+ niri.event_loop
+ .insert_source(
+ Timer::from_duration(Duration::from_secs(10)),
+ move |_, _, state| {
+ state
+ .niri
+ .dmabuf_state
+ .destroy_global::<State>(&state.niri.display_handle, global);
+ TimeoutAction::Drop
+ },
+ )
+ .unwrap();
+
+ self.primary_allocator = None;
+
+ // Clear the dmabuf feedbacks for all surfaces.
+ for device in self.devices.values_mut() {
+ for surface in device.surfaces.values_mut() {
+ surface.dmabuf_feedback = None;
+ }
+ }
+ }
+ self.gpu_manager.as_mut().remove_node(&device.render_node);
niri.event_loop.remove(device.token);
}
fn connector_connected(
&mut self,
niri: &mut Niri,
+ node: DrmNode,
connector: connector::Info,
crtc: crtc::Handle,
) -> anyhow::Result<()> {
@@ -446,10 +593,7 @@ impl Tty {
return Ok(());
}
- let device = self
- .output_device
- .as_mut()
- .context("missing output device")?;
+ let device = self.devices.get_mut(&node).context("missing device")?;
// FIXME: print modes here until we have a better way to list all modes.
for m in connector.modes() {
@@ -559,10 +703,9 @@ impl Tty {
output.change_current_state(Some(wl_mode), None, Some(Scale::Integer(scale)), None);
output.set_preferred(wl_mode);
- output.user_data().insert_if_missing(|| TtyOutputState {
- device_id: device.id,
- crtc,
- });
+ output
+ .user_data()
+ .insert_if_missing(|| TtyOutputState { node, crtc });
let mut planes = surface.planes().clone();
@@ -581,19 +724,10 @@ impl Tty {
Some(device.gbm.clone())
};
- let egl_context = device.gles.egl_context();
- let texture_formats = egl_context.dmabuf_texture_formats();
+ let renderer = self.gpu_manager.single_renderer(&device.render_node)?;
+ let egl_context = renderer.as_ref().egl_context();
let render_formats = egl_context.dmabuf_render_formats();
- let scanout_formats = planes
- .primary
- .formats
- .iter()
- .chain(planes.overlay.iter().flat_map(|p| &p.formats))
- .copied()
- .collect::<HashSet<_>>();
- let scanout_formats = scanout_formats.intersection(texture_formats).copied();
-
// Create the compositor.
let compositor = DrmCompositor::new(
OutputModeSource::Auto(output.clone()),
@@ -602,15 +736,31 @@ impl Tty {
allocator,
device.gbm.clone(),
SUPPORTED_COLOR_FORMATS,
+ // This is only used to pick a good internal format, so it can use the surface's render
+ // formats, even though we only ever render on the primary GPU.
render_formats.clone(),
device.drm.cursor_size(),
cursor_plane_gbm,
)?;
- let dmabuf_feedback = DmabufFeedbackBuilder::new(device.id, texture_formats.clone())
- .add_preference_tranche(device.id, Some(TrancheFlags::Scanout), scanout_formats)
- .build()
- .context("error building dmabuf feedback")?;
+ let mut dmabuf_feedback = None;
+ if let Ok(primary_renderer) = self.gpu_manager.single_renderer(&self.primary_render_node) {
+ let primary_formats = primary_renderer.dmabuf_formats().collect::<HashSet<_>>();
+
+ match surface_dmabuf_feedback(
+ &compositor,
+ primary_formats,
+ self.primary_render_node,
+ device.render_node,
+ ) {
+ Ok(feedback) => {
+ dmabuf_feedback = Some(feedback);
+ }
+ Err(err) => {
+ warn!("error building dmabuf feedback: {err:?}");
+ }
+ }
+ }
let vblank_frame_name =
tracy_client::FrameName::new_leak(format!("vblank on {output_name}"));
@@ -654,12 +804,14 @@ impl Tty {
fn connector_disconnected(
&mut self,
niri: &mut Niri,
+ node: DrmNode,
connector: connector::Info,
crtc: crtc::Handle,
) {
debug!("disconnecting connector: {connector:?}");
- let Some(device) = self.output_device.as_mut() else {
- error!("missing output device");
+
+ let Some(device) = self.devices.get_mut(&node) else {
+ error!("missing device");
return;
};
@@ -673,7 +825,7 @@ impl Tty {
.outputs()
.find(|output| {
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
- tty_state.device_id == device.id && tty_state.crtc == crtc
+ tty_state.node == node && tty_state.crtc == crtc
})
.cloned();
if let Some(output) = output {
@@ -685,14 +837,20 @@ impl Tty {
self.connectors.lock().unwrap().remove(&surface.name);
}
- fn on_vblank(&mut self, niri: &mut Niri, crtc: crtc::Handle, meta: DrmEventMetadata) {
+ fn on_vblank(
+ &mut self,
+ niri: &mut Niri,
+ node: DrmNode,
+ crtc: crtc::Handle,
+ meta: DrmEventMetadata,
+ ) {
let span = tracy_client::span!("Tty::on_vblank");
let now = get_monotonic_time();
- let Some(device) = self.output_device.as_mut() else {
+ let Some(device) = self.devices.get_mut(&node) else {
// I've seen it happen.
- error!("missing output device in vblank callback for crtc {crtc:?}");
+ error!("missing device in vblank callback for crtc {crtc:?}");
return;
};
@@ -744,7 +902,7 @@ impl Tty {
.outputs()
.find(|output| {
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
- tty_state.device_id == device.id && tty_state.crtc == crtc
+ tty_state.node == node && tty_state.crtc == crtc
})
.cloned()
else {
@@ -864,7 +1022,11 @@ impl Tty {
&mut self,
f: impl FnOnce(&mut GlesRenderer) -> T,
) -> Option<T> {
- self.output_device.as_mut().map(|d| f(&mut d.gles))
+ let mut renderer = self
+ .gpu_manager
+ .single_renderer(&self.primary_render_node)
+ .ok()?;
+ Some(f(renderer.as_gles_renderer()))
}
pub fn render(
@@ -877,32 +1039,48 @@ impl Tty {
let mut rv = RenderResult::Skipped;
- let Some(device) = self.output_device.as_mut() else {
+ let tty_state: &TtyOutputState = output.user_data().get().unwrap();
+ let Some(device) = self.devices.get_mut(&tty_state.node) else {
error!("missing output device");
return rv;
};
+ let Some(surface) = device.surfaces.get_mut(&tty_state.crtc) else {
+ error!("missing surface");
+ return rv;
+ };
+
+ span.emit_text(&surface.name);
+
if !device.drm.is_active() {
warn!("device is inactive");
return rv;
}
- let tty_state: &TtyOutputState = output.user_data().get().unwrap();
- let Some(surface) = device.surfaces.get_mut(&tty_state.crtc) else {
- error!("missing surface");
+ let Some(allocator) = self.primary_allocator.as_mut() else {
+ warn!("no primary allocator");
return rv;
};
- span.emit_text(&surface.name);
-
- let renderer = &mut device.gles;
+ let mut renderer = match self.gpu_manager.renderer(
+ &self.primary_render_node,
+ &device.render_node,
+ allocator,
+ surface.compositor.format(),
+ ) {
+ Ok(renderer) => renderer,
+ Err(err) => {
+ error!("error creating renderer for primary GPU: {err:?}");
+ return rv;
+ }
+ };
// Render the elements.
- let elements = niri.render::<GlesRenderer>(renderer, output, true);
+ let elements = niri.render::<TtyRenderer>(&mut renderer, output, true);
// Hand them over to the DRM.
let drm_compositor = &mut surface.compositor;
- match drm_compositor.render_frame::<_, _, GlesTexture>(renderer, &elements, [0.; 4]) {
+ match drm_compositor.render_frame::<_, _, GlesTexture>(&mut renderer, &elements, [0.; 4]) {
Ok(res) => {
if self
.config
@@ -917,7 +1095,9 @@ impl Tty {
}
niri.update_primary_scanout_output(output, &res.states);
- niri.send_dmabuf_feedbacks(output, &surface.dmabuf_feedback);
+ if let Some(dmabuf_feedback) = surface.dmabuf_feedback.as_ref() {
+ niri.send_dmabuf_feedbacks(output, dmabuf_feedback, &res.states);
+ }
if res.damage.is_some() {
let presentation_feedbacks =
@@ -979,7 +1159,7 @@ impl Tty {
}
pub fn toggle_debug_tint(&mut self) {
- if let Some(device) = self.output_device.as_mut() {
+ for device in self.devices.values_mut() {
for surface in device.surfaces.values_mut() {
let compositor = &mut surface.compositor;
compositor.set_debug_flags(compositor.debug_flags() ^ DebugFlags::TINT);
@@ -988,8 +1168,15 @@ impl Tty {
}
pub fn import_dmabuf(&mut self, dmabuf: &Dmabuf) -> Result<(), ()> {
- let device = self.output_device.as_mut().ok_or(())?;
- match device.gles.import_dmabuf(dmabuf, None) {
+ let mut renderer = match self.gpu_manager.single_renderer(&self.primary_render_node) {
+ Ok(renderer) => renderer,
+ Err(err) => {
+ debug!("error creating renderer for primary GPU: {err:?}");
+ return Err(());
+ }
+ };
+
+ match renderer.import_dmabuf(dmabuf, None) {
Ok(_texture) => Ok(()),
Err(err) => {
debug!("error importing dmabuf: {err:?}"