mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
tty: Fall back to primary GPU for display-only devices
This enables DisplayLink support. Co-authored-by: Christian Meissl <meissl.christian@gmail.com> Co-authored-by: Scott McKendry <39483124+scottmckendry@users.noreply.github.com>
This commit is contained in:
+71
-26
@@ -132,7 +132,8 @@ type GbmDrmCompositor = DrmCompositor<
|
||||
|
||||
pub struct OutputDevice {
|
||||
token: RegistrationToken,
|
||||
render_node: DrmNode,
|
||||
// Can be None for display-only devices such as DisplayLink.
|
||||
render_node: Option<DrmNode>,
|
||||
drm_scanner: DrmScanner,
|
||||
surfaces: HashMap<crtc::Handle, Surface>,
|
||||
known_crtcs: HashMap<crtc::Handle, CrtcInfo>,
|
||||
@@ -140,6 +141,7 @@ pub struct OutputDevice {
|
||||
// See https://github.com/Smithay/smithay/issues/1102.
|
||||
drm: DrmDevice,
|
||||
gbm: GbmDevice<DrmDeviceFd>,
|
||||
// For display-only devices this will be the allocator from the primary device.
|
||||
allocator: GbmAllocator<DrmDeviceFd>,
|
||||
|
||||
pub drm_lease_state: Option<DrmLeaseState>,
|
||||
@@ -751,18 +753,39 @@ impl Tty {
|
||||
GbmDevice::new(device_fd)
|
||||
}?;
|
||||
|
||||
let display = unsafe { EGLDisplay::new(gbm.clone())? };
|
||||
let egl_device = EGLDevice::device_for_display(&display)?;
|
||||
let mut try_initialize_gpu = || {
|
||||
let display = unsafe { EGLDisplay::new(gbm.clone())? };
|
||||
let egl_device = EGLDevice::device_for_display(&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")?;
|
||||
// Software EGL devices (e.g., llvmpipe/softpipe) are rejected for now. They have some
|
||||
// problems (segfault on importing dmabufs from other renderers) and need to be
|
||||
// excluded from some places like DRM leasing.
|
||||
ensure!(
|
||||
!egl_device.is_software(),
|
||||
"software EGL renderers are skipped"
|
||||
);
|
||||
|
||||
if render_node == self.primary_render_node {
|
||||
let render_node = egl_device
|
||||
.try_get_render_node()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(node);
|
||||
self.gpu_manager
|
||||
.as_mut()
|
||||
.add_node(render_node, gbm.clone())
|
||||
.context("error adding render node to GPU manager")?;
|
||||
|
||||
Ok(render_node)
|
||||
};
|
||||
|
||||
let render_node = try_initialize_gpu()
|
||||
.inspect_err(|err| {
|
||||
debug!("failed to initialize renderer, falling back to primary gpu: {err:?}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
if render_node == Some(self.primary_render_node) {
|
||||
let render_node = self.primary_render_node;
|
||||
debug!("this is the primary render node");
|
||||
|
||||
let mut renderer = self
|
||||
@@ -807,13 +830,14 @@ impl Tty {
|
||||
assert!(self.dmabuf_global.replace(dmabuf_global).is_none());
|
||||
|
||||
// Update the dmabuf feedbacks for all surfaces.
|
||||
for device in self.devices.values_mut() {
|
||||
for (node, device) in self.devices.iter_mut() {
|
||||
for surface in device.surfaces.values_mut() {
|
||||
match surface_dmabuf_feedback(
|
||||
&surface.compositor,
|
||||
primary_formats.clone(),
|
||||
self.primary_render_node,
|
||||
device.render_node,
|
||||
*node,
|
||||
) {
|
||||
Ok(feedback) => {
|
||||
surface.dmabuf_feedback = Some(feedback);
|
||||
@@ -826,8 +850,15 @@ impl Tty {
|
||||
}
|
||||
}
|
||||
|
||||
let allocator_gbm = if render_node.is_some() {
|
||||
gbm.clone()
|
||||
} else if let Some(primary_device) = self.devices.get(&self.primary_node) {
|
||||
primary_device.gbm.clone()
|
||||
} else {
|
||||
bail!("no allocator available for device");
|
||||
};
|
||||
let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT;
|
||||
let allocator = GbmAllocator::new(gbm.clone(), gbm_flags);
|
||||
let allocator = GbmAllocator::new(allocator_gbm, gbm_flags);
|
||||
|
||||
let token = niri
|
||||
.event_loop
|
||||
@@ -1065,8 +1096,8 @@ impl Tty {
|
||||
lease_state.disable_global::<State>();
|
||||
}
|
||||
|
||||
if device.render_node == self.primary_render_node {
|
||||
match self.gpu_manager.single_renderer(&device.render_node) {
|
||||
if device.render_node == Some(self.primary_render_node) {
|
||||
match self.gpu_manager.single_renderer(&self.primary_render_node) {
|
||||
Ok(mut renderer) => renderer.unbind_wl_display(),
|
||||
Err(err) => {
|
||||
warn!("error creating renderer during device removal: {err}");
|
||||
@@ -1101,9 +1132,11 @@ impl Tty {
|
||||
}
|
||||
}
|
||||
|
||||
self.gpu_manager.as_mut().remove_node(&device.render_node);
|
||||
// Trigger re-enumeration in order to remove the device from gpu_manager.
|
||||
let _ = self.gpu_manager.devices();
|
||||
if let Some(render_node) = device.render_node {
|
||||
self.gpu_manager.as_mut().remove_node(&render_node);
|
||||
// Trigger re-enumeration in order to remove the device from gpu_manager.
|
||||
let _ = self.gpu_manager.devices();
|
||||
}
|
||||
|
||||
niri.event_loop.remove(device.token);
|
||||
|
||||
@@ -1296,13 +1329,16 @@ impl Tty {
|
||||
output.user_data().insert_if_missing(|| PanelOrientation(x));
|
||||
}
|
||||
|
||||
let renderer = self.gpu_manager.single_renderer(&device.render_node)?;
|
||||
let render_node = device.render_node.unwrap_or(self.primary_render_node);
|
||||
let renderer = self.gpu_manager.single_renderer(&render_node)?;
|
||||
let egl_context = renderer.as_ref().egl_context();
|
||||
let render_formats = egl_context.dmabuf_render_formats();
|
||||
|
||||
// Filter out the CCS modifiers as they have increased bandwidth, causing some monitor
|
||||
// configurations to stop working.
|
||||
//
|
||||
// For display only devices, restrict to linear buffers for best compatibility.
|
||||
//
|
||||
// The invalid modifier attempt below should make this unnecessary in some cases, but it
|
||||
// would still be a bad idea to remove this until Smithay has some kind of full-device
|
||||
// modesetting test that is able to "downgrade" existing connector modifiers to get enough
|
||||
@@ -1311,6 +1347,10 @@ impl Tty {
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|format| {
|
||||
if device.render_node.is_none() {
|
||||
return format.modifier == Modifier::Linear;
|
||||
}
|
||||
|
||||
let is_ccs = matches!(
|
||||
format.modifier,
|
||||
Modifier::I915_y_tiled_ccs
|
||||
@@ -1391,6 +1431,7 @@ impl Tty {
|
||||
primary_formats,
|
||||
self.primary_render_node,
|
||||
device.render_node,
|
||||
node,
|
||||
) {
|
||||
Ok(feedback) => {
|
||||
dmabuf_feedback = Some(feedback);
|
||||
@@ -1747,7 +1788,7 @@ impl Tty {
|
||||
|
||||
let mut renderer = match self.gpu_manager.renderer(
|
||||
&self.primary_render_node,
|
||||
&device.render_node,
|
||||
&device.render_node.unwrap_or(self.primary_render_node),
|
||||
surface.compositor.format(),
|
||||
) {
|
||||
Ok(renderer) => renderer,
|
||||
@@ -2099,7 +2140,7 @@ impl Tty {
|
||||
let device = self
|
||||
.devices
|
||||
.values()
|
||||
.find(|d| d.render_node == self.primary_render_node);
|
||||
.find(|d| d.render_node == Some(self.primary_render_node));
|
||||
// Otherwise, try to get the device corresponding to the primary node.
|
||||
let device = device.or_else(|| self.devices.get(&self.primary_node));
|
||||
|
||||
@@ -2650,7 +2691,8 @@ fn surface_dmabuf_feedback(
|
||||
compositor: &GbmDrmCompositor,
|
||||
primary_formats: FormatSet,
|
||||
primary_render_node: DrmNode,
|
||||
surface_render_node: DrmNode,
|
||||
surface_render_node: Option<DrmNode>,
|
||||
surface_scanout_node: DrmNode,
|
||||
) -> Result<SurfaceDmabufFeedback, io::Error> {
|
||||
let surface = compositor.surface();
|
||||
let planes = surface.planes();
|
||||
@@ -2675,7 +2717,10 @@ fn surface_dmabuf_feedback(
|
||||
|
||||
// HACK: AMD iGPU + dGPU systems share some modifiers between the two, and yet cross-device
|
||||
// buffers produce a glitched scanout if the modifier is not Linear...
|
||||
if primary_render_node != surface_render_node {
|
||||
//
|
||||
// Also limit scan-out formats to Linear if we have a device without a render node (i.e.
|
||||
// we're rendering on a different device).
|
||||
if surface_render_node != Some(primary_render_node) {
|
||||
primary_scanout_formats.retain(|f| f.modifier == Modifier::Linear);
|
||||
primary_or_overlay_scanout_formats.retain(|f| f.modifier == Modifier::Linear);
|
||||
}
|
||||
@@ -2694,12 +2739,12 @@ fn surface_dmabuf_feedback(
|
||||
let scanout = builder
|
||||
.clone()
|
||||
.add_preference_tranche(
|
||||
surface_render_node.dev_id(),
|
||||
surface_scanout_node.dev_id(),
|
||||
Some(TrancheFlags::Scanout),
|
||||
primary_scanout_formats,
|
||||
)
|
||||
.add_preference_tranche(
|
||||
surface_render_node.dev_id(),
|
||||
surface_scanout_node.dev_id(),
|
||||
Some(TrancheFlags::Scanout),
|
||||
primary_or_overlay_scanout_formats,
|
||||
)
|
||||
@@ -2707,7 +2752,7 @@ fn surface_dmabuf_feedback(
|
||||
|
||||
// If this is the primary node surface, send scanout formats in both tranches to avoid
|
||||
// duplication.
|
||||
let render = if primary_render_node == surface_render_node {
|
||||
let render = if surface_render_node == Some(primary_render_node) {
|
||||
scanout.clone()
|
||||
} else {
|
||||
builder.build()?
|
||||
|
||||
Reference in New Issue
Block a user